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. Item templates are deliberately not part of that closure: a pack's
features reference the shipped equipment catalog only, since a bundled item id
belongs to the one adventure that carries it and would arrive dangling in any
other. 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
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.
to_document
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, and the one narrowing — schema 3
made TrapSpec reject trigger="enter"
on a treasure trap — is repaired in place, rewriting the dead value to
"open" exactly as the save migration does. Unknown payload fields are
ignored, per the additive-schema contract.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
document
|
Mapping[str, object]
|
A document produced by
|
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.
PackFinding
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.
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 shipped 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
|
required |
equipment
|
EquipmentCatalog
|
The shipped equipment catalog feature contents resolve against. Packs carry no item bundle of their own, so there is nothing to union here: an entry naming an item some adventure bundles reports as a gap, which is the honest answer for a portable pack. |
required |
Returns:
| Type | Description |
|---|---|
PackFinding
|
One finding per gap, in section and entry order; empty means the pack is |
...
|
self-contained. |