§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:
parent
753356e47e
commit
50b5cfd476
4 changed files with 359 additions and 18 deletions
119
DESIGN.md
119
DESIGN.md
|
|
@ -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 ×3–4 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 30–40 fails |
|
||||
| maple-court | 56 m | 162 m | **2.9× short** | plateaus 74–84 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** | 30–40 fails (§13.11) | 15 (`evolved-3M-nols-3`, **1.7 M evals / 2.4 days**) |
|
||||
| maple-court | 56 m | 162 m | **2.9× short** | 74–84 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
|
||||
|
|
|
|||
151
experiments/ab_crinkliness_mode_ssz.py
Normal file
151
experiments/ab_crinkliness_mode_ssz.py
Normal 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()
|
||||
|
|
@ -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 --- #
|
||||
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue