Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 58 additions & 8 deletions qa/test_generate_town.py
Original file line number Diff line number Diff line change
Expand Up @@ -217,18 +217,68 @@ def test_plates_fragment_pins_carry_the_full_camera_contract(tmp_path):


def test_dressing_bars_fail_loud_when_a_pass_places_nothing():
"""codex P2 pair (#1611): a narrow/dense crop can defeat dress_focal (returns silently) or leave
dress_tall_anchors no connectivity-safe pair AFTER focal placement — either silent miss re-ships
the exact drift/generic class the dressing exists to fix. check_dressing_bars is the emit-time
"""codex P2 pair (#1611) + beacon geometry (#1618): a narrow/dense crop can defeat dress_focal
(returns silently), leave dress_tall_anchors no connectivity-safe pair AFTER focal placement, or
leave the fire beacons short/collinear — any silent miss re-ships the exact drift/generic/
degenerate-plate class the dressing exists to fix. check_dressing_bars is the emit-time
enforcement generate_town folds into its self-gate (escape hatch: --allow-undressed)."""
base = {"cols": 5, "rows": 5, "door_cells": [[0, 2]], "walls": [],
base = {"cols": 6, "rows": 6, "door_cells": [[0, 2]], "walls": [],
"props": [{"id": "w", "kind": "wall_run", "cells": [[0, 0]]}]}
both_missing = d2f.check_dressing_bars(dict(base, props=list(base["props"])), name="bare")
assert len(both_missing) == 2 and any("tall" in f for f in both_missing) and any("focal" in f for f in both_missing)
all_missing = d2f.check_dressing_bars(dict(base, props=list(base["props"])), name="bare")
# three bars: tall mass, focal presence, beacon geometry (>=3 non-collinear fire)
assert len(all_missing) == 3
assert any("tall" in f for f in all_missing)
assert any("focal" in f for f in all_missing)
assert any("beacon" in f for f in all_missing)
tall_only = dict(base, props=base["props"] + [
{"id": "a", "kind": "pillar", "cells": [[2, 2], [2, 3]]}])
fails = d2f.check_dressing_bars(tall_only, name="tallonly")
assert len(fails) == 1 and "focal" in fails[0]
# tall bar met; focal + beacon still fail (no fire at all)
assert len(fails) == 2 and any("focal" in f for f in fails) and any("beacon" in f for f in fails)
# fully dressed: pillar (tall) + THREE non-collinear braziers (focal + beacon geometry)
dressed = dict(base, props=tall_only["props"] + [
{"id": "b", "kind": "brazier", "cells": [[3, 2]]}])
{"id": "b0", "kind": "brazier", "cells": [[1, 1]]},
{"id": "b1", "kind": "brazier", "cells": [[4, 1]]},
{"id": "b2", "kind": "brazier", "cells": [[2, 4]]}])
assert d2f.check_dressing_bars(dressed, name="ok") == []


def test_beacon_geometry_bar_needs_three_non_collinear_fire():
"""#1618: the focal braziers double as plate-registration beacons. The beacon-geometry bar fails
a room with <3 fire beacons (a 2-point similarity fit is vertical-scale-blind) OR 3+ fire beacons
that are collinear (zero triangle area — same failure in disguise); it passes only a real triangle
with area >= _FIRE_TRI_MIN_AREA. A `pillar` clears the tall bar; braziers clear the focal bar, so
each case isolates the beacon bar."""
base = {"cols": 8, "rows": 8, "door_cells": [], "walls": [],
"props": [{"id": "a", "kind": "pillar", "cells": [[3, 3], [3, 4]]}]}
two_fire = dict(base, props=base["props"] + [
{"id": "b0", "kind": "brazier", "cells": [[1, 1]]},
{"id": "b1", "kind": "brazier", "cells": [[5, 1]]}])
f2 = d2f.check_dressing_bars(two_fire, name="two")
assert any("beacon" in f and "<3" in f for f in f2), f2
collinear = dict(base, props=base["props"] + [
{"id": "b0", "kind": "brazier", "cells": [[1, 1]]},
{"id": "b1", "kind": "brazier", "cells": [[3, 1]]},
{"id": "b2", "kind": "brazier", "cells": [[5, 1]]}]) # all on row 1 -> area 0
fc = d2f.check_dressing_bars(collinear, name="coll")
assert any("beacon" in f and "COLLINEAR" in f for f in fc), fc
non_collinear = dict(base, props=base["props"] + [
{"id": "b0", "kind": "brazier", "cells": [[1, 1]]},
{"id": "b1", "kind": "brazier", "cells": [[5, 1]]},
{"id": "b2", "kind": "brazier", "cells": [[3, 5]]}]) # triangle area 8.0
assert d2f._best_tri_area([(1, 1), (5, 1), (3, 5)]) >= d2f._FIRE_TRI_MIN_AREA
assert d2f.check_dressing_bars(non_collinear, name="ok") == []


def test_every_room_has_three_non_collinear_fire_beacons(town):
"""#1618: the dwing recovery proved the focal braziers double as plate-registration beacons; two
on the same row make the 2-point similarity fit blind to vertical scale (room_1's plate was
unfixable by warping). Every generated room must carry >=3 fire-kind props (brazier/campfire)
whose best triangle area >= _FIRE_TRI_MIN_AREA, so the plate solve is observable in both axes with
residual redundancy."""
for rid, geo in town.items():
fire = d2f._fire_cells(geo)
assert len(fire) >= 3, f"{rid}: only {len(fire)} fire beacons {fire}"
area = d2f._best_tri_area(fire)
assert area >= d2f._FIRE_TRI_MIN_AREA, (
f"{rid}: fire beacons near-collinear, best triangle area {area} < {d2f._FIRE_TRI_MIN_AREA}")
98 changes: 92 additions & 6 deletions tools/dungen_to_fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,39 @@ def _door_landing(cell: tuple, cols: int, rows: int) -> tuple:


_FOCAL_KINDS = {"altar", "stone_well", "brazier", "sarcophagus", "campfire", "hearth"}
# Fire-BEARING focal kinds — the only ones qa/overlay_boxes.py::blob_solve detects as registration
# beacons (bright fire bowls); an `altar` is a focal narrative mass but casts NO fire blob, so it does
# NOT count toward the beacon geometry (#1618).
_FIRE_KINDS = {"brazier", "campfire"}
# Min area (cell²) of the triangle formed by a room's three fire beacons. Two beacons on the same row
# are collinear (zero area): the 2-point plate-registration similarity fit is then blind to vertical
# scale (dwing room_1: a 0.07-cell beacon error coexisted with ~1.5 cells of bottom-wall misfit). A
# real triangle makes the solve both-axes-observable with residual redundancy (3 pts = 6 constraints
# vs 4 dof). 2.0 is comfortably above the sub-cell jitter of adjacent placements.
_FIRE_TRI_MIN_AREA = 2.0


def _tri_area(a: tuple, b: tuple, c: tuple) -> float:
"""Absolute area (cell²) of the triangle on three (col, row) cells — shoelace. 0.0 when collinear."""
(ax, ay), (bx, by), (cx, cy) = a, b, c
return abs((bx - ax) * (cy - ay) - (cx - ax) * (by - ay)) / 2.0


def _fire_cells(geo: dict) -> list:
"""Representative (col, row) cell per fire-bearing prop (its first/anchor cell — every dressed fire
beacon is a single-cell footprint, so this is exact)."""
return [tuple(p["cells"][0]) for p in geo.get("props", [])
if p.get("kind") in _FIRE_KINDS and p.get("cells")]


def _best_tri_area(cells: list) -> float:
"""Largest triangle area (cell²) over any three of `cells` (0.0 when fewer than three)."""
best = 0.0
for i in range(len(cells)):
for j in range(i + 1, len(cells)):
for k in range(j + 1, len(cells)):
best = max(best, _tri_area(cells[i], cells[j], cells[k]))
return best


def dress_focal(geo: dict, *, name: str = "room") -> dict:
Expand All @@ -344,7 +377,15 @@ def dress_focal(geo: dict, *, name: str = "room") -> dict:
two BRAZIERS by the crossing centre. Fire doubles as the paint stage's warm-core chiaroscuro
anchor (the scorers' own lever) and the runtime's animated-VFX anchor. Skipped when the room
already carries any focal kind. Same safety machinery as dress_tall_anchors: deterministic
grid-derived candidates, never on/adjacent to a door landing, flood-fill connectivity-verified."""
grid-derived candidates, never on/adjacent to a door landing, flood-fill connectivity-verified.

v2 (#1618): every plan's two lane/shrine braziers share a ROW — a collinear fire pair, which the
2-point plate-registration similarity solve reads as vertical-scale-blind (dwing room_1's plate is
unfixable by warping for exactly this reason). So after the plan lands, author a THIRD fire beacon
(a corner watch-brazier) at the connectivity-safe cell that MAXIMISES the three-beacon triangle
area, guaranteeing a both-axes-solvable, residually-redundant beacon field. check_dressing_bars
then enforces >=3 fire kinds with best triangle area >= _FIRE_TRI_MIN_AREA, so a crop that defeats
the placement fails LOUD rather than shipping a degenerate plate."""
interior = [p for p in geo.get("props", []) if p.get("kind") != "wall_run"]
if any(p.get("kind") in _FOCAL_KINDS for p in interior):
return geo
Expand Down Expand Up @@ -407,14 +448,46 @@ def shapes(ideal: tuple, footprint: int) -> list:
("focal_brazier_e", "brazier", 1, (cols // 2 + 2, rows // 2))]

used: set = set()
fire_cells: list = []
for pid, kind, footprint, ideal in plan:
for cells in shapes(ideal, footprint):
if any(cell in used for cell in cells):
continue
if connectivity_ok(used | set(cells)):
geo["props"].append({"id": pid, "kind": kind, "cells": [list(c) for c in cells]})
used |= set(cells)
if kind in _FIRE_KINDS:
fire_cells.append(cells[0])
break

# THIRD fire beacon (#1618): break the collinear lane pair. Among all connectivity-safe interior
# candidate cells, take the one that MAXIMISES the min triangle area against every placed fire
# pair (with two beacons down that is a single triangle); ties broken by (row, col) for
# determinism. Max-area naturally lands it toward a far corner (the narrative "corner
# watch-brazier"). We place the best candidate even if its area is under the bar — a genuinely
# degenerate crop then trips check_dressing_bars and fails loud, rather than silently shipping.
if len(fire_cells) >= 2:
best = None # (-min_area, r, c, cell)
for c in range(1, cols - 1):
for r in range(1, rows - 1):
cell = (c, r)
if (cell in used or cell in doors or cell in landing_block
or cell not in base_free):
continue
if not connectivity_ok(used | {cell}):
continue
min_area = min(_tri_area(fa, fb, cell)
for i, fa in enumerate(fire_cells)
for fb in fire_cells[i + 1:])
key = (-min_area, r, c, cell)
if best is None or key < best:
best = key
if best is not None:
_neg, r, c, cell = best
geo["props"].append({"id": "focal_brazier_n", "kind": "brazier", "cells": [[c, r]]})
used |= {cell}
fire_cells.append(cell)

if used:
wall_cells = {tuple(c) for c in geo.get("walls", [])}
prop_cells = {tuple(c) for p in geo.get("props", []) if p.get("kind") != "wall_run"
Expand All @@ -425,13 +498,16 @@ def shapes(ideal: tuple, footprint: int) -> list:


def check_dressing_bars(geo: dict, *, name: str = "room") -> list:
"""Emit-time enforcement of the two DRESSING bars the generator promises (codex P2 pair,
#1611): (1) FLAT-INTERIOR bar — at least one interior prop with authored height >=
"""Emit-time enforcement of the three DRESSING bars the generator promises (codex P2 pair, #1611;
beacon geometry #1618): (1) FLAT-INTERIOR bar — at least one interior prop with authored height >=
_ANCHOR_MIN_TALL (a narrow crop can leave dress_tall_anchors no connectivity-safe pair AFTER
focal placement); (2) BEAUTY-FLOOR bar — at least one _FOCAL_KINDS prop (dress_focal returns
silently when every candidate is rejected). Returns failure strings; empty == both bars met.
A silent miss here would re-ship the exact drift/generic classes the dressing exists to fix —
the generator must fail LOUD instead so the crop gets deliberate attention."""
silently when every candidate is rejected); (3) BEACON-GEOMETRY bar — at least three _FIRE_KINDS
props whose best triangle area >= _FIRE_TRI_MIN_AREA, so the plate-registration solve is
both-axes-observable (two same-row braziers register in one axis only). Returns failure strings;
empty == all three bars met. A silent miss here would re-ship the exact drift/generic/degenerate-
plate classes the dressing exists to fix — the generator must fail LOUD instead so the crop gets
deliberate attention."""
fails = []
interior = [p for p in geo.get("props", []) if p.get("kind") != "wall_run"]
tallest = max((_KIND_HEIGHT.get(p.get("kind"), 0.0) for p in interior), default=0.0)
Expand All @@ -441,6 +517,16 @@ def check_dressing_bars(geo: dict, *, name: str = "room") -> list:
if not any(p.get("kind") in _FOCAL_KINDS for p in interior):
fails.append(f"{name}: NO narrative focal prop ({sorted(_FOCAL_KINDS)}) — beauty-floor bar; "
"focal placement found no connectivity-safe cell")
fire = _fire_cells(geo)
if len(fire) < 3:
fails.append(f"{name}: only {len(fire)} fire beacon(s) (<3) — beacon-geometry class; the "
"2-point plate-registration fit is vertical-scale-blind without a third beacon")
else:
area = _best_tri_area(fire)
if area < _FIRE_TRI_MIN_AREA:
fails.append(f"{name}: fire beacons COLLINEAR (best triangle area {area:.2f} < "
f"{_FIRE_TRI_MIN_AREA}) — beacon-geometry class; a same-row/near-collinear "
"beacon field is blind to vertical scale in the similarity solve")
return fails


Expand Down
Loading