A corridor may be corridor-shaped: no aspect cap on circulation

Owner's ruling: "there should be no cap on the proportion of a corridor,
especially for big buildings, the crinkliness rule is there to prevent these
becoming unpleasant spaces."

proportion_circulation was [1.5, 0.5], failing a corridor above aspect 2.57 --
at the 1.97 m minimum width the width factor allows, a corridor 5.1 m long; at
the 2.4 m target width, 6.2 m. The corpus shows the consequence: median
circulation leaf 14.3 m2 at aspect 1.67, a stubby room rather than a corridor.

The justification checks out arithmetically. The unpleasant space the cap was
standing in for is a long BURIED corridor -- and crinkliness already sends that
to zero, since a buried leaf has crink == 0. A long corridor along a facade
scores 0.90. Aspect cannot tell those two apart; crinkliness can, so the cap was
duplicating a rule that already exists and does the job better.

Shipped: proportion_circulation = None (no aspect requirement).
quality_proportion returns 1.0 for circulation, and shapecurve.leaf_constraints
yields rmax = inf so the DP agrees with the fitness instead of pruning
topologies the objective would accept. A habitable room's aspect target is
untouched. The narrow side still holds -- width_circulation keeps a corridor
>= 1.97 m, and "edge too long" still caps a single wall at 8 m.

Unlike 39.14/39.18/39.19 this DOES change the fail set, which is the point.
Across the twelve baseline artefacts it removes exactly 7 corridor proportion
fails and adds none: harbor 33/43/42 -> 33/42/40, maple 54/73/55 -> 54/71/54,
health-centre 4/9/5 -> 3/9/5, programme-house unchanged.

Also recorded in 39.22, and a retraction: hxi was titled "search is rewarded
for deleting the circulation spine", which 39.8 had already measured and
refuted -- 0 of the 7 connectivity-breaking deletions sampled were rewarded. My
own earlier comment on the bead restated that retracted claim; corrected, and
the bead retitled.

What binds next: removing the cap roughly doubles a corridor leaf's reach, from
proportion at ~6.2 m to size_circulation at 12.5 m (2.4 m wide hits the 30 m2
fail edge there). size_circulation's target area is ZERO, the other half of the
double-charge -- circulation priced as overhead once in value_circulation = 50
and again in a size factor whose optimum is non-existence. Not changed: it is a
distinct parameter with its own rationale, unruled, and 39.16 is a standing
reminder about inherited constants.

415 passed.

Refs homemaker-py-hxi.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MJ84Feep79Hhm3E4zZJmnB
This commit is contained in:
Claude 2026-09-06 12:56:39 +00:00
parent 32973cfe95
commit 9a8852a82b
No known key found for this signature in database
4 changed files with 199 additions and 3 deletions

View file

@ -7814,3 +7814,88 @@ Next: `homemaker-py-hxi` — circulation at 0.07 return against a room's 0.66,
which the owner has ruled needs fixing — and the corpus re-baseline which the owner has ruled needs fixing — and the corpus re-baseline
(`homemaker-py-bk9`), which §39.19 made necessary and which every future number (`homemaker-py-bk9`), which §39.19 made necessary and which every future number
depends on. depends on.
### 39.22 A corridor may be corridor-shaped (`homemaker-py-hxi`)
Asked whether the scoring of outdoor and circulation space is likely correct,
the answer for circulation is no — but **not** for the reason the bead carried.
**First, a retraction of the bead's own premise.** `hxi` was titled "search is
rewarded for deleting the circulation spine". §39.8 already measured that and
found the opposite: of 64 sampled deletions across harbor and maple, 7 broke
connectivity and **0 of those 7 were rewarded** (×0.00 to ×0.58). Severing costs
one or two connectivity fails plus the cascade behind them, which outweighs the
×6 value gain. The low value rate does not translate into the spine being
deleted. (A comment on the bead restating the retracted claim has been
corrected, and the bead retitled.)
**What the parameters actually say.** Over the 111 circulation leaves in the
twelve baseline runs, per leaf:
| factor | mean | fails |
|---|---|---|
| perpendicular | 0.982 | 0/111 |
| proportion | 0.710 | 7/111 |
| size | 0.539 | 16/111 |
| width | 0.772 | 2/111 |
| crinkliness | 0.485 | 29/111 |
| access | 1.000 | 0/111 |
Median corridor 14.3 m², median aspect **1.67** — a stubby room, not a
corridor. Two parameters explain that, and both are the double-charge shape
§39.14 and §39.18 found elsewhere:
1. **`proportion_circulation = [1.5, 0.5]`** caps aspect at 2.57. At the 1.97 m
minimum width the width factor allows, that is a corridor **5.1 m long**; at
the 2.4 m target width, 6.2 m. A corridor is long and thin by nature, and
the bigger the building the longer the spine must be.
2. **`size_circulation = [0.0, 14.0]`** — the target area of a corridor is
**zero**, so a median 14.3 m² corridor scores 0.592 on size purely for
existing. Circulation is then priced as overhead twice: once in
`value_circulation` = 50 (a sixth of a room) and again in a size factor
whose optimum is non-existence.
**Owner's ruling on the first:** *there should be no cap on the proportion of a
corridor, especially for big buildings, the crinkliness rule is there to
prevent these becoming unpleasant spaces.*
That justification is exactly right, and it is checkable. The unpleasant space
the aspect cap was being used to prevent is a long *buried* corridor — and
crinkliness already sends that to zero, because a buried leaf has `crink == 0`.
A long corridor *along a facade* is a perfectly good corridor, and crinkliness
scores it **0.90**. The cap was standing in for a rule that already exists and
does the job better, since it distinguishes the two cases where aspect cannot.
**Shipped:** `proportion_circulation = None`, meaning no aspect requirement.
`quality_proportion` returns 1.0 for circulation; `shapecurve.leaf_constraints`
yields `rmax = inf` so the DP agrees with the fitness rather than pruning
topologies the objective would accept. A habitable room's aspect target is
untouched — the ruling is about corridors.
The narrow side is still held: `width_circulation` keeps a corridor ≥ 1.97 m
wide, and `edge too long` still caps any single wall at 8 m.
**Unlike §39.14/§39.18/§39.19, this changes the fail set** — that is the point
of it. Across the twelve baseline artefacts it removes exactly **7** corridor
proportion fails and adds none:
| programme | fails before → after |
|---|---|
| harbor-house | 33/43/42 → 33/42/40 |
| maple-court | 54/73/55 → 54/71/54 |
| health-centre | 4/9/5 → 3/9/5 |
| programme-house | 1/1/1 → unchanged |
**What binds next, and it is worth knowing before reading too much into this.**
Removing the aspect cap roughly doubles how long a single corridor leaf may be:
the binding constraint moves from proportion at ~6.2 m to `size_circulation` at
**12.5 m** (a 2.4 m wide leaf reaches the 30 m² fail edge there). A longer spine
is still representable as several corridor leaves in series, so this is a real
relaxation rather than a cosmetic one — but the zero-area size target is still
there, and it is the other half of the double-charge above. It has **not** been
changed: it is a distinct parameter with its own rationale (circulation is
overhead, minimise it), the owner has not ruled on it, and §39.16 is a standing
reminder about how often an inherited constant has turned out to be right.
Whether any of this helps the search is unmeasured and, as with everything since
§39.19, gated on the re-baseline (`homemaker-py-bk9`).

View file

@ -233,7 +233,17 @@ CONF_DEFAULTS: dict = {
"size_circulation": [0.0, 14.0], "size_circulation": [0.0, 14.0],
"size_inside": [16.0, 3.5], "size_inside": [16.0, 3.5],
"proportion_outside": [1.5, 50], "proportion_outside": [1.5, 50],
"proportion_circulation": [1.5, 0.5], # homemaker-py-hxi (DESIGN.md §39.22). Was [1.5, 0.5], which fails a
# corridor above aspect 2.57 -- at the 1.97 m minimum width the width factor
# allows, that is a corridor 5.1 m long. A corridor is long and thin by
# nature, and the longer the building the longer the spine has to be.
#
# `None` means NO proportion requirement. What stops a corridor becoming an
# unpleasant space is crinkliness, not aspect: a long buried corridor has
# crink == 0 and fails outright, while a long one along a facade scores
# 0.90 -- which is the right answer for both. Width still holds the
# narrow side (>= 1.97 m) and `edge too long` still caps a single wall.
"proportion_circulation": None,
"proportion_inside": [1.5, 0.5], "proportion_inside": [1.5, 0.5],
"width_outside": [3.0, 0.3], "width_outside": [3.0, 0.3],
"width_circulation": [2.4, 0.2], "width_circulation": [2.4, 0.2],
@ -1182,6 +1192,8 @@ class Fitness:
params = self.conf("proportion_outside") params = self.conf("proportion_outside")
elif t0 == "c": elif t0 == "c":
params = self.conf("proportion_circulation") params = self.conf("proportion_circulation")
if params is None:
return 1.0 # no aspect requirement -- §39.22
else: else:
params = self.get_space_params(leaf.type, "proportion") params = self.get_space_params(leaf.type, "proportion")
co_type = self._leaf_co_type(leaf) co_type = self._leaf_co_type(leaf)

View file

@ -233,6 +233,9 @@ def leaf_constraints(fit, leaf: dom_mod.Node) -> LeafBounds:
params = fit.conf("proportion_circulation") params = fit.conf("proportion_circulation")
else: else:
params = fit.get_space_params(leaf.type, "proportion") params = fit.get_space_params(leaf.type, "proportion")
if params is None:
rmax = math.inf # no aspect requirement (§39.22, circulation)
else:
target, sigma = params[0], params[1] target, sigma = params[0], params[1]
rmax = max(1.0 + 1e-9, target + _K * sigma) rmax = max(1.0 + 1e-9, target + _K * sigma)

View file

@ -0,0 +1,96 @@
"""Corridors have no aspect requirement (homemaker-py-hxi, DESIGN.md §39.22).
Owner's ruling: "there should be no cap on the proportion of a corridor,
especially for big buildings, the crinkliness rule is there to prevent these
becoming unpleasant spaces."
The tests below pin both halves of that the cap is gone, AND crinkliness
still does the job the cap was wrongly doing, so removing it did not leave
long buried corridors unpunished.
"""
from __future__ import annotations
import copy
import math
from pathlib import Path
import pytest
from homemaker_layout import dom as dom_mod
from homemaker_layout.fitness import (
CONF_DEFAULTS, FAIL_THRESHOLD, Fitness, gaussian, load_config,
)
EXAMPLES = Path(__file__).resolve().parent.parent / "examples"
pytestmark = pytest.mark.skipif(not (EXAMPLES / "harbor-house").is_dir(),
reason="examples absent")
def _fit(**ov):
conf, cost = load_config(EXAMPLES / "harbor-house", overrides=ov)
return Fitness(conf, cost)
def test_there_is_no_corridor_aspect_requirement():
assert CONF_DEFAULTS["proportion_circulation"] is None
fit = _fit()
assert dom_mod.is_circulation(dom_mod.Node(type="C"))
# any aspect at all scores 1.0 -- checked through the real code path
for aspect in (1.0, 3.0, 12.0, 40.0):
fit_leaf = dom_mod.Node(
type="C", node=[[0.0, 0.0], [aspect, 0.0], [aspect, 1.0], [0.0, 1.0]])
assert fit.quality_proportion(fit_leaf) == 1.0, aspect
def test_a_room_still_has_one():
"""The ruling is about corridors. A habitable room's aspect target stands."""
fit = _fit()
room = dom_mod.Node(type="r", node=[[0.0, 0.0], [12.0, 0.0], [12.0, 1.0], [0.0, 1.0]])
assert fit.quality_proportion(room) < FAIL_THRESHOLD
def test_crinkliness_is_what_punishes_an_unpleasant_corridor():
"""The ruling's own justification, as arithmetic.
A long BURIED corridor is exactly the unpleasant space the aspect cap was
being used to prevent; crinkliness sends it to zero. A long corridor along
a facade is a perfectly good corridor, and crinkliness likes it.
"""
target, sigma = CONF_DEFAULTS["uncrinkliness_circulation"]
h, width, length = 3.0, 2.4, 30.0
area = width * length
buried = 0.0 # no illuminated wall at all
assert buried == 0.0
lit_one_side = (length * h) / area # the long wall is a facade
q = gaussian(1.0 / lit_one_side, 1.0, target, sigma)
assert q > 0.85, "a daylit corridor along a facade should score well"
def test_removing_the_cap_only_ever_removes_proportion_fails():
"""It cannot introduce a failure, and it touches nothing but corridors."""
old = {"proportion_circulation": [1.5, 0.5]}
seen = 0
for name in ("harbor-house", "maple-court", "health-centre", "programme-house"):
d = EXAMPLES / name
for p in sorted(d.glob("coldstart-500000-s*.dom")):
root = dom_mod.load(str(p))
c_old, cost = load_config(d, overrides=old)
c_new, _ = load_config(d)
_, f_old = Fitness(c_old, cost).score_with_fails(copy.deepcopy(root))
_, f_new = Fitness(c_new, cost).score_with_fails(copy.deepcopy(root))
assert not (set(f_new) - set(f_old)), "must not add a failure"
assert all(f.endswith(" proportion") for f in set(f_old) - set(f_new))
seen += 1
assert seen >= 4
def test_the_shape_curve_dp_accepts_an_unbounded_aspect():
"""`leaf_constraints` feeds rmax into the DP; None must become inf, not crash
and not silently fall back to a finite cap."""
from homemaker_layout import shapecurve
fit = _fit()
leaf = dom_mod.Node(type="C", node=[[0.0, 0.0], [4.0, 0.0], [4.0, 4.0], [0.0, 4.0]])
assert math.isinf(shapecurve.leaf_constraints(fit, leaf).rmax)