DESIGN.md §39: config audit — programme codes collide with the c/o/s type namespace

Answers "are any config requirements actively fighting the engine". One is.

§39.1 NEGATIVE (recorded): no room spec in any corpus programme is internally
contradictory. Using shapecurve.leaf_constraints' validated FAIL_THRESHOLD
inversions, every code admits an (area, aspect) satisfying size, width,
proportion and crinkliness at once, and none needs more than one exposed side.
The "estimated targets are mutually unsatisfiable" hypothesis is falsified.

§39.2 SEVERE: Urb's type system is prefix-based (c = circulation, o/s =
outside) and programme codes share that namespace. A code starting with those
letters is silently reinterpreted, with three unannounced consequences:
check_space_counts SKIPS it outright (never required, no missing or too-many
fail); get_space_params returns generic *_circulation/*_outside params before
consulting self.spaces; and is_circulation/is_outside flip, changing value
rate, crinkliness exemption, and whether it supplies daylight to neighbours.

harbor-house is affected (maple-court, health-centre, programme-house are
clean): cr1 "Common Room with Fireplace" has all three declared targets
overridden (size 80.0 -> 0.0/14.0) and is valued at 50/m2 not 300; of x2 and
st1/st2 lose width/proportion and are treated as outside space. 5 of 37 room
instances (14%) are silently optional.

Measured: the two cr1 leaves converged to 32.9 and 17.1 m2 against a declared
80, with no too-many-spaces fail despite count:1; of/st1/st2 are absent from
the result with zero fails. Compounds with §38.2 -- the largest room in the
programme sits on the wrong side of the x6 circulation value gap, so the
objective is paid to shrink it.

Benchmark validity: every harbor-house fail count in this document was
measured against a 32-instance effective programme, not the 37 its config
declares.

Adds experiments/audit_programme_config.py (namespace + satisfiability
reports). Filed homemaker-py-ju3 (P0). No src changes; 336 passed, same 7
pre-existing fixture failures.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MJ84Feep79Hhm3E4zZJmnB
This commit is contained in:
Claude 2026-08-26 08:27:49 +00:00
parent 1b3a0adc2e
commit fd9802e499
No known key found for this signature in database
3 changed files with 299 additions and 0 deletions

View file

@ -1,3 +1,4 @@
{"_type":"issue","id":"homemaker-py-ju3","title":"Programme codes share a namespace with the generic c/o/s type prefixes: 14% of harbor-house is silently optional and cr1's declared targets are all discarded","description":"Urb's type system is prefix-based (a type starting with 'c' is circulation, 'o'/'s' is outside) and programme room codes live in the SAME namespace, so any code whose name happens to start with c, o or s is silently reinterpreted as a generic type. Three separate consequences, none announced anywhere in the output:\\n\\n1. graph.check_space_counts line ~530 does 'if code[0].lower() in (\"c\",\"o\",\"s\"): continue' -- the code is SKIPPED ENTIRELY. Never required, never counted, no missing fail, no too-many fail.\\n2. Fitness.get_space_params returns the generic *_circulation / *_outside params BEFORE consulting self.spaces, so declared size/width/proportion are overridden.\\n3. dom.is_circulation / is_outside become true, changing the leaf's value rate, exempting it from crinkliness, and making it supply daylight to neighbours.\\n\\nharbor-house is affected; maple-court, health-centre and programme-house are namespace-clean.\\n\\n cr1 'Common Room with Fireplace' (c): size 80.0 -\u003e 0.0/14.0, width 6.0 -\u003e 2.4, proportion 2.0 -\u003e 1.5, ALL THREE overridden; is_circulation=True so value_rate 50 not 300.\\n of 'Staff Office' x2 (o): width/proportion overridden; is_outside=True; value_rate 100.\\n st1/st2 'Storage' (s): width/proportion overridden; is_outside AND is_circulation True; value_rate 100.\\n\\n5 of 37 room instances (14%) are silently optional. MEASURED CONSEQUENCE: in a 20k-eval run the two cr1 leaves converged to 32.9 and 17.1 m2 against a declared 80 m2, and produced no too-many-spaces fail despite count:1; of/st1/st2 are absent from the result entirely with zero fails, because nothing ever asked for them. Compounds with homemaker-py-2v1: cr1 is the single largest room in the programme and is classified circulation, so the x6 value gap pays the search to shrink it.","design":"Separate the namespaces. Cleanest is an explicit per-space 'class:' key in patterns.config (inside/circulation/outside) defaulting to inside, with the prefix rule used ONLY for untyped generic leaves the search creates -- programme codes then never collide regardless of spelling. A cheaper stopgap is a load-time validation error in programme.load_programme_dir that refuses a programme code starting with c/o/s, which at least converts a silent misread into a loud one. Renaming harbor's four codes would fix that one programme but leaves the trap armed for the next author.","acceptance_criteria":"A programme declaring a code starting with c/o/s either honours its declared params and count, or fails loudly at load. harbor-house re-baselined against its real 37-instance programme, and every DESIGN.md harbor fail count re-stated or annotated as measured against the 32-instance effective programme.","status":"open","priority":0,"issue_type":"bug","owner":"noreply@anthropic.com","created_at":"2026-08-26T08:26:37Z","created_by":"Claude","updated_at":"2026-08-26T08:26:37Z","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"homemaker-py-2v1","title":"Connectivity is under-priced ~3x against the circulation-\u003ehabitable value gap: the objective is net-positive on severing the spine","description":"Sharper root cause than homemaker-py-ssz, isolated by the 38.6 A/B (which showed none of the three crinkliness modes removes the deletion incentive). Deleting a circulation leaf merges it into its sibling, converting corridor into habitable area. value_circulation=50 vs value_inside=300, so that is a flat 6x value gain. The only counter-pressure is the 'level N not connected' fail, worth 0.5x under value *= 0.5**len(failures). Break-even needs 0.5^k \u003c 50/300, i.e. k \u003e 2.58 -- severing must cost AT LEAST 3 fails to be net-negative, and it costs 1. Net incentive to sever = 6 * 0.5 = 3.0x in favour; measured 4.06x on a well-lit (q_crink=0.736) circulation leaf, so this is NOT the zero-exposure effect and is not fixable inside quality_uncrinkliness. This is the cleanest explanation of why 'level 0 not connected' and 'level 1 not connected' are still present in evolved-3M-nols-3, the best layout found after 1.7M evals: the search is being paid 3-4x to create them.","design":"Options: (a) emit connectivity fails with a multiplicity \u003e= 3 (cheapest, but stacks with the 1i8 cascade-weighting problem and is a magic number); (b) make the connectivity penalty multiplicative and explicit rather than riding the generic 0.5^n (a dedicated building_factor term, sized from the value-rate gap so it tracks value_circulation/value_inside instead of being hard-coded); (c) revalue circulation as infrastructure -- its worth is that it makes other rooms reachable, which the current per-leaf value rate cannot express; the principled version credits circulation with the access it provides rather than its own floor area. (c) is the architecturally correct one and the biggest change. Recommend measuring (a) first purely to confirm the mechanism (does the 3x threshold flip the deletion test?), then designing (b) or (c) properly.","acceptance_criteria":"Deletion test (experiments/diag_exposure_frontage.py value + the ssz A/B harness) shows lit and buried C/O deletions are no longer rewarded; then harbor-house reaches the 15-fail floor in materially fewer than 1.7M evals AND without 'level 0/1 not connected' in the result.","status":"open","priority":0,"issue_type":"bug","owner":"noreply@anthropic.com","created_at":"2026-08-26T07:37:39Z","created_by":"Claude","updated_at":"2026-08-26T07:37:39Z","dependency_count":0,"dependent_count":2,"comment_count":0}
{"_type":"issue","id":"homemaker-py-hxi","title":"Buried circulation and outside space are negative-value: search is rewarded for deleting the circulation spine","description":"Direct consequence of the zero-exposure bug. Measured on a constructed harbor-house seed: deleting a buried O leaf improved the raw score 85x and removed 7 fails; deleting a buried C leaf improved it 62x and removed 6 fails. Programme rooms are held in place ONLY by the missing-space fail cascade, not by contributing value -- deleting a buried k1/da1/m costs +15 fails, so they stay, but nothing positive keeps them. Circulation and outside leaves carry no missing-space requirement, so nothing keeps them at all. Observed live: in a 20k-eval harbor-house run, undivide/core_undivide account for a large share of recorded improvements (16 occurrences in the log) -- the search is literally deleting circulation to score better. This explains three prior negative results as a single mechanism: 18 graded circulation-connectivity (a tie-break signal cannot beat a 60x scalar gradient), 21/22 bridge_circulation (the operator inserts corridor leaves the objective immediately punishes), and the 'level N not connected' hard fails surviving \u003e1M evals in the 3M run.","notes":"Depends on the zero-exposure fix; may need no separate fix if (a)/(c) there restores a value gradient for circulation. Worth re-running the 18 and 21/22 A/Bs afterwards -- both may have been measuring a broken gradient rather than a bad idea.","status":"open","priority":1,"issue_type":"bug","owner":"noreply@anthropic.com","created_at":"2026-08-25T23:14:40Z","created_by":"Claude","updated_at":"2026-08-25T23:14:40Z","dependencies":[{"issue_id":"homemaker-py-hxi","depends_on_id":"homemaker-py-ssz","type":"blocks","created_at":"2026-08-25T23:15:12Z","created_by":"Claude","metadata":"{}"},{"issue_id":"homemaker-py-hxi","depends_on_id":"homemaker-py-2v1","type":"blocks","created_at":"2026-08-26T07:37:47Z","created_by":"Claude","metadata":"{}"}],"dependency_count":2,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"homemaker-py-ssz","title":"Zero-exposure leaves score hard quality=0: the objective assigns no value to any interior room","description":"fitness.quality_uncrinkliness returns a hard 0.0 when a leaf has no daylit wall (area_outside==0 =\u003e crink==0 =\u003e 'if not crink: return 0.0'). Because evaluate_leaf MULTIPLIES factors into quality, and process_storey accumulates 'value += quality * rate * area', such a leaf contributes EXACTLY ZERO value while still adding cost. Measured on the full default construction stack (leaf_sharing, depth_balanced, interior_outside, collapse_insearch), 3 seeds each: harbor-house 46% of interior leaves, health-centre 45%, maple-court 56% are zero-exposure. On a converged 20k-eval harbor-house run (seed 1, 57 fails) 14 of 17 crinkliness fails are zero-exposure, and ~470 m2 of the 721 m2 ground floor plate sits at zero value. This is the mathematically consistent limit of the gaussian (1/crink -\u003e inf), so it is a faithful port, not a porting bug -- but it means the objective's gradient does not describe a good building.","design":"Options, none yet chosen: (a) floor the factor at a small epsilon instead of 0 so buried leaves keep a value gradient and remain rankable; (b) make the gaussian one-sided (clip to 1.0 on the compact side) so being LESS exposed than target is not punished as hard as being over-exposed -- architecturally, a compact well-insulated room is not a defect; (c) exempt circulation/store types from the daylight requirement entirely (uncrinkliness_circulation currently uses the same [5/6, 1.1/3] as habitable rooms, so internal corridors -- completely normal architecture -- are guaranteed failures). Any change here invalidates prior fail-count baselines, so it needs its own A/B and a DESIGN.md section.","acceptance_criteria":"A/B on harbor-house + maple-court at fixed budget showing the chosen variant lowers hard-fail count without inflating soft; DESIGN.md section recording the result; prior baselines re-stated under the new objective.","status":"open","priority":1,"issue_type":"bug","owner":"noreply@anthropic.com","created_at":"2026-08-25T23:14:40Z","created_by":"Claude","updated_at":"2026-08-25T23:14:40Z","dependencies":[{"issue_id":"homemaker-py-ssz","depends_on_id":"homemaker-py-2v1","type":"blocks","created_at":"2026-08-26T07:37:47Z","created_by":"Claude","metadata":"{}"}],"dependency_count":1,"dependent_count":2,"comment_count":0}

View file

@ -5079,3 +5079,88 @@ stagnation) is worth deferring until after `ssz`: an LLM asked to propose a
valley-crossing multi-edit against an objective that pays ×85 to delete the
corridor it just inserted will have its work reverted by the next selection
step.
## 39. Config audit: requirements that actively fight the engine (`homemaker-py-ju3`) — measured 2026-08-25
The corpus `patterns.config` targets and `costs.config` values were estimated
years ago, on the principle that exact values are irrelevant to getting the
evolution engine working. This section asks the inverse question — *do any of
them actively work against it?* — and finds that one does, badly.
Reproduce with `experiments/audit_programme_config.py` (two reports:
namespace collisions, and per-room-spec satisfiability).
### 39.1 Per-room-spec satisfiability — CLEAN (negative result, recorded)
Modelling each leaf as a rectangle of area `A` and aspect `r = w/h ≥ 1`, and
taking the FAIL_THRESHOLD-inverted bounds from the already-validated
`shapecurve.leaf_constraints` (§37.2), no room spec in any of the four corpus
programmes is internally contradictory: for every code there exists an `(A, r)`
satisfying size, width, proportion *and* crinkliness simultaneously, and none
needs more than one exposed side (i.e. none secretly demands a corner). The
hypothesis that the estimated targets are mutually unsatisfiable is
**falsified**, and the audit is kept so the question stays answered.
(One instance of exactly this class was already known and handled:
`get_space_params` derives a width from size and proportion for small
programme spaces rather than falling back to `width_inside` `[4.0, 1.0]`,
"which is impossible for small programme spaces (e.g. a 3 m² WC)".)
### 39.2 Programme codes collide with the generic type namespace — SEVERE
Urb's type system is **prefix-based** — a type starting with `c` is
circulation, `o`/`s` is outside — and programme room codes live in the **same
namespace**. Any code whose name happens to begin with one of those letters is
silently reinterpreted as a generic type. Three independent consequences, none
of them announced anywhere in the output:
1. **`graph.check_space_counts` skips the code entirely** —
`if code[0].lower() in ("c", "o", "s"): continue`. The room is never
required, never counted, and produces neither a missing nor a too-many
failure. **It is optional, silently.**
2. **`Fitness.get_space_params` returns the generic `*_circulation` /
`*_outside` parameters *before* consulting `self.spaces`**, so the declared
size/width/proportion are overridden.
3. **`dom.is_circulation` / `is_outside` become true**, changing the leaf's
value rate, exempting it from crinkliness, and making it supply daylight to
its neighbours.
`harbor-house` — the project's primary benchmark — is affected;
`maple-court`, `health-centre` and `programme-house` are namespace-clean.
| code | name | declared → effective | flags | value rate |
|---|---|---|---|---|
| `cr1` | Common Room with Fireplace | size **80.0 → 0.0/14.0**, width **6.0 → 2.4**, proportion **2.0 → 1.5** (all three) | `is_circulation` | **50** (vs 300) |
| `of` ×2 | Staff Office | width 2.5 → 3.0, proportion — → 1.5/50 | `is_outside` | 100 |
| `st1` | Ground Floor Storage | width 3.0 → 3.0/0.3, proportion — → 1.5/50 | `is_outside` **and** `is_circulation` | 100 |
| `st2` | First Floor Storage | width 3.0 → 3.0/0.3, proportion — → 1.5/50 | `is_outside` **and** `is_circulation` | 100 |
**5 of harbor-house's 37 room instances (14%) are silently optional.**
**Measured consequences**, on the 20 000-eval run of §38:
- The two `cr1` leaves converged to **32.9 m² and 17.1 m² against a declared
80 m²** — and produced **no `too many spaces` failure despite `count: 1`**,
because the count check skips the code.
- `of`, `st1` and `st2` are **absent from the result entirely, with zero
failures**, because nothing ever asked for them.
This compounds directly with §38.2: `cr1` is the single largest room in the
programme, and classifying it as circulation puts it on the wrong side of the
×6 `value_circulation`/`value_inside` gap — so the objective is paid to shrink
the common room, which is exactly what it did.
**Benchmark-validity consequence.** Every harbor-house fail count in this
document was measured against a **32-instance effective programme, not the
37-instance one its config declares**. Cross-programme comparisons involving
harbor-house (§13.9, §13.11, §17, §20, §23, §37.1, §37.7) are internally
consistent but are not measuring the programme as written.
**Fix direction** (`homemaker-py-ju3`): separate the namespaces — an explicit
per-space `class:` key (`inside`/`circulation`/`outside`, defaulting to
`inside`), with the prefix rule used *only* for the untyped generic leaves the
search itself creates. A cheaper stopgap is a load-time validation error in
`programme.load_programme_dir` refusing a code that starts with `c`/`o`/`s`,
which at least converts a silent misread into a loud one. Renaming harbor's
four codes fixes that one programme but leaves the trap armed for the next
author.

View file

@ -0,0 +1,213 @@
"""Per-room-spec satisfiability audit of `patterns.config` targets.
Evidence for `homemaker-py-2v1`/`ssz`/`tdp` (DESIGN.md §38/§39). The corpus
configs were estimated years ago on the principle that exact values do not
matter for getting the engine working. This asks the opposite question: **does
any individual room spec make itself impossible to satisfy?**
For one room code, model the leaf as a rectangle of area ``A`` and aspect
``r = w/h >= 1`` (``h`` is `length_narrowest`, the width metric). The
FAIL_THRESHOLD-inverted bounds come from the already-validated
``shapecurve.leaf_constraints`` (§37.2), so this is not a reimplementation of
the Gaussians:
* size ``amin <= A <= amax``
* width ``h >= wmin`` => ``r <= A / wmin^2``
* proportion ``r <= rmax``
* crinkliness ``L_exposed >= A / (X * height)`` with ``X = 1.6202`` (§38.3)
The last one depends on how much of the leaf's boundary is external, which is a
*placement* property, not a spec property so the audit reports the **minimum
number of exposed sides** each spec needs. A spec needing 2 adjacent sides is
demanding a corner; a rectangular storey has only four corners, so a programme
wanting more corner rooms than the plot has corners is over-subscribed before
the search starts.
Usage::
python experiments/audit_programme_config.py
python experiments/audit_programme_config.py examples/harbor-house --verbose
"""
from __future__ import annotations
import argparse
import math
from pathlib import Path
import numpy as np
import yaml
from homemaker_layout import dom, fitness, programme, shapecurve
from homemaker_layout.dom import Node
# 1/crink bounds from §38.3; recomputed from the live conf, never hard-coded.
def crink_bounds(fit: fitness.Fitness, circulation: bool = False) -> tuple[float, float]:
key = "uncrinkliness_circulation" if circulation else "uncrinkliness"
target, sigma = fit.conf(key)
k = math.sqrt(-2 * sigma * sigma
* math.log(fitness.FAIL_THRESHOLD) / math.log(fitness._E))
return target + k, max(1e-12, target - k)
# Exposure patterns, cheapest first: name -> exposed length given (w, h), w >= h.
EXPOSURE = [
("1 short side", lambda w, h: h),
("1 long side", lambda w, h: w),
("2 adjacent (corner)", lambda w, h: w + h),
("2 opposite long", lambda w, h: 2 * w),
("3 sides", lambda w, h: 2 * h + w),
("4 sides (freestanding)", lambda w, h: 2 * (w + h)),
]
def _synthetic_leaf(code: str) -> Node:
"""A bare typed leaf — ``leaf_constraints`` reads only its type/flags."""
return Node(node=[[0.0, 0.0], [4.0, 0.0], [4.0, 4.0], [0.0, 4.0]], type=code)
def audit_code(fit: fitness.Fitness, code: str, height: float,
grid: int = 240) -> dict:
"""Feasibility of one room spec, and the exposure it needs."""
bounds = shapecurve.leaf_constraints(fit, _synthetic_leaf(code))
amin, amax, wmin, rmax = bounds.amin, bounds.amax, bounds.wmin, bounds.rmax
if not math.isfinite(amax):
amax = max(amin * 4, 200.0)
hi, lo = crink_bounds(fit, circulation=code[:1].lower() == "c")
areas = np.linspace(max(amin, 1e-6), amax, grid)
ratios = np.linspace(1.0, max(rmax, 1.0), grid)
A, R = np.meshgrid(areas, ratios, indexing="ij")
W, H = np.sqrt(A * R), np.sqrt(A / R)
swp = (H >= wmin) & (R <= rmax) # size is satisfied by construction
result = {"code": code, "amin": amin, "amax": amax, "wmin": wmin,
"rmax": rmax, "swp": bool(swp.any()), "needs": None,
"swp_only_at": None}
if not result["swp"]:
return result
# smallest square-ish area that satisfies width at r=1, for the report
result["swp_only_at"] = float(max(amin, wmin * wmin))
for name, length_of in EXPOSURE:
L = length_of(W, H)
crink_ok = (L >= A / (hi * height)) & (L <= A / (lo * height))
if bool((swp & crink_ok).any()):
result["needs"] = name
break
return result
def audit_namespace(progdir: str) -> int:
"""Report programme codes that collide with the generic type prefixes.
Urb's type system is prefix-based — a type starting with ``c`` is
circulation, ``o``/``s`` is outside and programme codes live in the *same
namespace*. So a room code that happens to start with one of those letters
is silently reinterpreted as a generic type. Three separate consequences,
none of them announced anywhere in the output:
1. ``graph.check_space_counts`` **skips the code entirely**
(``if code[0].lower() in ("c", "o", "s"): continue``) the room is never
required, never counted, and never produces a missing/too-many failure.
2. ``Fitness.get_space_params`` returns the generic
``*_circulation``/``*_outside`` parameters *before* consulting the
programme, so declared size/width/proportion are overridden.
3. ``dom.is_circulation``/``is_outside`` become true, changing the leaf's
value rate, its crinkliness treatment, and whether it supplies daylight
to its neighbours.
"""
reqs = programme.load_programme_dir(progdir)
conf, cost = fitness.load_config(progdir)
fit = fitness.Fitness(conf, cost)
spaces = conf.get("spaces") or {}
hits = [c for c in sorted(reqs) if c[:1].lower() in ("c", "o", "s")]
total = sum(r.count for r in reqs.values())
if not hits:
print(f"=== {Path(progdir).name}: namespace clean "
f"({len(reqs)} codes / {total} instances)\n")
return 0
skipped = sum(reqs[c].count for c in hits)
print(f"=== {Path(progdir).name}: {len(hits)} code(s) collide with the "
f"generic c/o/s type prefixes")
print(f" {skipped} of {total} room instances ({100 * skipped / total:.0f}%) "
f"are SILENTLY OPTIONAL — check_space_counts skips them\n")
for code in hits:
spec = spaces.get(code, {})
leaf = _synthetic_leaf(code)
print(f" {code} \"{reqs[code].name}\" (count {reqs[code].count})")
for param in ("size", "width", "proportion"):
declared = spec.get(param)
effective = fit.get_space_params(code, param)
flag = "" if declared == effective else " <-- OVERRIDDEN"
print(f" {param:<11} declared={str(declared):<16} "
f"effective={effective}{flag}")
print(f" is_circulation={dom.is_circulation(leaf)} "
f"is_outside={dom.is_outside(leaf)} "
f"value_rate={fit.value_rate(leaf)} (inside={fit.conf('value_inside')})")
print()
return skipped
def audit(progdir: str, verbose: bool) -> tuple[int, int]:
reqs = programme.load_programme_dir(progdir)
conf, cost = fitness.load_config(progdir)
fit = fitness.Fitness(conf, cost)
seed = yaml.safe_load(open(f"{progdir}/init.dom"))
height = seed.get("height") or 3.0
print(f"=== {Path(progdir).name} (height {height} m)")
hdr = f" {'code':<7}{'count':<7}{'area ok':<18}{'min width':<11}{'max aspect':<12}needs"
print(hdr)
print(" " + "-" * (len(hdr) - 2))
impossible = corner_demand = 0
for code in sorted(reqs) + ["C", "O"]:
count = reqs[code].count if code in reqs else 0
r = audit_code(fit, code, height)
if not r["swp"]:
verdict = "IMPOSSIBLE (size/width/proportion contradict)"
impossible += max(count, 1)
elif r["needs"] is None:
verdict = "IMPOSSIBLE even fully exposed (crinkliness)"
impossible += max(count, 1)
else:
verdict = r["needs"]
if "corner" in verdict or "opposite" in verdict or "3 sides" in verdict \
or "4 sides" in verdict:
corner_demand += max(count, 1)
area_col = "%.1f-%.1f m2" % (r["amin"], r["amax"])
width_col = "%.2f m" % r["wmin"]
aspect_col = "%.2f" % r["rmax"]
count_col = str(count) if count else "-"
print(f" {code:<7}{count_col:<7}{area_col:<18}"
f"{width_col:<11}{aspect_col:<12}{verdict}")
print(f"\n room instances that are impossible as specified : {impossible}")
print(f" room instances requiring >=2 exposed sides : {corner_demand}"
f" (a rectangular storey has 4 corners)\n")
return impossible, corner_demand
def main() -> None:
ap = argparse.ArgumentParser(description=__doc__,
formatter_class=argparse.RawDescriptionHelpFormatter)
ap.add_argument("progdir", nargs="?", default=None)
ap.add_argument("--verbose", action="store_true")
args = ap.parse_args()
dirs = [args.progdir] if args.progdir else [
"examples/harbor-house", "examples/maple-court",
"examples/health-centre", "examples/programme-house"]
print("### namespace collisions (generic c/o/s type prefixes)\n")
for d in dirs:
audit_namespace(d)
print("### per-room-spec satisfiability\n")
for d in dirs:
audit(d, args.verbose)
if __name__ == "__main__":
main()