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 and seen cells key by "{dungeon}:{level}", doors by edge_ref, traps and caches by "{dungeon}:{level}:{area_or_feature_id}", piles by cell_ref. explored is the party's footprint — the cells it has physically entered — while seen is its map memory: the cells its own light has shown it, read by the player projection only. 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]] = {}

seen class-attribute instance-attribute

seen: 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

mark_seen

mark_seen(dungeon_id: str, level_number: int, positions: Iterable[Position]) -> None

Mark cells seen (idempotent): map memory, what the party has glimpsed by light.

Seen cells are read by the player projection only, so a front end's automap remembers a room the party's light showed it after the party walks on. They never affect movement cost — that reads the explored footprint via is_explored — or drop-pile visibility, which stays on explored cells (piles only ever form where the party has stood). New positions append in sorted (x, y) order so serialization is deterministic across runs.

Parameters:

Name Type Description Default
dungeon_id str

The dungeon id.

required
level_number int

The 1-based level number.

required
positions Iterable[Position]

The cells to remember; already-seen cells are skipped.

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), magic_item_ids (any id from load_magic_items, see the magic item id index), and coins — plus an optional treasure trap, so a cache's contents can be dropped, found, and recovered like any other treasure. Hand-placed magic items instantiate when the cache is emptied: the author names the item, and its creation details (charges, quantities, sword sentience) roll on the treasure stream via instantiate_magic_item. 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, ...] = ()

magic_item_ids class-attribute instance-attribute

magic_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. hoard=True (the default) means the engine generates the keyed monsters' lair hoard the first time the encounter spawns; hoard=False is the treasure-absent keyed room — a monster room the stocking roll gave no treasure — expressible because SRD stocking puts treasure on only some monster rooms, while an encounter would otherwise always bring its lair letters.

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

hoard class-attribute instance-attribute

hoard: bool = True

KeyedMonster

Bases: BaseModel

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

template_id is any id in the session's effective catalog — a shipped id from load_monsters (see the monster id index) or one bundled by the adventure's monsters.

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).