Skip to content

osrforge.contracts.overrides

The overrides contract: the human correction channel, overrides.yaml.

Overrides are a human-authored input, not a produced artifact, and carry no version field in v1 — strict unknown-key rejection means a future revision that needs one can add it detectably. Every entry carries a required, non-empty reason. Replacement payloads are osrlib's own models embedded directly — never re-declared — so an override osrlib would reject fails at overrides load time, not assembly time.

Field semantics, pinned: absent means untouched; explicit null means clear. Pydantic's unset-vs-None distinction models this exactly — check model_fields_set to tell them apart. Application happens during assembly, in osrforge.overrides.

EdgeKeyString module-attribute

EdgeKeyString = Annotated[str, AfterValidator(_validate_edge_key)]

An edge key: x,y:direction, any of the four directions.

osrlib's canonical edge_key form stores only north/west keys (a cell's east edge is its eastern neighbour's west edge); override keys accept all four directions and application canonicalizes them (canonicalize_edge_key).

AreaGeometryOverride

Bases: BaseModel

Replace one area's cell cluster.

cells class-attribute instance-attribute

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

AreaOverride

Bases: BaseModel

Replace fields of one keyed area, add an area, or remove one.

An entry addressing an area the draft doesn't have is an area add and must carry the full required payload — enforced at application time, since only assembly knows what the draft contains.

name class-attribute instance-attribute

name: str | None = None

Replace the area's name.

description class-attribute instance-attribute

description: str | None = None

Replace the area's description.

encounter class-attribute instance-attribute

encounter: KeyedEncounter | None = None

Replace the area's whole built encounter (an osrlib spec, post-mapping).

trap class-attribute instance-attribute

trap: TrapSpec | None = None

Replace the area's trap.

treasure class-attribute instance-attribute

treasure: AreaTreasureSpec | None = None

Replace the area's treasure — the parsed grammar never runs on an overridden slot.

features class-attribute instance-attribute

features: tuple[FeatureSpec, ...] | None = None

Replace the area's features.

remove class-attribute instance-attribute

remove: bool = False

Remove the area from the draft entirely.

reason instance-attribute

reason: Reason

Why this correction is right; every override entry carries one.

GeometryOverride

Bases: BaseModel

Correct one level's geometry: area cells, edges, entrance, transitions.

The nesting follows osrlib's models: a level owns edges, entrance, and transitions; an area owns only its cells.

areas class-attribute instance-attribute

areas: dict[_AreaKeyString, AreaGeometryOverride] = {}

Area key → its replacement cell cluster.

edges class-attribute instance-attribute

edges: dict[EdgeKeyString, Edge] = {}

Edge key → its replacement edge; an override edge always wins over the synthesized one.

entrance class-attribute instance-attribute

entrance: Position | None = None

Replace the level's entrance cell.

transitions class-attribute instance-attribute

transitions: tuple[TransitionSpec, ...] | None = None

Replace the level's transitions wholesale.

reason instance-attribute

reason: Reason

Why this correction is right; every override entry carries one.

ModuleOverride

Bases: BaseModel

Replace adventure metadata fields: name, description, hooks.

name class-attribute instance-attribute

name: str | None = None

description class-attribute instance-attribute

description: str | None = None

hooks class-attribute instance-attribute

hooks: tuple[str, ...] | None = None

reason instance-attribute

reason: Reason

MonsterOverride

Bases: BaseModel

Remap one extracted monster name to a catalog template.

template_id instance-attribute

template_id: Annotated[str, StringConstraints(min_length=1)]

The catalog template the name should resolve to — including an emitted custom template's id.

reason instance-attribute

reason: Reason

Why this correction is right; every override entry carries one.

Overrides

Bases: BaseModel

The overrides.yaml document: the v1 override kinds.

monsters class-attribute instance-attribute

monsters: dict[_NonEmptyKey, MonsterOverride] = {}

Extracted monster name → catalog remap. Keys match under the monsters stage's normalization (casefold, whitespace collapsed).

monster_templates class-attribute instance-attribute

monster_templates: dict[_NonEmptyKey, StatBlockOverride] = {}

Extracted monster name → stat-block patch or supply, same key normalization. The same name under both this and monsters: is rejected as contradictory.

areas class-attribute instance-attribute

Area address → its field replacements, add, or removal.

geometry class-attribute instance-attribute

Level address → its geometry corrections.

town class-attribute instance-attribute

town: TownOverride | None = None

Base-town metadata replacements.

module class-attribute instance-attribute

module: ModuleOverride | None = None

Adventure metadata replacements.

StatBlockOverride

Bases: BaseModel

Patch fields of one name's extracted stat block, or supply a complete one.

Keyed by monster name like monsters:, matched under the same normalization. Fields mirror the raw printed block (RawStatBlock) — corrections land pre-mapping, so one correction fixes a printed value once instead of both derived forms. Absent means untouched; explicit null clears the field back to unprinted. An entry on a name with no cached block supplies the candidate block from its own fields alone; an entry on a name the tiers resolved forces emission — the remedy for a flagless wrong LLM pick.

ac class-attribute instance-attribute

ac: str | None = None

ac_notation class-attribute instance-attribute

ac_notation: AcNotation | None = None

thac0 class-attribute instance-attribute

thac0: str | None = None

hit_dice class-attribute instance-attribute

hit_dice: str | None = None

class_level class-attribute instance-attribute

class_level: str | None = None

hp class-attribute instance-attribute

hp: Annotated[int, Field(ge=1)] | None = None

attacks class-attribute instance-attribute

attacks: tuple[str, ...] | None = None

movement class-attribute instance-attribute

movement: str | None = None

saves class-attribute instance-attribute

saves: str | None = None

morale class-attribute instance-attribute

morale: Annotated[int, Field(ge=2, le=12)] | None = None

alignment class-attribute instance-attribute

alignment: str | None = None

xp class-attribute instance-attribute

xp: Annotated[int, Field(ge=0)] | None = None

number_appearing class-attribute instance-attribute

number_appearing: str | None = None

special class-attribute instance-attribute

special: tuple[str, ...] | None = None

reason instance-attribute

reason: Reason

TownOverride

Bases: BaseModel

Replace base-town metadata fields (TownSpec fields).

name class-attribute instance-attribute

name: str | None = None

Replace the town's name.

description class-attribute instance-attribute

description: str | None = None

Replace the town's description.

services class-attribute instance-attribute

services: tuple[str, ...] | None = None

Replace the town's services list.

travel_turns class-attribute instance-attribute

travel_turns: dict[str, int] | None = None

Replace the dungeon id → town-to-entrance travel cost (in exploration turns) table.

reason instance-attribute

reason: Reason

Why this correction is right; every override entry carries one.

load_overrides

load_overrides(path: Path) -> Overrides

Load an overrides file, or the empty Overrides when it doesn't exist.

Parsing uses a safe loader that rejects duplicate mapping keys; the result is model-validated, so typos and payloads osrlib would reject fail here.

Parameters:

Name Type Description Default
path Path

The overrides.yaml path. A missing file is an empty overrides set.

required

Returns:

Type Description
Overrides

The validated overrides.

Raises:

Type Description
OverrideError

If the document repeats a mapping key — two contradictory corrections, one of which would silently lose.

ValidationError

If the document doesn't match the contract.

YAMLError

If the file is not valid YAML.

Examples:

from pathlib import Path

from osrforge.contracts.overrides import load_overrides

overrides = load_overrides(Path("module.forge/overrides.yaml"))
for name, entry in overrides.monsters.items():
    print(name, entry.template_id, entry.reason)