Skip to content

osrlib.crawl.content_pack

Content packs: portable, geometry-free keyed content an editor can carry between adventures.

A ContentPack is finished room content with the geometry left behind: sections of entries that mirror AreaSpec minus its cells, each section optionally carrying a level's WanderingSpec, plus the bundled MonsterTemplates the entries' encounters and the sections' wandering tables reference — the pack's closure. Packs are how an authoring tool moves stocked rooms from one adventure to another: the consumer writes an entry's content into a target area it already has, so a pack never places cells, transitions, or any other geometry.

Identity is the contract consumers stand on: section ids, entry ids (pack-wide), and bundled monster ids are each unique, enforced at construction — a pack that breaks them refuses to load rather than lingering as a diagnostic. Dangling references are legal by the same posture as Adventure: an encounter may name a template neither the pack nor the shipped catalog carries, and validate_content_pack reports such gaps as structured PackFindings instead of raising — the first consumer is a panel that lists findings, not a gate.

Packs serialize as stamped "content_pack" documents (CONTENT_PACK_KIND), the longest-lived artifacts in the document family, and own their acceptance rules: a document stamped by an older schema version is accepted on load, one stamped by a newer version fails with SaveVersionError, and any write re-stamps at the current schema and engine versions — a loaded older pack saves as a current one.

from osrlib.crawl.content_pack import ContentPack, ContentPackEntry, PackSection, validate_content_pack
from osrlib.crawl.dungeon import KeyedEncounter, KeyedMonster
from osrlib.data import load_equipment, load_monsters

pack = ContentPack(
    name="The gnawing dark",
    sections=(
        PackSection(
            id="level-1",
            label="Level 1",
            entries=(
                ContentPackEntry(
                    id="guard-post",
                    name="Guard post",
                    encounter=KeyedEncounter(monsters=(KeyedMonster(template_id="orc", count_fixed=4),)),
                ),
            ),
        ),
    ),
)
document = pack.to_document()
assert document["kind"] == "content_pack"
assert ContentPack.from_document(document) == pack
assert validate_content_pack(pack, load_monsters(), load_equipment()) == ()

CONTENT_PACK_KIND module-attribute

CONTENT_PACK_KIND = 'content_pack'

The stamped-document kind for serialized content packs.

ContentPack

Bases: BaseModel

A content pack: sections of geometry-free entries plus their monster closure.

monsters bundles the MonsterTemplates the pack's encounters and wandering tables reference beyond the shipped catalog. id defaults empty: a pack derived on the fly takes its identity from its source, and only a persisted pack mints an id of its own.

id class-attribute instance-attribute

id: str = ''

name class-attribute instance-attribute

name: str = ''

description class-attribute instance-attribute

description: str = ''

author class-attribute instance-attribute

author: str = ''

sections class-attribute instance-attribute

sections: tuple[PackSection, ...] = ()

monsters class-attribute instance-attribute

monsters: tuple[MonsterTemplate, ...] = ()

to_document

to_document() -> dict[str, object]

Serialize to a stamped document with schema and engine versions.

A write always stamps the current versions: re-serializing a pack loaded from an older document re-stamps it as a current one.

Returns:

Type Description
dict[str, object]

The stamped document envelope wrapping the serialized pack.

from_document classmethod

from_document(document: Mapping[str, object]) -> ContentPack

Load a content pack from a stamped document.

A schema_version older than the current one is accepted — pack payload changes are additive within a version, so an older document validates directly. Unknown payload fields are ignored, per the additive-schema contract.

Parameters:

Name Type Description Default
document Mapping[str, object]

A document produced by to_document.

required

Returns:

Type Description
ContentPack

The reconstructed pack.

Raises:

Type Description
ContentValidationError

If the envelope or payload is malformed or of the wrong kind.

SaveVersionError

If the document's schema version is newer than this library understands.

ContentPackEntry

Bases: BaseModel

One portable room: AreaSpec minus its geometry.

The entry carries the content slots an area has — prose, encounter, trap, treasure, features — and nothing that binds to a grid: there are no cells, and a consumer writes the carried slots into a target area it already has. An entry trap must be a room trap, the same rule AreaSpec enforces; the treasure-trap coupling needs no restatement because it lives on FeatureSpec itself.

id class-attribute instance-attribute

id: str = Field(min_length=1)

name class-attribute instance-attribute

name: str = ''

description class-attribute instance-attribute

description: str = ''

encounter class-attribute instance-attribute

encounter: KeyedEncounter | None = None

trap class-attribute instance-attribute

trap: TrapSpec | None = None

treasure class-attribute instance-attribute

treasure: AreaTreasureSpec | None = None

features class-attribute instance-attribute

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

PackFinding

Bases: BaseModel

A structured self-containment gap in a content pack.

code is dotted snake_case namespaced by subsystem, the Rejection discipline. entry_id names the entry the gap sits on, or None for a section-scoped gap (a wandering table's); message carries the specifics either way.

code instance-attribute

code: str

message instance-attribute

message: str

entry_id class-attribute instance-attribute

entry_id: str | None = None

PackSection

Bases: BaseModel

A pack's level grouping: entries plus the level's optional wandering table.

Sections are structural because three consumers operate on a level grouping — a panel's level rows, wandering's level scope, and a level-scoped capture. Entry ids are unique pack-wide, so consumers address entries by id alone; the section contributes presentation grouping and the wandering slot.

id class-attribute instance-attribute

id: str = Field(min_length=1)

label class-attribute instance-attribute

label: str = ''

entries class-attribute instance-attribute

entries: tuple[ContentPackEntry, ...] = ()

wandering class-attribute instance-attribute

wandering: WanderingSpec | None = None

validate_content_pack

validate_content_pack(
    pack: ContentPack, monsters: MonsterCatalog, equipment: EquipmentCatalog
) -> tuple[PackFinding, ...]

Report a pack's self-containment gaps — references its closure does not cover.

Checks every monster reference (keyed-encounter lines and wandering-table rows) against the union of the shipped catalog and the pack's bundled monsters, every feature's item_ids against the equipment catalog, and every feature's magic_item_ids against the shipped magic-item catalog (load_magic_items — packs bundle no magic items, so the check loads it itself). Findings are data, not errors: a dangling reference is legal in a pack exactly as it is while editing an adventure, and a caller wanting a gate checks for a non-empty result.

Parameters:

Name Type Description Default
pack ContentPack

The pack to check.

required
monsters MonsterCatalog

The base monster catalog — the check unions it with pack.monsters internally.

required
equipment EquipmentCatalog

The equipment catalog feature contents resolve against.

required

Returns:

Type Description
PackFinding

One finding per gap, in section and entry order; empty means the pack is

...

self-contained.