Commit graph

119 commits

Author SHA1 Message Date
Claude
f5612ce06c
Pre-flight: check the ground floor's frontage, not the per-storey average
39.16 relocated the crinkliness residual to a plan-form question. Four
measurements answer it, and two of them refute the premises 773 was filed on.

The search DOES build courtyards -- harbor 8 (273 m2), maple 16 (404 m2),
health-centre 13 (107 m2) over three seeds -- and they work: of 524 lit edges
44% come from the plot wall, 30% from a courtyard, 26% from a perimeter void,
and a courtyard supplies at least one side for 53% of the two-aspect leaves.
No operator is missing. (The shape-curve DP does NOT model exposure --
shapecurve.py:25 -- but per 38.24 it fires ~8 times in 500k evals, so that gap
is not what is costing anything.)

The answer is per-storey. Comparing each storey's demand, sum A_i/(1.6202*h),
with the lit wall its leaves actually hold: every harbor and maple ground floor
is below 1.0 and every top floor above 1.2, and the ratio predicts the fail
rate almost exactly -- above ~1.2 near-zero fails, below 1.0 40-55% of the
storey. health-centre and programme-house sit at 1.6-4.2 throughout and fail
essentially nothing.

That corrects 39.11, which divided demand evenly across storeys and concluded
harbor and maple were frontage-feasible "with room to spare". Programmes pin
rooms to level 0 and the ground floor cannot set itself back to buy perimeter:
harbor's pinned 347 m2 needs 71.4 m against the plot's 53.0 m, maple's 414 m2
needs 85.2 m against 55.0 m, while health-centre and programme-house have 51.0
and 14.2 m spare. Same ordering as the corpus fail counts, and fixed before any
search runs. The averaged check is not just weaker: on maple it asks for a
22 m2 courtyard where the ground floor needs 57 m2.

New third _preflight check, advisory like the others, silent on the two
programmes with slack. tests/test_evolve_preflight.py covers all three checks
and asserts the ground-floor figure exceeds the averaged one -- if they ever
agree, one has stopped earning its place. 39.11 annotated in place.

Also recorded, not acted on: the open space is on the wrong storey (harbor puts
50 m2 of courtyard on the starved ground floor and 223 m2 on the surplus first
floor), because value_rate pays an outside leaf above ground value_supported =
300 -- a room's rate -- against a cost of 110, with nothing tying its value to
whether it illuminates anything. Filed as homemaker-py-ecx.

411 passed, 72 skipped.

Refs homemaker-py-773.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MJ84Feep79Hhm3E4zZJmnB
2026-09-05 17:46:52 +00:00
Claude
e92a96ac99
Correct 39.14/39.15: the crinkliness constant is Alexander 159
The owner supplied the provenance the analysis was missing. The constant is
Christopher Alexander, A Pattern Language 159, "Light on Two Sides of Every
Room", and it changes what the numbers mean.

1/crink = A/(L*h) is floor area per metre of ILLUMINATED wall over storey
height. It is not room depth -- it equals depth only for a room lit on one
side. So 5/6 * h = 2.5 m is 2.5 m of room depth PER WINDOW WALL: one side
allows 2.5 m at the peak and 4.86 m at the fail edge, two opposite sides allow
5.00 m and 9.72 m. A 4 m room scores 0.395 lit on one side and 0.902 lit on
two. The factor is the pattern stated as a ratio, and it is not
miscalibrated.

WITHDRAWN from 39.14: the "2.5 m absurd optimum" reading, and "the corpus's
realised median depth is 2.95 m, so the search built what it was paid for" --
2.95 was the median A/L, while the corpus's single-aspect leaves are a median
3.46 m deep and its two-opposite leaves 4.42 m. Ordinary rooms. Section
retitled, passage struck in place.

WITHDRAWN from 39.15: calling six specs "self-contradictory". They are large
rooms, and under Alexander a large room is supposed to need two aspects; the
audit's new column reports the pattern working, not a mis-specification. What
is real is the tension between that demand and what the plan form supplies.

SURVIVES, on a better argument: crinkliness_shape="daylight". 159 states a
MINIMUM, and a two-sided gaussian turns a minimum into a target -- 68% of the
133 leaves in the clipped region are lit on two or more sides, mean quality
0.770, docked for satisfying the pattern well, on top of the
exterior_wall/boundary_wall charge those windows already carry in cost.

New 39.16 records this and relocates the residual. Over the 430 graded
baseline leaves: unlit 77 (100% fail), one side 208 (15%), two-corner 87 (2%),
two-opposite 34 (0%), three/four 24 (4%). Light on two sides all but
guarantees a pass and only 33.7% of leaves get it, so the open question is why
a binary slicing tree on a convex plot can only give a third of its leaves two
aspects -- a plan-form question, not a scoring one. Filed as
homemaker-py-773; 39.11's courtyard finding is the same question from the
other side.

Both errors came from reading a dimensionless ratio as a length, so the
provenance and the interpretation now sit next to the constant in fitness.py,
not only in DESIGN.md.

405 passed, 72 skipped.

Refs homemaker-py-u5q.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MJ84Feep79Hhm3E4zZJmnB
2026-09-05 16:04:44 +00:00
Claude
84907694d0
Record what the crinkliness examination found: 39.13, 39.14, 39.15
39.13 -- the tail rescale, and its verdict. NULL, and not for want of power:
12 of 12 pairs byte-identical, both programmes, same trajectories. The failing
tail is 0.034% of corpus value, so making it orderable cannot move a search.
Kept (free, and k54 needs the region orderable) but recorded as correct and
inert, not as a fix. It also corrects gvb's premise: zero exposure is NOT
beyond the inner loop's reach -- perturbing division ratios alone moves the
zero-exposure set on 6-12 of 12 trials at +-25%, and in the direction wanted
(harbor s1 8 -> 6 buried leaves).

39.14 -- what the factor actually rewards. 1/crink is the room's depth from
its daylit wall in storey-heights, so the variable is sound and its fail
boundary (1.62h = 4.86 m) agrees with 38.3's independently-derived frontage
bound. The two-sided gaussian on it is not: the near side penalises surplus
daylight that edge_cost and outside_edge_cost already bill at 100 and 133.3
per m2, it has never once produced a fail (it needs crink > 21.5; corpus max
is 3.95), and its peak sits at a 2.5 m deep room -- an ordinary 4 m room
scores 0.395 and the corpus's realised median depth is 2.95 m. The search
built what it was paid for. A/B at pilot budget is underpowered rather than
null: the arms reach different layouts but the same fail counts.

39.15 -- the magic numbers. A sigma is not a preference, it is an acceptance
interval target +- 2.1460*sigma, so it decides failures. The blanket
hypothesis does not survive -- programme-house reaches 1 fail, structural on
two of three seeds. The specific one does, and it shows 39.1's CLEAN verdict
answered a weaker question: sweeping a spec's tolerance box asks whether SOME
shape is feasible, and all 67 pass, but at the DECLARED target area and aspect
harbor needs 7 corner rooms and maple 6, while health-centre and
programme-house need none. Within-programme, those codes fail 62% and 78% of
their instances against 26% and 31% for all others. Three declared quantities
are jointly contradictory and nothing said so; the resolution is an author
decision, not a retuned constant.

Also recorded: 82% of size fails are rooms larger than target, which is the
same shape of double-charge but explicitly NOT the same case -- size's upper
bound is the main brake on growth and must not be removed on the analogy.

405 passed, 72 skipped.

Closes homemaker-py-9gj, homemaker-py-u5q.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MJ84Feep79Hhm3E4zZJmnB
2026-09-05 15:11:11 +00:00
Claude
4180a9eb88
Replace the stale 15-fail acceptance target with the cold-start baseline
DESIGN.md 38.7 pinned Phase 9's acceptance test to "harbor-house reaches its
known 15-fail floor". That figure was measured before 39.4, against harbor's
*effective* programme of 32 instances -- cr1/of/st1/st2 were being read as
generic circulation and silently dropped. Against the real 37-instance
programme the same artefact scores 89, so the target is not measurable; and
2v1, the fix it was the acceptance test for, closed NULL (39.8), so there is
no combined fix left to accept.

New 39.12 records:

* the five evolved-3M*.dom artefacts rescored under the current objective
  (69/85/87/89/145), and why they are not a floor -- they were evolved under
  one objective and are scored under another;
* that the bead's migration premise is stale: experiments/migrate_ju3_rename.py
  does not exist, because ju3 tightened the matching rule at source (39.3)
  instead, so the old artefacts parse correctly with no migration;
* the 4x3-seed 500k cold-start baseline (~430 h) as the reference from here,
  with each programme's sd and its minimum detectable difference at n=3 --
  harbor mean 39.3, sd 5.5, MDD 13.7;
* zero missing-space fails in all twelve runs: the dominant term in the 3M
  artefacts is not one the live search still fails on;
* crinkliness at 112 of the 321 corpus fails (35%), all soft, all in the
  regime 9gj says quality_uncrinkliness returns a flat 0.0 for -- the largest
  single component of the residual is one the objective cannot descend;
* 66 of the 84 hard fails as one access-topology family (not-adjacent-to,
  inaccessible usable space, not connected), mechanism in 39.9.

38.7's acceptance paragraph is annotated in place rather than rewritten. The
connectivity clause is demoted to a separately tracked standing defect: it
appears in 10 of the 12 baseline runs.

tests/test_collapse_insearch.py carried its own stale "82 -> 58"; the same
layout now scores 89 -> 64, so the docstring dates the figure instead of
asserting a current one.

Closes homemaker-py-ut5.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MJ84Feep79Hhm3E4zZJmnB
2026-09-04 17:16:10 +00:00
Claude
481e83cdbf
Shape-curve warm-start cannot pay off as wired; no A/B run
With tym landed the DP finally runs on real leaf-sharing searches, so v4s
asked for the search-level A/B. No A/B was run: two structural facts make
the payoff zero before any seed is drawn, and measuring a no-op would have
produced a null that reads like a measurement rather than a fact.

Reach. driver._evaluate gates the warm-start on `x0 is None`, and every
child gets x0 = innerloop.warm_x0(...) from its parent (driver.py:764), so
it fires only for bootstrap-population individuals. Instrumented over a
4000-eval run: 8 DP solves, exactly pop_size. At the corpus baseline's 500k
budget that is 8 evaluations out of 500,000.

Applicability. Constructed-seed feasibility over 6 seeds: harbor-house 0/6
with leaf_sharing on AND off, programme-house 0/6, harbor-house-l0 4-5/6
(75% over a real search). Even those 8 bootstrap individuals get no warm
start on the real programmes; only l0, the reduced programme the DP was
validated on, is feasible.

The infeasibility is correct, checked because it is the dangerous
direction: on 4 harbor topologies the DP rejects, an NM polish minimising
the shape-fail family reaches 14/16/17/16 fails, never 0 -- 0/4 false
negatives. Full harbor is genuinely shape-infeasible per topology where l0
is not. Per topology only: programme-house reaches 0 hard / 1 soft in the
500k baseline while its constructed seeds are DP-infeasible.

shapecurve_prune is separately inert -- it acts only inside the
feasibility_max_shape_fails branch and its exact arm needs best_n_fails<=0.

tym was still worth doing: the DP now models leaf-sharing exactly (38.23)
and fires on real runs, which is what turned an untestable question into a
structural answer.

Filed homemaker-py-ldj for what would have to change: give the DP reach
beyond the bootstrap, and/or use infeasibility as a ranking signal (an
infeasible topology still has a shape-fail floor, which is what a
pre-filter wants) rather than a precondition that discards it.

Closes homemaker-py-v4s.

Lint at parity (46).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MJ84Feep79Hhm3E4zZJmnB
2026-08-30 07:41:16 +00:00
Claude
4c95505907
Shape-curve DP models leaf-sharing, so it can fire on real runs
shapecurve.leaf_constraints derived each leaf's feasible area from its own
type's base (target, sigma). quality_size does not: a leaf holding k
same-code rooms is centred on k*target with sigma*k, and a co-typed leaf
adds both codes' targets. The DP modelled neither, so eligible() excluded
leaf_sharing/max_share/multi_use -- and leaf_sharing defaults True in
driver.search, so the guard excluded essentially every real run. The DP was
correct and unreachable.

Why the guard could not just be dropped, measured before touching it: on 6
harbor constructed seeds, 24 of 24 shared leaves (100%) have a real area
outside the unscaled single-room bounds. Relaxing eligible without
modelling k would have made the DP call every one of those topologies
infeasible -- false negatives that prune feasible topologies and misdirect
the NM warm-start. The guard was load-bearing.

Fix: mirror quality_size by asking the SAME Fitness object -- k =
graph.leaf_share(leaf, fit._max_share) when fit._leaf_sharing, then
target*k / sigma*k, else fit._leaf_co_type for the additive case. Same
object, same flags, same branch order, deliberately not re-derived: 39.5's
cpsat._matches bug was a solver optimising a relation the scorer had moved,
and this is the same hazard class.

Verified as an exact inversion: for every shared leaf in a real seed,
quality_size evaluated at the DP's amin and amax returns FAIL_THRESHOLD to
1e-9 (k=3 n-leaf: bounds [128.50, 231.50], both 0.100000).

superpose stays excluded for a different reason than the others: it does
not rescale a target, it changes which type the leaf is scored as, and the
collapse happens after the DP has read leaf.type.

shapecurve_warmstart/shapecurve_prune remain default off, so no current run
changes -- including the cold-start baseline in progress. They are now
applicable, which unblocks homemaker-py-v4s.

Closes homemaker-py-tym.

Lint at parity (46); tests 387 passed (3 new, 1 legacy rewritten to the new
contract rather than deleted), 0 failed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MJ84Feep79Hhm3E4zZJmnB
2026-08-29 21:36:45 +00:00
Claude
104ba12237
A/Bs now report what their sample could resolve
Three times in this log a verdict rested on a sample that could not have
produced it: 38.19 (programme-house claimed at N=20, resolves at N=60),
38.21 (harbor at n=3 resolves nothing finer than ~15 fails, yet every
recorded margin is smaller), 39.5/38.20 (a 10-fail cpsat margin inside a
+-23-fail noise band). Each was found years later.

experiments/ab_report.py makes it visible when the verdict is made:

  minimum detectable difference = t_crit(0.975, N-1) * sd / sqrt(N)

A margin below the MDD is not a weak result but an absent one -- the
experiment could not have distinguished it from zero however it came out.
The report flags that, refuses to endorse a winner, and states the N needed.
Validated against both datasets measured this session, reproducing the
hand-computed figures exactly:

  programme-house N=60  +0.567 p=0.017  MDD 0.462  verdict supported
  programme-house N=20  +0.700 p=0.085  MDD 0.805  UNDERPOWERED, N~=26
  harbor N=24           +1.208 p=0.502  MDD 3.669  UNDERPOWERED, N~=202

Harbor needing ~200 seeds means it cannot answer the collapse_insearch
question at any N this project would realistically run.

Fixed a defect in my own first version: with all-ties (sd=0) the MDD
collapses to zero and the naive abs(mean) < mdd reported "margin exceeds
the MDD -- verdict supported" for a margin of 0.000, with t=nan. A reporter
that endorses a zero margin is worse than none. Degenerate cases are now
explicit and distinguish all-ties from a constant non-zero difference.

Separate correction found while validating: 38.19's published p-values for
N=20 and N=40 were 0.069 and 0.045, from a normal approximation. The exact
paired t-test gives 0.085 and 0.052 -- so N=40 did NOT reach significance
either; it took N=60. The approximation was anti-conservative, making
results look more significant than they are, the same direction of error
this thread is about. Corrected at all four citing sites, and the "needs
N >= 40" guidance raised to N >= 60.

Closes homemaker-py-tco.

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
2026-08-29 19:51:47 +00:00
Claude
caf480aa32
Harbor A/Bs at n=3 could never resolve their own margins
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
2026-08-29 19:34:30 +00:00
Claude
57d8391c52
Recover most of the cpsat test-suite cost; keep the part that is real
38.20's cap fix took the suite from ~4.5 to ~10 min because the
assign_cpsat tests now solve to optimality. Recovered to ~6.8 min.

The bigger win was not the threading. The secondary-adjacency test ran the
cpsat arm THREE times and averaged, and its own comment says why: the cpsat
path "is not yet bit-reproducible (homemaker-py-fdp)", so one 10-seed
aggregate could straddle greedy's deterministic value and the test was
flaky by construction. fdp is fixed (38.15), so one pass says exactly what
three did -- that was work spent papering over a bug that no longer exists.

constructive_topology and _assign_adjacency_aware now forward an optional
cpsat_limits=(time_limit_s, deterministic_limit); default None keeps
solve_room_labels' defaults, so production is unchanged -- verified 24/24
harbor solves still OPTIMAL at the defaults. It is not a tuning knob: it
exists so a test whose claim does not depend on optimality can economise.
test_construction_assign_cpsat_yields_valid_seed asserts invariants only
and uses it, 91s -> 53s.

That test now also guards a real trap: too small a budget makes
solve_room_labels return None, _assign_adjacency_aware falls back to
greedy, and the test would pass while exercising nothing. It counts
fallbacks and fails if any occur.

The two quality comparisons keep the full budget deliberately -- their
claims are about the optimum, and cheapening them would weaken what they
assert. That is why the suite does not return to 4.5 min; the residue is
the honest price of optimal deterministic solves.

Also corrected a stale claim in the secondary-adjacency comment: it
measures only "not adjacent to" fails and is not a claim that cpsat seeds
better overall, which 38.20 measured markedly worse.

Closes homemaker-py-7t1.

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
2026-08-29 18:55:54 +00:00
Claude
6e8bcce8f3
Correct a wrong bead ID in 38.20 (2xk -> 7t1)
The follow-up issue was cited by an ID written before the issue was
created. The real one is homemaker-py-7t1.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MJ84Feep79Hhm3E4zZJmnB
2026-08-29 14:47:53 +00:00
Claude
03c1c5edcf
CP-SAT seeding re-measured deterministically: it loses
39.5 concluded the exact CP-SAT seeder beats greedy (harbor 102 -> 92,
maple 156 -> 154). Re-checked because fdp made the arms deterministic and
3qj made the model 7.5x slower. Three findings.

A live bug in the cap, found on the way. solve_room_labels sets a
deterministic work-unit budget (4.0) and a wall-clock backstop, commented
as "a pathological-case backstop only". At 2.0s it had become THE BINDING
CONSTRAINT: 2 of 24 harbor solves returned FEASIBLE not OPTIMAL, wall time
hit exactly 2010 ms, and the deterministic budget was never reached (max
2.483/4.0). Those labellings were suboptimal AND load-dependent -- the wall
clock is exactly the cap 39.5 added the deterministic one to escape. Cause:
38.14's t -> n adjacency makes the model much harder, and the 2s value
dated from when solves took ~124 ms. Raised to 30s; 24/24 harbor and 36/36
maple now OPTIMAL, deterministic budget still in headroom (3.569/4.0).

The verdict reverses. Deterministic, 12 seeds, scored canonically:
  harbor  greedy 1323 (722h)  0.079 s/seed    cpsat 1548 (908h)  1.623
  maple   greedy 1764 (777h)  0.063 s/seed    cpsat 2256 (1213h) 1.327
cpsat loses on both, +225 and +492 fails at ~21x the seeding time,
concentrated in hard fails.

Time and quality have different causes. Removing t -> n from harbor takes
cpsat 1.623 -> 0.193 s/seed (8.4x faster) but it is still +205 vs greedy
(was +225) -- so the adjacency explains the time blow-up and ~9% of the
quality gap; the regression is otherwise pre-existing.

Squaring with 39.5: that section records cpsat returning 194/180/171/182
over four identical 10-seed aggregates before the determinism work. Its
10-fail harbor margin sits well inside a noise band that wide, and was
measured with fdp's id()-ordered room_slots live. The seeder-level claim
was never established rather than overturned. 39.5 annotated in place.

Absolute totals are ~6x 39.5's because the objective has changed, so they
are not comparable to that table; the within-measurement comparison is
like-for-like and is what the verdict rests on.

No default changes: assign_solver was already greedy for 37.7's independent
reason. What changes is that "cpsat wins the seeder A/B" should no longer
be cited as a reason to pursue it.

The cap fix takes the suite from ~4.5 to ~10 min and the tests cannot opt
out, since constructive_topology does not thread the solver limits through.
Filed as homemaker-py-2xk.

Closes homemaker-py-vjd.

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
2026-08-29 14:47:36 +00:00
Claude
e9e7060fb4
Re-validate collapse_insearch's default under the current objective
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
2026-08-29 13:57:45 +00:00
Claude
50fb293fa5
Re-verify 1ph: the iio bug could never have touched it
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
2026-08-29 13:21:29 +00:00
Claude
f6eeee7191
n_workers is an algorithm parameter, not noise
14 recorded "harbor seed 2 scored 71 then 73 on byte-identical re-runs --
parallel/BLAS non-determinism", and b8g carried that forward as noise
widening the error bars on every A/B run at n_workers>1. The premise does
not survive measurement. Nothing is non-deterministic:

  score a frozen .dom, 20 repeats in one process   bit-identical
  same .dom, 8 processes, varied PYTHONHASHSEED    bit-identical
  full search, harbor seeds 0-3, n_workers 1..4,
    repeated across processes                      bit-identical PER count
  the same with OMP/OPENBLAS/MKL_NUM_THREADS=1     IDENTICAL to unpinned

The last line matters most: b8g proposed "likely a one-line env pin in the
worker pool initializer". Pinning BLAS threads changes nothing bit-for-bit,
so shipping that would have looked like a fix, done nothing, and retired
the issue.

What is real is not noise: the trajectory is a deterministic function of
n_workers. harbor seed 3, budget 1500 -- w=1/2/3 all give 64 fails with
identical bits, w=4 gives 65. Each stable across processes. The mechanism
is batch_n = min(n_workers, ...) children bred from ONE population snapshot
before any is admitted, with the shared rng consumed in a different
pattern; at w=1 each child sees the population its predecessor updated. A
4-worker run is partly generational, a 1-worker run steady-state -- same
seed, different search. Divergence is occasional (seeds 0/1/2 agreed, seed
3 did not), which is how it reads as noise when sampled.

14's observation was most likely homemaker-py-xcy, the as_completed
admission-ordering bug, which WAS non-deterministic and is fixed.

Shipped instead of a no-op env pin: driver.search's docstring states the
contract; test_search_is_reproducible_at_a_fixed_worker_count parametrises
over 2/3/4 workers, asserting each is internally stable and deliberately
NOT that they agree; test_scoring_a_frozen_design_is_deterministic guards
the floor.

The run_*_ab.sh harnesses already pin WORKERS=4, so arms inside one harness
are sound. The exposure is comparing across harnesses, or against a
historical figure whose worker count was never recorded.

Closes homemaker-py-b8g.

Lint at parity (46); tests 384 passed (3 new), 0 failed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MJ84Feep79Hhm3E4zZJmnB
2026-08-29 12:52:33 +00:00
Claude
225e673ae3
Staged harness re-scored under a different objective than it searched
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
2026-08-29 12:11:38 +00:00
Claude
148988df14
constructive_topology was ordered by memory address on the cpsat path
assign_solver="cpsat" gave a different leaf-type signature on every run
from an identical seed, in the same process. One line:

  assignable = scope if scope is not None else set(leaves)
  noncirc = [L for L in assignable if L not in circ]      # id() order

assignable is a set of dom.Node, and Node hashes by id() -- a memory
address -- so iterating it ordered noncirc, and hence room_slots, by where
the objects happened to land in memory. That shifts between calls within
one process as allocation patterns change, with no seed involved.

Only cpsat showed it. The greedy path re-sorts every slot list with -idx[L]
as a unique tiebreak and is immune to the incoming order; CP-SAT consumes
room_slots order as its model's variable order, and the labelling problem
has many equally-optimal solutions. Greedy was not more correct, it was
masking a defect that had been there all along.

Fix: iterate the tree-ordered list, use the set only for membership.

Verified on programme-house, harbor-house and maple-court: 1 distinct
signature over 5 runs on both solvers, and 1 across 4 processes started
with different PYTHONHASHSEED, so context_types' string sets are not a
second source. test_constructive_topology_is_bit_reproducible guards both.

Method: rather than guess which set was at fault, instrument
solve_room_labels with an id-free fingerprint of inputs and outputs and
isolate the FIRST call, since later calls legitimately depend on earlier
ones through leaf types. Five runs gave five distinct first-call inputs,
placing the fault upstream of the solver in one step.

Every A/B on the cpsat path was comparing arms that differed partly by
memory layout -- 39.5's cpsat-vs-greedy verdict included, already down for
re-measurement under homemaker-py-vjd. Same id()-keying hazard as the
documented geometry._cache issue and a plausible contributor to
homemaker-py-b8g, which stays open: n_workers>1 has its own BLAS mechanism
and is not addressed here.

Closes homemaker-py-fdp.

Lint at parity (46); tests 381 passed (2 new), 0 failed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MJ84Feep79Hhm3E4zZJmnB
2026-08-29 11:45:19 +00:00
Claude
ee050532d6
Declare toilet-to-sleeping adjacency where the brief supports it
A toilet next to a sleeping room is a positive even with no door between
them (Brand): the adjacency is what makes a later knock-through possible.
The engine already scores it -- check_adjacency runs against the unfiltered
graph_base_pre -- but only where a programme declares it, and only
programme-house did.

Declared:
  harbor-house  t -> n    bathrooms serve the Neighborhoods (communal
                          sleeping); both unpinned, 6 t / 5 n
  maple-court   tt -> r   Upper Bathrooms among Individual Rooms, both
                          level 2, already 62% adjacent at seed time

NOT declared, and checking before declaring is what caught these:

  maple t -> n is IMPOSSIBLE. Adjacency is evaluated per level, and maple
  pins t to level 0, n to level 1. Declaring it would have added six
  permanently unsatisfiable fails; the 0% seed-time rate was a hard
  impossibility, not search difficulty. maple's ground floor has six
  bathrooms and one sleeping room (Clinic Room x1) -- a ground-floor WC in
  a communal building is public, so Brand does not apply anyway.

  health-centre has no dedicated WC. The ruling was that a treatment room
  "may give access to a toilet, but this would be a dedicated toilet"; t9
  is a Public WC and t10 a Staff WC. Earning the credit needs a WC added to
  the brief -- programme authoring, filed as homemaker-py-5nw.

Both declarations are reachable (best of 8 seeds 2/3 harbor, 2/2 maple), so
the search gets a gradient not a permanent penalty. evolved-3M-nols-3
84 -> 89 fails, all five the new requirement.

Cost: cpsat assignment ~7.5x slower on harbor (0.28 -> 2.11s per seed);
greedy, the default, unchanged at 0.06s. Ordinary runs pay nothing, but
39.5's cpsat-vs-greedy verdict was measured on a cheaper problem than the
corpus now poses -- filed as homemaker-py-vjd.

Two tests were over-fitted to the old seeds and are repaired to assert
their intent, not relaxed to pass: reassign now sweeps six constructive
seeds (seed 0's better-seeded design legitimately has nothing to improve,
5 of 6 others fire), and repair_circulation asserts that repair strictly
helps plus a >=85% bar rather than a sampled 100% hardened into a
guarantee (measured 25% -> 92%, stable over 6 and 12 seeds).

Closes homemaker-py-3qj.

Lint at parity (46); tests 379 passed, 0 failed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MJ84Feep79Hhm3E4zZJmnB
2026-08-29 10:57:44 +00:00
Claude
98d403b966
health-centre: enlarge the plot for a courtyard typology
39.11 found health-centre demanding 240 m2 of floor on a 183 m2 plot --
131%, single storey, nowhere for the overflow to go. Every room came out at
0.60x its declared target, 100% undersized, uniformly, and no amount of
searching could fix it.

Owner's ruling: enlarge the plot, sized assuming the building has a
courtyard. The plot is a quadrilateral here, so the courtyard is interior
space the search carves out, not a hole in the site. Sizing must therefore
allow for rooms (240, declared), circulation (~65, ~27%, NOT in demand --
the search creates C leaves) and courtyard (~36).

Scaled about the polygon centroid by k = 1.4606, chosen so the INSET area
lands on 400 m2. Centroid scaling preserves the site's irregular shape and
its one private edge: same site, larger.

  plot          183.2 -> 400.0 m2
  rooms/plot    131%  -> 60%      (harbor's ratio exactly, and harbor works)
  daylit perim  41.5  -> 61.3 m   (49.4 needed)
  median room area / (target x share)  0.60x -> 1.00x

The last figure is the one that matters -- 0.60x was the geometric
signature of the infeasibility, now level with harbor/maple's 1.01x.

The courtyard stays REQUIRED by the geometry, not merely permitted: a room
is at most 4.86 m deep before failing crinkliness, so a ~20x20 plot with a
daylit ring leaves a 106 m2 core that cannot reach an external wall.
Enlarging made room for a courtyard; it did not remove the pressure that
produces one.

evolve._preflight is now silent on health-centre.

Closes homemaker-py-7b7, unblocks homemaker-py-7xb.

Lint at parity (46); tests 379 passed, 0 failed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MJ84Feep79Hhm3E4zZJmnB
2026-08-29 10:20:01 +00:00
Claude
df84bfe1e6
Missing-space cascade no longer weighted by YAML verbosity
check_space_counts emitted, per missing room instance, two base failures
plus one placeholder for each optional key the author happened to type --
has_size/has_width/has_proportion are literally "size" in c from the YAML.
So a missing room cost 3, 4 or 5 fails depending on nothing but how
verbosely its space was written, and under value *= 0.5 ** len(failures)
that is a 4x difference in penalty between two single rooms. The tiered
comparator inherits it directly, since n_hard is dominated by these
cascades -- the search's primary key was partly a measure of config style.

The two paths disagreed about the same room. A PRESENT room is checked on
all three qualities regardless of declaration: get_space_params fills width
and proportion from defaults, deriving width from size when absent, so
programme-house's t2 declares size: alone and still gets a real width
target of 1.633 it can fail on. Missing, it emitted one placeholder where
b1 emitted three. The cascade stands in for the checks that could not run,
and it stood in for the wrong number of them.

Fix: emit all three placeholders always -- a fixed 5 per missing instance,
mirroring the present-room path. 36 of 67 corpus codes were under-counted.
Max weight ratio between two single rooms 4x -> 1x (programme-house),
2x -> 1x (harbor, maple).

This makes fail counts LARGER and that is the point; it is a correctness
fix, not an improvement. harbor evolved-3M-nols-3 82 -> 84, generated
155 -> 174, evolved-3M 131 -> 144; maple generated unchanged (no missing
instances).

NOT taken: 1i8's other option, one fail per instance with the placeholders
informational. It fixes the verbosity dependence too but silently rescales
a missing room from 1/32 to 1/2, the same weight as one crinkliness fail.
Whether it SHOULD cost 1/32 is a real and separate question; bundling it
here would change the objective's priorities under cover of a bug fix.
Magnitude left exactly where it was, filed as homemaker-py-3i3.

Every historical corpus fail count is invalidated again, on top of 39.4 and
38.10/38.11 -- which is why the cold-start re-baseline belongs after the
objective work, not before it.

Closes homemaker-py-1i8.

Lint at parity (46); tests 379 passed (3 new), 0 failed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MJ84Feep79Hhm3E4zZJmnB
2026-08-29 09:33:40 +00:00
Claude
45eff37f96
Records Room ruled a store; stays exempt
No config change -- health-centre re1 already declares crinkliness: none.
This settles the corpus classification at 14 spaces: storage,
plant/mechanical, utilities closets, records room. Replaces the hedge in
38.11 with the ruling.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MJ84Feep79Hhm3E4zZJmnB
2026-08-28 23:22:02 +00:00
Claude
c9b9eafef6
Owner's daylight ruling: corridors need it; retract the 61% headline
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
2026-08-28 23:14:21 +00:00
Claude
055d7101cd
ssz: crinkliness is declared per space; there is no daylight attribute
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
2026-08-28 22:39:09 +00:00
Claude
bb27a5e548
ssz: 61% of reported crinkliness fails are not defects; two corrections
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
2026-08-26 18:07:01 +00:00
Claude
6405ca1a59
ssz: record the A/B result honestly -- not a pass at n=3
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
2026-08-26 17:57:11 +00:00
Claude
189efdbfc7
ssz: daylight is required of rooms that do not need it
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
2026-08-26 16:45:10 +00:00
Claude
8522576467
Frontage feasibility as a pre-flight check; retract the "3x short" claim
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
2026-08-26 16:20:52 +00:00
Claude
024311a2c2
§39.10: preserving constructed connectivity is NULL — and it reframes §39.9
§39.9 named the upstream fix: keep circulation connected DURING the resize
rather than rebuilding it after. Built and measured. It does not help, and the
reason matters more than the lever.

Both halves of the re-cut do damage, in different proportions per programme.
Freezing rotations and letting only ratios move (% levels connected, 12 seeds):
harbor 100 -> 71 -> 50, health-centre 100 -> 8 -> 8, maple 100 -> 92 -> 67. So
health-centre is destroyed entirely by the ratio and maple mostly by the
rotation; a fix must be able to give back either.

operators._size_divisions_preserving_circulation snapshots every cut, resizes,
then reverts the cuts on the tree path between each circulation pair the resize
broke -- programme fully intact, no retyping, only geometry given back. It works
on connectivity (harbor 50->92%, maple 67->97%, health-centre 8->17%) and costs
area accuracy: constructed-seed fails harbor 96.6->141.5, maple 141.8->175.8,
size fails roughly double. (A greedy single-cut revert barely moved -- it stalls
where no ONE revert helps though two would. Targeting the broken pairs is what
made connectivity work.)

The obvious defence -- raw constructed seeds understate it, the resize is only a
warm start, the inner loop should recover -- was TESTED AND FAILS. Full search,
harbor-house, 12000 evals, seed 1:
  OFF  43 fails,  9 hard, 3 connectivity
  ON   65 fails, 26 hard, 4 connectivity
Worse on every axis, including connectivity itself.

REFRAMING: §39.9's fact stands (the resize destroys 41 of 49 circulation edges)
but is NOT ACTIONABLE, because construction-time connectivity does not determine
final connectivity. The search discards and rebuilds the seeder's circulation
either way, and constraining the seed only spends area quality the search cannot
recover. Together with §39.8 (not an incentive problem) that retires the framing
this thread inherited from §38: connectivity is neither a construction problem
nor an incentive one.

Both flags (repair_circulation, preserve_circulation) stay default off with the
numbers recorded, plus byte-identical-default tests. Do not revisit either
without a new formulation -- the standing this document gives bubble.py.

356 passed (+1 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
2026-08-26 15:11:46 +00:00
Claude
7c41069226
§39.9: level-not-connected is destroyed by the resize, not by the search
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
2026-08-26 14:39:07 +00:00
Claude
3aee813ccd
§39.8: homemaker-py-2v1 connectivity weighting — MEASURED NULL, premise retracted
§38.2 concluded the objective is net-positive on severing a level's
circulation: merging a corridor into a habitable sibling gains x6
(value_inside/value_circulation), while "level N not connected" costs x0.5, so
break-even needs 0.5^w < 50/300, w > 2.58 -- "severing must cost at least 3
fails and costs 1". The arithmetic is right. The premise is wrong.

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

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

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

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

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

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

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

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MJ84Feep79Hhm3E4zZJmnB
2026-08-26 14:15:22 +00:00
Claude
03aadec1ba
§39.7: access requirements become a declared usage: attribute (homemaker-py-sel)
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
2026-08-26 13:39:41 +00:00
Claude
a25dc2cb59
§39.4 completion + §39.5 retraction + §39.6: the usage namespace is NOT clean
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
2026-08-26 10:09:14 +00:00
Claude
7ec4e5d121
§39.4: tighten generic-type matching, reverting the harbor rename
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
2026-08-26 09:45:28 +00:00
Claude
697914fac8
ju3: reject programme codes colliding with the c/o/s generic type prefixes
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
2026-08-26 09:06:08 +00:00
Claude
fd9802e499
DESIGN.md §39: config audit — programme codes collide with the c/o/s type namespace
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
2026-08-26 08:27:49 +00:00
Claude
50b5cfd476
§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
2026-08-26 07:40:37 +00:00
Claude
c0c47a6e2d
DESIGN.md §38: the plateau is an objective-gradient problem, not a search problem
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
2026-08-25 23:18:20 +00:00
5f5e640906 DESIGN.md sec 13.11: correct stale 7ua note, cross-ref follow-up 4ok
homemaker-py-7ua is fixed but only covers run_staged_search.py's
LEAFSHARE/MULTIUSE-gated rescore path. Baseline (LEAFSHARE=0/MULTIUSE=0)
runs still MISMATCH for the same underlying reason -- driver.search_staged
has no collapse_insearch param and always runs with it on -- tracked as
the new homemaker-py-4ok.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014uCyZJCh5mBuA2yEFxgQpo
2026-08-05 13:54:42 +01:00
30adbf4948 homemaker-py-2g7.6: NO-GO on graph-first slicing-tree construction via rectangular dualization
Research spike (DESIGN.md §37.8): classical rectangular dualization assumes
one-vertex-one-rectangle, which breaks on harbor's circulation hub (an
emergent-shape multi-leaf region, not a fixed single module) and is overkill
on the room-only adjacency graph (a trivial 3-edge matching already fully
satisfied by §11.7's seeding). No literature precedent for the multi-storey
stacking constraint either. Not prototyping; no code changes.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014uCyZJCh5mBuA2yEFxgQpo
2026-08-05 00:25:23 +01:00
9692c912ed DESIGN.md sec 37.7: update CP-SAT assignment writeup with final A/B result
Full harbor+maple/3-seed/20k-budget run superseded the earlier inconclusive
pilot table; section now records the closed verdict (no clear win, both
flags stay default off) instead of "driver-level INCONCLUSIVE at pilot
scale".

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LSwQwpEaHFBkeVSDDWd75S
2026-08-05 00:06:03 +01:00
cf634ae949 homemaker-py-2g7.5: CP-SAT exact room-code assignment (seeder + reassign op)
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
2026-08-04 09:19:36 +01:00
3aa08ee6ff homemaker-py-koo: multi-storey (below-link) support for the shape-curve DP
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
2026-08-03 23:30:18 +01:00
467c732260 homemaker-py-wkh: DP-exact hard pre-filter for driver.py's shape-feasibility prune
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
2026-08-03 21:10:28 +01:00
229c3c6933 homemaker-py-6xh: wire shape-curve DP into driver.py as an NM warm-start
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
2026-08-03 18:43:28 +01:00
9296f75cdb homemaker-py-2g7.1: plan->dom composer (SVG trace -> slicing-tree .dom)
Composer half of the ground-truth bead: examples/harbor-house/drawings/
harbor-house 1.svg turned out to be a Bonsai/Blender render of 3m.dom's own
IFC (32 IfcSpace paths == 3m.dom's upper-storey leaf count), not a human
trace, so no usable reference exists yet -- this builds and tests the
pipeline that will consume one once traced. compose.py parses storey-N
Inkscape layers of cut-lines + labels against a boundary-stub .dom (plot/
height/elevation only, no room shapes to keep aligned across storeys) and
recursively detects guillotine cuts, mirroring geometry.py's own
division-line algebra; non-slicible regions and label mismatches are
reported by location rather than guessed at. homemaker-compose CLI added.
Renamed dom._link to public dom.link since compose.py needs to re-link from
outside dom.py. Full design writeup in DESIGN.md sec 37.3; actual human
tracing of harbor-house/programme-house is tracked as follow-up under
2g7.1, still open.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LSwQwpEaHFBkeVSDDWd75S
2026-08-03 11:03:02 +01:00
d148f219c8 homemaker-py-2g7.4: fix shape-curve DP to be rotation-invariant
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
2026-08-03 07:06:48 +01:00
85c1183d4c homemaker-py-2g7.4: shape-curve DP prototype (Otten/Stockmeyer) — PASS
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
2026-08-02 23:43:30 +01:00
c644f279eb homemaker-py-2g7.3: record A/B acceptance result, close bead
DESIGN.md §37.1: hard/soft tiering A/B (harbor-house + maple-court, 3 seeds,
20k evals/run) shows hard-fail mean strictly better under the tiered
comparator on both programmes (harbor 11.67->5.33, maple 19.33->14.00) at
the cost of higher soft/total fails — the intended trade. ACCEPTANCE: PASS.

Filed homemaker-py-p6t as a non-blocking follow-up: race tiered vs flat to
0 hard fails (convergence speed) rather than composition at a fixed budget.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LSwQwpEaHFBkeVSDDWd75S
2026-08-02 19:00:48 +01:00
522f9b1d68 homemaker-py-2g7: file Phase 9 epic — ground truth, exact evaluation, solver-directed search (DESIGN.md §37)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NjyStTdLWFMtdScrpbgQur
2026-08-02 10:16:56 +01:00
7257fce7ca homemaker-py-zrx: expert review of numeric/scoring path — 3 confirmed bugs filed (DESIGN.md §36)
Review of fitness.py/solver.py/collapse_cmd.py/innerloop.py/driver.py for
silent score-corrupting bugs (the iio class). Filed with verified repros:
r5a (stale-share resurrection via collapse commit), cvw (parallel staged
stale id()-keyed geometry cache), sd3 (collapse_best keep-better guard
vacuous under baked-in collapse_insearch), pek (shadowed process_storey).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NjyStTdLWFMtdScrpbgQur
2026-08-02 09:21:58 +01:00
3069217c29 homemaker-py-iio: document retroactive impact of stale-share collapse bug (DESIGN.md §35)
Adds a full writeup of the root cause (already fixed in 929be5b) plus a
same-codebase fix-vs-no-fix re-verification: harbor-house qpk-protocol
seeds 1-3 show collapse_insearch=OFF unaffected, but ON diverges by 5-8
fails on 2/3 seeds, non-directionally. Confirms the bug was not merely
theoretical for historical leaf_sharing+collapse_insearch runs, though the
noise is unlikely to have flipped 1ph's aggregate N=20 verdict. Adds a
caveat postscript to §20 (qpk) and files homemaker-py-d86 for the rigorous
historical-commit re-verification this session didn't do.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R8agJBT2ZpmF3ErW7wi2wY
2026-08-02 07:56:33 +01:00