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
2026-09-06 12:56:39 +00:00
|
|
|
"""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)
|
No size cap on circulation: twice the corridor is twice as bad, and no worse
Owner's ruling: "as long as circulation is more expensive to build than it has
value then we have a linear ramp. a gaussian ramp is probably not appropriate
here as double the amount of corridor is simply twice as bad, so it should
score the same as two half size corridors".
Both halves check out. The linear ramp is already there -- value_circulation 50
against a build cost of 200, so every m2 of corridor is worth -150 and the
objective pushes for less of it without needing a cap. And the AMOUNT of
circulation is separately governed at building level by ratio_circulation
[0.00, 0.20], a gaussian on the circulation fraction, which is where that
question belongs. The per-leaf size gaussian was a third charge on the same
thing.
It was also the only one of the three that depended on how the corridor was cut
up. One 20 m2 corridor scored gaussian(20,0,14) = 0.360 and contributed 360;
two 10 m2 halves scored 0.775 each and contributed 775 between them. Splitting a
corridor in half multiplied its value by 2.15x -- an artefact of where the tree
happened to cut, rewarding the search for fragmenting its own spine. The
ruling's test (one 2A leaf must score as two A leaves) is exactly what a
gaussian on an amount cannot satisfy, and is now a test.
size_circulation = None; quality_size returns 1.0 for circulation and
shapecurve gives amin, amax = 0, inf.
BUG this exposed: get_space_params falls through to a habitable default when a
generic family key is missing and could not tell "missing" from "present but
null", so a corridor silently inherited a room's 16 m2 size target.
_generic_param now returns (found, value); pinned by a test. The same trap
applied to 39.22's proportion_circulation.
Fail-set effect of 39.22 and 39.23 together: 16 corridor size fails and 7
proportion fails removed, none added. harbor 33/43/42 -> 32/40/38, maple
54/73/55 -> 51/65/52, health-centre 4/9/5 -> 3/9/5, programme-house unchanged.
The layouts are identical -- these are failures the objective should never have
been reporting.
Two shape-curve tests moved fixture: both built an infeasible upper storey from
a 'C' leaf, infeasible precisely because of the bounds now removed. The fixture
is a cr1 leaf, whose infeasibility is a contradiction between two of its own
bounds (needs >= 180 m2 for its aspect bound, <= 101.5 m2 for its size bound
across the box's fixed 23.52 m span) rather than a tight fit. The invariants
they test are unchanged.
Left open on hxi: the rate gap, value_circulation 50 against value_inside 300
on identical build cost. Whether a corridor is worth a sixth of a room per m2
is a design judgement, and the linear ramp is only as steep as that number.
419 passed.
Refs homemaker-py-hxi.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MJ84Feep79Hhm3E4zZJmnB
2026-09-06 15:01:12 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
# --------------------------------------------------------------------------- #
|
|
|
|
|
# No size requirement either (homemaker-py-hxi, DESIGN.md §39.23)
|
|
|
|
|
# --------------------------------------------------------------------------- #
|
|
|
|
|
|
|
|
|
|
def test_there_is_no_corridor_size_requirement():
|
|
|
|
|
assert CONF_DEFAULTS["size_circulation"] is None
|
|
|
|
|
fit = _fit()
|
|
|
|
|
for area in (5.0, 14.0, 30.0, 60.0, 200.0):
|
|
|
|
|
leaf = dom_mod.Node(
|
|
|
|
|
type="C", node=[[0.0, 0.0], [area, 0.0], [area, 1.0], [0.0, 1.0]])
|
|
|
|
|
assert fit.quality_size(leaf) == 1.0, area
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_a_corridor_does_not_inherit_a_rooms_size_target():
|
|
|
|
|
"""`get_space_params` falls through to a habitable default when a generic
|
|
|
|
|
family key is missing. A key that is present but null must not fall
|
|
|
|
|
through -- or a corridor silently acquires a 16 m2 target."""
|
|
|
|
|
fit = _fit()
|
|
|
|
|
assert fit.get_space_params("C", "size") is None
|
|
|
|
|
assert fit.get_space_params("C", "proportion") is None
|
|
|
|
|
assert fit.get_space_params("C", "width") == CONF_DEFAULTS["width_circulation"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_twice_the_corridor_is_exactly_twice_as_bad():
|
|
|
|
|
"""The owner's argument, as arithmetic.
|
|
|
|
|
|
|
|
|
|
"double the amount of corridor is simply twice as bad, so it should score
|
|
|
|
|
the same as two half size corridors". Under a gaussian on area that was
|
|
|
|
|
false -- splitting a corridor in two raised its total value, which is a
|
|
|
|
|
pure artefact of how the tree happens to be cut. Value must be linear in
|
|
|
|
|
corridor area, so that one 2A leaf and two A leaves contribute the same.
|
|
|
|
|
"""
|
|
|
|
|
fit = _fit()
|
|
|
|
|
rate = fit.conf("value_circulation")
|
|
|
|
|
|
|
|
|
|
def value(area):
|
|
|
|
|
leaf = dom_mod.Node(
|
|
|
|
|
type="C", node=[[0.0, 0.0], [area, 0.0], [area, 1.0], [0.0, 1.0]])
|
|
|
|
|
return fit.quality_size(leaf) * rate * area
|
|
|
|
|
|
|
|
|
|
assert value(20.0) == pytest.approx(2 * value(10.0))
|
|
|
|
|
assert value(60.0) == pytest.approx(6 * value(10.0))
|
|
|
|
|
|
|
|
|
|
# and under the old gaussian it was not -- this is what changed.
|
|
|
|
|
# One 20 m2 corridor scored gaussian(20) = 0.360, two 10 m2 halves
|
|
|
|
|
# gaussian(10) = 0.775 each, so merely cutting the same corridor in two
|
|
|
|
|
# multiplied its value by 2.15x.
|
|
|
|
|
old_whole = gaussian(20.0, 1.0, 0.0, 14.0) * rate * 20.0
|
|
|
|
|
old_halves = 2 * (gaussian(10.0, 1.0, 0.0, 14.0) * rate * 10.0)
|
|
|
|
|
assert old_halves / old_whole == pytest.approx(2.15, abs=0.02), (
|
|
|
|
|
"the old gaussian rewarded splitting a corridor; if that is no longer "
|
|
|
|
|
"so, §39.23's justification needs revisiting")
|
|
|
|
|
|
|
|
|
|
|
Remove ratio_circulation: the score is already a ratio
Owner: "I think a corridor could be worth a sixth of a room, this is ok. maybe
we should dump the ratio_circulation altogether if there is already a pressure
in circulation caused by the cost benefit ratio per msq. this is the kind of
thing we want to root out of the scoring model: anything that is double
counting, or using a gaussian where a linear ramp is appropriate, etc."
value_circulation = 50 stands; hxi's rate question is closed.
The duplication argument is stronger than it first looks. score = value/cost is
already a ratio, so the per-m2 economics (50 against a build cost of 200) is not
merely an absolute pressure -- adding corridor moves value/cost by an amount
that depends on how much of the building is already corridor. It is ALREADY
proportional. ratio_circulation said the same thing again as a whole-building
multiplier, on a curve where twice the corridor is far more than twice as bad.
Correction to my own first measurement: I overrode ratio_circulation and got
scores going DOWN when a <=1 multiplier was removed, which is impossible. All
four corpus programmes DECLARE ratio_circulation, so the CONF_DEFAULTS value I
had changed was never in play and the two arms differed only in sigma. Same
trap as value_supported in 39.19.
Against the keep-it case, recorded because it is the one real argument: three of
four declare a POSITIVE target (harbor/maple 0.08, health-centre 0.10), making
the term formally two-sided rather than "less is better". It does not survive
the numbers -- the lower side is worth at most 13.3% on the large programmes
against 99% on the upper side, and "a building needs some circulation" is
enforced structurally by access and connectivity, which no amount of value can
buy off.
Disabled in CONF_DEFAULTS and the four corpus configs, each with the reason
inline and a note that a [target, sigma] pair re-enables it. Fail sets
unchanged; it was always a value multiplier. Scores +42% to +7712%.
39.24 also sweeps every remaining term against the owner's two tests. Verdicts:
perpendicular, proportion, width, crinkliness, access, size's lower side,
ratio_outside, staircase volume and the count/limit fails are all sound.
Filed as homemaker-py-dpt: size's UPPER side (cost already charges area; 82% of
size fails are over-target), the minimum-internal-area factor (a third
statement of "build the rooms"), the 0.5**n_fails curve (a ruling, not a
measurement), and two dead paths -- ratio_public/private_outside, which no
config declares, and the daylight factor pinned to 1.0 since the descope.
Also updated a test I added last turn which asserted ratio_circulation was the
second charge; it now pins that the linear ramp is the ONLY one.
419 passed.
Closes homemaker-py-hxi.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MJ84Feep79Hhm3E4zZJmnB
2026-09-06 15:28:33 +00:00
|
|
|
def test_the_amount_of_circulation_is_priced_exactly_once():
|
|
|
|
|
"""Removing the per-leaf cap must not make corridors free -- but the charge
|
|
|
|
|
that remains must also be the ONLY one (§39.24).
|
|
|
|
|
|
|
|
|
|
The linear ramp is `value_circulation` against the build cost: every square
|
|
|
|
|
metre of corridor is worth less than it costs, and because the score is
|
|
|
|
|
`value / cost` that pressure is already proportional to how much of the
|
|
|
|
|
building is corridor. `ratio_circulation` said the same thing a second time
|
|
|
|
|
and is disabled; if it comes back, either it has a justification this test
|
|
|
|
|
does not know about or the double count has returned.
|
|
|
|
|
"""
|
No size cap on circulation: twice the corridor is twice as bad, and no worse
Owner's ruling: "as long as circulation is more expensive to build than it has
value then we have a linear ramp. a gaussian ramp is probably not appropriate
here as double the amount of corridor is simply twice as bad, so it should
score the same as two half size corridors".
Both halves check out. The linear ramp is already there -- value_circulation 50
against a build cost of 200, so every m2 of corridor is worth -150 and the
objective pushes for less of it without needing a cap. And the AMOUNT of
circulation is separately governed at building level by ratio_circulation
[0.00, 0.20], a gaussian on the circulation fraction, which is where that
question belongs. The per-leaf size gaussian was a third charge on the same
thing.
It was also the only one of the three that depended on how the corridor was cut
up. One 20 m2 corridor scored gaussian(20,0,14) = 0.360 and contributed 360;
two 10 m2 halves scored 0.775 each and contributed 775 between them. Splitting a
corridor in half multiplied its value by 2.15x -- an artefact of where the tree
happened to cut, rewarding the search for fragmenting its own spine. The
ruling's test (one 2A leaf must score as two A leaves) is exactly what a
gaussian on an amount cannot satisfy, and is now a test.
size_circulation = None; quality_size returns 1.0 for circulation and
shapecurve gives amin, amax = 0, inf.
BUG this exposed: get_space_params falls through to a habitable default when a
generic family key is missing and could not tell "missing" from "present but
null", so a corridor silently inherited a room's 16 m2 size target.
_generic_param now returns (found, value); pinned by a test. The same trap
applied to 39.22's proportion_circulation.
Fail-set effect of 39.22 and 39.23 together: 16 corridor size fails and 7
proportion fails removed, none added. harbor 33/43/42 -> 32/40/38, maple
54/73/55 -> 51/65/52, health-centre 4/9/5 -> 3/9/5, programme-house unchanged.
The layouts are identical -- these are failures the objective should never have
been reporting.
Two shape-curve tests moved fixture: both built an infeasible upper storey from
a 'C' leaf, infeasible precisely because of the bounds now removed. The fixture
is a cr1 leaf, whose infeasibility is a contradiction between two of its own
bounds (needs >= 180 m2 for its aspect bound, <= 101.5 m2 for its size bound
across the box's fixed 23.52 m span) rather than a tight fit. The invariants
they test are unchanged.
Left open on hxi: the rate gap, value_circulation 50 against value_inside 300
on identical build cost. Whether a corridor is worth a sixth of a room per m2
is a design judgement, and the linear ramp is only as steep as that number.
419 passed.
Refs homemaker-py-hxi.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MJ84Feep79Hhm3E4zZJmnB
2026-09-06 15:01:12 +00:00
|
|
|
fit = _fit()
|
|
|
|
|
assert fit.conf("value_circulation") < fit.cost("inside"), (
|
|
|
|
|
"a corridor must cost more to build than it is worth, or there is no "
|
|
|
|
|
"linear ramp pushing the search to use less of it")
|
Remove ratio_circulation: the score is already a ratio
Owner: "I think a corridor could be worth a sixth of a room, this is ok. maybe
we should dump the ratio_circulation altogether if there is already a pressure
in circulation caused by the cost benefit ratio per msq. this is the kind of
thing we want to root out of the scoring model: anything that is double
counting, or using a gaussian where a linear ramp is appropriate, etc."
value_circulation = 50 stands; hxi's rate question is closed.
The duplication argument is stronger than it first looks. score = value/cost is
already a ratio, so the per-m2 economics (50 against a build cost of 200) is not
merely an absolute pressure -- adding corridor moves value/cost by an amount
that depends on how much of the building is already corridor. It is ALREADY
proportional. ratio_circulation said the same thing again as a whole-building
multiplier, on a curve where twice the corridor is far more than twice as bad.
Correction to my own first measurement: I overrode ratio_circulation and got
scores going DOWN when a <=1 multiplier was removed, which is impossible. All
four corpus programmes DECLARE ratio_circulation, so the CONF_DEFAULTS value I
had changed was never in play and the two arms differed only in sigma. Same
trap as value_supported in 39.19.
Against the keep-it case, recorded because it is the one real argument: three of
four declare a POSITIVE target (harbor/maple 0.08, health-centre 0.10), making
the term formally two-sided rather than "less is better". It does not survive
the numbers -- the lower side is worth at most 13.3% on the large programmes
against 99% on the upper side, and "a building needs some circulation" is
enforced structurally by access and connectivity, which no amount of value can
buy off.
Disabled in CONF_DEFAULTS and the four corpus configs, each with the reason
inline and a note that a [target, sigma] pair re-enables it. Fail sets
unchanged; it was always a value multiplier. Scores +42% to +7712%.
39.24 also sweeps every remaining term against the owner's two tests. Verdicts:
perpendicular, proportion, width, crinkliness, access, size's lower side,
ratio_outside, staircase volume and the count/limit fails are all sound.
Filed as homemaker-py-dpt: size's UPPER side (cost already charges area; 82% of
size fails are over-target), the minimum-internal-area factor (a third
statement of "build the rooms"), the 0.5**n_fails curve (a ruling, not a
measurement), and two dead paths -- ratio_public/private_outside, which no
config declares, and the daylight factor pinned to 1.0 since the descope.
Also updated a test I added last turn which asserted ratio_circulation was the
second charge; it now pins that the linear ramp is the ONLY one.
419 passed.
Closes homemaker-py-hxi.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MJ84Feep79Hhm3E4zZJmnB
2026-09-06 15:28:33 +00:00
|
|
|
assert fit.conf("ratio_circulation") is None, (
|
|
|
|
|
"ratio_circulation duplicates the per-m2 economics (§39.24)")
|
|
|
|
|
assert fit.conf("size_circulation") is None, "§39.23"
|
|
|
|
|
assert fit.conf("proportion_circulation") is None, "§39.22"
|