Skip to content

osrforge.check

The playability lint and smoke delve: findings = check(workdir).

check loads the adventure exactly as a consumer does (check_document + Adventure.model_validate), so every run also exercises the artifact contract, then runs its two tiers and merges the findings into report.json. Everything is deterministic — the delve seed is a module constant — so the purity guarantee extends: assemble && check twice is byte-identical.

Tier 1 is validate_adventure, osrlib's own content gate; its outcome feeds the CLI verdict, not findings — osrlib's errors are strings with an existing report home (validation), and duplicating them as findings would make two sources of truth. Tier 2 is the static graph checks plus the smoke delve.

Three graph flavors over each level's edge map, pinned once: inclusive (open edges plus every door, any state — what a party can traverse in principle), non-secret (secret-door edges become walls), and deterministic (open edges plus plain doors only — no stuck, locked, or secret; what a scripted walk can traverse without probabilistic commands). Transitions are directed edges between cells. The smoke delve walks the deterministic subgraph reactively and the static checks own the rest.

CHECK_SEED module-attribute

CHECK_SEED = 20260709

The pinned delve seed: the party roll-up and every in-play die derive from it.

SEVERITY module-attribute

SEVERITY: Mapping[LintCheck, Literal["error", "warning"]] = {
    LintCheck.EDGE_INVALID: "error",
    LintCheck.AREA_UNREACHABLE: "error",
    LintCheck.ORPHAN_CELL: "warning",
    LintCheck.SECRET_ONLY_ACCESS: "warning",
    LintCheck.TRANSITION_UNPAIRED: "warning",
    LintCheck.DELVE_BLOCKED: "error",
    LintCheck.DELVE_INCOMPLETE: "warning",
}

Each check's severity — the producer's pin, hoisted to one importable table.

Severity is a field on LintFinding rather than a function of the id (the contract needn't change if a check's severity is ever re-judged), so this table is where the producer's judgment lives: every emission site and the generated vocabulary page read it here.

check

check(workdir_path: Path) -> tuple[LintFinding, ...]

Run the two-tier playability check and merge the findings into report.json.

Parameters:

Name Type Description Default
workdir_path Path

The workdir root; adventure.json and report.json must exist (an assembled draft is the input).

required

Returns:

Type Description
LintFinding

The findings, exactly as merged into the rewritten report. A draft

...

that fails tier 1 skips the smoke delve — its findings would be noise

tuple[LintFinding, ...]

— but keeps the static checks.

Raises:

Type Description
ValueError

If adventure.json or report.json is missing — assemble first.

Examples:

from pathlib import Path

from osrforge import check

for finding in check(Path("module.forge")):
    print(finding.severity, finding.id, finding.location, finding.message)