osrforge.geometry
Stage 5: deterministic geometry synthesis from the extracted room graph, map proposals reconciled.
Deterministic code only; recomputed inside every assembly (no cache),
completing the run.json geometry entry. Input: the survey index, the
levels' content caches, and (when present) the map-reading cache — merged
with the prose connections through osrforge.reconcile,
the shared policy the eval scorer consumes too; output: a frozen per-level
result (area cell clusters, the edges dict, entrance, transitions,
width/height, the dispute channels) that assembly folds into osrlib
LevelSpecs and map_disputed flags.
The impassability hazard, defused here: osrlib grids are walls by default —
an edge absent from LevelSpec.edges is a wall, and the boundary is an
implicit wall — so synthesis emits open edges between every orthogonally
adjacent pair of same-area cells, along every corridor path, and at every
room-corridor junction. A connection whose stated mechanism is a door kind
realizes as a door edge on the stating area's wall; overrides remain the
last word.
Every choice below is pinned for determinism: BFS visit order, candidate placement order, component ordering, edge-key ordering, and row-major cell sorting — the byte-stability tests rely on all of them.
DEFAULT_ROOM_CELLS
module-attribute
The room size in cells when an area's description states no dimensions.
LevelGeometry
dataclass
LevelGeometry(
dungeon_id: str,
level_number: int,
width: int,
height: int,
areas: dict[str, tuple[Position, ...]],
corridors: tuple[Position, ...],
edges: dict[str, Edge],
entrance: Position | None,
transitions: tuple[TransitionSpec, ...],
unresolved_connections: tuple[tuple[str, str], ...],
unknown_direction_connections: tuple[tuple[str, str], ...],
disconnected_areas: tuple[str, ...],
guessed_transitions: tuple[tuple[str, str], ...],
map_disputes: tuple[tuple[str, str], ...],
map_dropped: tuple[str, ...],
)
One level's synthesized geometry, ready for LevelSpec assembly.
Attributes:
| Name | Type | Description |
|---|---|---|
dungeon_id |
str
|
The canonical dungeon id. |
level_number |
int
|
The 1-based level number. |
width |
int
|
The grid width (bounding box). |
height |
int
|
The grid height (bounding box). |
areas |
dict[str, tuple[Position, ...]]
|
Area key → cell cluster, in survey order; cells sorted row-major (y, then x). |
corridors |
tuple[Position, ...]
|
Corridor cells (cells in no area), sorted row-major. |
edges |
dict[str, Edge]
|
The |
entrance |
Position | None
|
The entrance area's first cell on the dungeon's entrance
level; |
transitions |
tuple[TransitionSpec, ...]
|
This level's transition specs, in link derivation order (stairs reciprocal, trapdoors and chutes one-way). |
unresolved_connections |
tuple[tuple[str, str], ...]
|
|
unknown_direction_connections |
tuple[tuple[str, str], ...]
|
|
disconnected_areas |
tuple[str, ...]
|
Area keys joined by a synthetic component link — assembly flags them not connected to the entrance. |
guessed_transitions |
tuple[tuple[str, str], ...]
|
|
map_disputes |
tuple[tuple[str, str], ...]
|
|
map_dropped |
tuple[str, ...]
|
Module-scope dropped-proposal details (unresolvable
keys, self-pairs, secondary entrance proposals), each carrying
its level address — assembly emits them verbatim as module-scope
|
unknown_direction_connections
instance-attribute
edge_sort_key
The pinned edge-map key ordering — (y, x, side) over canonical keys.
Synthesis emits its edge map in this order, and override application
re-sorts the merged map with it, so the serialized edges dict stays
byte-stable regardless of where an edge came from.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key
|
str
|
A canonical edge key, |
required |
Returns:
| Type | Description |
|---|---|
tuple[int, int, str]
|
The sort key. |
parse_dimensions
Parse an area's stated dimensions into a cell-count rectangle.
The first match of a feet-by-feet pattern (30' x 40', a multiplication
sign, 30 feet by 40 feet) wins; at least one unit marker is required.
Cells are
ceil(feet / 10) per axis (the pinned 10' cell), minimum 1; the first
number is width (east-west), the second height (north-south).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
description
|
str
|
The area's extracted description. |
required |
Returns:
| Type | Description |
|---|---|
tuple[int, int] | None
|
|
synthesize_geometry
synthesize_geometry(
index: SurveyIndex, levels: Sequence[LevelContent], map_reading: MapReading | None = None
) -> tuple[LevelGeometry, ...]
Synthesize every level's geometry, in survey order, map proposals reconciled.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
index
|
SurveyIndex
|
The normalized survey index. |
required |
levels
|
Sequence[LevelContent]
|
The available content caches; a level absent here gets default-sized rooms and no connections. Assembly and the preview path both enforce cache completeness upstream — the tolerance serves direct callers (tests, future partial-cache paths). |
required |
map_reading
|
MapReading | None
|
The map-reading cache, or |
None
|
Returns:
| Type | Description |
|---|---|
tuple[LevelGeometry, ...]
|
One result per survey level, in survey order, postconditions asserted. |
transition_via
Narrow a mention's via to a transition family: trapdoors and chutes as themselves, else stairs.
Public because the eval scorer's transition family narrows each mention
through this exact function (the usable_stat_block
precedent: the metric shares geometry's own predicate so the two can
never disagree).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
via
|
str
|
A connection mention's stated mechanism. |
required |
Returns:
| Type | Description |
|---|---|
str
|
|