Skip to content

osrforge.reconcile

Deterministic reconciliation of prose edge facts with map proposals, plus the shared entrance selection.

Pure code consumed by both geometry synthesis and the eval scorer — the shared-predicate precedent (transition_via, usable_stat_block) extended from a function to the whole policy, so the two consumers can never merge or pick differently. The shared code owns the map-versus-prose policy only — the prose side of the merge stays each consumer's own derivation (geometry's _GraphEdge merge, the scorer's edge-fact dedup), including their recorded door-kind asymmetry on two-mention edges.

The precedence rule, the doctrine extended across evidence sources: a stated prose fact survives any map conflict, flagged; a prose absence fills from the map, flagged as adopted; agreement is silent. The door: "none" corner, pinned: a proposed pair's door reading is complete (the truth convention's own posture), so a prose-stated door on a pair the map proposes with door: "none" is a presence disagreement — prose survives, flagged — while prose with no stated door on that pair agrees silently, whatever its non-door mechanism (the map's vocabulary doesn't carry mechanisms).

EntranceSelection dataclass

EntranceSelection(
    level_number: int | None,
    area_key: str | None,
    provenance: Literal["map", "heuristic"],
    dispute: str | None,
    dropped: tuple[tuple[int, str], ...],
)

One dungeon's entrance selection — the single source geometry and the scorer consume.

Attributes:

Name Type Description
level_number int | None

The selected entrance level, or None when the dungeon has no non-empty level.

area_key str | None

The selected entrance area's canonical key, or None with level_number.

provenance Literal['map', 'heuristic']

map when a resolvable map proposal won, else heuristic (the positional pick, or nothing to select).

dispute str | None

The both-picks detail when the map candidate and the heuristic differ (entrance map <key>, survey-order <key>), or None — flagged on the selected entrance area.

dropped tuple[tuple[int, str], ...]

(level number, detail) per dropped proposal — unresolvable keys and secondary resolvable proposals — the no-silent-proposals rule's channel.

level_number instance-attribute

level_number: int | None

area_key instance-attribute

area_key: str | None

provenance instance-attribute

provenance: Literal['map', 'heuristic']

dispute instance-attribute

dispute: str | None

dropped instance-attribute

dropped: tuple[tuple[int, str], ...]

MergedLevelEdges dataclass

MergedLevelEdges(
    adopted_doors: Mapping[frozenset[str], Literal["door", "secret_door"]],
    map_only: tuple[tuple[str, str, Literal["door", "secret_door"] | None], ...],
    disputes: tuple[tuple[str, str], ...],
    dropped: tuple[str, ...],
)

One level's merge outcome, in the shapes both consumers map from.

Attributes:

Name Type Description
adopted_doors Mapping[frozenset[str], Literal['door', 'secret_door']]

Undirected pair → the map door kind adopted onto an existing prose edge whose mentions stated no mechanism.

map_only tuple[tuple[str, str, Literal['door', 'secret_door'] | None], ...]

(first, second, door kind or None) per adopted map-only pair, endpoints ordered survey-order-first, in proposal order.

disputes tuple[tuple[str, str], ...]

(survey-order-first endpoint key, detail) per disagreement and adoption, the flag emission home's addressing.

dropped tuple[str, ...]

Bare details for dropped proposals (unresolvable endpoints, self-pairs) — the caller prefixes its level address.

adopted_doors instance-attribute

adopted_doors: Mapping[frozenset[str], Literal['door', 'secret_door']]

map_only instance-attribute

map_only: tuple[tuple[str, str, Literal['door', 'secret_door'] | None], ...]

disputes instance-attribute

disputes: tuple[tuple[str, str], ...]

dropped instance-attribute

dropped: tuple[str, ...]

ProseEdge dataclass

ProseEdge(stated_via: str | None = None, door_kind: Literal['door', 'secret_door'] | None = None)

One undirected prose edge's merged facts, as the consumer derived them.

Attributes:

Name Type Description
stated_via str | None

The merged stated (non-passage) mechanism, or None — the absence test, matching geometry's: via == "passage" on every mention is the only absence.

door_kind Literal['door', 'secret_door'] | None

The merged prose door kind (door / secret_door), or None when the prose states no door. Each consumer's own merge supplies it — the recorded first-stated vs more-specific asymmetry stays put.

stated_via class-attribute instance-attribute

stated_via: str | None = None

door_kind class-attribute instance-attribute

door_kind: Literal['door', 'secret_door'] | None = None

merge_level_edges

merge_level_edges(
    prose: Mapping[frozenset[str], ProseEdge], reading: MapLevelReading | None, level_keys: Sequence[str]
) -> MergedLevelEdges

Merge one level's prose edge facts with its map proposals under the precedence rule.

Proposal normalization first: each endpoint resolves exactly-then-slug against level_keys; unresolvable endpoints and self-pairs drop into dropped with a detail; duplicate proposals of one pair dedup with the more-specific door winning (secret_door beats door beats none — the scorer's precedent). Then, per undirected pair, the enumerated rule:

  • agreement → silent;
  • a map door onto a prose edge with no stated mechanism → adopted, flagged;
  • a prose-stated door (or kind, or non-door mechanism) contradicted by the map → prose survives, flagged;
  • a prose edge absent from a read map → kept, flagged;
  • a map-only pair → adopted as an edge (plus its door), flagged.

An unread (or absent) reading produces no adoptions and no disagreements — the prose-only path, bit for bit.

Parameters:

Name Type Description Default
prose Mapping[frozenset[str], ProseEdge]

Undirected pair → the consumer's own merged prose fact.

required
reading MapLevelReading | None

The level's map reading, or None (no cache — a pre-phase-11 workdir).

required
level_keys Sequence[str]

The level's canonical keys in survey order — the resolution universe and the endpoint-ordering authority.

required

Returns:

Type Description
MergedLevelEdges

The merge outcome; every field empty when the level is unread.

resolve_key

resolve_key(raw: str, level_keys: Sequence[str]) -> str | None

Resolve a stated key against one level's canonical keys: exact match, else slug match.

The endpoint-resolution rule, public for the shared-predicate reason: geometry resolves prose connection targets and this module resolves map proposal endpoints through the same function, so the two evidence sources can never resolve one printed spelling differently.

Parameters:

Name Type Description Default
raw str

The key as stated (a prose to_key or a map proposal endpoint).

required
level_keys Sequence[str]

The level's canonical keys.

required

Returns:

Type Description
str | None

The resolved canonical key, or None.

select_entrance

select_entrance(dungeon: SurveyDungeon, readings: Mapping[int, MapLevelReading]) -> EntranceSelection

Select one dungeon's entrance: a resolvable map proposal wins over the positional heuristic.

The heuristic pick is the first listed area of the lowest-numbered non-empty level — geometry's zero-evidence guess, kept as the fallback. The map candidate is the first resolvable entrance_key in level-number order over the dungeon's read levels, resolved exactly-then-slug against its own level's keys; evidence beats position, so a present candidate wins, with a dispute detail when the two differ. An unresolvable proposal drops with map names '<key>'; the survey does not; a secondary resolvable proposal (another level also labeling a way in) is neither adopted nor discarded silently — it drops with entrance also proposed <level>/<key>, a reviewable oddity.

Parameters:

Name Type Description Default
dungeon SurveyDungeon

The surveyed dungeon.

required
readings Mapping[int, MapLevelReading]

Level number → the level's map reading, for this dungeon.

required

Returns:

Type Description
EntranceSelection

The selection, provenance, and dispute channels.