Outdoor space: enforce the per-level rule, drop the fraction

Owner: "maybe plot_ratio is having unintended consequences, Alexander simply
says that all levels should have accessible outside space, he doesn't say how
much."

Both halves are right, and the codebase had the two rules the wrong way round.

ratio_outside is a gaussian on the outdoor FRACTION, applied as a whole-building
multiplier, and its declared targets have no stated basis and contradict each
other: health-centre targets 0.06 and sits at 0.096-0.129, so it is penalised
x0.39-x0.77 for having too MUCH; programme-house targets 0.30 and sits at
0.098-0.293, penalised x0.40-x0.999 for having too LITTLE. Penalties as large as
the ratio_circulation ones 39.24 removed, pulling two programmes in opposite
directions on the same quantity.

force_roof_garden already implements the rule Alexander actually states -- per
level, no outdoor space at all is a hard fail, no quantity attached. It has
existed all along and was switched OFF in every corpus config.

Near-miss worth recording: measuring first, I found zero "no outside space"
fails across the twelve baseline runs and briefly read that as the requirement
being met everywhere. It meant the check never ran. Same shape as 39.20's parity
tests -- no failures from a test that is not executing looks exactly like no
failures from a test that passes, and the tell was again the config, not the
code.

Enabled, it bites on 4 of 25 baseline levels: maple s0 level 1, and
programme-house level 0 in all three seeds. A house with no outdoor space on its
own ground floor is a fair criticism of the layout, and exactly what a
building-level fraction cannot catch, since 22% outdoor concentrated on one
storey satisfies it perfectly.

The upper side ratio_outside used to provide is covered in a better currency by
the minimum-internal-area factor (internal area >= 1.2x the programme's declared
room area), which is live -- binding on harbor s0 x0.920 and programme-house s0
x0.787.

This is the one change in 39.22-39.25 whose risk is NOT measured: nothing here
proves the outdoor fraction will not drift up once the search is free to raise
it, and outdoor space is profitable (1.64 return against a room's 0.66). The
re-baseline (bk9) is what shows it, and 39.25 asks for the fraction to be
recorded there.

4 hard fails added, none removed. 426 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 15:41:22 +00:00
parent f96ec0f316
commit 4931070602
No known key found for this signature in database
7 changed files with 199 additions and 14 deletions

View file

@ -8051,3 +8051,78 @@ The three SUSPECT entries and the fail multiplier are filed rather than changed
— each needs a ruling or a rate change behind it, and §39.16 is the standing
reminder about acting on an inherited constant before understanding what it
measures. The two DEAD entries are inert and cost nothing but confusion.
### 39.25 The rule Alexander states was off; the one he doesn't was on (`homemaker-py-hxi`)
> maybe plot_ratio is having unintended consequences, Alexander simply says that
> all levels should have accessible outside space, he doesn't say how much
Both halves of that are right, and the codebase had the two rules the wrong way
round.
**`ratio_outside` is a gaussian on the outdoor fraction**, applied as a
whole-building multiplier. Its declared targets have no stated basis and
contradict one another across the corpus:
| programme | target | actual | effect |
|---|---|---|---|
| health-centre | 0.06 | 0.096 0.129 | ×0.39 ×0.77, penalised for having **too much** |
| programme-house | 0.30 | 0.098 0.293 | ×0.40 ×0.999, penalised for having **too little** |
| harbor / maple | 0.15 | 0.131 0.197 | mild, both sides |
The same objective is pulling two programmes in opposite directions on the same
quantity, at penalties as large as the `ratio_circulation` ones §39.24 removed.
**And `force_roof_garden` already implements the rule Alexander actually
states** — per level, no outdoor space at all is a hard failure,
`level N no outside space`, with no quantity attached. It has existed all along.
**It was switched off in every corpus config.** `force_roof_garden: 0` in all
four, and in `harbor-house-l0`, `programme-house2` and every `y51-sweep-*`.
**A near-miss worth recording.** Measuring first, I found zero
`no outside space` failures across the twelve baseline runs and briefly read
that as "the requirement is already met everywhere". It meant nothing of the
sort: the check never ran. This is the same shape as §39.20's parity tests —
an absence of failures from a test that is not executing looks exactly like an
absence of failures from a test that is passing. The tell was the same too: the
config, not the code.
Enabled, the rule bites on **4 of 25** levels in the baseline artefacts:
| | |
|---|---|
| maple-court s0 | level 1 |
| programme-house s0/s1/s2 | **level 0**, all three seeds |
programme-house putting no outdoor space on its own ground floor, in every
seed, is a fair criticism of those layouts rather than a false positive — and
it is exactly what the fraction rule failed to catch, because 22% outdoor space
concentrated on one storey satisfies a building-level fraction perfectly.
**Shipped:** `force_roof_garden` on, `ratio_outside` off, in `CONF_DEFAULTS` and
the four corpus configs, each with the reason inline.
**On the side `ratio_outside` was the only guard for.** Outdoor space is
profitable (§39.19: ground outdoor returns 1.64 per unit cost against a room's
0.66), so an upper bound is not obviously redundant. It is covered, though, and
in a better currency: the minimum-internal-area factor requires built internal
area ≥ 1.2 × the programme's declared room area, and it is live — binding on
harbor s0 (×0.920) and programme-house s0 (×0.787) in the baseline. "Build the
rooms you were asked for, and then some" bounds non-room space directly, where
a fraction only does so by proxy.
That said, this is the one change in §39.22§39.25 whose risk is **not**
measured: nothing here proves the outdoor fraction will not drift upward once
the search is free to raise it. The re-baseline (`homemaker-py-bk9`) is what
shows that, and the fraction is worth recording in it explicitly.
**Fail-set and score effect** — 4 hard fails added, none removed; scores rise
where the fraction penalty lifted and fall where a new hard fail landed:
| programme | fails | score |
|---|---|---|
| harbor-house | 32/40/38 unchanged | +1% … +12% |
| maple-court | 51→**52**, 65, 52 | 50% (new fail), +1%, +0.4% |
| health-centre | 3/9/5 unchanged | +29% … +156% |
| programme-house | 1→**2** all seeds | 43%, +24%, 50% |

View file

@ -264,7 +264,11 @@ spaces:
# Building constraints
storey_minimum: 2
storey_limit: 3
force_roof_garden: 0
# homemaker-py-hxi (DESIGN.md §39.25): ENABLED. Fails a level that has no
# outdoor space at all -- Alexander's actual requirement, per level and with no
# quantity attached. It had been off in every corpus config, so the only thing
# expressing outdoor space was ratio_outside's arbitrary fraction.
force_roof_garden: 1
# Circulation ratio (allow more circulation for larger building)
# homemaker-py-hxi (DESIGN.md §39.24): disabled. The score is value/cost, a
@ -277,9 +281,11 @@ force_roof_garden: 0
ratio_circulation:
# Outside space ratio (courtyard requirement)
# homemaker-py-hxi (DESIGN.md §39.25): disabled. Alexander says every level
# should have accessible outside space; he does not say how much. The
# qualitative requirement is force_roof_garden below. Set a [target, sigma]
# pair to re-enable this quantitative one.
ratio_outside:
- 0.15
- 0.10
# Staircase requirements (main + secondary)
staircase_min: 2

View file

@ -349,7 +349,11 @@ spaces:
# Building constraints
storey_minimum: 1
storey_limit: 2
force_roof_garden: 0
# homemaker-py-hxi (DESIGN.md §39.25): ENABLED. Fails a level that has no
# outdoor space at all -- Alexander's actual requirement, per level and with no
# quantity attached. It had been off in every corpus config, so the only thing
# expressing outdoor space was ratio_outside's arbitrary fraction.
force_roof_garden: 1
# homemaker-py-hxi (DESIGN.md §39.24): disabled. The score is value/cost, a
# ratio, so the per-m2 economics (circulation worth 50 against a build cost of
@ -360,9 +364,11 @@ force_roof_garden: 0
# and connectivity checks. Set a [target, sigma] pair to re-enable.
ratio_circulation:
# homemaker-py-hxi (DESIGN.md §39.25): disabled. Alexander says every level
# should have accessible outside space; he does not say how much. The
# qualitative requirement is force_roof_garden below. Set a [target, sigma]
# pair to re-enable this quantitative one.
ratio_outside:
- 0.06
- 0.05
staircase_min: 1
staircase_max: 2

View file

@ -403,7 +403,11 @@ spaces:
# Building constraints
storey_minimum: 3
storey_limit: 4
force_roof_garden: 0
# homemaker-py-hxi (DESIGN.md §39.25): ENABLED. Fails a level that has no
# outdoor space at all -- Alexander's actual requirement, per level and with no
# quantity attached. It had been off in every corpus config, so the only thing
# expressing outdoor space was ratio_outside's arbitrary fraction.
force_roof_garden: 1
# Circulation ratio (allow more circulation for larger building)
# homemaker-py-hxi (DESIGN.md §39.24): disabled. The score is value/cost, a
@ -416,9 +420,11 @@ force_roof_garden: 0
ratio_circulation:
# Outside space ratio (courtyard requirement)
# homemaker-py-hxi (DESIGN.md §39.25): disabled. Alexander says every level
# should have accessible outside space; he does not say how much. The
# qualitative requirement is force_roof_garden below. Set a [target, sigma]
# pair to re-enable this quantitative one.
ratio_outside:
- 0.15
- 0.10
# Staircase requirements (main + secondary)
staircase_min: 2

View file

@ -79,7 +79,11 @@ spaces:
# Building constraints
storey_minimum: 2
storey_limit: 5
force_roof_garden: 0
# homemaker-py-hxi (DESIGN.md §39.25): ENABLED. Fails a level that has no
# outdoor space at all -- Alexander's actual requirement, per level and with no
# quantity attached. It had been off in every corpus config, so the only thing
# expressing outdoor space was ratio_outside's arbitrary fraction.
force_roof_garden: 1
# Circulation ratio (penalise excessive corridors)
# homemaker-py-hxi (DESIGN.md §39.24): disabled. The score is value/cost, a
@ -92,9 +96,11 @@ force_roof_garden: 0
ratio_circulation:
# Outside space ratio
# homemaker-py-hxi (DESIGN.md §39.25): disabled. Alexander says every level
# should have accessible outside space; he does not say how much. The
# qualitative requirement is force_roof_garden below. Set a [target, sigma]
# pair to re-enable this quantitative one.
ratio_outside:
- 0.30
- 0.15
# Staircase requirements
staircase_min: 1

View file

@ -209,7 +209,21 @@ CONF_DEFAULTS: dict = {
"latitude": 53.3814,
"door_width": 1.2,
"plot_ratio": [2.00, 0.50],
"ratio_outside": [0.33, 0.15],
# homemaker-py-hxi (DESIGN.md §39.25). Was [0.33, 0.15] -- a gaussian on
# the OUTDOOR FRACTION. Alexander says every level should have accessible
# outside space; he does not say how much, and the corpus's four declared
# targets (0.06, 0.15, 0.15, 0.30) have no stated basis and contradict each
# other -- health-centre was penalised for exceeding 6% while
# programme-house was penalised for not reaching 30%.
#
# `None` disables it. The requirement Alexander actually states is
# `force_roof_garden`, which fails a level outright when it has no outdoor
# space at all -- qualitative, per level, no quantity. That check already
# existed and was switched OFF in every corpus config; §39.25 turns it on
# and turns this off. The upper side this used to provide is covered by the
# minimum-internal-area factor, which bounds non-room space in the currency
# that matters (build the rooms you were asked for).
"ratio_outside": None,
# homemaker-py-hxi (DESIGN.md §39.24). Was [0.00, 0.20] -- a gaussian on
# the circulation FRACTION, targeting zero, applied as a multiplier to the
# whole building's value (0.013..0.70 across the corpus). `None` disables
@ -2063,6 +2077,7 @@ class Fitness:
ratios = self._ratios(root)
factor = 1.0
if self.conf("ratio_outside") is not None: # §39.25: off by default
factor *= self.ratio_o(ratios)
circ_ratio = self.conf("ratio_circulation")

View file

@ -0,0 +1,71 @@
"""Outdoor space is a per-level requirement, not a fraction (DESIGN.md §39.25).
Owner: "Alexander simply says that all levels should have accessible outside
space, he doesn't say how much."
The codebase had both rules and had them the wrong way round: the qualitative
one Alexander states (`force_roof_garden`, which fails a level with no outdoor
space at all) was switched OFF in every corpus config, while the quantitative
one he does not state (`ratio_outside`, a gaussian on the outdoor fraction) was
switched on with four mutually contradictory targets.
"""
from __future__ import annotations
import copy
from pathlib import Path
import pytest
from homemaker_layout import dom as dom_mod
from homemaker_layout.fitness import Fitness, load_config
EXAMPLES = Path(__file__).resolve().parent.parent / "examples"
CORPUS = ["harbor-house", "maple-court", "health-centre", "programme-house"]
pytestmark = pytest.mark.skipif(not (EXAMPLES / "harbor-house").is_dir(),
reason="examples absent")
@pytest.mark.parametrize("name", CORPUS)
def test_the_qualitative_rule_is_on_and_the_quantitative_one_is_off(name):
conf, _ = load_config(EXAMPLES / name)
assert conf["force_roof_garden"], (
"Alexander's requirement -- every level has accessible outside space --"
" must actually be enforced")
assert conf["ratio_outside"] is None, (
"the outdoor FRACTION is not a rule Alexander states (§39.25)")
def test_a_level_with_no_outdoor_space_fails():
"""The rule has to bite, or turning it on achieved nothing. programme-house
puts no outdoor space on its ground floor in every baseline seed."""
d = EXAMPLES / "programme-house"
seen = 0
for p in sorted(d.glob("coldstart-500000-s*.dom")):
conf, cost = load_config(d)
_, fails = Fitness(conf, cost).score_with_fails(dom_mod.load(str(p)))
assert any("no outside space" in f for f in fails), p.name
seen += 1
assert seen == 3
def test_that_failure_is_hard():
"""It is a structural provision no ratio-solve can supply, so it must tier
HARD -- a soft fail would let the search buy it off with shape quality."""
from homemaker_layout.fitness import classify_fail_tier
assert classify_fail_tier("level 1 no outside space") == "hard"
def test_disabling_the_fraction_removes_no_failure():
"""`ratio_outside` was a value multiplier, never a fail source, so the only
fail-set movement in §39.25 comes from switching the per-level rule ON."""
for name in CORPUS:
d = EXAMPLES / name
for p in sorted(d.glob("coldstart-500000-s*.dom")):
root = dom_mod.load(str(p))
conf, cost = load_config(d)
with_frac, _ = load_config(
d, overrides={"ratio_outside": [0.15, 0.1]})
_, f_off = Fitness(conf, cost).score_with_fails(copy.deepcopy(root))
_, f_on = Fitness(with_frac, cost).score_with_fails(copy.deepcopy(root))
assert f_off == f_on, f"{p.name}: ratio_outside moved a failure"