§38.2 refinement: connectivity is under-priced ~3x, not just a crinkliness bug

Follow-up measurement corrects the first draft of §38 in two ways.

1. Harbor-house's floor is 15 fails (evolved-3M-nols-3, 1.7M evals), not the
   30-40 I quoted from §13.11's 20k-budget runs. Frontage deficit predicts the
   COST of solving, not impossibility: ~150x budget gap between a
   frontage-short and a frontage-surplus programme. Table corrected.

2. Zero-exposure is only half the mechanism, and not the dominant half.
   Splitting the deletion test by lit vs buried shows a WELL-DAYLIT corridor
   (q_crink=0.736) is still worth x4.06 to delete. Cause: value_circulation=50
   vs value_inside=300, so merging corridor into room is a flat x6 gain, while
   'level N not connected' costs only x0.5. Break-even needs 0.5^k < 50/300,
   i.e. k > 2.58 -- severing must cost at least 3 fails and costs 1. Net x3.0
   predicted, x4.06 measured. The objective is net-positive on severing the
   spine even when the circulation is perfectly lit, which explains why both
   'level N not connected' fails survive in the best layout after 1.7M evals.

Adds fitness.quality_uncrinkliness crinkliness_mode (EXPERIMENTAL, default
"urb" = stock hard 0.0, byte-identical: 336 passed vs 331 before, same 7
pre-existing fixture failures). A/B harness ab_crinkliness_mode_ssz.py shows
none of the three modes removes the incentive, and the lit column is 3/8 under
every mode including stock -- clean isolation of the two mechanisms.

Filed homemaker-py-2v1 (P0) for the pricing fix; ssz/hxi now depend on it.
Acceptance test recorded up front: harbor must reach 15 fails in materially
fewer than 1.7M evals AND without either not-connected fail.

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 07:40:37 +00:00
parent 753356e47e
commit 50b5cfd476
No known key found for this signature in database
4 changed files with 359 additions and 18 deletions

119
DESIGN.md
View file

@ -4835,7 +4835,7 @@ future effort on that plateau (`homemaker-py-2g7.7`'s LLM repair operator, or
a level-connectivity-targeted operator) is better aimed than a graph-dual
construction pass would have been.
## 38. The plateau is an objective-gradient problem, not a search problem (`homemaker-py-ssz`/`hxi`/`tdp`/`gvb`/`1i8`) — measured 2026-08-25
## 38. The plateau is an objective-gradient problem, not a search problem (`homemaker-py-2v1`/`ssz`/`hxi`/`tdp`/`gvb`/`1i8`) — measured 2026-08-25
Independent review of why the search "finds solutions that are clearly not the
best and gets stuck in local minima", prompted by the §37 scoreboard: *every*
@ -4898,8 +4898,41 @@ Measured on a constructed harbor-house seed (`value` report):
circulation spine.** Observed live: in the 20 000-eval harbor-house run above,
`undivide`/`core_undivide` appear 16 times in the improvement log.
This retro-explains three prior results as one mechanism, and suggests two of
them were measuring a broken gradient rather than a bad idea:
**Refinement (measured 2026-08-25, after the first draft of this section):
zero-exposure is only half of it, and not the half that matters most.**
Repeating the deletion test separately for *lit* and *buried* circulation:
| leaf | exposed wall | `q_crink` | deleting it |
|---|---|---|---|
| `0/llll` `O` | buried | 0 | **×85.6 rewarded** |
| `0/lllr` `C` | buried | 0 | **×61.6 rewarded** |
| `1/rlrr` `C` | buried | 0 | **×6.9 rewarded** |
| `0/rlll` `C` | 27.4 m² | 0.105 | ×0.01 kept |
| `0/lrrl` `O` | 21.2 m² | 0.0004 | ×0.61 kept |
| `0/rrll` `C` | 40.7 m² | **0.736** | **×4.1 rewarded** |
The last row is the important one: a **well-lit** circulation leaf, scoring
0.736 on the very factor §38.1 is about, is *still* worth ×4 to delete. So
there is a second, independent mechanism, and it is the structural one:
**Circulation is priced at `value_circulation = 50` against
`value_inside = 300`** — one sixth the value per m² of the habitable space it
could become. Deleting a circulation leaf merges it into its sibling, which
converts corridor into room: a flat **×6 value gain**. The only thing pushing
back is the `level N not connected` fail, worth **×0.5**. Break-even needs
`0.5^k < 50/300`, i.e. **k > 2.58 — severing must cost at least 3 fails to be
net-negative. It costs 1.** Net incentive to sever: `6 × 0.5 = ×3.0` in
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
spine even when the circulation is perfectly daylit.**
That is the cleanest available explanation of why `level 0 not connected` and
`level 1 not connected` are still present in the best layout found after
1.7 M evals: the search is not failing to fix them, it is being paid ×34 to
create them.
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:
- **§18 graded circulation-connectivity — NEGATIVE.** A secondary comparator
key cannot beat a ×60 primary-scalar gradient pulling the other way.
@ -4917,16 +4950,33 @@ so every interior leaf needs exposed wall `L ≥ A/(1.6202·h)` — per storey,
`fortified` perimeter edges, and harbor-house/maple-court mark **half their
plot perimeter `private`**:
| programme | daylit frontage | needed per built storey | verdict | observed floor |
|---|---|---|---|---|
| harbor-house | 54 m | 148 m | **2.7× short** | plateaus 3040 fails |
| maple-court | 56 m | 162 m | **2.9× short** | plateaus 7484 fails |
| health-centre | 43 m | 41 m | feasible | §32 clean null |
| programme-house | 24 m | 12 m | 2× surplus | **1 fail** (12k evals) |
| programme | daylit frontage | needed per built storey | verdict | floor @20k evals | best known |
|---|---|---|---|---|---|
| harbor-house | 54 m | 148 m | **2.7× short** | 3040 fails (§13.11) | 15 (`evolved-3M-nols-3`, **1.7 M evals / 2.4 days**) |
| maple-court | 56 m | 162 m | **2.9× short** | 7484 fails (§13.11) | — |
| health-centre | 43 m | 41 m | feasible | §32 clean null | — |
| programme-house | 24 m | 12 m | 2× surplus | — | **1 fail @ 12k evals** |
**The corpus fail-count plateau is predicted by frontage deficit alone.** The
two programmes that are frontage-short are exactly the two that plateau; the
two with surplus are the two that effectively solve. Causal check
**Frontage deficit predicts the COST of solving, not impossibility.** An
earlier draft of this section claimed the deficit predicts the plateau
outright, quoting §13.11's 20k-budget figure as harbor's floor; that was
wrong. Harbor-house *does* reach 15 fails — it just needs 1.7 M evals and 2.4
days to get there, against programme-house's 1 fail in 12 k. That ~150×
budget gap between a frontage-short and a frontage-surplus programme is the
real signature, and it is what §38.1/§38.2 predict: the deficit forces the
search to find a specific courtyard topology, and the objective punishes every
intermediate step toward one.
**The best-known harbor layout corroborates the mechanism directly.** Its
residual (§37.8) is 8 geometry fails plus 4 structural — and two of those four
are `level 0 not connected` and `level 1 not connected`. After 1.7 M evals,
the best layout ever found still has a **severed circulation spine on both
storeys**. That is not a search failure; it is §38.2 working as designed: the
objective pays ×60 to delete buried circulation, so connectivity is the one
thing that never survives to the end. Any fix to §38.1 should be judged first
on whether those two fails disappear.
Causal check
(`experiments/diag_exposure_frontage.py`, 6 seeds): relabelling harbor's two
`private` edges as open — identical geometry, identical programme, perimeter
labels only — cuts zero-exposure leaves **52% → 19%** and seeder crinkliness
@ -4971,7 +5021,36 @@ fitness weight between two single rooms**. In programme-house, missing `b1`
3 fails = 1/8. The tiered comparator inherits it: `n_hard` is dominated by
these cascades, so the primary search key is weighted by config verbosity.
### 38.6 Consequences for the Phase 9 plan
### 38.6 First repair attempt: three crinkliness modes — NOT SUFFICIENT ALONE
`fitness.quality_uncrinkliness` gained `crinkliness_mode` (config key,
EXPERIMENTAL, default `"urb"` = stock hard 0.0, byte-identical to all prior
runs). Three candidate repairs, A/B'd on the §38.2 deletion test (harbor-house,
3 constructed seeds, unpinned `C`/`O` leaves only):
Splitting the deleted leaves by whether they were buried or lit is what makes
the result legible (`experiments/ab_crinkliness_mode_ssz.py`):
| mode | buried rewarded | **lit rewarded** | all | median × |
|---|---|---|---|---|
| `urb` (stock) | 5/8 | **3/8** | 8/16 | ×1.02 |
| `floor` (clamp to 0.01, keep the fail) | 5/8 | **3/8** | 8/16 | ×1.02 |
| `compact_ok` (one-sided: compact is not a defect) | 5/8 | **3/8** | 8/16 | ×1.00 |
| `exempt_circulation` (corridors need no daylight) | **4/8** | **3/8** | 7/16 | ×0.61 |
**None of them removes the incentive, and the `lit` column does not move at
all — 3/8 under every mode, including stock.** That column is mechanism (2)
in isolation: deleting a *well-daylit* corridor is rewarded for reasons that
have nothing to do with crinkliness, so nothing written inside
`quality_uncrinkliness` can ever reach it. `floor` is inert (0.01 of a unit
quality is still ~zero against the cost saving); `exempt_circulation` removes
exactly one buried case.
The modes are kept, default off, as one half of a fix that needs both halves.
**Do not ship any of them as a standalone lever and expect the connectivity
fails to move** — `homemaker-py-2v1` is the half that matters.
### 38.7 Consequences for the Phase 9 plan
§37 track 1 ("no ground truth … the residual taxonomy may be miscalibrated
rather than unmet") was aimed at the right target, and §38.3 supplies a cheap
@ -4985,9 +5064,17 @@ part, an unsatisfiable constraint.
Tracks 2 and 3 (cheaper evaluation, exact sub-solvers) remain sound but are
orthogonal: making an evaluation 97× faster, or a labelling exact, does not
change which direction the objective points. Recommended ordering is now
`ssz``hxi`/`gvb` (restore a value gradient for interior space, re-tier),
then `tdp` (ship the pre-flight bound and re-baseline the corpus), and only
then resume `2g7.9`/`2g7.10`. In particular `2g7.7` (LLM repair operator at
`2v1` (price connectivity above the ×6 circulation→habitable value gap — the
dominant mechanism, and the one the §38.6 A/B isolated) → `ssz`/`hxi`/`gvb`
(restore a value gradient for interior space, re-tier), then `tdp` (ship the
pre-flight bound and re-baseline the corpus), and only then resume
`2g7.9`/`2g7.10`.
**Acceptance test for the combined fix, stated up front so it cannot be
moved:** harbor-house must reach its known 15-fail floor in materially fewer
than 1.7 M evals, *and* `level 0 not connected` / `level 1 not connected` must
be absent from the result. Fail-count parity alone is not a pass — the whole
claim of §38 is that those two fails are bought, not missed. In particular `2g7.7` (LLM repair operator at
stagnation) is worth deferring until after `ssz`: an LLM asked to propose a
valley-crossing multi-edit against an objective that pays ×85 to delete the
corridor it just inserted will have its work reverted by the next selection

View file

@ -0,0 +1,151 @@
"""A/B the `crinkliness_mode` repairs against the circulation-deletion incentive.
Evidence for `homemaker-py-ssz` / `homemaker-py-2v1` (DESIGN.md §38.6). The
test: on a constructed layout, delete each unpinned circulation/outside leaf
(by undividing its parent, which merges it into its sibling) and re-score. A
healthy objective should not pay you to do that.
Reports the count of deletions that still improve the score, and the median
score ratio, per mode. Splitting the rows by whether the deleted leaf was
buried or lit is what separates the two mechanisms:
* buried leaves are rewarded because ``quality_uncrinkliness`` returns a hard
0.0, so they contribute zero value (§38.1);
* **lit** leaves are rewarded because ``value_circulation`` (50) is one sixth
of ``value_inside`` (300), so merging corridor into room is a flat ×6 gain
against a ×0.5 connectivity penalty (§38.2 refinement) which no
``crinkliness_mode`` can touch.
Usage::
python experiments/ab_crinkliness_mode_ssz.py
python experiments/ab_crinkliness_mode_ssz.py --seeds 5 --progdir examples/maple-court
"""
from __future__ import annotations
import argparse
import copy
import numpy as np
from homemaker_layout import dom as dom_mod
from homemaker_layout import driver, fitness, geometry
from homemaker_layout import graph as graph_mod
from homemaker_layout import operators, programme
MODES = ("urb", "floor", "compact_ok", "exempt_circulation")
def make_fitness(progdir: str, mode: str) -> fitness.Fitness:
"""Evaluator matching ``driver.search``'s defaults, with one mode override."""
overrides = dict(driver._overrides_for(
leaf_sharing=True, superpose=False, max_share=None, conn_grade=False,
collapse_insearch=True, multi_use=False) or {})
overrides["crinkliness_mode"] = mode
conf, cost = fitness.load_config(progdir, overrides=overrides)
return fitness.Fitness(conf, cost)
def constructed_seed(progdir: str, seed: int) -> dom_mod.Node:
reqs = programme.load_programme_dir(progdir)
return operators.constructive_topology(
dom_mod.load(f"{progdir}/init.dom"), reqs, np.random.default_rng(seed),
sorted(reqs) + ["C", "O"],
min_storeys=programme.storey_minimum(progdir),
adjacency_aware=True, proportion_aware=True, circ_divisor=3,
leaf_sharing=True, leaf_share_factor=3, depth_balanced=True,
interior_outside=True, outside_divisor=3)
def unpinned_leaves(fit: fitness.Fitness, root: dom_mod.Node) -> list[tuple]:
"""(level, id, type, exposed_area) for circulation/outside leaves — the ones
no missing-space cascade pins in place."""
tree = copy.deepcopy(root)
geometry.clear_cache()
dom_mod.canonicalize_shares(tree)
fit.preprocess_building(tree)
dom_mod.merge_divided(tree)
geometry.clear_cache()
graphs = graph_mod.build_graphs(tree, fit.conf("door_width") or 1.2)
out = []
for li, lvl in enumerate(dom_mod.levels(tree)):
for leaf in lvl.leaves():
if dom_mod.is_outside(leaf) and not dom_mod.is_covered(leaf):
continue
if (leaf.type or "")[:1].upper() not in ("C", "O"):
continue
out.append((li, leaf.id, leaf.type,
fit.area_outside(leaf, graphs[li], {})))
return out
def delete_leaf(root: dom_mod.Node, li: int, lid: str) -> "dom_mod.Node | None":
"""Undivide the leaf's parent, merging it into its sibling. None if the cut
is inherited (not owned at this storey) or the sibling is itself divided."""
cand = copy.deepcopy(root)
lvls = dom_mod.levels(cand)
if li >= len(lvls):
return None
node = lvls[li].by_id(lid)
if node is None or node.parent is None:
return None
parent = node.parent
if parent.below is not None and parent.below.divided:
return None
sibling = parent.right if parent.left is node else parent.left
if sibling is None or sibling.divided:
return None
parent.division = None
parent.left = parent.right = None
parent.type = sibling.type
dom_mod.link(cand)
geometry.clear_cache()
return cand
def run(progdir: str, seeds: int) -> None:
print(f"programme: {progdir}, {seeds} constructed seeds")
print("a healthy objective rewards NO deletions\n")
header = f"{'mode':<20}{'buried':<14}{'lit':<14}{'all':<14}median x"
print(header)
print("-" * len(header))
for mode in MODES:
fit = make_fitness(progdir, mode)
counts = {"buried": [0, 0], "lit": [0, 0]}
ratios = []
for s in range(seeds):
root = constructed_seed(progdir, s)
base, _ = fit.score_with_fails(copy.deepcopy(root))
for li, lid, _typ, exposed in unpinned_leaves(fit, root):
cand = delete_leaf(root, li, lid)
if cand is None:
continue
score, _ = fit.score_with_fails(copy.deepcopy(cand))
bucket = "buried" if exposed == 0 else "lit"
counts[bucket][1] += 1
ratios.append(score / base)
if score > base:
counts[bucket][0] += 1
tot = [counts["buried"][0] + counts["lit"][0],
counts["buried"][1] + counts["lit"][1]]
median = sorted(ratios)[len(ratios) // 2] if ratios else float("nan")
buried = "%d/%d" % tuple(counts["buried"])
lit = "%d/%d" % tuple(counts["lit"])
both = "%d/%d" % tuple(tot)
print(f"{mode:<20}{buried:<14}{lit:<14}{both:<14}x{median:.2f}")
def main() -> None:
ap = argparse.ArgumentParser(description=__doc__,
formatter_class=argparse.RawDescriptionHelpFormatter)
ap.add_argument("--progdir", default="examples/harbor-house")
ap.add_argument("--seeds", type=int, default=3)
args = ap.parse_args()
run(args.progdir, args.seeds)
if __name__ == "__main__":
main()

View file

@ -372,6 +372,21 @@ class Fitness:
# target combines both codes' area (quality_size).
self._multi_use = bool(self.conf("multi_use"))
self._colocate_pairs: list | None = None # lazily derived
# homemaker-py-ssz (DESIGN.md §38.1): how quality_uncrinkliness treats a
# leaf with no daylit wall. "urb" (default) = stock hard 0.0, byte-
# identical to every prior run. "floor"/"compact_ok"/"exempt_circulation"
# are the three candidate repairs — see quality_uncrinkliness.
self._crinkliness_mode = str(self.conf("crinkliness_mode") or "urb")
if self._crinkliness_mode not in (
"urb", "floor", "compact_ok", "exempt_circulation"):
raise ValueError(
f"unknown crinkliness_mode: {self._crinkliness_mode!r}")
# The floored value stays BELOW FAIL_THRESHOLD, so a buried leaf still
# emits its crinkliness failure and the fail count is unchanged — only
# the value gradient is restored. Raising this above FAIL_THRESHOLD
# would silently delete a whole fail category.
self._crinkliness_floor = float(
self.conf("crinkliness_floor") or 0.01)
# ------------------------------------------------------------------ #
# Type superposition + collapse (homemaker-py-9o5)
@ -1093,9 +1108,35 @@ class Fitness:
key = "uncrinkliness_circulation" if dom_mod.is_circulation(leaf) else "uncrinkliness"
distance, sigma = self.conf(key)
crink = self.crinkliness(leaf, G, groups)
# homemaker-py-ssz (DESIGN.md §38.1), EXPERIMENTAL, all default OFF —
# `crinkliness_mode="urb"` reproduces the stock behaviour exactly.
#
# Stock Urb returns a hard 0.0 for a leaf with no daylit wall. That is
# the correct limit of the formula (1/crink -> inf, gaussian -> 0), but
# because evaluate_leaf MULTIPLIES factors into quality and
# process_storey accumulates `value += quality * rate * area`, such a
# leaf contributes EXACTLY ZERO value while still costing — so the
# objective cannot rank buried rooms at all, and buried circulation/
# outside leaves (which no missing-space cascade pins) are pure
# liabilities worth ~x60-x85 to delete. Measured: 45-56% of interior
# leaves are in this state. These modes restore a gradient there.
mode = self._crinkliness_mode
if mode == "exempt_circulation" and dom_mod.is_circulation(leaf):
# (c) internal corridors are ordinary architecture; stop requiring
# every circulation leaf to reach daylight.
return 1.0
if not crink:
return 0.0
return gaussian(1 / crink, 1.0, distance, sigma)
# (a) floor: keep buried leaves rankable by their other factors
# instead of collapsing the whole quality product to zero.
return self._crinkliness_floor if mode in ("floor", "compact_ok") else 0.0
q = gaussian(1 / crink, 1.0, distance, sigma)
if mode == "compact_ok" and 1 / crink > distance:
# (b) one-sided: being MORE compact than target is not a defect the
# way over-exposure is, so clip to 1.0 on the compact side rather
# than decaying symmetrically into a fail.
return 1.0
return max(q, self._crinkliness_floor) if mode in ("floor", "compact_ok") else q
# --- access --- #

View file

@ -468,3 +468,65 @@ def test_classify_fail_tier_covers_full_corpus():
classify_fail_tier(line) # raises on failure
checked += 1
assert checked > 0
# --------------------------------------------------------------------------- #
# homemaker-py-ssz / DESIGN.md §38.1 — crinkliness_mode (EXPERIMENTAL)
# --------------------------------------------------------------------------- #
class _StubCrink(Fitness):
"""Fitness with ``crinkliness`` stubbed, so the modes can be tested without
building a real tree/graph (the value under test is the branch, not the
geometry)."""
_stub = 0.0
def crinkliness(self, leaf, G, groups): # noqa: D102 - test stub
return self._stub
def _stub_fit(mode=None, stub=0.0, type_="t1"):
conf = dict(CONF_DEFAULTS)
if mode is not None:
conf["crinkliness_mode"] = mode
f = _StubCrink(conf, dict(COST_DEFAULTS))
f._stub = stub
return f, _leaf(type_)
def test_crinkliness_mode_defaults_to_urb_and_reproduces_hard_zero():
"""Default must be byte-identical to stock Urb: buried leaf -> exactly 0.0."""
f, leaf = _stub_fit()
assert f._crinkliness_mode == "urb"
assert f.quality_uncrinkliness(leaf, None, {}) == 0.0
def test_crinkliness_floor_restores_gradient_but_keeps_the_failure():
"""The floor must stay BELOW FAIL_THRESHOLD: it restores a value gradient
without silently deleting a whole fail category."""
f, leaf = _stub_fit("floor")
q = f.quality_uncrinkliness(leaf, None, {})
assert q > 0.0, "buried leaf should no longer be worth exactly nothing"
assert q < FAIL_THRESHOLD, "buried leaf must still emit its crinkliness fail"
def test_crinkliness_compact_ok_clips_on_the_compact_side_only():
"""Being more compact than target is not a defect; being over-exposed is."""
target = CONF_DEFAULTS["uncrinkliness"][0]
# 1/crink > target => more compact than target => clipped to 1.0
f, leaf = _stub_fit("compact_ok", stub=1.0 / (target * 2))
assert f.quality_uncrinkliness(leaf, None, {}) == 1.0
# 1/crink < target => over-exposed => still decays
f, leaf = _stub_fit("compact_ok", stub=1.0 / (target / 2))
assert f.quality_uncrinkliness(leaf, None, {}) < 1.0
def test_crinkliness_exempt_circulation_only_exempts_circulation():
f, circ = _stub_fit("exempt_circulation", type_="C")
assert f.quality_uncrinkliness(circ, None, {}) == 1.0
f, room = _stub_fit("exempt_circulation", type_="t1")
assert f.quality_uncrinkliness(room, None, {}) == 0.0
def test_crinkliness_mode_unknown_raises():
with pytest.raises(ValueError, match="crinkliness_mode"):
_stub_fit("nonsense")