§39.8: homemaker-py-2v1 connectivity weighting — MEASURED NULL, premise retracted

§38.2 concluded the objective is net-positive on severing a level's
circulation: merging a corridor into a habitable sibling gains x6
(value_inside/value_circulation), while "level N not connected" costs x0.5, so
break-even needs 0.5^w < 50/300, w > 2.58 -- "severing must cost at least 3
fails and costs 1". The arithmetic is right. The premise is wrong.

Shipped anyway, EXPERIMENTAL and default off (byte-identical):
fitness.connectivity_weight_for(value_inside, value_circulation) returns the
smallest weight making severing net-negative -- 3.0 at the defaults, DERIVED
from the rates rather than hard-coded so it tracks them if either is retuned.
conf["connectivity_weight"] takes 1.0 / "auto" / a number and counts each
connectivity failure as w failures in the 0.5^n penalty.

MEASUREMENT: at auto (=3) the §38.2 deletion test does not move at all -- 5/25
rewarded either way, median x0.26 vs x0.27. Reason: the connectivity fail count
is UNCHANGED in every rewarded deletion (115->107 fails but 5->5 connectivity;
107->99 but 3->3; 78->71 but 3->3). Weighting a fail that never fires changes
nothing.

And when a deletion DOES break connectivity, it is already punished. Every such
case, 4 seeds per programme: harbor-house 2 of 32 sampled deletions, both
punished (x0.00, x0.01); maple-court 5 of 32, all punished (x0.58 .. x0.07).
Severing costs 1-2 connectivity fails PLUS the cascade after them, which
already outweighs the x6 gain. The flat rule was never the problem.

Where §38.2 went wrong: the x4.06 "well-daylit circulation leaf" that motivated
the bead was a deletion that did NOT change the connectivity fail count. It was
rewarded for removing the leaf's own quality failures -- §38.1's zero-value
finding -- and I misread it as a pricing mechanism. §38.2 now carries the
retraction inline. Two lessons recorded: a plausible closed-form arithmetic is
not a measurement, and when a fix produces exactly no effect, suspect the
premise before the implementation.

Still standing from §38: §38.1 (buried leaves score zero quality and contribute
no value) and §38.3 (frontage budget) are direct measurements. §39.7 remains
the better lever on the same symptom -- it made the connectivity fails FIRE,
where this would only have made them cost more.

Re-opened as homemaker-py-yql: why level-not-connected persists in the best
layout when severing is already punished. Evidence now points at reachability,
not incentive, and it is newly measurable because §39.7 stopped store cupboards
standing in for corridors.

353 passed (+3 new), same 7 pre-existing fixture failures, lint unchanged.

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 14:15:22 +00:00
parent 03aadec1ba
commit 3aee813ccd
No known key found for this signature in database
4 changed files with 167 additions and 7 deletions

File diff suppressed because one or more lines are too long

View file

@ -4929,10 +4929,18 @@ favour, against a measured ×4.06. **The connectivity fail is under-priced by
roughly 3×, so the objective is net-positive on destroying the circulation roughly 3×, so the objective is net-positive on destroying the circulation
spine even when the circulation is perfectly daylit.** spine even when the circulation is perfectly daylit.**
That is the cleanest available explanation of why `level 0 not connected` and **RETRACTED — see §39.8.** The inference above ("the objective is net-positive
`level 1 not connected` are still present in the best layout found after on severing the spine") does not survive measurement. It assumed severing costs
1.7 M evals: the search is not failing to fix them, it is being paid ×34 to exactly one failure; it does not. Every deletion that actually breaks
create them. connectivity is already punished — measured ×0.00 to ×0.58 across harbor-house
and maple-court, not one rewarded. The ×4.06 figure above is real but was
measured on a deletion that did **not** change the connectivity fail count, so
it is not evidence for this mechanism. The deletions that are rewarded are
rewarded because they remove the deleted leaf's OWN quality failures (79 of
them), which is §38.1's zero-value finding, not a connectivity mispricing.
Why `level 0/1 not connected` persist in the best layout is therefore still
open, but it is not that the search is paid to create them.
Together these retro-explain three prior results as one mechanism, and suggest Together these retro-explain three prior results as one mechanism, and suggest
two of them were measuring a broken gradient rather than a bad idea: two of them were measuring a broken gradient rather than a bad idea:
@ -5422,3 +5430,67 @@ missed, because consulting rooms and storage stood in for them. With that
substitution gone, `homemaker-py-2v1` (connectivity priced at ×0.5 against a ×6 substitution gone, `homemaker-py-2v1` (connectivity priced at ×0.5 against a ×6
circulation→habitable value gap) is the remaining half of the same problem — circulation→habitable value gap) is the remaining half of the same problem —
and now measurable, because the fails it should be preventing actually fire. and now measurable, because the fails it should be preventing actually fire.
### 39.8 `homemaker-py-2v1` connectivity weighting — MEASURED NULL, premise retracted
§38.2 concluded that the objective is net-positive on severing a level's
circulation: merging a corridor into a habitable sibling gains
`value_inside / value_circulation` = ×6, while `level N not connected` costs
only ×0.5, so break-even needs `0.5^w < 50/300`, i.e. w > 2.58 — "severing must
cost at least 3 fails and costs 1". **The arithmetic is right and the premise is
wrong.**
**What shipped anyway** (EXPERIMENTAL, default off, byte-identical):
`fitness.connectivity_weight_for(value_inside, value_circulation)` returns the
smallest weight making severing net-negative — 3.0 at the defaults, *derived*
from the rates rather than hard-coded, so it tracks them if either is retuned.
`conf["connectivity_weight"]` takes `1.0` (default, the flat rule), `"auto"`, or
an explicit number, and counts each connectivity failure as w failures in the
`0.5^n` penalty. `is_connectivity_fail` identifies the two strings.
**The measurement.** At `auto` (=3) the §38.2 deletion test does not move at
all: 5/25 deletions rewarded either way, median ×0.26 vs ×0.27. The reason is
immediate once looked for — **the connectivity fail count is unchanged in every
rewarded deletion**:
| seed | deleted | | score | fails | connectivity fails |
|---|---|---|---|---|---|
| 0 | `rlrrr` `O` | buried | ×238 | 115 → 107 | 5 → **5** |
| 0 | `rrrl` `O` | lit | ×346 | 115 → 106 | 5 → **5** |
| 1 | `lrlll` `cr1` | lit | ×257 | 107 → 99 | 3 → **3** |
| 2 | `rlrrl` `O` | buried | ×127 | 78 → 71 | 3 → **3** |
Weighting a failure that never fires changes nothing. And when the deletion
*does* break connectivity, the objective already punishes it — every such case
across harbor-house and maple-court, 4 seeds each:
| programme | deletions sampled | break connectivity | of those, rewarded |
|---|---|---|---|
| harbor-house | 32 | 2 | **0** (×0.00, ×0.01) |
| maple-court | 32 | 5 | **0** (×0.58 … ×0.07) |
So severing is already net-negative: it costs 12 connectivity failures *plus*
the cascade that follows them (inaccessible space, broken adjacency), and that
total already outweighs the ×6 value gain. The flat rule was never the problem.
**Where §38.2 went wrong.** The ×4.06 "well-daylit circulation leaf" that
motivated the whole bead was a deletion that did **not** change the
connectivity fail count. It was rewarded for removing the leaf's own quality
failures — §38.1's zero-value finding — and was misread as evidence for a
pricing mechanism. Two lessons, both cheap to state and expensive to learn: a
plausible closed-form arithmetic is not a measurement, and when a fix produces
*exactly* no effect, suspect the premise before the implementation.
**What is still true from §38.** §38.1 (buried leaves score a hard quality of
zero and contribute no value) and §38.3 (the frontage budget) are direct
measurements and stand. §39.7's finding — that the connectivity model was ~4×
too permissive — also stands and is the more useful lever: it made the fails
*fire*, where this bead would only have made them *cost more*.
**Verdict: NULL.** The flag stays default off with this write-up, per house
style for a measured-null lever. `homemaker-py-2v1` is closed. Why
`level 0/1 not connected` survive in the best-known layout is re-opened as a
question (`homemaker-py-yql`) — the evidence now says it is a reachability problem
(connected topologies are hard to construct and hold onto), not an incentive
one. It is newly measurable: §39.7 made the fails fire on constructed seeds
instead of being hidden by routes through store cupboards.

View file

@ -118,6 +118,40 @@ _SOFT_FAIL_MARKERS = (
) )
# homemaker-py-2v1 (DESIGN.md §39.8) — the fails that punish severing a level's
# circulation. These are the ONLY counter-pressure against a structural x6 gain:
# deleting a circulation leaf merges it into its sibling, converting corridor
# into habitable area, and value_inside/value_circulation is 300/50.
_CONNECTIVITY_FAIL_MARKERS = ("not connected", "inaccessible usable space")
def is_connectivity_fail(fail: str) -> bool:
"""True for a level-connectivity failure (``level N not connected`` /
``N inaccessible usable space``)."""
return any(m in fail for m in _CONNECTIVITY_FAIL_MARKERS)
def connectivity_weight_for(value_inside: float, value_circulation: float) -> float:
"""Smallest integer weight at which severing circulation is net-NEGATIVE.
Merging a circulation leaf into a habitable sibling multiplies value by
``value_inside / value_circulation`` (x6 at the defaults). One failure costs
x0.5. So the penalty only outweighs the gain once
``0.5**w < value_circulation / value_inside``, i.e.
``w > log(vc/vi) / log(0.5)`` 2.58 at the defaults, hence 3.
Derived from the value rates rather than hard-coded, so the two stay in step
if either rate is ever retuned.
"""
import math
if value_inside <= 0 or value_circulation <= 0:
return 1.0
ratio = value_circulation / value_inside
if ratio >= 1.0: # circulation already worth as much
return 1.0
return float(math.ceil(math.log(ratio) / math.log(0.5)))
def classify_fail_tier(fail: str) -> str: def classify_fail_tier(fail: str) -> str:
"""Return ``"hard"`` or ``"soft"`` for one failure string. """Return ``"hard"`` or ``"soft"`` for one failure string.
@ -390,6 +424,18 @@ class Fitness:
# leaf with no daylit wall. "urb" (default) = stock hard 0.0, byte- # leaf with no daylit wall. "urb" (default) = stock hard 0.0, byte-
# identical to every prior run. "floor"/"compact_ok"/"exempt_circulation" # identical to every prior run. "floor"/"compact_ok"/"exempt_circulation"
# are the three candidate repairs — see quality_uncrinkliness. # are the three candidate repairs — see quality_uncrinkliness.
# homemaker-py-2v1 (§39.8), EXPERIMENTAL: 1.0 (default) is the flat rule,
# byte-identical to every prior run. "auto" derives the smallest weight
# that makes severing circulation net-negative; a number sets it explicitly.
cw = self.conf("connectivity_weight")
if cw is None:
self._connectivity_weight = 1.0
elif isinstance(cw, str) and cw.lower() == "auto":
self._connectivity_weight = connectivity_weight_for(
float(self.conf("value_inside")),
float(self.conf("value_circulation")))
else:
self._connectivity_weight = float(cw)
self._crinkliness_mode = str(self.conf("crinkliness_mode") or "urb") self._crinkliness_mode = str(self.conf("crinkliness_mode") or "urb")
if self._crinkliness_mode not in ( if self._crinkliness_mode not in (
"urb", "floor", "compact_ok", "exempt_circulation"): "urb", "floor", "compact_ok", "exempt_circulation"):
@ -1925,7 +1971,22 @@ class Fitness:
building_factor = self.evaluate_building(root, tracking) building_factor = self.evaluate_building(root, tracking)
value *= building_factor value *= building_factor
# 0.5^n failure penalty (programme-driven mode, not 0.1^n) # 0.5^n failure penalty (programme-driven mode, not 0.1^n).
#
# homemaker-py-2v1: connectivity failures may carry EXTRA weight. Under
# the flat rule every failure costs x0.5, but severing a level's
# circulation *gains* value_inside/value_circulation = x6 (the corridor
# becomes habitable area when it merges into its sibling), so the
# objective was net-positive on destroying the spine — measured x4.06 on
# a well-daylit circulation leaf. ``connectivity_weight`` counts each
# connectivity fail as w failures; ``"auto"`` derives the smallest w that
# makes severing net-negative from the value rates themselves.
w = self._connectivity_weight
if w != 1.0:
n_conn = sum(1 for f in failures if is_connectivity_fail(f))
n_other = len(failures) - n_conn
value *= 0.5 ** (n_other + w * n_conn)
else:
value *= 0.5 ** len(failures) value *= 0.5 ** len(failures)
score = value / cost if cost != 0.0 else 0.0 score = value / cost if cost != 0.0 else 0.0

View file

@ -531,3 +531,29 @@ def test_crinkliness_exempt_circulation_only_exempts_circulation():
def test_crinkliness_mode_unknown_raises(): def test_crinkliness_mode_unknown_raises():
with pytest.raises(ValueError, match="crinkliness_mode"): with pytest.raises(ValueError, match="crinkliness_mode"):
_stub_fit("nonsense") _stub_fit("nonsense")
# --------------------------------------------------------------------------- #
# homemaker-py-2v1 / DESIGN.md §39.8 — connectivity_weight (EXPERIMENTAL, NULL)
# --------------------------------------------------------------------------- #
def test_connectivity_weight_defaults_to_flat_rule():
"""Default must reproduce the flat 0.5^n penalty exactly."""
assert Fitness(conf={})._connectivity_weight == 1.0
def test_connectivity_weight_auto_is_derived_from_the_value_gap():
"""Not a magic number: the smallest w making 0.5^w < value_circulation /
value_inside, so it tracks the rates if either is retuned."""
from homemaker_layout.fitness import connectivity_weight_for
assert connectivity_weight_for(300.0, 50.0) == 3.0 # 0.5^3 < 1/6 < 0.5^2
assert connectivity_weight_for(100.0, 100.0) == 1.0 # no gap, no extra weight
assert connectivity_weight_for(400.0, 50.0) == 3.0 # 1/8 -> exactly 3
assert Fitness(conf={"connectivity_weight": "auto"})._connectivity_weight == 3.0
def test_is_connectivity_fail_matches_both_strings():
from homemaker_layout.fitness import is_connectivity_fail
assert is_connectivity_fail("level 0 not connected")
assert is_connectivity_fail("1 inaccessible usable space")
assert not is_connectivity_fail("0/llr crinkliness")
assert not is_connectivity_fail("missing required space: b1")