0wr asked which harbor A/Bs were decided by a narrow margin before 39.4.
Measuring harbor's variance makes the margin-by-margin triage moot.
Harbor's paired seed-to-seed sd is 6.19 fails (24 paired ON/OFF runs,
budget 2500), so n=3 resolves nothing finer than 15.4 fails. Every recorded
harbor margin is below that: 13.9 share_edge_cap 3.7, 20 qpk 8.3, 23 f1d
mixed, 37.1 tiering 6.3. So it is not that SOME harbor results were narrow
-- no harbor A/B run at three seeds could resolve the margin it reported,
independently of what 39.4 did to the programme. Of the 220 possible
3-seed subsets of the 24 runs, 56 (25%) show a clean 3/3 sweep for ON.
Re-measured 20's harbor arm, the one backing a live default:
N=3 2W/1L/0T +2.67 p=0.560
N=12 8W/3L/1T +3.50 p=0.076
N=24 13W/10L/1T +1.21 p=0.502 CI [-2.46,+4.88]
Null. The published "harbor: ON wins 3/3, 80.3 -> 72.0" was a lucky draw --
even seeds 1-3 measured here give 2W/1L, not a sweep.
So 20's claim that the qpk verdict "holds at both example scales tested" is
withdrawn and annotated in place. collapse_insearch's default rests on
programme-house alone (38.19, N=60, +0.57, p=0.017). It is not refuted on
harbor -- direction positive but indistinguishable from zero -- but harbor
must not be cited as corroboration.
Harness generalised (PROG/BUDGET/WORKERS) and results kept.
Filed homemaker-py-... : A/B harnesses should report the minimum detectable
difference for the N they run, so an underpowered verdict is visible when
it is made rather than years later.
Closes homemaker-py-0wr.
Lint at parity (46).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MJ84Feep79Hhm3E4zZJmnB
Cold-start re-baseline after the DESIGN.md 38.10/38.11 objective
change. Single worker (avoids homemaker-py-b8g), scored by the
shipped scorer from the programme directory.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MJ84Feep79Hhm3E4zZJmnB
38.18 confirmed the 1ph default-flip was sound for its own era, but that
measurement predates three changes to the objective it was measured
against -- 39.4's namespace fix, 38.10/38.11's per-space crinkliness, and
38.12's missing-space cascade -- and collapse_insearch runs collapse_global
inside every eval, valued against exactly the factors those touched. A
default carried on a superseded measurement is an assumption, not a result.
Re-ran the 1ph protocol as published on the current codebase:
N OFF ON W/L/T diff t p
published 1ph 20 7.95 7.10 11/6/3 +0.85 2.38 0.028
current objective 20 7.85 7.15 10/7/3 +0.70 1.82 0.069
current objective 40 7.60 7.03 21/14/5 +0.57 2.01 0.045
current objective 60 7.58 7.02 29/19/12 +0.57 2.45 0.017
Verdict: the default STANDS. At N=60, mean diff +0.567 fails/seed, paired
t=2.454 (df=59), p=0.0171 exact, 95% CI [+0.105, +1.029] excluding zero;
Wilcoxon signed-rank cross-check agrees (p=0.0138), which matters because
fail counts are small integers and normality is not obvious.
Two caveats. The effect is about a third smaller than published (+0.57 vs
+0.85) -- partly regression from a lucky N=20 draw, partly plausible real
erosion, since several fails collapse_global used to clear have been
redefined out of existence or made harder.
More usefully: the published N=20 can no longer detect its own effect. At
exactly that sample size the current answer is p ~= 0.069, a null by the
conventional threshold. Had I stopped at N=20 the honest report would have
been "the 1ph verdict no longer reproduces" and the default would have
looked unjustified. It took N=60 to resolve. That is the 8sh/1ph/qi6/lj3
pattern this log warns about, now biting the flagship result itself: any
future re-validation of this default needs N >= 40.
20 annotated in place so a reader of the original claim sees the current
figure. Harness takes a seed range now (APPEND=1 to extend a sweep).
Closes homemaker-py-ioe.
Lint at parity (46); tests 384 passed, 0 failed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MJ84Feep79Hhm3E4zZJmnB
d86 asked for the rigorous version of 35's spot-check -- check out the
codebase at the 1ph commit, backport the iio stale-leaf-share fix, re-run
the historical seed sets.
One constraint had to be worked around: this repository's history begins
2026-07-30, six days AFTER the 1ph commit (2026-07-24), so that checkout
does not exist here. Closest reachable stand-in is 391f510 (2026-07-30), a
genuine ancestor of the iio fix and therefore pre-iio, carrying that era's
examples/ and objective. Measured there, not at the true 1ph commit, and
that is a real limitation of the reproduction.
Protocol as published: programme-house, budget 3000, 4 workers, seeds 1-20,
ON vs OFF, both arms finished with --collapse. Run twice over one worktree,
as-is then with the 22-line iio hunk applied.
OFF ON W/L/T mean diff t(df=19)
published 1ph 7.95 7.10 11/6/3 +0.85 2.38
pre-iio 391f510 8.05 7.10 11/6/3 +0.95 2.59
same + iio fix 8.05 7.10 11/6/3 +0.95 2.59
The published verdict reproduces, and the iio fix changes nothing: 0 of 40
(seed, arm) cells differ, per-seed counts equal cell by cell.
It could not have been otherwise. The bug needs a leaf carrying a STALE
share/share_type, and leaf-sharing only stamps a share where a code has
count > 1 -- programme-house declares count: 1 for all six codes. Over 8
constructed seeds at that commit, programme-house had share_type set on 0
of 56 leaves; harbor-house on 24 of 128. _collapse_value reads
leaf.share_type, so the bug is structurally unreachable on the 1ph
protocol. That is why 35 saw 2/3 harbor seeds diverge by 5-8 fails while
programme-house at N=20 moves not one cell: harbor has codes at counts 10,
6 and 5.
20's retroactive caveat is discharged for the 1ph section and stays live
for harbor-house/qpk, where shares exist and divergence was measured.
Kept experiments/rerun_1ph_protocol.sh and both result TSVs.
Closes homemaker-py-d86.
Lint at parity (46); tests 384 passed, 0 failed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MJ84Feep79Hhm3E4zZJmnB
run_staged_search.py reported MISMATCH on its BASELINE arm -- the
LEAFSHARE=0/MULTIUSE=0 control every A/B compares against. Two facts
combined: driver.search_staged had no collapse_insearch parameter at all,
so every inner search() call inherited search()'s True default
unconditionally; and no example patterns.config sets the key, so the final
_native_score rescore got False from a bare load_config. Search optimised
one objective, the rescore graded another.
The 7ua fix pinned the key inside a fitness.load_config monkeypatch, but
that patch was installed only `if leaf_share or multi_use` -- so it fixed
every arm except the control.
Fixed in the right place: search_staged now HAS the parameter (default
True, byte-identical to the inherited default), threaded into all three
internal search() calls. The harness chooses the arm explicitly (COLLAPSE,
default 1), passes it to the search, and passes the SAME value to
_native_score, which overrides the key rather than hoping the config
carries it. The rescore mirrors the search by construction.
Verified on programme-house, budget 150:
baseline MISMATCH 1.56663e-08 vs 1.51708e-08 -> OK
COLLAPSE=0 (knob did not exist) -> OK 1.66216e-08
LEAFSHARE=1 / MULTIUSE=1 -> OK
COLLAPSE=0 scoring differently confirms the knob is not a no-op, and the
default arm's search result is unchanged, so no prior staged number moves.
Audited the other three search_staged callers: run_and_capture_91f.py
already pins collapse_insearch: True; run_island_ab.py never re-scores;
probe_harbor_floor.py did NOT pin it and had the same bug -- now fixed, and
that is the harness which produced every 13.x floor number.
The recorded mitigating factor -- only the continuous score moved, the fail
count matched, and the run_*_ab.sh greps read only the count -- is true and
is exactly what made it dangerous: a harness that reports MISMATCH on its
own control, invisibly to the metric of record, trains everyone to ignore
the warning.
Closes homemaker-py-4ok.
Lint at parity (46); tests 381 passed, 0 failed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MJ84Feep79Hhm3E4zZJmnB
All four 500k runs died about 10 minutes in when the container was
reclaimed. No SIGTERM fired, so no .dom was written and 0 of 12 runs
completed. My plan committed results per finished run, which protected
nothing because no run reached its commit point. The bad assumption was
reading "reclaimed after inactivity" as CPU inactivity; it is conversation
inactivity, and background compute does not hold the box open.
Progress reached before the loss (from the tracked logs): harbor 24,960
evals / 40 fails, maple 14,880 / 79, health-centre 25,920 / 33,
programme-house 138,800 / 2.
The underlying gap is not environmental: a search's only output lands at
the very end or on SIGTERM, so ANY abrupt loss -- reclaimed container, OOM,
power cut -- takes the whole run with it. On a 3M-eval search that is 2.4
days of compute with no recoverable artefact.
- driver.search gains checkpoint=/checkpoint_every=: the current best is
handed to a callback at most every N evals. Rate-limited by evals, not
improvements, which come in bursts early. A failing checkpoint is logged
and swallowed -- losing a checkpoint is bad, losing the search because a
checkpoint failed is worse.
- homemaker-evolve --checkpoint-every N writes <out>.dom.checkpoint via
mkstemp + os.replace, so a crash can never catch it half-written. It is
deliberately NOT the output path: a checkpoint is a leaf-sharing run's
internal best, dishonest under the canonical scorer until the finish
stage unfolds it (homemaker-py-3l6), and must not be mistaken for the
finished article.
- Verified the written checkpoint re-loads as a valid .dom.
Default off, so behaviour is unchanged without the flag.
Lint at parity (46); tests 372 passed (3 new), same 2 pre-existing failures.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MJ84Feep79Hhm3E4zZJmnB
Cold-start re-baseline after the DESIGN.md 38.10/38.11 objective
change. Single worker (avoids homemaker-py-b8g), scored by the
shipped scorer from the programme directory.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MJ84Feep79Hhm3E4zZJmnB
Ruling: corridors need daylight. Only rooms not occupied from day to day --
a cupboard, a store, a plant room -- do without it.
That overturns the classification 38.8 and 38.9 rested on. Those sections
exempted internal circulation, covered courtyards, WCs, laundries and
reception on my inference rather than any ruling. None are exempt: a
corridor is occupied all day every day, a waiting room likewise, a laundry
is a room people spend time in, a sterilisation room is a workplace.
Re-measured with the classification READ FROM THE CORPUS (a space is exempt
exactly when its own patterns.config declares crinkliness: none) instead of
inferred: 24 of 271 crinkliness fails (9%) were not defects -- not 136
(50%), not 61%. Overstated by about six times.
- uncrinkliness_circulation stays at [5/6, 1.1/3]. 38.10 called its
equality with the habitable target the purest case of an untuned value;
it is not a bug, it is the correct answer reached by default, since
corridors want daylight on the same terms as rooms.
- corpus declarations narrowed 18 -> 14. usage: utility was too coarse and
swept in Laundry Rooms and a Sterilisation Room. What remains is
storage, plant/mechanical, utilities closets and a records room.
- audit_crinkliness_truth.py rebuilt to read the classification from the
configs rather than infer it from usage:, so it cannot drift from the
ruling again.
- 38.8/38.9's inflated figures flagged in place, with 38.11 carrying the
correction.
What survives, none of it dependent on the classification: the 38.6
critique of the three modes; usage_daylight being mis-keyed; the 38.8 A/B
yardstick being wrong; and the per-space crinkliness mechanism itself.
ssz's opening measurement was right but its reading was wrong -- under this
ruling a buried leaf usually IS a defect, so scoring it zero is largely
correct. The narrowed remnant is that the score is FLAT rather than low, so
two equally-buried layouts cannot be ranked. Filed as homemaker-py-9gj; it
is a search-mechanics problem, not a calibration one.
Lint at parity (46); tests 369 passed, 2 pre-existing failures (ut5's stale
baseline and 1ue), both failing identically before this change.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MJ84Feep79Hhm3E4zZJmnB
Owner's ruling, and it corrects the design not just the classification: the
daylight requirement is already defined in the crinkliness. The gaussian's
compact side IS "too little exposed wall per unit floor"; its exposed side
is envelope cost. 38.9's proposed daylight: axis was redundant, and keying
it off usage: was worse than redundant.
What was actually missing: crinkliness is the only leaf quality factor with
no per-space target. size, width and proportion are all declared by the
space; crinkliness was one global number for every room in every building.
crinkliness: none -> no minimum-exposure requirement, may be buried
crinkliness: [t, s] -> this space's own target
key absent -> the global uncrinkliness target, as today
`none` clips the factor on the compact side, it does not switch it off:
over-exposure is still penalised, because a crinkly leaf costs envelope
whatever it holds. A store may be buried; a store may not be a starfish.
The mechanism is backward compatible -- an absent key resolves to the
global target, so shipping it changes no score. Behaviour changes only
where a config declares something, which keeps the objective change
visible per programme in config rather than hidden in a default.
Owner's classification: everything a person occupies wants a window, WCs
and reception/waiting/foyer included; only stores, plant, records and
laundry do not. migrate_crinkliness_key.py declared crinkliness: none on 18
corpus spaces. Crinkliness fails 271 -> 243, of which not-defects 136 (50%)
-> 108 (44%); the 28 that went are exactly the utility fails.
usage_daylight and needs_daylight are removed as mis-keyed, and
DAYLIGHT_USAGES with them -- a vocabulary value should exist only where the
engine treats it differently. The historical crinkliness_mode modes stay,
default off, so 38.6/38.8 remain reproducible.
uncrinkliness_circulation is now settable to none like any space, but its
default is left unchanged pending a ruling: corridors were not among the
groups ruled on and are 63% of the remaining phantom fails.
Lint at parity (46); tests 364 passed, same 7 pre-existing fixture failures.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MJ84Feep79Hhm3E4zZJmnB
Correction 1: 38.8's A/B scores every arm under stock urb, which is only
sound if stock is ground truth -- and stock is what this section shows is
miscalibrated. Scoring the repair under the objective it repairs penalises
it for repairing. That result is not evidence against usage_daylight.
The measurement that matters needs no search: are the emitted failures
true? audit_crinkliness_truth.py classifies every stock crinkliness fail by
the leaf's declared usage. 164 of 271 (61%) are reported against spaces
that want no daylight -- stores, WCs, plant, corridors, covered courtyards
-- and it holds on evolved artefacts (maple generated.dom: 64%), not just
constructed seeds. Under value *= 0.5 ** len(failures) each one halves the
fitness of a design that did nothing wrong.
Correction 2: usage: is the wrong key, and usage_daylight as written
mis-keys it. 39.7 established usage: as an ACCESS-requirement class.
"Needs no special access" and "needs no window" are different questions,
and the corpus separates them: usage: none is Waiting Room, Reception,
Reception Office, Entrance Foyer -- a waiting room plainly wants daylight
and DAYLIGHT_USAGES exempts it. The usage: bedroom half is right, but by
luck of where the access axis happened to fall.
The audit is robust to that error (61% -> 57% if none is reclassified), so
the finding stands; the design does not. Daylight needs its own declared
per-space axis, decided by the programme author as usage: was.
usage_daylight stays default off and is not the shipping fix. The
compact-side clip is the right shape for the factor, so the mechanism is
retained pending a daylight: attribute to key it on.
Higher-power A/B stopped: it was chasing a score against a flawed objective
with a mis-keyed fix.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MJ84Feep79Hhm3E4zZJmnB
Fixed-budget A/B (3000 evals, 3 seeds, harbor + maple, every arm scored
under stock urb so the permissive modes cannot win by deleting a fail
category).
usage_daylight's paired hard-fail deltas are harbor [0,-1,-10] and maple
[0,+2,-10]. The means (-3.7, -2.7) flatter it: the whole signal is seed 2
in both programmes, and seed 2 is the seed where stock itself does worst.
Two seeds in three are flat or slightly worse. On that seed soft rises as
much as hard falls (harbor -10h/+9s, maple -10h/+15s), so totals go
62->61 on harbor and 120->125 on maple.
Because the scoring is stock, that is a genuine trade of hard failures for
soft ones, not a relabelling -- progress under the tiered comparator, where
n_hard is primary, but a fail against this issue's acceptance criterion
("without inflating soft"). Which yardstick is right is now the live
question, and it is gvb's question as much as ssz's.
usage_daylight stays default off; undecided, not refuted. Higher-power run
(urb vs usage_daylight, 10 seeds) is running.
The diagnostic half stands independent of the search A/B: the objective
demands daylight of two thirds of the buried population that does not want
it, and 38.6's contrary null was an artefact of three modes that never
touched those leaves.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MJ84Feep79Hhm3E4zZJmnB
fitness.py already imports the module as `_programme` at the top and uses
that idiom elsewhere (`_programme.SOCIABLE_USAGES`). The local re-import
sat in the per-leaf hot path for no reason.
Also snapshots the in-progress ab_ssz_search.csv; the full run is still
going and will supersede it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MJ84Feep79Hhm3E4zZJmnB
DESIGN.md 38.6 concluded the three crinkliness modes were inert against the
circulation-deletion incentive. Two things were wrong with that measurement.
Its premise, 38.2, is retracted. And its script selected leaves with the
pre-39.4 prefix rule `type[:1].upper() in ("C","O")`, which sweeps every
programme room starting with c or o -- cr1, of1 -- in as circulation.
The simpler problem is that none of the three modes ever touched the leaves
ssz is about. quality_uncrinkliness reaches `if not crink` before any mode
logic that matters, so for a zero-exposure leaf: floor returns 0.01 (one
percent of a unit quality, multiplied into a product and weighed against a
whole leaf's cost -- inert); compact_ok is self-contradictory, announcing
that compact is not a defect and then returning the floor for the most
compact case of all; exempt_circulation reaches at most a third of them.
Measured: 0% / 0% / 0% / 21-33% of buried leaves rescued.
What the buried leaves are, now that 39.7 gives every space a usage: two
thirds of them are spaces that architecturally do not want a window --
stores, WCs, plant, corridors, covered courtyards -- scored identically
with a windowless bedroom. harbor 22/33, maple 33/46, health 9/18.
- crinkliness_mode="usage_daylight": daylight required of the uses a
person occupies (programme.DAYLIGHT_USAGES) and nothing else. Elsewhere
the factor is clipped on the compact side only, so being buried stops
being a defect while over-exposure still costs -- a crinkly leaf costs
envelope whatever it is used for. A windowless bedroom stays the hard
zero it is under stock: 11/11, 13/13, 9/9 still failing.
- compact_ok repaired to score the buried limit as compact, the behaviour
its name always claimed. It now rescues 100% including bedrooms, and is
kept as the upper-bound control, not a candidate.
- ab_ssz_search.py: the fixed-budget search A/B ssz's acceptance criteria
actually asks for. Every arm is optimised under its own objective and
re-scored under stock urb, because the permissive modes return 1.0
where stock fails and would otherwise win by deleting a fail category.
- ab_crinkliness_mode_ssz.py: prefix rule fixed, retracted premise
flagged in its docstring.
- 38.7's remaining claims from the retracted 38.2/38.3 corrected.
Default is unchanged ("urb"), byte-identical to all prior runs. Lint at
parity (46 pre-existing); tests 366 passed, 10 new, same 7 pre-existing
fixture failures (homemaker-py-bdf).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MJ84Feep79Hhm3E4zZJmnB
Closes homemaker-py-tdp. The bound it was built on (every interior leaf
needs L >= A/(1.6202*h) of daylit wall) is sound, but tdp applied it to a
FULLY BUILT plot. None of these programmes ask for a fully built plot.
Recomputed against the area each programme actually demands, harbor-house
and maple-court are not frontage-infeasible: they need 49 m2 and 22 m2 of
courtyard against 277 m2 and 424 m2 of spare plot. The "2.7x / 2.9x short"
figures are withdrawn, and with them the claim that the plateau programmes
are unsatisfiable as specified -- the plateau remains unexplained.
One corpus programme is genuinely infeasible, for a much cruder reason:
health-centre demands 240 m2 of floor on a 183 m2 plot (131%), single
storey. Every room lands at 0.60x its declared target, 100% undersized,
uniformly. Filed as homemaker-py-7b7, blocking homemaker-py-7xb.
- evolve._preflight: two closed-form checks at startup (does the demand
fit the plot; is there enough daylit wall for it). Advisory only, it
never blocks a run -- an author may be exploring an over-tight brief
deliberately. Silent on programme-house.
- diag_exposure_frontage.frontage_budget reports the full budget.
- DESIGN.md 39.11 with the corrected corpus table; 38.3 marked PARTLY
RETRACTED and cross-referenced.
Both measure plot area and frontage through geometry rather than the raw
init.dom corners, so they carry the wall_outer inset and plot rotation,
and "daylit" means what Fitness.area_outside means by it. A hand-rolled
first version skipped the inset and read ~1 m / ~14 m2 optimistic per
plot; 39.11 carries the corrected numbers.
Lint unchanged at 46 pre-existing findings; tests unchanged at 7
pre-existing failures (the uncommitted evolved-3M*.dom fixtures,
homemaker-py-bdf).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MJ84Feep79Hhm3E4zZJmnB
Answers homemaker-py-yql. §39.8 established the search is not PAID to sever
circulation; this establishes where connectivity actually goes.
CONSTRUCTED, THEN LOST -- at construction time, in the resize.
_assign_adjacency_aware picks circulation as a CONNECTED dominating set and
succeeds every time. _size_divisions_from_targets then moves every wall to hit
the programme's area targets and destroys it.
Measured over 20 constructed seeds per programme, fully-connected seeds:
harbor-house 1/20, health-centre 1/20, maple-court 0/20. The control -- same
seeds with proportion_aware=False, i.e. no resize -- is 100% connected on all
three. Mechanism confirmed on health-centre: 41 of 49 circulation-to-circulation
edges destroyed by the resize, surviving shared walls squeezed to 0.54-1.11 m
against door_width=1.2, so they stop counting as edges. This is the failure mode
§37.7 recorded for CP-SAT assignment, never looked for in connectivity, where it
costs 35-95 points.
§39.7 COST CHECK: zero. Identical rates under prefix-inferred vs declared
usages -- has_circulation never trims C-C edges, so last commit's usage change
could not and did not make connectivity harder to achieve.
REPAIR MEASURED NEGATIVE. operators.repair_circulation_settled applies §37.7's
own alternating-minimisation fix (re-connect against the settled geometry by
retyping the cheapest bridging leaves to C). It restores 100% connectivity on
all three programmes -- and is still the wrong trade: connectivity fails fall
0.8-1.7 per seed while missing-room fails rise 5.0-8.5, because every retyped
leaf displaces a required room at a 3-5 fail cascade (§38.5). Kept default off
with the write-up, per house style for a null lever, plus a byte-identical
default test and a test asserting it does reconnect every storey.
NEXT LEVER, FILED: preserve the connection during the resize (constrain
_size_divisions_from_targets so a shared C-C boundary cannot fall below
door_width) rather than rebuild it afterwards at the programme's expense --
a constraint on an existing solve, not a new repair pass. solver.py's existing
min_width_generic is the same idea applied to leaf width rather than to a shared
boundary, so it may belong beside it.
Adds experiments/diag_connectivity_yql.py (construct / cost / survive reports).
355 passed (+2 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
Closes the second namespace sharing a first character with programme codes: the
usage prefixes b/t/l/k, under which a room silently inherited another room's
connectivity rules from its spelling.
usage is a plain, MANDATORY attribute of the space definition -- not a lookup
table. An interim design proposed a top-level usage_classes: table binding
author-coined names to behaviour; withdrawn, because an indirect name->behaviour
mapping living apart from the thing it describes is exactly the shape of the
prefix rule §39 exists to remove, it would be the only such table in a schema
where every other space property is a plain attribute, and the need it served
was already met -- "building specific" is about what a room is CALLED, and
name: is already free text.
Rule that settles it: a usage value exists iff the engine treats it differently
somewhere. Config selects among behaviours; it cannot invent them.
- programme.USAGES (living/kitchen/bedroom/toilet/utility/none) plus the
behaviour groupings PRIVATE_USAGES / PRIVATE_STRIPS / TOILET_STRIPS /
SOCIABLE_USAGES. Missing or unknown usage is a load error naming the code,
from BOTH parse paths.
- Code-level, never leaf-level: usage_of(leaf.type) is looked up fresh, so a
retype changes the class automatically. 51 sites assign leaf.type, and
share/share_type plus the r5a resurrection are the precedent for why
leaf-level attributes rot.
- graph.has_circulation takes the usage map and trims on declared class;
fitness.access and the public-access check likewise. fitness._t0 is DELETED --
no first-character type test remains anywhere in the codebase.
- utility is distinct from bedroom (same access requirements today) because it
is a different use and gives derive_interchange_classes an axis to relax on.
- A toilet now keeps its edge to a terminal room -- the Brand adjacency, which
the old b-before-t loop ordering severed.
- All 107 corpus entries migrated by experiments/migrate_usage_key.py, comments
and layout preserved.
MEASURED -- the connectivity model was ~4x too permissive. `none` is not
neutral: nothing is trimmed, so the graph may route THROUGH the room, and 34 of
52 codes had no class (Dental Surgery, Records Room, Utilities Closet all served
as corridors). Edges trimmed, prefix-inferred vs declared, 3 seeds each:
harbor-house 18 (9%) -> 79 (39%) inaccessible fails 0 -> 4
health-centre 12 (8%) -> 59 (40%) inaccessible fails 2 -> 3
maple-court 53 (17%) -> 123 (39%) inaccessible fails 1 -> 5
Re-baseline (seed 1, 20k, harbor): 58 fails (15h/43s) -> 61 (16h/45s), now
reporting 1-inaccessible-usable-space x2 plus level 0 and level 1 not connected.
The count rose because the objective got honest -- those failures were always
true of the layout and the old model could not see them. Every harbor number
before this was measured against a graph crediting routes through store
cupboards.
Sharpens §38.2: the objective pays x60-85 to delete circulation, and until now
the deleted corridors were not missed because storage stood in for them. With
that substitution gone, homemaker-py-2v1 is the remaining half -- and now
measurable, because the fails it should prevent actually fire.
350 passed (+5 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
The proposed top-level `usage_classes:` table (binding author-coined names to
engine behaviour, e.g. craft -> living) is withdrawn. Wrong three ways:
a) Same bug in another costume. An indirect name -> behaviour mapping living
apart from the thing it describes is exactly the shape of the
first-character prefix rule. §39 exists to remove that shape, not relocate
it into a table.
b) Inconsistent with the schema. Every other space property is a plain
attribute -- name, size, width, proportion, adjacency, level,
requires_below, count, share, interchange, co_locate -- and no key anywhere
in patterns.config is an indirection table. Verified across the corpus.
c) The need was already met. "Programmes are building specific" is about what a
room is CALLED, and name: is already free text. usage: records ACCESS
REQUIREMENTS, which are not building-specific -- a craft room and a lounge
have the same ones, which is the entire reason the key exists. The earlier
draft conflated the two.
So: spaces: ws1: {name: Workshop Space, usage: living} -- one place, no
binding step, no unbound-name error class.
Rule that falls out and is worth keeping: A USAGE VALUE EXISTS IF AND ONLY IF
THE ENGINE TREATS IT DIFFERENTLY SOMEWHERE. Config selects among behaviours; it
cannot invent them. An alias produces no behaviour that the aliased value does
not. `utility` passes the test -- not on edge-stripping, which is identical to
bedroom today, but because it is intended to drive derive_interchange_classes.
No src changes; proposal file only.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MJ84Feep79Hhm3E4zZJmnB
Rulings settle every previously-unresolved code: zt1 Sterilisation -> utility;
ph1 Pharmacy -> bedroom (a private room, not public, like an office); n
"Neighborhood" -> bedroom (a communal sleeping area, much like a dormitory);
gh1 Greenhouse -> living (grows plants, but a living area like a conservatory);
ws1 Workshop -> living conditionally, since it is building-specific (communal
craft room vs maintenance area) and both corpus programmes carrying it are
communal living facilities.
Final: living 12, bedroom 17, utility 11, toilet 7, kitchen 2, none 3. Zero
unresolved, zero missing.
DESIGN 1 — `utility` is a distinct value, not an alias for `bedroom`. Same
edge-stripping today (identical access requirements) but separately declared,
because they are very different uses AND because it gives
programme.derive_interchange_classes (§13/§26, homemaker-py-9o5) a natural
axis: bedroom- and utility-class leaves could be interchangeable DURING search
and collapse to their real use at scoring time, which is exactly the
superposition relaxation that machinery already implements.
DESIGN 2 — vocabulary controlled but NOT closed. The earlier "closed enum,
unknown value errors" recommendation is withdrawn. The six known names drive
behaviour; a programme may coin its own (`usage: craft`) provided it binds it
in the same file via `usage_classes: {craft: living}`. An unbound name is a
load error, so naming stays building-specific without an unrecognised usage
ever falling through to a silent default.
VERIFIED — the Brand adjacency point is not an engine bug. check_adjacency runs
against graph_base_pre, the UNFILTERED graph, so a declared adjacency is
satisfied by a neighbouring room regardless of what has_circulation strips; the
stripping only denies routing, which is correct. "A toilet adjacent to a
bedroom is a positive even if there is no door" is therefore already
expressible -- but only where DECLARED, and just one corpus programme declares
it (programme-house t1 -> [b1]). harbor-house, maple-court and health-centre
leave that adaptability credit on the table. Filed separately; it is programme
authoring and can land before or after the usage key.
No src changes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MJ84Feep79Hhm3E4zZJmnB
Bruno's rulings settle the framing: usage is an ACCESS-REQUIREMENT class, not a
room-name category. li1 "Library Corner" and br1 "Staff Room" are living rooms
(same access requirements); tr1 "Treatment Room" has a bedroom's requirements
(terminal, not a circulation route); la1 "Laundry Room" is utility. Ground-floor
external cases (fuel stores, bins) need no class — level: 0 plus adjacency: [o]
already expresses that.
SCOPE CORRECTION. My "93% pure annotation, 7 entries change" estimate was
mechanically true but misleading: `none` is not neutral. Nothing is stripped, so
has_circulation MAY ROUTE THROUGH the room. 34 of 52 distinct corpus codes
currently have no usage class and are therefore modelled as permeable corridors
-- including Dental Surgery, GP Consulting, Minor Surgery, Physiotherapy,
Counselling, Records, Sterilisation, six storage rooms, four offices and
"Individual Room" (a resident's own room).
Consequence for §38: the model OVERSTATES connectivity, since a level can be
found connected through rooms that are dead ends in reality. "level N not
connected" is therefore rarer than it should be, compounding §38.2 -- the
objective already pays x60-85 to delete circulation, and the corridors it
deletes are then not missed because store cupboards stand in for them.
Adds experiments/usage_map_proposal.yaml: all 52 codes covered (47 assigned, 5
unresolved, 0 missing) -- living 10, kitchen 2, bedroom 15, toilet 7, utility
10, none 3. Nothing reads it yet; it is the review artifact and, once agreed,
the migration input.
Also verified and recorded (Q2): a dedicated toilet off a bedroom-class room
does NOT survive scoring. has_circulation runs its bedroom loop before its
toilet loop and the bedroom loop strips bedroom->toilet, so the ensuite is
severed -- for real bedrooms too. Existing Urb behaviour, neither caused nor
fixed by the usage key.
No src changes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MJ84Feep79Hhm3E4zZJmnB
Answering "are we clean". Generic namespace: yes. Usage namespace: no.
FINISH §39.4. The first sweep missed sites, found by a full re-grep:
graph.py's free-area budget, operators.py host-preference / keep-type /
repair-candidate, fitness.py's ("l","c","k") public-access test, bubble.py's
generic adjacency reference, and -- the important one -- cpsat.py, which was
still matching adjacency by raw startswith. graph.code_matches_requirement is
now the single public answer to "does this leaf count as the thing the
programme asked to be next to", shared by has_adjacency, has_vertical_connection
and cpsat.
RETRACT §39.5. It concluded 2g7.5's CP-SAT seeder win did not survive the
correction. That was wrong. The cause was the missed cpsat matcher above: the
exact solver was optimising a different relation than the scorer checked, so a
failing test reporting an incomplete sweep was misread as a baseline shift.
Re-measured over 6 seeds, cpsat now wins on both programmes (harbor 102/92,
maple 156/154). xfail removed.
REAL BUG UNDERNEATH: CP-SAT was never deterministic despite
num_search_workers=1 and a comment claiming it. neighbors[slot] is a set of
dom.Node, which hashes by id() -- a memory address -- so raw iteration made the
model-build order vary and CP-SAT returned a different equally-optimal
assignment each run (measured 194/180/171/182 over four identical aggregates).
sorted() on the slot indices fixes it. Also paired the wall-clock cap with
max_deterministic_time (solves run ~124ms against a 2s cap, so nothing was
timing out -- latent hazard, not the cause). solve_room_labels is now
reproducible on every captured instance; constructive_topology on the cpsat
path still is not, filed as homemaker-py-fdp (plausible contributor to b8g).
§39.6 THE SECOND NAMESPACE. Usage prefixes b/t/l/k (bedroom/toilet/living/
kitchen) classify programme codes by first letter and stay prefix-based by
design, but they are not inert: has_circulation deletes graph edges from them.
Four corpus rooms are misclassified by spelling -- la1 "Laundry Room" and li1
"Library Corner" as living, br1 "Staff Room" as bedroom, tr1 "Treatment Room"
as toilet. Measured on a health-centre seed: tr1 loses its edge to the adjacent
O, br1 loses its edge to t10 "Staff WC" -- both feed the connectivity fails §38
found persisting. Filed homemaker-py-sel; an explicit usage: key is the fix,
but it changes fitness for correctly-spelled programmes too so it needs its own
A/B.
DOCS. README gains a "Room codes and reserved names" section; CLAUDE.md and
AGENTS.md gain the same summary for agents. audit_programme_config.py now
reports the usage class each code picks up alongside the namespace and
satisfiability checks. DESIGN §37.2's note calling the c/o/s quirk "existing
product behaviour, not a bug" is annotated as superseded.
Corpus audit: zero generic-namespace violations across all ten example
programmes. 346 passed, 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
Supersedes the previous commit's approach. Renaming harbor's four colliding
codes fixed one programme; tightening the matching rule fixes the rule, so a
room may be called anything. cr1/of/st1/st2 are restored and the examples are
byte-identical to their pre-§39 state -- which also means existing .dom
artefacts (evolved-3M*) stay valid, so migrate_ju3_rename.py is deleted.
The rule: Urb has exactly three GENERIC structural types (get_space_types:
qw/C O S/), the leaves the search creates. Measured across the corpus: 154 C,
110 O, 1 S, not one lowercase generic -- while every programme code is
lowercase, including single-character ones (r, t, m, n). Case is the
discriminator, not length. Every generic test was type[0].lower() in (...), a
case-insensitive PREFIX that swept up any programme code starting with those
letters; they now match the generic set exactly. 30 sites across dom, fitness,
graph, operators, programme, shapecurve and bubble.
NOT applied to the SEMANTIC prefixes: l/k/b/t classify programme codes by first
letter (graph.py builds bedroom<->toilet and kitchen<->living relations from
them) and stay prefix-based. Where the namespaces were mixed in one expression
they were split -- has_circulation's ("b","l","k","c") is three semantic
prefixes plus dom.is_circulation; access()'s ("l","c","s") is semantic l plus
the generic circulation set.
New: dom.GENERIC_{CIRCULATION,OUTSIDE,TYPES} + is_generic(); fitness.
_generic_class(), replacing the _t0 dispatch in quality_size/quality_width/
quality_proportion/value_rate -- the four terms that mattered most and that a
first sweep missed, since they dispatch through a t0 variable rather than an
inline test. graph._adjacency_target resolves a generic adjacency requirement
(programmes write "adjacency: [c, o]") to the generic set while every other
requirement keeps Perl's prefix semantics.
Two subtleties: S is in both generic sets but takes the OUTSIDE parameter
families -- a first translation tested circulation first and silently gave S
the circulation params, caught by test_get_space_params_sahn_proportion. And
validate_codes survives, narrowed to a code spelled exactly C/O/S, which is a
genuine ambiguity; merely starting with c/o/s is now fine.
Invariant asserted as a test: test_scoring_is_invariant_under_programme_code_
spelling relabels one tree and its config together and re-scores. Bit-identical
across 12 comparisons (6 seeds x collapse on/off).
Re-baseline (seed 1, 20k, original names): 58 fails (15 hard / 43 soft) against
the real 37-instance programme, with cr1 at 79.1 m2 vs declared 80 (was 32.9
and 17.1), of/st1/st2 all present and in band, and one fail naming any of them.
57 -> 58 on a 5-instance-harder programme is within noise: "did not regress".
Fallout (§39.5): 2g7.5's CP-SAT seeder win does not survive. Over 6 seeds --
harbor real 102/114 (cpsat loses), harbor old-effective 98/99 (tie, so the win
was already marginal), maple-court 156/144 (cpsat wins). maple is the control:
the solver did not regress, harbor's programme changed. Test xfail'd with that
reason plus a maple companion; both assign_solver flags stay default off.
Filed homemaker-py-w6x to re-check other narrow-margin harbor A/Bs.
345 passed, 1 xfailed, same 7 pre-existing fixture failures.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MJ84Feep79Hhm3E4zZJmnB
Closes homemaker-py-ju3. DESIGN.md §39.3.
The class: key from the bead's design was deliberately NOT built. Auditing
every use of the prefix rule first showed it runs deeper than c/o/s -- l/k/b/t
carry real adjacency semantics (graph.py builds bedroom<->toilet and
kitchen<->living relations from first characters) -- so re-plumbing the type
system would invalidate the whole corpus and every baseline, for a problem
whose damage is the silence, not the convention. Two findings made the smaller
fix sufficient: no corpus programme has ever declared a bare c/o/s code, so
check_space_counts' skip only ever discarded declared rooms; and nothing
references harbor's four codes in any adjacency or co_locate list.
- programme.validate_codes raises on a reserved-prefix code, with the full
explanation. Called from BOTH parse paths (programme._parse_spaces and
fitness.Fitness._load_programme parse conf["spaces"] independently, so
validating one would leave the other door open). l/k/b/t stay unreserved.
- harbor-house and harbor-house-l0 renamed: cr1->fr1, of->ao, st1->gs1,
st2->gs2. New prefixes are unused in harbor and semantically neutral, and
the two storage codes still share a prefix, preserving the structure
evaluate_building's per-code plot-ratio term depends on. name: unchanged.
- experiments/migrate_ju3_rename.py migrates .dom files written before the
rename (--check dry-runs). Pre-rename artefacts, notably evolved-3M*.dom,
must be migrated or their leaves read as unmatched generics.
- test_collapse_global's c/o/s exclusion test now uses a generic C leaf, which
is what the exclusion is actually for; it previously relied on a programme
code colliding, which is no longer possible.
Re-baseline (seed 1, 20k evals, same settings as §38's run): 57 fails against
the 32-instance effective programme -> 55 against the real 37-instance one,
with all five previously-lost room instances now placed inside their declared
sigma bands (fr1 87.2 vs declared 80, was 32.9/17.1; ao/gs1/gs2 were absent
entirely) and no failure naming any of the four codes. At one seed each,
57 vs 55 is within noise -- the robust result is the room placement, not the
count. Historical harbor numbers are not comparable to post-ju3 ones; filed
homemaker-py-t3s to restate 2v1's acceptance figure once evolved-3M is
migrated.
346 passed (+10 new), same 7 pre-existing fixture failures.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MJ84Feep79Hhm3E4zZJmnB
Answers "are any config requirements actively fighting the engine". One is.
§39.1 NEGATIVE (recorded): no room spec in any corpus programme is internally
contradictory. Using shapecurve.leaf_constraints' validated FAIL_THRESHOLD
inversions, every code admits an (area, aspect) satisfying size, width,
proportion and crinkliness at once, and none needs more than one exposed side.
The "estimated targets are mutually unsatisfiable" hypothesis is falsified.
§39.2 SEVERE: Urb's type system is prefix-based (c = circulation, o/s =
outside) and programme codes share that namespace. A code starting with those
letters is silently reinterpreted, with three unannounced consequences:
check_space_counts SKIPS it outright (never required, no missing or too-many
fail); get_space_params returns generic *_circulation/*_outside params before
consulting self.spaces; and is_circulation/is_outside flip, changing value
rate, crinkliness exemption, and whether it supplies daylight to neighbours.
harbor-house is affected (maple-court, health-centre, programme-house are
clean): cr1 "Common Room with Fireplace" has all three declared targets
overridden (size 80.0 -> 0.0/14.0) and is valued at 50/m2 not 300; of x2 and
st1/st2 lose width/proportion and are treated as outside space. 5 of 37 room
instances (14%) are silently optional.
Measured: the two cr1 leaves converged to 32.9 and 17.1 m2 against a declared
80, with no too-many-spaces fail despite count:1; of/st1/st2 are absent from
the result with zero fails. Compounds with §38.2 -- the largest room in the
programme sits on the wrong side of the x6 circulation value gap, so the
objective is paid to shrink it.
Benchmark validity: every harbor-house fail count in this document was
measured against a 32-instance effective programme, not the 37 its config
declares.
Adds experiments/audit_programme_config.py (namespace + satisfiability
reports). Filed homemaker-py-ju3 (P0). No src changes; 336 passed, same 7
pre-existing fixture failures.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MJ84Feep79Hhm3E4zZJmnB
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
Diagnostic investigation of why search stalls in local minima. Adds
experiments/diag_exposure_frontage.py (frontage/exposure/value reports,
no search run required) and records the findings as §38.
Core mechanism: quality_uncrinkliness returns a hard 0.0 for any leaf with
no daylit wall, and since leaf quality is a product feeding
value += quality * rate * area, every buried room contributes exactly zero
value while still costing. 45-56% of interior leaves are in this state under
the default construction stack.
Consequences measured, not inferred:
- Deleting a buried O leaf improves the score x85, a buried C leaf x62.
Nothing pins circulation or outside space, so the search is rewarded by two
orders of magnitude for deleting the circulation spine. This retro-explains
§18, §21/§22 and the level-not-connected fails surviving >1M evals.
- Closed-form frontage bound: every interior leaf needs exposed wall
L >= A/(1.6202*h). harbor-house supplies 54m against 148m needed (2.7x
short), maple-court 56 vs 162; health-centre and programme-house are
feasible. The corpus plateau is predicted by frontage deficit alone.
- Crinkliness is tiered SOFT but 60-100% of its fails are zero-exposure,
which is topological, so §37.1's tiered comparator is mis-informed about
the largest fail category.
- The missing-space cascade emits one extra fail per declared size/width/
proportion key, so under 0.5^n a missing room is weighted 4x differently
depending on patterns.config verbosity.
Filed as homemaker-py-ssz, hxi, tdp, gvb, 1i8 (plus bdf for the pre-existing
fresh-clone test failures found en route).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MJ84Feep79Hhm3E4zZJmnB
driver.search_staged has no param to disable collapse_insearch, so its
inner evaluator always runs with search()'s collapse_insearch=True
default. The final _native_score() sanity rescore loaded patterns.config
fresh with no override, so it silently diverged from search-time conf
whenever leaf_sharing was on, producing a false MISMATCH. Verified with
a smoke run: pre-fix MISMATCH, post-fix OK with identical scores.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014uCyZJCh5mBuA2yEFxgQpo
CP-SAT assignment does not clearly beat greedy end-to-end: worse on
harbor-house hard fails, roughly a wash on maple-court, and the reassign
operator fired in only 1 of 18 runs. Keeps assign_solver="greedy" and
enable_reassign=False as defaults; closes the bead's remaining acceptance
criterion. Raw results in experiments/results/.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LSwQwpEaHFBkeVSDDWd75S
Adds src/homemaker_layout/cpsat.py (OR-Tools CP-SAT) as an exact alternative
to operators._assign_adjacency_aware's greedy/beam room-code placement,
wired in as assign_solver="greedy"|"cpsat" (EXPERIMENTAL, default "greedy",
byte-identical to before) through constructive_topology/lift_base_to_storeys/
driver.search, plus a new operators.mutate_reassign in-search repair
operator (driver.search's enable_reassign=False default, mirrors
enable_ruin_recreate). Both found and fixed a resize-fragility bug (a
second CP-SAT pass against settled geometry, operators._cpsat_relabel_settled)
and a CP-SAT symmetry-blowup stall (explicit interchangeable-code grouping).
Seeder-level A/B on harbor-house is a solid, low-noise positive (~13% fewer
real fitness-scored secondary-adjacency fails, 10 seeds). Full driver.search
A/B is only pilot-scale (budget=3000 vs the bead's own 20k target) and
inconclusive -- both flags stay default-off pending a larger-N confirmation.
Full writeup: DESIGN.md §37.7. Bead left in_progress (own acceptance
criteria not fully met); homemaker-py-5bv tracks the deferred post-collapse
repair item.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LSwQwpEaHFBkeVSDDWd75S
Generalise shapecurve.py's DP to process dom.levels(root) bottom-up per
storey instead of assuming a single free tree. A divided node's split is
free only per solver.free_branches' own criterion (below is None or
undivided there) -- geometry.coordinate always mirrors a below-linked
node's corners from the storey below regardless of whether that storey's
counterpart is divided, so every free region at any storey reduces to the
exact same single-region problem the pre-existing _check/realise already
solved. New _region_roots finds below-fixed leaves (checked directly,
gridless) and below-fixed-box/free-split fringe nodes per storey;
_solve_all_levels realises each storey before checking the one above and
snapshots+restores on any infeasibility, preserving solve()'s all-or-nothing
and is_feasible()'s never-writes contracts across the whole tree.
eligible() now allows any storey count.
Validated on the real (non-de-risked) examples/harbor-house: 200 random
2-storey topologies, DP-vs-NM agreement 99.5%, 0 false negatives, 117.7x
speedup (DESIGN.md §37.6). Full suite 397 passed.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LSwQwpEaHFBkeVSDDWd75S
Adds shapecurve.is_feasible() (a non-mutating refactor of solve()'s check
phase) and a shapecurve_prune flag composing the DP's exact feasible/
infeasible verdict with operators.predicted_shape_fails' existing heuristic
prune: DP-feasible vetoes a heuristic prune outright; DP-infeasible only
hard-prunes when the incumbent already has zero total fails (exact, since
infeasible proves the shape-fail floor is >=1); otherwise defers unchanged
to today's heuristic threshold. Conservative by design since a wrong prune
is unrecoverable.
Validated 0/400 false negatives across two structurally distinct plots
(harbor-house-l0 + a newly-added programme-house sweep, the first genuinely
non-rectangular plot this DP has been checked against). The real
driver.search A/B on harbor-house-l0 measured NULL (byte-identical off/on)
for a root-caused, pre-existing reason: predicted_shape_fails rarely
triggers organically at this scale, so neither new branch had an opening to
fire -- not a defect in this change. Full writeup: DESIGN.md §37.5.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LSwQwpEaHFBkeVSDDWd75S
Promotes the validated shape-curve DP (experiments/shapecurve_spike.py,
2g7.4, DESIGN.md §37.2) from a reference-only spike into
src/homemaker_layout/shapecurve.py, and wires it into driver._evaluate as a
warm-start for innerloop.optimise: when eligible (single storey, no
leaf_sharing/superpose/max_share/multi_use) and no caller-supplied x0, the
DP's exact shape-feasible ratio point is written onto the tree before NM
runs, off by default (shapecurve_warmstart=/--shapecurve-warmstart).
Caught and fixed a latent bug promoting the spike: realise() could leave
numpy.float64 in `division`, which yaml.safe_dump can't serialise — the
original spike never round-tripped through dom.dumps so this was never hit.
A/B on harbor-house-l0 (experiments/ab_shapecurve_warmstart.py, budget=2000,
5 seeds): mean total fails 16.6 (on) vs 19.6 (off), ~3.5x mean fitness
improvement; mean hard-fail count alone was a noise-level wash at this
sample size. Full writeup in DESIGN.md §37.4.
Deliberately deferred to new tracked beads (children of 2g7): DP-exact hard
pre-filter (wkh), multi-storey below-link support (koo), leaf_sharing/
co_type modelling (tym), true skew-quad polygon algebra (ekc) — 6xh stays
in_progress pending those.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LSwQwpEaHFBkeVSDDWd75S
User review caught a real gap: the DP approximated each quad's (w,h)
via its axis-aligned bounding box in global x/y, correct only because
harbor-house-l0's plot happens to be near-parallel to its own axes
(~7.5% area error). A real building's orthogonal walls need not align
to the survey/CRS axes at all -- confirmed by rotating the plot 45deg,
where the old bbox error jumped to 102% (up to 2x for a rotated square).
Fixed in two steps: (1) measure (w,h) from edge lengths
((edge0+edge2)/2, (edge1+edge3)/2, the geometry.aspect() pairing)
instead of global bbox -- rotation-invariant by construction. (2) this
alone regressed accuracy (99.0% -> 95.5%) because a child's own
rotation parity determines whether its local edge0/edge2 pair aligns
with its parent's edge0/edge2 or edge1/edge3 -- not a matter of degree
to measure empirically (as attempted first) but an exact algebraic
identity (verified float-exact: left.w + right.h == parent.w whenever
left.rotation is even and right.rotation is odd). _child_contrib now
applies this directly, replacing the empirical _orientation/
annotate_orientations machinery entirely -- simpler and correct.
Re-validated: 99.0% agreement on harbor-house-l0 unrotated (back to
matching the original result, same 2 residual mismatches, 0 false
negatives), 100% agreement at 97x speedup on the same plot rotated
45deg (new, via validate_shapecurve.py's rotated_plot_dir helper).
DESIGN.md §37.2 updated with the full correction history.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LSwQwpEaHFBkeVSDDWd75S
Prototype + validation for an exact size/width/proportion feasibility DP
over a frozen slicing topology, replacing the ~80-200 eval Nelder-Mead
inner loop's approximate answer to the same question with one bottom-up
pass (experiments/shapecurve_spike.py). Leaf feasible regions are exact
FAIL_THRESHOLD-inversions of fitness.py's quality_size/width/proportion;
internal-node composition runs on a shared discretised grid.
Validated on harbor-house-l0 (experiments/validate_shapecurve.py, 200
random topologies vs NM minimising shape-fail-count directly): 99.0%
agreement (0 false negatives), 93.6x speedup at grid_n=150, plot-level
bbox approximation error quantified at +7.5% (root-causing both observed
false positives). All three acceptance criteria cleared -- see DESIGN.md
§37.2 for full results and the caveats/scope not covered (multi-storey,
leaf_sharing/co_type, true skew-quad regions). Kept as a reference spike,
same status as experiments/autodiff_spike.py (§34); production wiring
into driver.py filed as homemaker-py-6xh.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LSwQwpEaHFBkeVSDDWd75S
Splits the flat outer-search comparator (-n_fails, fitness) into a tiered
(-n_hard, -n_soft, fitness) so search budget stops being spent polishing
SOFT shape fails (crinkliness/proportion/size/width/edge-too-long/
staircase-volume) while HARD structural fails (missing space, wrong/
required level, level/circulation/vertical connectivity, adjacency,
stairs, covered-outside, storey limits, public access) remain unfixed.
fitness.classify_fail_tier/tier_counts classify every fail string emitted
across fitness.py and graph.py, raising on anything unrecognised so new
fail sites must declare a tier. Validated against all real fail strings in
the checked-in corpus plus every fail-emission call site read from source.
driver.Individual gains n_hard/n_soft (populated from innerloop.Result.
fail_lines); search(use_tiers=...) swaps the comparator when set (default
off, so existing runs are unaffected — inner-loop 0.5^n cliff untouched).
evolve.py exposes --use-tiers / HOMEMAKER_USE_TIERS.
experiments/tier_ab_2g7_3.py runs the acceptance A/B (harbor+maple, 3
seeds, 20k evals) in the background; results pending.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LSwQwpEaHFBkeVSDDWd75S
Re-ran the §13.1/§13.2-style per-leaf fail-breakdown diagnostic on real
driver.search_staged runs (budget 20000, seeds 0-2, harbor-house and
maple-court) under the current full default stack (leaf-sharing x3,
depth-balanced, interior-O, share-aware edge cap) -- never decomposed by
category since those defaults were flipped on.
Finding: crinkliness (48%) and size (20.6%) now dominate the residual on
both programmes (~69% combined); construction-completeness fails
(missing space, adjacency, level, connectivity) are down to a small
tail (<=6% each). This revises erc.1's old recommendation to deprioritise
compactness-cuts in favour of leaf-sharing -- leaf-sharing is now fully
deployed and crinkliness is proportionally more dominant than ever, so
DESIGN.md §13.11 recommends reopening a compactness/crinkliness-targeted
construction lever as the next concrete step.
Also files two bugs found while validating the methodology: dumping and
reloading a .dom under leaf_sharing+collapse_insearch does not reproduce
the search's own in-process fail count (homemaker-py-iio), and
run_staged_search.py's own sanity rescore omits the collapse_insearch
override (homemaker-py-7ua). experiments/run_and_capture_91f.py sidesteps
this by capturing the true in-process fails list instead of rescoring
from disk; experiments/diag_residual_91f.py tallies fail categories from
those sidecars.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R8agJBT2ZpmF3ErW7wi2wY
Build a torch-differentiable local proxy for the ratio-to-fitness path (exact
port of geometry.py's coordinate recursion + the 5 continuous per-leaf quality
factors, with discrete/structural facts frozen from a real fitness.py
snapshot and the 0.5^n cliff relaxed to a sigmoid) and compare Adam ascent
against nm_search on frozen topologies from programme-house and harbor-house.
Result: ~30-35x slower per unit of search progress than nm_search at both
6 DOF and 36 DOF (per-op torch tensor dispatch overhead with no batching
opportunity, plus snapshot/resnapshot cost on par with a full oracle eval),
and no better quality at matched budget. A step-size sensitivity check
confirmed the flagged 0.5^n cliff risk is real, but autodiff doesn't make the
gradient direction any cheaper to obtain here. Not recommended; kept as
reference only, not wired into innerloop.py. Full writeup in DESIGN.md §34.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R8agJBT2ZpmF3ErW7wi2wY
The N=3 A/B (previous commits) found the precision-weighted shape
combination improved both example programmes (harbor-house -1.4%,
health-centre -13.9%), but N=3 is a thin sample by this project's own
standard (xyu/9yx use N=15). Two confirmations:
- N=15, plain search, budget=3000 (mirrors xyu/9yx's own protocol exactly):
both programmes trend NEGATIVE (harbor +6.1%, health-centre +6.6%,
Wilcoxon p=0.044)
- N=15, staged search, budget=20000 (true same-conditions replication --
identical to the original A/B except seed count): both programmes AGAIN
trend negative (harbor +6.6% p=0.15, health-centre +4.7% p=0.48)
The same-conditions replication disagrees with the original result's
direction on both programmes. Conclusion: the N=3 positive signal was
sampling noise, not a real effect -- health-centre's -13.9% was driven
substantially by one seed (71->43 fails) that didn't hold up.
multi_use stays default OFF and is not recommended even as a promising
lever -- this is a clean NULL, closing out both halves of §26's original
multi-use-leaves question (path a was NULL/NEGATIVE, path b is NULL after
replication). Mechanism itself is unchanged, complete, and fully tested.
DESIGN.md §33 rewritten with all three measurements and the honest verdict.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R8agJBT2ZpmF3ErW7wi2wY
Builds path (b) from §26 -- a leaf permanently serving two DIFFERENT
compatible programme codes at once, extending leaf-sharing's same-code
multiplicity mechanism to different-but-compatible codes. Architect-declared
`co_locate` pairs (validated against interchangeable()'s S1-S4 bounds, no
transitive closure so the b3v chain problem can't recur), threaded through
graph.py's checks via a new leaf_codes() resolver and fitness.py's quality
terms (additive size, stricter-of-both width/proportion). Construction-time
only, gated behind `multi_use` (default OFF, bit-identical when off).
End-to-end A/B (20k evals x 3 seeds x 2 programmes) came back net negative:
harbor-house -4.0% but health-centre +24.5% worse (3/3 seeds), because
fusing different codes' shape targets via stricter-of-both can impose a
tighter joint constraint than either code needed alone, which the tightly-
packed health-centre programme can't absorb. Written up as DESIGN.md §33;
multi_use stays default OFF, no default-flip recommended.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R8agJBT2ZpmF3ErW7wi2wY
A small primary-care health centre: 19 distinct, individually-sized room
codes at n=20 room instances (only one deliberate duplication: two public
WCs), filling the gap between programme-house's duplicated-count sweep
sizes and harbor-house's out-of-range 37 real-diversity instances. Widths
are deliberately tiered (>1.3x gaps at three boundaries) so the auto-derived
interchange relation resolves to three bounded utility/office/clinical
classes instead of one whole-building chain, which a first pass produced.
experiments/run_9yx_sweep.sh repeats xyu's ruin_recreate ON/OFF protocol
(budget 3000, 4 workers, N=15 seeds) against this programme.
Extends y51's n=18 synthetic sweep (strongest of four sizes at N=10) to
N=15 seeds, matching f1d's own confirmation sample size. Effect shrank
(9.3%->6.4%, two-sided Wilcoxon p 0.098->0.059) but didn't evaporate or
reverse — an ambiguous middle case, not a clean confirm or null. Refiled
option (b) (non-synthetic third example programme) as homemaker-py-9yx
since extending N alone doesn't address the interchangeable-room-code
confound §24 already flagged. enable_ruin_recreate stays default OFF.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R8agJBT2ZpmF3ErW7wi2wY
N=15 driver.search sweep of construction_beam_width 1 vs 4 (protocol
identical to c94's original 5-seed run, DESIGN.md §29): 6W/4L/5T, mean
fails 57.0->56.6, Wilcoxon p=0.84. Excluding seed 2's outlier the mean
flips slightly negative (56.3->56.9), confirming the §29 5-seed
"improvement" was that one outlier. construction_beam_width stays
default 1 on confirmed rather than precautionary grounds. DESIGN.md §30.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R8agJBT2ZpmF3ErW7wi2wY
Locates the threshold f1d's programme-house/harbor-house split implied,
using four synthetic sizes (10/14/18/22 rooms) derived from programme-house
by scaling its bedroom+ensuite module count, since no natural third example
programme sits between the two. Results are noisy and non-monotonic (n=10
mild win, n=14 clean null, n=18 strongest trend at p=0.098, n=22 near-null)
rather than a clean decay with room count -- documented in DESIGN.md #24.
enable_ruin_recreate stays default OFF; filed homemaker-py-xyu as a
low-priority follow-up (larger-N at n=18, or a non-synthetic third example).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds operators.mutate_ruin_recreate: un-divides one wing of a storey and
rebuilds it with the same adjacency-aware constructor the seeders use
(_assign_adjacency_aware, generalised with a new `scope` param), seeded
from the surviving circulation bordering the wing. Gated behind
enable_ruin_recreate (default off) / --ruin-recreate, same pattern as
reassociate/bridge_circulation.
A/B (qpk protocol, DESIGN.md §23): initial uniform-weight run was
underpowered (fired ~1/32 children), null. A weight=3.0 follow-up
(_MUTATION_WEIGHTS["ruin_recreate"]) showed a statistically significant
win on programme-house across 15 seeds (8W/1L/6T, mean fails 7.07->6.00,
Wilcoxon p=0.041) but no consistent effect on harbor-house across 8 seeds
(3W/2L/3T). Kept default off pending a size-threshold follow-up.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Combined follow-up to 8sh (DESIGN.md §22): raised bridge_circulation's
_MUTATION_WEIGHTS entry to 2.0 (lj3) and re-ran the qi6/qpk-protocol A/B
at 4x the sample size (qjg) in one sweep, since the two variables were
confounded if tested separately. Result is null in the opposite direction
from 8sh's small-N signal -- no total-fail benefit (p=0.71 programme-house
N=20, p=0.69 harbor-house N=12) and a higher rate of trajectory-divergence
-induced new not-connected fails than at the original uniform weight.
Reverted the weight bump; enable_bridge_circulation stays default off.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GDZjAATDWW1xFfc7xnJqSt
Adds operators.mutate_bridge_circulation: retypes the cheapest path between
two disconnected circulation components to circulation, directly clearing a
'level N not connected' fail instead of relying on the qi6 graded comparator
key (measured negative, DESIGN.md §18). Gated off by default via
driver.search's enable_bridge_circulation flag and evolve.py
--bridge-circulation, mirroring enable_reassociate's clean-toggle pattern.
qi6/qpk-protocol A/B (DESIGN.md §21) is directionally positive but mixed at
N=3/N=5 (never worse on total fails; clears 2/5 baseline not-connected fails
vs qi6's 0/4; one seed's RNG-trajectory divergence adds 2 new not-connected
fails) — kept default off pending a larger-N confirmation sweep
(homemaker-py-qjg) and a mutation-weight bump experiment (homemaker-py-lj3).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GDZjAATDWW1xFfc7xnJqSt
conn_grade ON vs OFF (qpk protocol, experiments/run_qi6_ab.sh): harbor-house
(budget 2500, seeds 1-3) byte-identical output in every seed — the secondary
comparator key never fired. programme-house (budget 3000, seeds 1-5) 3/5 seeds
tie exactly; seeds 1/2 diverge to a different topology but the fail delta is
adjacency/crinkliness/width/access/size, never connectivity. Zero of 4 cases
where a not-connected fail was present got cleared by the grade.
Mechanism (b)/(c) (graded proximity as tertiary comparator key) is falsified,
not just unconfirmed. Kept default OFF (already was). Closed qi6; filed
homemaker-py-8sh for the remaining candidate (mechanism (a): an explicit
insert/relocate-circulation operator that doesn't depend on the search
stumbling onto a fail-count tie).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GDZjAATDWW1xFfc7xnJqSt
Expose tournament_k (default 2) on search()/search_staged(), threaded into
both _tournament call sites and the staged path's internal search() calls;
HOMEMAKER_TOURNAMENT_K env knob in the scaled/staged harnesses; run_6zy_ab.sh
joint niche×k grid (RESUME-able).
Result (negative, acceptable): no (niche,k) cell beats the legacy (off,k=2)
baseline. Blank-slate programme-house (5 seeds) baseline mean 4.80 fails is the
best of the 6-cell grid; every k>2 and every niche=on cell is 6.0-7.0. Niching
bites (pop_distinct 16/16 vs 4-11) but sharper pressure does not convert it to
lower fails — §11.5 'diffuses effort' null is robust to selection pressure;
plateau stays reachability-bound (confirms §11.4/§11.5).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Prime a population from N independent converged elites + crossover-heavy
migration phase, vs best-of-N at equal total budget. Island does NOT win:
harbor 68 vs control 67 (within parallel noise), maple 124 vs control 116
(decisive). Default-off child_probe hook on driver.search instruments the
deciding mechanism: area-matched crossover across independently-converged
elites rarely synthesizes (1/65 harbor, 3/63 maple beat the better parent,
max fail-drop 2-5), confirming the alignment hypothesis (non-canonical 9gp
encoding -> disruptive splice). Search-machinery null #3; residual stays
geometry/shape-bound. 233 tests pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>