Skip to content

osrlib.crawl.dungeon

The multi-level dungeon grid: cells, edges, doors, areas, traps, and state.

Authored content is frozen; play mutates a state overlay — the template/instance split applied to space. DungeonSpec and its levels, areas, features, and traps are game content, never mutated; DungeonState carries everything play changes (explored cells, door state, sprung traps, dropped piles, the party's location) and serializes into saves.

Geometry, as API convention: cells are 10' squares addressed (x, y) with x increasing east and y increasing south from the level's northwest corner. Edges are the single spatial truth for walls and doors: an edges map keyed by the canonical edge key (a cell plus north or west, so each physical edge has exactly one entry). An edge absent from the map is a wall — authored content declares its passages (open) and doors explicitly — and the level boundary is implicitly wall.

Position module-attribute

Position = tuple[int, int]

A cell address: (x, y), x increasing east, y increasing south, from (0, 0).

AreaSpec

Bases: BaseModel

A keyed area (a room or cave): a named region over cells with content bindings.

Areas annotate the grid; cells not in any area are corridor. Content prose lives here — events carry ids and front ends resolve prose against the adventure.

id instance-attribute

id: str

name class-attribute instance-attribute

name: str = ''

description class-attribute instance-attribute

description: str = ''

cells class-attribute instance-attribute

cells: tuple[Position, ...] = Field(min_length=1)

encounter class-attribute instance-attribute

encounter: KeyedEncounter | None = None

features class-attribute instance-attribute

features: tuple[FeatureSpec, ...] = ()

trap class-attribute instance-attribute

trap: TrapSpec | None = None

treasure class-attribute instance-attribute

treasure: AreaTreasureSpec | None = None

AreaTreasureSpec

Bases: BaseModel

An area's generated-treasure declaration: explicit type letters, or unguarded.

Generates on first entry into the area — how content places generated treasure in an area with no monsters. letters names one or more treasure type letters (see the treasure type index); unguarded=True instead rolls the dungeon level's unguarded-treasure band. Exactly one of the two applies.

letters class-attribute instance-attribute

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

unguarded class-attribute instance-attribute

unguarded: bool = False

Direction

Bases: StrEnum

The four grid directions.

The wire values are lowercase — they serialize into commands, events, and saves; changing them is a schema_version bump.

NORTH class-attribute instance-attribute

NORTH = 'north'

EAST class-attribute instance-attribute

EAST = 'east'

SOUTH class-attribute instance-attribute

SOUTH = 'south'

WEST class-attribute instance-attribute

WEST = 'west'

vector property

vector: tuple[int, int]

The (dx, dy) step for one cell in this direction.

opposite property

opposite: Direction

The reverse direction.

DoorSpec

Bases: BaseModel

A door on an edge, exactly as authored.

kind="secret" doors are invisible until discovered (a successful secret-door search marks them in the state overlay). stuck and locked are the authored starting conditions; play mutates the overlay, never this spec.

kind class-attribute instance-attribute

kind: Literal['normal', 'secret'] = 'normal'

stuck class-attribute instance-attribute

stuck: bool = False

locked class-attribute instance-attribute

locked: bool = False

starts_open class-attribute instance-attribute

starts_open: bool = False

DoorState

Bases: BaseModel

One door's mutable overlay: open, wedged, discovered, unlocked.

opened_by_party is the swing-shut rule's memory: only doors the party opened (by whatever means) swing shut behind it; authored-open doors stay.

open class-attribute instance-attribute

open: bool = False

wedged class-attribute instance-attribute

wedged: bool = False

discovered class-attribute instance-attribute

discovered: bool = False

unlocked class-attribute instance-attribute

unlocked: bool = False

opened_by_party class-attribute instance-attribute

opened_by_party: bool = False

DropPile

Bases: BaseModel

Dropped items and coins on a cell — droppable, recoverable, distraction bait.

Drops and loot round-trip: battle-end loot, death-save survivors, and player drops all land here and TakeTreasure recovers them.

items class-attribute instance-attribute

items: list[DroppedItem] = []

coins class-attribute instance-attribute

coins: Coins = Coins()

valuables class-attribute instance-attribute

valuables: list[ValuableInstance] = []

magic_items class-attribute instance-attribute

magic_items: list[MagicItemInstance] = []

DroppedItem

Bases: BaseModel

One dropped item stack in a pile.

item_id instance-attribute

item_id: str

quantity class-attribute instance-attribute

quantity: int = Field(ge=1)

DungeonSpec

Bases: BaseModel

A dungeon: one or more levels joined by transitions.

id instance-attribute

id: str

name class-attribute instance-attribute

name: str = ''

levels class-attribute instance-attribute

levels: tuple[LevelSpec, ...] = Field(min_length=1)

level

level(number: int) -> LevelSpec

Return the level with number.

Parameters:

Name Type Description Default
number int

The 1-based level number.

required

Returns:

Type Description
LevelSpec

The level spec.

Raises:

Type Description
ValueError

If no level has that number.

DungeonState

Bases: BaseModel

The mutable overlay play writes over the frozen adventure content.

References are strings so the overlay serializes flat: explored cells key by "{dungeon}:{level}", doors by edge_ref, traps and caches by "{dungeon}:{level}:{area_or_feature_id}", piles by cell_ref. Attempt memory (listen once per character per door, search once per character per cell per kind, the pick-lock lockout with the thief's level at failure) lives here too — it is game state, not procedure-local bookkeeping.

location class-attribute instance-attribute

location: PartyLocation = PartyLocation(kind='town')

explored class-attribute instance-attribute

explored: dict[str, list[Position]] = {}

doors class-attribute instance-attribute

doors: dict[str, DoorState] = {}

sprung_traps class-attribute instance-attribute

sprung_traps: list[str] = []

removed_traps class-attribute instance-attribute

removed_traps: list[str] = []

found_traps class-attribute instance-attribute

found_traps: list[str] = []

found_tricks class-attribute instance-attribute

found_tricks: list[str] = []

discovered_features class-attribute instance-attribute

discovered_features: list[str] = []

emptied_caches class-attribute instance-attribute

emptied_caches: list[str] = []

piles class-attribute instance-attribute

piles: dict[str, DropPile] = {}

generated_caches class-attribute instance-attribute

generated_caches: dict[str, GeneratedCache] = {}

generated_treasure_areas class-attribute instance-attribute

generated_treasure_areas: list[str] = []

resolved_encounters class-attribute instance-attribute

resolved_encounters: list[str] = []

listen_attempts class-attribute instance-attribute

listen_attempts: dict[str, list[str]] = {}

search_attempts class-attribute instance-attribute

search_attempts: dict[str, list[str]] = {}

inspect_attempts class-attribute instance-attribute

inspect_attempts: dict[str, list[str]] = {}

removal_attempts class-attribute instance-attribute

removal_attempts: dict[str, list[str]] = {}

lock_failures class-attribute instance-attribute

lock_failures: dict[str, dict[str, int]] = {}

is_explored

is_explored(dungeon_id: str, level_number: int, position: Position) -> bool

Return whether the party has explored a cell.

Parameters:

Name Type Description Default
dungeon_id str

The dungeon id.

required
level_number int

The 1-based level number.

required
position Position

The cell.

required

Returns:

Type Description
bool

True when the cell is in the explored set.

mark_explored

mark_explored(dungeon_id: str, level_number: int, position: Position) -> None

Mark a cell explored (idempotent).

Parameters:

Name Type Description Default
dungeon_id str

The dungeon id.

required
level_number int

The 1-based level number.

required
position Position

The cell.

required

door

door(ref: str) -> DoorState

Return (creating on first touch) the mutable state for one door.

Parameters:

Name Type Description Default
ref str

The door's edge_ref.

required

Returns:

Type Description
DoorState

The door's overlay entry.

Edge

Bases: BaseModel

One authored edge entry: its kind, plus the door when kind="door".

kind instance-attribute

kind: EdgeKind

door class-attribute instance-attribute

door: DoorSpec | None = None

EdgeKind

Bases: StrEnum

What occupies an edge between two cells.

OPEN class-attribute instance-attribute

OPEN = 'open'

WALL class-attribute instance-attribute

WALL = 'wall'

DOOR class-attribute instance-attribute

DOOR = 'door'

FeatureSpec

Bases: BaseModel

A keyed feature: a treasure cache, a construction trick, or custom content.

Stairs are TransitionSpec's alone — no second home. Caches carry hand-placed contents — item_ids (any id from load_equipment, see the equipment id index) and coins — plus an optional treasure trap, so a cache's contents can be dropped, found, and recovered like any other treasure. cell binds the feature to a cell; a feature listed on an area with cell=None binds to the whole area.

id instance-attribute

id: str

kind instance-attribute

kind: Literal['treasure_cache', 'construction_trick', 'custom']

description class-attribute instance-attribute

description: str = ''

cell class-attribute instance-attribute

cell: Position | None = None

item_ids class-attribute instance-attribute

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

coins class-attribute instance-attribute

coins: Coins = Coins()

valuables class-attribute instance-attribute

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

trap class-attribute instance-attribute

trap: TrapSpec | None = None

GeneratedCache

Bases: BaseModel

An engine-created treasure cache in the state overlay.

Authored FeatureSpecs are frozen content; the state overlay owns play-created treasure — the template/instance split applied to loot. Generated hoards are always untrapped: traps are authored content only, never a generation outcome (see the adaptations register).

cell_ref instance-attribute

cell_ref: str

treasure_types class-attribute instance-attribute

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

coins class-attribute instance-attribute

coins: Coins = Coins()

valuables class-attribute instance-attribute

valuables: list[ValuableInstance] = []

magic_items class-attribute instance-attribute

magic_items: list[MagicItemInstance] = []

KeyedEncounter

Bases: BaseModel

An area's keyed encounter: monsters with counts and optional pins.

aware=True means the monsters expect intruders (they never roll surprise); stance pins the reaction outright (no reaction roll); alignment fixes the spawn alignment for multi-option templates.

monsters class-attribute instance-attribute

monsters: tuple[KeyedMonster, ...] = Field(min_length=1)

alignment class-attribute instance-attribute

alignment: Alignment | None = None

aware class-attribute instance-attribute

aware: bool = False

stance class-attribute instance-attribute

stance: ReactionResult | None = None

KeyedMonster

Bases: BaseModel

One monster line of a keyed encounter: the template and its count.

template_id is any id from load_monsters — see the monster id index.

template_id instance-attribute

template_id: str

count_dice class-attribute instance-attribute

count_dice: str | None = None

count_fixed class-attribute instance-attribute

count_fixed: int | None = None

LevelSpec

Bases: BaseModel

One dungeon level: a grid of 10' cells with edges, areas, and transitions.

number is 1-based and rules-visible — it keys the encounter-table band. entrance is where EnterDungeon and town travel land (required on some level per adventure validation).

number class-attribute instance-attribute

number: int = Field(ge=1)

width class-attribute instance-attribute

width: int = Field(ge=1)

height class-attribute instance-attribute

height: int = Field(ge=1)

edges class-attribute instance-attribute

edges: dict[str, Edge] = {}

areas class-attribute instance-attribute

areas: tuple[AreaSpec, ...] = ()

features class-attribute instance-attribute

features: tuple[FeatureSpec, ...] = ()

transitions class-attribute instance-attribute

transitions: tuple[TransitionSpec, ...] = ()

wandering class-attribute instance-attribute

entrance class-attribute instance-attribute

entrance: Position | None = None

in_bounds

in_bounds(position: Position) -> bool

Return whether a cell lies on this level's grid.

Parameters:

Name Type Description Default
position Position

The cell to test.

required

Returns:

Type Description
bool

True when 0 <= x < width and 0 <= y < height.

edge

edge(position: Position, direction: Direction) -> Edge

Return the authored edge on one side of a cell.

An edge absent from the map is a wall (authored content declares its passages), and the level boundary is implicitly wall.

Parameters:

Name Type Description Default
position Position

The cell.

required
direction Direction

Which of the cell's four edges.

required

Returns:

Type Description
Edge

The edge entry.

area_at

area_at(position: Position) -> AreaSpec | None

Return the keyed area covering a cell, or None for corridor.

Parameters:

Name Type Description Default
position Position

The cell.

required

Returns:

Type Description
AreaSpec | None

The first area whose cells include the position, in authored order.

transition_at

transition_at(position: Position) -> TransitionSpec | None

Return the transition on a cell, or None.

Parameters:

Name Type Description Default
position Position

The cell.

required

Returns:

Type Description
TransitionSpec | None

The transition, if one is authored there.

PartyLocation

Bases: BaseModel

Where the party is: the base town, or a dungeon cell with facing.

kind instance-attribute

kind: Literal['town', 'dungeon']

dungeon_id class-attribute instance-attribute

dungeon_id: str | None = None

level_number class-attribute instance-attribute

level_number: int | None = None

position class-attribute instance-attribute

position: Position | None = None

facing class-attribute instance-attribute

facing: Direction | None = None

TransitionSpec

Bases: BaseModel

A level transition on a cell: stairs, trapdoor, or chute.

The destination is (dungeon_id, level_number, position, facing). Chutes are one-way — UseStairs rejects on arrival cells that have no transition back.

kind instance-attribute

kind: Literal['stairs_up', 'stairs_down', 'trapdoor', 'chute']

position instance-attribute

position: Position

to_dungeon_id instance-attribute

to_dungeon_id: str

to_level_number class-attribute instance-attribute

to_level_number: int = Field(ge=1)

to_position instance-attribute

to_position: Position

to_facing instance-attribute

to_facing: Direction

TrapEffect

Bases: BaseModel

What a sprung trap does: damage, a save, a condition, a fall, or a transition.

damage_dice rolls once; volley_dice is the darts form (1d6 projectiles, each rolling damage_dice — a count-times-damage form the dice grammar alone can't say). save gates the whole effect (negates or half damage). kills marks save-or-die forms (poison gas). condition with its duration is the blindness form; fall_feet is the pit's falling damage; transition drops the victim elsewhere (slides). manual keeps prose for the rest.

damage_dice class-attribute instance-attribute

damage_dice: str | None = None

volley_dice class-attribute instance-attribute

volley_dice: str | None = None

save class-attribute instance-attribute

save: SaveSpec | None = None

kills class-attribute instance-attribute

kills: bool = False

condition class-attribute instance-attribute

condition: Condition | None = None

condition_duration_dice class-attribute instance-attribute

condition_duration_dice: str | None = None

condition_duration_amount class-attribute instance-attribute

condition_duration_amount: int | None = None

condition_duration_unit class-attribute instance-attribute

condition_duration_unit: TimeUnit | None = None

fall_feet class-attribute instance-attribute

fall_feet: int | None = None

transition class-attribute instance-attribute

transition: TransitionSpec | None = None

manual class-attribute instance-attribute

manual: str | None = None

TrapSpec

Bases: BaseModel

A trap: room (over an area) or treasure (on a feature).

trigger names the springing action (enter a cell of the trapped area, open a trapped cache). affects defaults to the triggerer; party covers forms like poison gas filling the room.

kind instance-attribute

kind: Literal['room', 'treasure']

trigger instance-attribute

trigger: Literal['enter', 'open']

effect instance-attribute

effect: TrapEffect

affects class-attribute instance-attribute

affects: Literal['triggerer', 'party'] = 'triggerer'

TreasureBundle

Bases: BaseModel

A mutable generated-treasure bundle: coins, valuables, and magic items.

coins class-attribute instance-attribute

coins: Coins = Coins()

valuables class-attribute instance-attribute

valuables: list[ValuableInstance] = []

magic_items class-attribute instance-attribute

magic_items: list[MagicItemInstance] = []

empty property

empty: bool

Whether the bundle holds nothing at all.

ValuableSpec

Bases: BaseModel

An authored named valuable in a cache — instantiated on take.

The authoring surface for named treasure: a unique gem or piece of jewellery with its own display name, rather than a generic generated one. name is the display name; the instance id comes from the session allocator when the cache is emptied.

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)

WanderingSpec

Bases: BaseModel

A level's wandering-monster parameters.

The defaults are RAW: a 1-in-6 check every two turns. table overrides the compiled level-band table with an inline custom list (same row model).

chance_in_six class-attribute instance-attribute

chance_in_six: int = Field(default=1, ge=0, le=6)

interval_turns class-attribute instance-attribute

interval_turns: int = Field(default=2, ge=1)

table class-attribute instance-attribute

table: EncounterTable | None = None

cell_ref

cell_ref(dungeon_id: str, level_number: int, position: Position) -> str

Return the structured cell reference used by location-bound effects.

The format is cell:{dungeon}:{level}:{x},{y} — an ActiveEffect.target_ref in this form anchors the effect to a dungeon cell.

Parameters:

Name Type Description Default
dungeon_id str

The dungeon id.

required
level_number int

The 1-based level number.

required
position Position

The cell.

required

Returns:

Type Description
str

The cell reference string.

edge_key

edge_key(position: Position, direction: Direction) -> str

Return the canonical key for the edge on direction's side of position.

Each physical edge has exactly one entry: the key is a cell plus north or west, so a cell's south edge is its southern neighbour's north edge and its east edge the eastern neighbour's west. The format is "{x},{y}:{side}".

Parameters:

Name Type Description Default
position Position

The cell.

required
direction Direction

Which of the cell's four edges.

required

Returns:

Type Description
str

The canonical edge key.

edge_ref

edge_ref(dungeon_id: str, level_number: int, position: Position, direction: Direction) -> str

Return the state-overlay reference for one physical edge (door bookkeeping).

Parameters:

Name Type Description Default
dungeon_id str

The dungeon id.

required
level_number int

The 1-based level number.

required
position Position

The cell.

required
direction Direction

Which of the cell's four edges.

required

Returns:

Type Description
str

The edge reference string, canonicalized like

str

step

step(position: Position, direction: Direction) -> Position

Return the cell one step from position in direction.

Parameters:

Name Type Description Default
position Position

The starting cell.

required
direction Direction

The direction to step.

required

Returns:

Type Description
Position

The adjacent cell address (which may lie outside the level).