§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:
parent
03aadec1ba
commit
3aee813ccd
4 changed files with 167 additions and 7 deletions
File diff suppressed because one or more lines are too long
80
DESIGN.md
80
DESIGN.md
|
|
@ -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 ×3–4 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 (7–9 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 1–2 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.
|
||||||
|
|
|
||||||
|
|
@ -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,8 +1971,23 @@ 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).
|
||||||
value *= 0.5 ** len(failures)
|
#
|
||||||
|
# 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)
|
||||||
|
|
||||||
score = value / cost if cost != 0.0 else 0.0
|
score = value / cost if cost != 0.0 else 0.0
|
||||||
return score, tuple(sorted(failures)), grade
|
return score, tuple(sorted(failures)), grade
|
||||||
|
|
|
||||||
|
|
@ -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")
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue