From 8522576467e046297fe58d5552acfac6524b2d83 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 26 Aug 2026 16:20:52 +0000 Subject: [PATCH] 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 Claude-Session: https://claude.ai/code/session_01MJ84Feep79Hhm3E4zZJmnB --- .beads/issues.jsonl | 5 +- DESIGN.md | 68 ++++++++++++++++++++ experiments/diag_exposure_frontage.py | 92 ++++++++++++++++++--------- src/homemaker_layout/evolve.py | 56 ++++++++++++++++ 4 files changed, 190 insertions(+), 31 deletions(-) diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl index 5403bbc..b011ccc 100644 --- a/.beads/issues.jsonl +++ b/.beads/issues.jsonl @@ -6,7 +6,7 @@ {"_type":"issue","id":"homemaker-py-ut5","title":"Update the 2v1 acceptance target: harbor's 15-fail reference was measured pre-ju3 against the 32-instance effective programme","description":"DESIGN.md 38.7 fixes the acceptance test for homemaker-py-2v1 as 'harbor-house reaches its known 15-fail floor in materially fewer than 1.7M evals, and without level 0/1 not connected'. That 15-fail figure comes from evolved-3M-nols-3, measured before homemaker-py-ju3 against the 32-instance EFFECTIVE programme (cr1/of/st1/st2 silently dropped or mis-parameterised). Against the real 37-instance programme the number will differ, so the target as written is not measurable any more.\\n\\nNeeded: migrate evolved-3M*.dom with experiments/migrate_ju3_rename.py, rescore against the renamed programme, and restate the 2v1 acceptance figure. The 39.3 re-baseline (55 fails, seed 1, 20k evals) is the new near-term reference but is not the long-budget floor.","acceptance_criteria":"evolved-3M*.dom committed and migrated; its post-ju3 fail count recorded in DESIGN.md; the 2v1 acceptance figure restated against it.","status":"open","priority":1,"issue_type":"task","owner":"noreply@anthropic.com","created_at":"2026-08-26T09:05:23Z","created_by":"Claude","updated_at":"2026-08-26T09:05:23Z","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"homemaker-py-hxi","title":"Buried circulation and outside space are negative-value: search is rewarded for deleting the circulation spine","description":"Direct consequence of the zero-exposure bug. Measured on a constructed harbor-house seed: deleting a buried O leaf improved the raw score 85x and removed 7 fails; deleting a buried C leaf improved it 62x and removed 6 fails. Programme rooms are held in place ONLY by the missing-space fail cascade, not by contributing value -- deleting a buried k1/da1/m costs +15 fails, so they stay, but nothing positive keeps them. Circulation and outside leaves carry no missing-space requirement, so nothing keeps them at all. Observed live: in a 20k-eval harbor-house run, undivide/core_undivide account for a large share of recorded improvements (16 occurrences in the log) -- the search is literally deleting circulation to score better. This explains three prior negative results as a single mechanism: 18 graded circulation-connectivity (a tie-break signal cannot beat a 60x scalar gradient), 21/22 bridge_circulation (the operator inserts corridor leaves the objective immediately punishes), and the 'level N not connected' hard fails surviving \u003e1M evals in the 3M run.","notes":"Depends on the zero-exposure fix; may need no separate fix if (a)/(c) there restores a value gradient for circulation. Worth re-running the 18 and 21/22 A/Bs afterwards -- both may have been measuring a broken gradient rather than a bad idea.","status":"open","priority":1,"issue_type":"bug","owner":"noreply@anthropic.com","created_at":"2026-08-25T23:14:40Z","created_by":"Claude","updated_at":"2026-08-25T23:14:40Z","dependencies":[{"issue_id":"homemaker-py-hxi","depends_on_id":"homemaker-py-ssz","type":"blocks","created_at":"2026-08-25T23:15:12Z","created_by":"Claude","metadata":"{}"},{"issue_id":"homemaker-py-hxi","depends_on_id":"homemaker-py-2v1","type":"blocks","created_at":"2026-08-26T07:37:47Z","created_by":"Claude","metadata":"{}"}],"dependency_count":2,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"homemaker-py-ssz","title":"Zero-exposure leaves score hard quality=0: the objective assigns no value to any interior room","description":"fitness.quality_uncrinkliness returns a hard 0.0 when a leaf has no daylit wall (area_outside==0 =\u003e crink==0 =\u003e 'if not crink: return 0.0'). Because evaluate_leaf MULTIPLIES factors into quality, and process_storey accumulates 'value += quality * rate * area', such a leaf contributes EXACTLY ZERO value while still adding cost. Measured on the full default construction stack (leaf_sharing, depth_balanced, interior_outside, collapse_insearch), 3 seeds each: harbor-house 46% of interior leaves, health-centre 45%, maple-court 56% are zero-exposure. On a converged 20k-eval harbor-house run (seed 1, 57 fails) 14 of 17 crinkliness fails are zero-exposure, and ~470 m2 of the 721 m2 ground floor plate sits at zero value. This is the mathematically consistent limit of the gaussian (1/crink -\u003e inf), so it is a faithful port, not a porting bug -- but it means the objective's gradient does not describe a good building.","design":"Options, none yet chosen: (a) floor the factor at a small epsilon instead of 0 so buried leaves keep a value gradient and remain rankable; (b) make the gaussian one-sided (clip to 1.0 on the compact side) so being LESS exposed than target is not punished as hard as being over-exposed -- architecturally, a compact well-insulated room is not a defect; (c) exempt circulation/store types from the daylight requirement entirely (uncrinkliness_circulation currently uses the same [5/6, 1.1/3] as habitable rooms, so internal corridors -- completely normal architecture -- are guaranteed failures). Any change here invalidates prior fail-count baselines, so it needs its own A/B and a DESIGN.md section.","acceptance_criteria":"A/B on harbor-house + maple-court at fixed budget showing the chosen variant lowers hard-fail count without inflating soft; DESIGN.md section recording the result; prior baselines re-stated under the new objective.","status":"open","priority":1,"issue_type":"bug","owner":"noreply@anthropic.com","created_at":"2026-08-25T23:14:40Z","created_by":"Claude","updated_at":"2026-08-25T23:14:40Z","dependencies":[{"issue_id":"homemaker-py-ssz","depends_on_id":"homemaker-py-2v1","type":"blocks","created_at":"2026-08-26T07:37:47Z","created_by":"Claude","metadata":"{}"}],"dependency_count":1,"dependent_count":2,"comment_count":0} -{"_type":"issue","id":"homemaker-py-tdp","title":"Frontage-budget feasibility bound: harbor-house and maple-court are ~3x short of the exposed wall their own crinkliness constraint demands","description":"Closed-form bound, no search needed. Crinkliness fails when 1/crink \u003e 1.6202 (solving gaussian(x,1,5/6,1.1/3)=FAIL_THRESHOLD), and crink = L*h/A, so every interior leaf needs exposed wall length L \u003e= A/(1.6202*h) -- per storey, A_storey/4.86 metres at h=3. Supply vs demand per storey: harbor-house 54m supplied / 148m needed (2.7x short); maple-court 56m / 162m (2.9x short); health-centre 43m / 41m (feasible); programme-house 24m / 12m (2x surplus). The deficit comes from half the plot perimeter being marked 'private' in harbor/maple -- area_outside skips private/fortified edges. This PREDICTS the corpus fail-count plateau: the two programmes that are frontage-short are exactly the two that plateau (harbor 30-40, maple 74-84 fails), and the two with surplus are the two that effectively solve (programme-house reached 1 fail in a 12k-eval run here). Causal check: relabelling harbor's two private edges as open (identical geometry and programme, perimeter labels only) cut zero-exposure leaves 52%-\u003e19% and seeder crinkliness fails 16.5-\u003e12.0 over 6 seeds.","design":"Ship the bound as a pre-flight check (a 'homemaker-programme-check' style report, or a warning from evolve.py at startup): given a plot and programme, report required vs available frontage and the courtyard area needed to close the gap. This is the cheap, no-ground-truth-needed version of what 2g7.2 objective calibration wanted -- it answers 'is this programme even satisfiable' without needing traced human plans.","acceptance_criteria":"A tool/check that reports the frontage budget per programme dir; DESIGN.md section with the derivation and the corpus table; the plateau programmes flagged as frontage-infeasible-as-specified.","status":"open","priority":1,"issue_type":"task","owner":"noreply@anthropic.com","created_at":"2026-08-25T23:14:40Z","created_by":"Claude","updated_at":"2026-08-25T23:14:40Z","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"homemaker-py-tdp","title":"Frontage-budget feasibility bound: harbor-house and maple-court are ~3x short of the exposed wall their own crinkliness constraint demands","description":"Closed-form bound, no search needed. Crinkliness fails when 1/crink \u003e 1.6202 (solving gaussian(x,1,5/6,1.1/3)=FAIL_THRESHOLD), and crink = L*h/A, so every interior leaf needs exposed wall length L \u003e= A/(1.6202*h) -- per storey, A_storey/4.86 metres at h=3. Supply vs demand per storey: harbor-house 54m supplied / 148m needed (2.7x short); maple-court 56m / 162m (2.9x short); health-centre 43m / 41m (feasible); programme-house 24m / 12m (2x surplus). The deficit comes from half the plot perimeter being marked 'private' in harbor/maple -- area_outside skips private/fortified edges. This PREDICTS the corpus fail-count plateau: the two programmes that are frontage-short are exactly the two that plateau (harbor 30-40, maple 74-84 fails), and the two with surplus are the two that effectively solve (programme-house reached 1 fail in a 12k-eval run here). Causal check: relabelling harbor's two private edges as open (identical geometry and programme, perimeter labels only) cut zero-exposure leaves 52%-\u003e19% and seeder crinkliness fails 16.5-\u003e12.0 over 6 seeds.","design":"Ship the bound as a pre-flight check (a 'homemaker-programme-check' style report, or a warning from evolve.py at startup): given a plot and programme, report required vs available frontage and the courtyard area needed to close the gap. This is the cheap, no-ground-truth-needed version of what 2g7.2 objective calibration wanted -- it answers 'is this programme even satisfiable' without needing traced human plans.","acceptance_criteria":"A tool/check that reports the frontage budget per programme dir; DESIGN.md section with the derivation and the corpus table; the plateau programmes flagged as frontage-infeasible-as-specified.","status":"closed","priority":1,"issue_type":"task","assignee":"Claude","owner":"noreply@anthropic.com","created_at":"2026-08-25T23:14:40Z","created_by":"Claude","updated_at":"2026-08-26T16:20:08Z","started_at":"2026-08-26T16:08:13Z","closed_at":"2026-08-26T16:20:08Z","close_reason":"Shipped as `evolve._preflight`, but the headline claim in this issue's title is\nRETRACTED. See DESIGN.md §39.11 (and the partial retraction inserted at §38.3).\n\nWhat was wrong: the \"2.7x / 2.9x short\" figures were computed for a FULLY BUILT\nplot. None of these programmes ask for a fully built plot. Recomputed against\nthe area each programme actually demands (demand / storeys), harbor-house and\nmaple-court are NOT frontage-infeasible -- they need 49 m2 and 22 m2 of\ncourtyard respectively, against 277 m2 and 424 m2 of spare plot. The bound\nitself (L \u003e= A/(1.6202*h)) is sound; it was applied to the wrong quantity.\n\nWhat survives: exactly one corpus programme is infeasible, and not for daylight\nreasons. health-centre demands 240 m2 of floor on a 183 m2 plot (131%). That is\nvisible in the geometry -- every room lands at 0.60x its declared target, 100%\nof them undersized, uniformly. harbor-house and maple-court hit target almost\nexactly (median area / (target * share) = 1.01x), so the plateau is NOT\nexplained by an unsatisfiable brief and remains unexplained.\n\nShipped:\n - `evolve._preflight(programme_dir)`, run at startup before the run banner.\n Two closed-form checks: (1) does demanded floor area fit the plot,\n (2) is there enough daylit wall for it. Advisory only, never blocks a run.\n Fires on health-centre (both) and harbor-house/maple-court (frontage only);\n silent on programme-house.\n - `experiments/diag_exposure_frontage.py frontage` reports the full budget.\n - DESIGN.md §39.11 with the corrected corpus table; §38.3 marked PARTLY\n RETRACTED and cross-referenced.\n\nBoth the check and the diagnostic measure plot area and frontage through\n`geometry`, not the raw init.dom corners, so they carry the `wall_outer` inset\nand plot rotation, and \"daylit\" means exactly what `Fitness.area_outside` means\nby it (external boundary, perimeter type not private/fortified). An earlier\nhand-rolled version of these figures skipped the inset and was ~1 m / ~14 m2\noptimistic per plot; the numbers in §39.11 are the corrected ones.\n\nAcceptance criteria 1 and 2 met. Criterion 3 (\"plateau programmes flagged as\nfrontage-infeasible-as-specified\") is withdrawn as false, not deferred.\n","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"homemaker-py-2g7.4","title":"Exact shape-curve inner loop (Otten/Stockmeyer DP) replacing Nelder-Mead","description":"The classic slicing-floorplan result applied to our exact representation: each leaf's size/width/proportion constraints define a feasible-shape region; these compose bottom-up through the slicing tree as piecewise shape curves, yielding in ONE linear pass (no iteration): (a) whether ANY ratio assignment satisfies all per-leaf shape constraints, and (b) the ratios that realize a chosen point on the root curve. Today the same question costs an 80-eval NM run per child (~all of the 3M-eval budget) and answers it only approximately. Plan: (1) prototype on harbor-house-l0 with a rectangular plot approximation; (2) validate against innerloop.optimise — DP-feasible topologies must score \u003e= NM result when polished, DP-infeasible must never reach 0 shape fails under NM; (3) wire as a PRE-FILTER: prune shape-infeasible children before any native eval, and warm-start NM from DP ratios (or replace NM entirely where the plot is near-rectangular; keep NM as final polish for skew). CAVEATS to model honestly: crinkliness/access/adjacency are NOT in the DP (graph terms, not per-leaf shape) — the DP handles the size/width/proportion family only, which is fine for pruning; equal-offset skew-quad geometry means DP areas are approximate — measure the approximation error on real plots first (harbor plot is a near-rect quad). Expected payoff: 100-1000x cheaper feasibility, turning topology search into enumerate-and-prune and unlocking the racing/MAP-Elites/CP issues. Cf. §34: autodiff failed on wall-clock; this is a different attack — exactness via structure, not gradients.","acceptance_criteria":"on harbor-house-l0: DP verdict agrees with NM-polished shape-fail outcome on \u003e=95% of 200 random topologies; measured speedup \u003e=50x per feasibility decision; approximation error on the skew plot quantified","status":"closed","priority":1,"issue_type":"feature","assignee":"Bruno Postle","owner":"bruno@postle.net","created_at":"2026-08-02T09:15:04Z","created_by":"Bruno Postle","updated_at":"2026-08-02T22:42:15Z","started_at":"2026-08-02T18:39:05Z","closed_at":"2026-08-02T22:42:15Z","close_reason":"Prototype PASS: 99.0% agreement (\u003e=95%), 93.6x speedup (\u003e=50x), approximation error quantified (7.5% bbox overestimate). See DESIGN.md §37.2. Not wired into product this session -- follow-up homemaker-py-6xh filed.","dependencies":[{"issue_id":"homemaker-py-2g7.4","depends_on_id":"homemaker-py-2g7","type":"parent-child","created_at":"2026-08-02T10:15:04Z","created_by":"Bruno Postle","metadata":"{}"}],"comments":[{"id":"019fc645-557c-7351-9349-d693e5eefa2b","issue_id":"homemaker-py-2g7.4","author":"Bruno Postle","text":"Post-close correction (user review): the prototype's rectangular\napproximation used an axis-aligned global bbox for each quad's (w,h) --\ncorrect only by coincidence on harbor-house-l0's near-axis-aligned plot\n(~7.5% area error). A real building's orthogonal walls need not align to\nthe plot's survey/CRS axes; confirmed by rotating the plot 45deg, where\nbbox error jumped to 102%.\n\nFixed: (w,h) now measured from edge lengths (edge0+edge2)/2,\n(edge1+edge3)/2 -- rotation-invariant by construction -- and the\nparent/child composition rule (which dimension sums vs. is shared) is now\nderived EXACTLY from child.rotation parity (verified float-exact\nidentity) instead of the empirical geometric heuristic the closed version\nused (which, tried alone without the parity fix, regressed accuracy\n99.0% -\u003e 95.5%).\n\nRe-validated: 99.0% on harbor-house-l0 unrotated (matches original, 0\nfalse negatives), 100% at 97x speedup on the SAME plot rotated 45deg.\nSee DESIGN.md §37.2 (Correction 1 / Correction 2) for full detail.\nhomemaker-py-6xh (production wiring follow-up) still applies.","created_at":"2026-08-03T06:17:40Z"}],"dependency_count":0,"dependent_count":1,"comment_count":1} {"_type":"issue","id":"homemaker-py-2g7.3","title":"Hard/soft fail tiering: 'solved' = zero hard fails","description":"Lex-by-total-count treats a crinkly wall the same as a missing room, so search polishes shape taxes instead of fixing structure — the 3M-run best still carries 'level 0/1 not connected' and wrong-level fails after 1.7M evals. Split fails into HARD (missing space, wrong/required level, level connectivity, circulation connectivity, stairs, covered-outside) and SOFT (crinkliness, proportion, size, width, edge-too-long) tiers. Outer comparator becomes (-hard, -soft, fitness); 'solved' is defined as zero hard fails. GUARDS: (1) the inner-loop 0.5^n cliff must keep protecting against trading into new fails (§4.5/§4.9 — rerun the 0/9 inner-loop-protection check); (2) rerun the §4.9 outer A/B: the scheme must not reintroduce the scalar pathology; (3) §11.4 warns comparator reshaping alone does not escape topology basins — the claim here is narrower: budget stops being spent on soft fails while hard fails remain, and reporting becomes meaningful. The tier map lives in fitness.py next to the fail emission sites so new fail strings must declare a tier. Can start before the calibration issue lands but final tier assignments should be reviewed against its findings.","acceptance_criteria":"tiered comparator behind a flag with A/B on harbor+maple (3 seeds, 20k evals): hard-fail count at budget strictly better or equal on mean, no §4.9 regression; report shows hard/soft split","notes":"ACCEPTANCE A/B COMPLETE — PASS (2026-08-02, experiments/tier_ab_2g7_3.py,\nharbor-house + maple-court, 3 seeds, budget 20000, leaf_sharing=True,\nn_workers=4, wall ~2h53m):\n\n harbor-house hard mean: flat 11.67 -\u003e tiered 5.33 (soft 29.00 -\u003e 42.33)\n maple-court hard mean: flat 19.33 -\u003e tiered 14.00 (soft 71.33 -\u003e 87.67)\n\nHard-fail mean strictly better on BOTH programmes at fixed budget — the\nrequired acceptance bar. Soft/total rise as expected (budget redirected from\npolishing shape fails to structural ones). Full per-seed log at\nscratch/tier_ab_2g7_3/log.txt (not committed — scratch output, regenerate via\nthe script if needed).\n\nGuards: (1) inner-loop 0.5^n cliff untouched by construction (no diff to\ninnerloop.py or the existing 0.5**len(failures) line) — not re-measured\nempirically, doesn't need to be. (2) tiered key is still lexicographic, not a\nblended scalar, so structurally immune to the §4.8 scalar pathology;\nencoded as tests/test_driver.py::test_use_tiers_prefers_fewer_hard_over_fewer_total_fails.\n\nDESIGN.md §37.1 written up with full table and rationale. Feature lands\ndefault-off (--use-tiers / HOMEMAKER_USE_TIERS / driver.search(use_tiers=)),\nso no existing reproduction changes.\n\nFollow-on (not blocking, filed separately): convergence-SPEED comparison\n(evals to 0 hard fails, tiered vs flat, same budget) — this A/B measured\nfail composition at a fixed budget snapshot, not time-to-solved.","status":"closed","priority":1,"issue_type":"feature","assignee":"Bruno Postle","owner":"bruno@postle.net","created_at":"2026-08-02T09:14:14Z","created_by":"Bruno Postle","updated_at":"2026-08-02T17:51:18Z","started_at":"2026-08-02T09:58:53Z","closed_at":"2026-08-02T17:51:18Z","close_reason":"Acceptance A/B passed on both harbor-house and maple-court (hard-fail mean strictly better under tiering); guards verified; DESIGN.md §37.1 written up.","dependencies":[{"issue_id":"homemaker-py-2g7.3","depends_on_id":"homemaker-py-2g7","type":"parent-child","created_at":"2026-08-02T10:14:14Z","created_by":"Bruno Postle","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"homemaker-py-2g7.2","title":"Calibrate the objective against human reference designs","description":"Score the traced human solutions (from the plan-\u003edom composer issue) and classify EVERY fail they raise as one of: (a) genuine spec violation (fix the trace or accept), (b) representation artifact (fix scoring, cf. §13.3/§13.8 share leaks), or (c) miscalibrated threshold (fix the constant/curve). Prime suspect: crinkliness — 48% of the evolved residual (§13.11), flat ~0.8/leaf tax even on squarest layouts (§13.1); if a real human plan pays it broadly, the gaussian on 1/crink is mis-tuned, not the designs. Outcome: either the human reference scores at/near 0 hard fails (objective validated, search is the gap) or a concrete list of scoring fixes. This finally makes 'the examples are solvable' a measured statement. Also record the human design's score as the per-programme target line on all future runs.","acceptance_criteria":"every fail on each human reference classified with evidence; miscalibrations filed/fixed; per-programme target scores recorded in DESIGN.md","status":"open","priority":1,"issue_type":"task","owner":"bruno@postle.net","created_at":"2026-08-02T09:14:11Z","created_by":"Bruno Postle","updated_at":"2026-08-02T09:14:11Z","dependencies":[{"issue_id":"homemaker-py-2g7.2","depends_on_id":"homemaker-py-2g7.1","type":"blocks","created_at":"2026-08-02T10:14:11Z","created_by":"Bruno Postle","metadata":"{}"},{"issue_id":"homemaker-py-2g7.2","depends_on_id":"homemaker-py-2g7","type":"parent-child","created_at":"2026-08-02T10:14:11Z","created_by":"Bruno Postle","metadata":"{}"}],"dependency_count":1,"dependent_count":0,"comment_count":0} @@ -36,6 +36,7 @@ {"_type":"issue","id":"homemaker-py-1p0","title":"Geometry inner loop: full-objective equal-offset ratio optimiser","description":"DESIGN.md §5.1, §7 Phase 1. Productionise experiments/optimize_fullfitness.py into homemaker: optimise(topology, x0=None) -\u003e (geometry, fitness). DOF = equal-offset division ratios of free branches (solver.free_branches, lowest-storey cut ownership), clipped to [eps, 1-eps]. Objective = full oracle fitness (never a proxy — §4.2 falsified). Must support warm-start x0 (§5.6) and a population/batch evaluation mode so each iteration scores via one batched oracle call (§4.6).","acceptance_criteria":"Reproduces or exceeds §4.5 gains (x1.24–x1.67, no new failures) on 2f45907, candidate-002, c964435; works as a library call on any corpus .dom","status":"closed","priority":1,"issue_type":"feature","assignee":"Bruno Postle","owner":"bruno@postle.net","created_at":"2026-06-11T23:36:58Z","created_by":"Bruno Postle","updated_at":"2026-06-12T08:46:31Z","started_at":"2026-06-12T00:14:19Z","closed_at":"2026-06-12T08:46:31Z","close_reason":"innerloop.optimise() lands: batched CMA-ES sigma ladder (0.05/0.15, IPOP popsize doubling, deterministic seeding) over equal-offset free-branch ratios vs full oracle fitness; warm-start x0 supported. Acceptance vs unprojected originals: x1.65/x1.66/x1.58 against bars x1.24/x1.67/x1.59, no new failures, 46 oracle calls vs NM's 200. Two near-bar results accepted as reproduced-within-noise (1% tol) — draw spread brackets the single-NM-draw bars; approved by Bruno 2026-06-12. Gotchas: equal-offset projection of legacy unequal cuts loses fitness/adds failures (midpoint projection used); pycma seed=0 means clock-seeded.","dependencies":[{"issue_id":"homemaker-py-1p0","depends_on_id":"homemaker-py-av5","type":"blocks","created_at":"2026-06-12T00:39:33Z","created_by":"Bruno Postle","metadata":"{}"}],"dependency_count":1,"dependent_count":3,"comment_count":0} {"_type":"issue","id":"homemaker-py-8cs","title":"Experiment: warm-vs-cold start of inner loop (Lamarckian inheritance)","description":"DESIGN.md §5.6, §4.6. Warm-starting a child topology's inner loop from the parent's optimised ratios is the main lever for cutting per-topology cost (~3 min/topology cold). Apply single topology mutations to optimised corpus designs, re-optimise warm (surviving cuts keep values, new cuts get heuristic defaults) vs cold, compare oracle-call counts to convergence at equal final fitness.","acceptance_criteria":"Speedup factor measured across \u003e=10 mutated topologies; decision recorded (expect order-of-magnitude; if \u003c2x, revisit §4.6 Phase-2 scoping)","notes":"Experiment script committed (experiments/warm_vs_cold.py, 1cc86c8) and machinery validated oracle-free; one mutated child scored through the oracle OK. Waiting on homemaker-py-gp2 reference run to finish, then execute under URB_NO_OCCLUSION=1 (3 parents x 400 evals + 12 children x 2 x 200 evals, ~1.5-2 h oracle time). Default budgets: parent 400, child 200; target = evals to 95% of best final.","status":"closed","priority":1,"issue_type":"task","owner":"bruno@postle.net","created_at":"2026-06-11T23:36:58Z","created_by":"Bruno Postle","updated_at":"2026-06-12T11:44:45Z","closed_at":"2026-06-12T11:44:45Z","close_reason":"Measured (URB_NO_OCCLUSION=1, parent budget 400, child 200, 12 single mutations across 3 designs): cold start reached 95% of warm final in 0/12 cases within budget — speedup unbounded at practical budgets; warm finals beat cold finals x1.2-x4 in 12/12; 6/12 warm starts were within 95% at 1 eval (near-neutral mutations). Decision: Lamarckian warm-starting is MANDATORY in the memetic driver (homemaker-py-b39), not an optimisation; cold starts produce strictly worse geometry at equal budget. Note: 2 undivides were exactly fitness-neutral (same-type merge == Merge_Divided equivalence) — locality datum for homemaker-py-nyb.","dependencies":[{"issue_id":"homemaker-py-8cs","depends_on_id":"homemaker-py-1p0","type":"blocks","created_at":"2026-06-12T00:39:34Z","created_by":"Bruno Postle","metadata":"{}"}],"dependency_count":1,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"homemaker-py-av5","title":"Batched oracle: score many .dom files per invocation","description":"oracle.py currently scores one .dom per urb-fitness.pl call (~1.65 s/dom). DESIGN.md §4.6: batching amortises Perl startup to ~0.99 s/dom and is required so population/batch optimisers can score a whole generation in one oracle call. Extend oracle.py with a batch API: write N .dom files, one perl invocation, parse N .score/.fails pairs. Keep the single-file path for compatibility.","acceptance_criteria":"Batch of 35 corpus files scores in one perl invocation; per-file results identical to single-file calls; measured s/dom reported","status":"closed","priority":1,"issue_type":"task","assignee":"Bruno Postle","owner":"bruno@postle.net","created_at":"2026-06-11T23:36:56Z","created_by":"Bruno Postle","updated_at":"2026-06-12T00:14:06Z","started_at":"2026-06-11T23:50:40Z","closed_at":"2026-06-12T00:14:06Z","close_reason":"score_batch() lands in oracle.py; 35-file corpus parity verified single-vs-batch (1e-12 rel fitness, exact fail sets); 0.98 s/dom batched vs 1.27 single, x1.30","dependency_count":0,"dependent_count":1,"comment_count":0} +{"_type":"issue","id":"homemaker-py-7b7","title":"health-centre demands 131% of its plot: the programme is unsatisfiable as written","description":"`evolve._preflight` (homemaker-py-tdp, DESIGN.md 39.11) reports health-centre demanding 240 m2 of floor per storey on a 183 m2 plot -- 131%. It is 1 storey (n_storeys_required=1, storey_minimum=1), so there is nowhere for the overflow to go.\n\nThe consequence is visible in every evaluated layout: 100% of rooms come out undersized, uniformly, at a median 0.60x their declared target. No search can fix this -- quality_size is being asked for something the plot cannot supply, so the size gaussian is pinned near-zero across the whole design and contributes no useful gradient. Contrast harbor-house and maple-court, where the seeder hits targets at median 1.01x.\n\nThis is a corpus data problem, not an engine problem. Either the plot is too small or the programme's sizes/counts were estimated too generously (the CLAUDE.md note that all corpus targets were 'estimated many years ago and never changed' applies).\n\nOptions, roughly in order of honesty:\n 1. enlarge the plot in examples/health-centre/init.dom to ~1.4x\n 2. scale down size/count in patterns.config until demand fits with headroom\n 3. raise the storey count so the demand spreads over 2 levels\n 4. leave it and treat health-centre as a deliberate over-tight brief, documented as such\n\nWhichever is chosen, note it in DESIGN.md 39.11 so the pre-flight warning is not read as a bug in the check.","acceptance_criteria":"health-centre's demanded area per storey fits its plot with headroom, OR the programme is explicitly documented as a deliberately over-tight brief; `evolve._preflight` no longer reports it as an unexplained anomaly; median room area / (target * share) on a constructed seed is materially above 0.60x.","status":"open","priority":2,"issue_type":"bug","owner":"noreply@anthropic.com","created_at":"2026-08-26T16:20:31Z","created_by":"Claude","updated_at":"2026-08-26T16:20:31Z","dependency_count":0,"dependent_count":1,"comment_count":0} {"_type":"issue","id":"homemaker-py-3qj","title":"Programmes do not declare bedroom/treatment-room to toilet adjacency, so the Brand adaptability credit is never earned","description":"Established while settling homemaker-py-sel. A toilet adjacent to a sleeping or treatment room is a positive even with no door between them -- Stewart Brand, How Buildings Learn: the building may need it knocked through later, and the adjacency is what makes that possible.\\n\\nThe engine already supports this. fitness._evaluate_full runs graph.check_adjacency against graph_base_pre, the UNFILTERED adjacency graph, so a declared adjacency requirement is satisfied by a neighbouring room regardless of the edges graph.has_circulation later strips for routing. (The stripping is correct on its own terms -- you cannot walk through a bedroom to reach the loo.)\\n\\nBut it is only ever scored where a programme DECLARES it, and only one corpus programme does:\\n programme-house (+ derivatives) t1 -\u003e adjacency: [b1] DECLARED\\n harbor-house t (Bathroom, count 6) vs r / n (sleeping) not declared\\n maple-court t, tt vs r / n not declared\\n health-centre t9 / t10 vs tr1 (Treatment Room) not declared\\n\\nSo three of the four real programmes leave the adaptability credit on the table: the search has no reason to place a WC next to a dormitory or a treatment room, because nothing scores it.\\n\\nThis is programme authoring, not an engine change, and it is independent of the usage-key work -- it can land before or after.","acceptance_criteria":"harbor-house, maple-court and health-centre declare the sleeping/treatment-to-toilet adjacencies their briefs imply; re-baselined so the change in fail counts is attributed rather than absorbed.","status":"open","priority":2,"issue_type":"task","owner":"noreply@anthropic.com","created_at":"2026-08-26T12:33:39Z","created_by":"Claude","updated_at":"2026-08-26T12:33:39Z","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"homemaker-py-fdp","title":"constructive_topology is not bit-reproducible on the assign_solver=cpsat path","description":"Established while fixing §39.5. Narrowed but not closed:\\n\\n- cpsat.solve_room_labels IS now reproducible on every captured instance, after two fixes: sorting the model-build order (neighbors[slot] is a set of dom.Node, which hashes by id() -- a memory address -- so raw iteration made the model order vary and CP-SAT returned a different equally-optimal assignment each run), and adding max_deterministic_time alongside the wall-clock cap.\\n- operators.constructive_topology(assign_solver='greedy') IS reproducible.\\n- operators.constructive_topology(assign_solver='cpsat') is NOT: identical seed in the same process gives different leaf-type signatures across runs.\\n\\nSo something upstream of the solver in _assign_adjacency_aware still varies on the cpsat branch. Disabling _cpsat_relabel_settled does not fix it, so it is the first call site. Suspect another id()-hashed set of Nodes feeding slot/neighbour ordering.\\n\\nPlausible contributor to homemaker-py-b8g (parallel/BLAS non-determinism in n_workers\u003e1 runs) -- same id-keying hazard class as the documented geometry._cache issue. Meanwhile tests/test_operators.py::test_assign_cpsat_matches_or_beats_greedy_secondary_adjacency averages three repeats instead of asserting on one run.","acceptance_criteria":"constructive_topology(assign_solver='cpsat') produces identical output for identical seeds in-process and across processes; the A/B test can go back to a single-run assertion.","status":"open","priority":2,"issue_type":"bug","owner":"noreply@anthropic.com","created_at":"2026-08-26T10:06:55Z","created_by":"Claude","updated_at":"2026-08-26T10:06:55Z","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"homemaker-py-0wr","title":"Re-check any harbor-house A/B decided by a small margin before the §39.4 namespace fix","description":"Until §39.4, harbor-house scored against a 32-instance EFFECTIVE programme -- cr1/of/st1/st2 (14% of room instances) were dropped by the count check and mis-parameterised by the generic c/o/s prefix rule. Any harbor A/B decided by a narrow margin was therefore decided against a programme missing a seventh of its rooms.\\n\\nOne confirmed casualty already: 2g7.5's CP-SAT seeder win (§37.7). Measured over 6 seeds on the corrected programme, greedy 102 / cpsat 114 -- cpsat now LOSES; on the old 32-instance programme it was 98/99, a tie, so the recorded win was marginal from the start. Control: on namespace-clean maple-court cpsat still wins 144/156, so the solver did not regress. Test marked xfail with a companion maple-court assertion; both assign_solver flags stay default off.\\n\\nWorth re-checking with the same lens: §13.9/§13.11 floors, §17/§20 collapse A/Bs, §23 ruin-recreate, §29/§30 beam width, §37.1 tiering -- anything whose harbor arm was close.","acceptance_criteria":"Each narrow-margin harbor result either re-measured on the corrected programme or annotated in DESIGN.md as pre-§39.4.","status":"open","priority":2,"issue_type":"task","owner":"noreply@anthropic.com","created_at":"2026-08-26T09:44:55Z","created_by":"Claude","updated_at":"2026-08-26T09:44:55Z","dependency_count":0,"dependent_count":0,"comment_count":0} @@ -107,7 +108,7 @@ {"_type":"issue","id":"homemaker-py-d86","title":"Rigorously re-verify qpk/1ph historical numbers against the homemaker-py-iio fix","description":"homemaker-py-iio (fixed 2026-08-02) found a stale-leaf-share metadata leak\nin Fitness._collapse_value/_usage_quality that could corrupt one cell of\ncollapse_global's Hungarian assignment during any leaf_sharing+collapse\nrun -- i.e. essentially the entire \"full default stack\" used from\nhomemaker-py-x3b (leaf_sharing default-on) onward, including the very\nstudies that justified defaulting collapse_insearch on (94g, qpk/1ph, 8sh).\n\nA same-codebase fix-vs-no-fix re-run of the qpk protocol (harbor-house,\nbudget 2500, seeds 1-3) confirmed the bug demonstrably perturbs real\nper-seed outcomes under collapse_insearch=ON (2/3 seeds diverged by 5-8\nfails, non-directionally) -- see DESIGN.md §35 for full details. That\nre-run used TODAY's codebase, not the actual historical commit, and only 3\nharbor-house seeds, not the original seed sets -- so it establishes the bug\nwas real and non-trivial but does NOT establish whether 1ph's aggregate\nN=20 programme-house verdict (mean 7.95-\u003e7.10, paired t-test p~=0.028)\nwould have changed under the fix.\n\nThis issue is to do the rigorous version: check out the codebase near the\n1ph commit (~2026-07-24, \"post-qpk commits through 161\"), backport the iio\nfix there in an isolated worktree, and re-run the ACTUAL historical seed\nsets (programme-house N=20 seeds 1-20, harbor-house N=3 seeds 1-3) at the\n1ph protocol's exact parameters, comparing per-seed and aggregate results\nagainst the published numbers. Low priority: the qualitative direction of\nthe qpk/1ph conclusion is probably still right (noise is non-directional\nand the N=20 statistical margin is comfortably above the observed per-seed\nswing), this is about tightening confidence, not expecting a reversal.","notes":"homemaker-py-r5a (fixed 2026-08-02) also affects this: it is the COMMIT-door companion to iio (a leaf relabelled back to its own stale share_type resurrects a stale multiplicity credit). Any re-verification run here should use the codebase state after BOTH iio and r5a, not iio alone.","status":"open","priority":3,"issue_type":"task","owner":"bruno@postle.net","created_at":"2026-08-02T06:53:51Z","created_by":"Bruno Postle","updated_at":"2026-08-02T09:44:35Z","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"homemaker-py-7ua","title":"run_staged_search.py final rescore omits collapse_insearch override, causing false MISMATCH under leaf-sharing","description":"experiments/run_staged_search.py's _native_score() (used for the final 're-scored (native): ... -\u003e OK/MISMATCH' sanity line) calls fitness.load_config(programme_dir) with NO overrides, but driver.search_staged's internal evaluator always runs with collapse_insearch=True (baked into driver.search's default, search_staged has no param to disable it). The script's monkeypatched fitness.load_config only injects leaf_sharing/share_edge_cap/multi_use, not collapse_insearch, so the final rescore conf silently diverges from the search-time conf whenever leaf_sharing is on (the current default stack). Observed during homemaker-py-91f: a WORKERS=4 budget=2000 harbor-house run reported best fails=38 during search but re-scored fails=34 -\u003e MISMATCH (partly parallel non-determinism per homemaker-py-b8g, but the missing collapse_insearch override is a separate, deterministic contributor). Fix: add collapse_insearch=True to the monkeypatched conf alongside leaf_sharing/share_edge_cap.","status":"closed","priority":3,"issue_type":"bug","assignee":"Bruno Postle","owner":"bruno@postle.net","created_at":"2026-08-01T11:32:58Z","created_by":"Bruno Postle","updated_at":"2026-08-05T09:04:05Z","started_at":"2026-08-05T07:33:29Z","closed_at":"2026-08-05T09:04:05Z","close_reason":"Fixed: added collapse_insearch=True to the monkeypatched load_config conf in run_staged_search.py's leaf_share/multi_use branch. Verified with a smoke run (programme-house, LEAFSHARE=1, budget=150): pre-fix reported MISMATCH (1.51708e-08 vs 1.56663e-08), post-fix reports OK with identical values. Full pytest suite: 405 passed, 5 pre-existing failures in test_cpsat.py/test_operators.py unrelated to this change (confirmed failing on main before this fix too).","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"homemaker-py-b8g","title":"Investigate parallel/BLAS non-determinism noise source in n_workers\u003e1 runs","description":"DESIGN.md §14 (psk, island-model experiment) flagged a real, uninvestigated noise source: 'Phase A is unaffected by the probe, yet harbor seed 2 scored 71 then 73 on byte-identical re-runs -- parallel/BLAS non-determinism, the same +/-2-3 effect §12.4 flagged.' This is DISTINCT from the homemaker-py-xcy bug (ProcessPoolExecutor as_completed ordering), which was fixed and made same-worker-count parallel runs reproducible for the SEARCH TRAJECTORY. This remaining noise is at the SCORING level (a single fitness eval on a fixed genome apparently returning different fail counts across runs), plausibly numpy/scipy BLAS thread nondeterminism in the geometry/inner-loop math. It was never root-caused or fixed, and it widens the error bars on every A/B in this log run at n_workers\u003e1 (the great majority of them, since serial sweeps are expensive). Investigate: reproduce minimally (score the same frozen .dom N times under workers\u003e1), bisect whether it's BLAS threading (try OMP_NUM_THREADS=1/OPENBLAS_NUM_THREADS=1), floating-point summation order, or something else; fix or document a mitigation (e.g. pin thread count in worker processes).","design":"Reference: DESIGN.md §14 'Noise caveat (carry forward)', §12.4 (homemaker-py-xcy, the related-but-distinct trajectory-ordering bug already fixed). If the cause is BLAS thread count, the fix is likely a one-line env pin in the worker pool initializer (driver.py's ProcessPoolExecutor setup).","notes":"homemaker-py-zrx review (2026-08-02) found a concrete, non-BLAS candidate mechanism for part of this noise in PARALLEL STAGED runs: homemaker-py-cvw — substrate_readiness in the parent process reads stale id()-keyed geometry cache entries (24/300 corrupted in a churn probe, worst error ~1.0), perturbing stage-1 selection address-dependently across byte-identical re-runs. Does not explain fixed-genome single-eval divergence (if that was ever actually isolated); re-test after cvw lands before chasing BLAS.","status":"open","priority":3,"issue_type":"bug","owner":"bruno@postle.net","created_at":"2026-08-01T10:07:45Z","created_by":"Bruno Postle","updated_at":"2026-08-02T08:20:13Z","dependency_count":0,"dependent_count":1,"comment_count":0} -{"_type":"issue","id":"homemaker-py-7xb","title":"Validate full winning construction stack generalises to health-centre","description":"The whole positive construction-quality stack (adjacency-aware + proportion-aware seeding, depth-balanced growth, leaf-sharing factor 3, interior-O odiv=3, share-aware edge cap) has only ever been measured end-to-end on harbor-house and maple-court (DESIGN.md §11-§13, cumulative -54%/-41% vs the leu.2 baseline per §13.7). examples/health-centre exists (built for homemaker-py-9yx, a non-synthetic ~20-room programme of a different building type -- primary care, not house/co-housing) but has only ever been used to NULL-test ruin_recreate; the positive stack itself has never been run there. Run the current default full stack (staged search, matching the §13.9/§13.10 default config) on health-centre at a comparable budget/seed count to harbor/maple's Phase-8 measurements, and report whether the fail-count reduction pattern (dominated by leaf-sharing, then depth-balance synergy, then interior-O) holds on a structurally different programme mix, or whether health-centre's room-type diversity (19 distinct codes, mostly single-instance, per §32) changes which lever dominates.","design":"Reference: DESIGN.md §13.3/§13.5/§13.6/§13.9 (the levers to validate), §32 (9yx, health-centre's construction and room-code tiering). No new code expected -- this is a measurement run with the existing default-on stack, comparable to the leu.1/§12.1 benchmark-establishment style.","status":"open","priority":3,"issue_type":"task","owner":"bruno@postle.net","created_at":"2026-08-01T10:07:29Z","created_by":"Bruno Postle","updated_at":"2026-08-01T10:07:29Z","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"homemaker-py-7xb","title":"Validate full winning construction stack generalises to health-centre","description":"The whole positive construction-quality stack (adjacency-aware + proportion-aware seeding, depth-balanced growth, leaf-sharing factor 3, interior-O odiv=3, share-aware edge cap) has only ever been measured end-to-end on harbor-house and maple-court (DESIGN.md §11-§13, cumulative -54%/-41% vs the leu.2 baseline per §13.7). examples/health-centre exists (built for homemaker-py-9yx, a non-synthetic ~20-room programme of a different building type -- primary care, not house/co-housing) but has only ever been used to NULL-test ruin_recreate; the positive stack itself has never been run there. Run the current default full stack (staged search, matching the §13.9/§13.10 default config) on health-centre at a comparable budget/seed count to harbor/maple's Phase-8 measurements, and report whether the fail-count reduction pattern (dominated by leaf-sharing, then depth-balance synergy, then interior-O) holds on a structurally different programme mix, or whether health-centre's room-type diversity (19 distinct codes, mostly single-instance, per §32) changes which lever dominates.","design":"Reference: DESIGN.md §13.3/§13.5/§13.6/§13.9 (the levers to validate), §32 (9yx, health-centre's construction and room-code tiering). No new code expected -- this is a measurement run with the existing default-on stack, comparable to the leu.1/§12.1 benchmark-establishment style.","status":"open","priority":3,"issue_type":"task","owner":"bruno@postle.net","created_at":"2026-08-01T10:07:29Z","created_by":"Bruno Postle","updated_at":"2026-08-01T10:07:29Z","dependencies":[{"issue_id":"homemaker-py-7xb","depends_on_id":"homemaker-py-7b7","type":"blocks","created_at":"2026-08-26T16:20:35Z","created_by":"Claude","metadata":"{}"}],"dependency_count":1,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"homemaker-py-fe2","title":"Experiment: 2-opt local-search polish inside collapse_insearch hot loop","description":"collapse_global's optional 2-opt adjacency polish (homemaker-py-9wi, §25) is proven positive and default-ON at finish-time (homemaker-py-cdl, §28: 46-file sweep, 0 regressions, 2 improvements incl. harbor evolved-anneal-3M 21-\u003e19). In-search collapse (collapse_insearch, homemaker-py-qpk/1ph, §20) is separately proven positive and default-ON (~11% mean fail reduction on both example programmes at N=15/20). But the two have never been combined: §28 explicitly left collapse_global's method-level local_search default OFF because 2-opt running inside the per-eval hot loop (thousands of calls per search) was 'untested and likely-costly, out of scope' for that issue -- only the one-shot finish-time cost (\u003c1s even on the largest file) was measured. This issue is the measurement: A/B collapse_insearch with local_search=True vs False (both already default-on baseline), on harbor-house and maple-court, staged search, matching the qpk/1ph protocol (equal budget, keep-better guard already monotone by construction). Report both the wall-clock cost multiplier and any fail-count effect; only recommend a default flip if positive and the cost is not prohibitive.","design":"Reference: DESIGN.md §20 (qpk), §25 (9wi), §28 (cdl) 'Where the default did NOT change' paragraph. Protocol: mirror experiments/run_qi6_ab.sh / run_lj3_qjg_ab.sh style equal-budget A/B, finish with standard --collapse, canonical homemaker-fitness re-score.","status":"open","priority":3,"issue_type":"task","owner":"bruno@postle.net","created_at":"2026-08-01T10:07:12Z","created_by":"Bruno Postle","updated_at":"2026-08-01T10:07:12Z","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"homemaker-py-9yx","title":"Non-synthetic third example programme to isolate ruin_recreate room-count threshold","description":"y51/xyu follow-up (option b, not run by xyu). The synthetic n=10/14/18/22 sweep scales room count by duplicating already-interchangeable programme-house room codes (count: on b1/t1/b2/t2) -- the same mechanism harbor-house itself uses 'to reduce complexity'. xyu extended n=18 to N=15 seeds (DESIGN.md 31): trend weakened but did not evaporate (9.3%-\u003e6.4%, two-sided Wilcoxon p 0.098-\u003e0.059), still ambiguous. A genuinely distinct third example programme with real room-type diversity at an intermediate room count (not a duplicated-code scale-up) would avoid the interchangeable-room confound and better isolate room count as the driving variable behind the wing-rebuild-fraction hypothesis from f1d (DESIGN.md 23).","notes":"RESOLVED (2026-07-30, DESIGN.md §32): built examples/health-centre, a 19-code/\nn=20 real health-centre programme (not duplicated-count). Wilcoxon N=15 vs\nxyu's own protocol: 8W/5L/2T, mean fails 46.13-\u003e45.13, delta=2.2%, two-sided\np=0.40, one-sided p=0.20 -- a clean null, weaker even than xyu's own\ninconclusive 6.4%/p=0.059 reading at the same room count. Converges with\nharbor-house's null-to-negative result rather than y51's synthetic sweep.\nConclusion: the y51/xyu signal was substantially an artifact of the\nduplicated-interchangeable-code mechanism, not a real room-count effect.\nenable_ruin_recreate stays OFF. No further follow-up filed.\n\nNote en route: first draft of health-centre's room sizes auto-derived into\none 19-code interchange class (9o5's transitive chain) -- fixed by tiering\nroom widths with \u003e1.3x gaps at 3 boundaries into 3 bounded classes. Worth\nremembering for any future non-synthetic programme design.","status":"closed","priority":3,"issue_type":"task","assignee":"Bruno Postle","owner":"bruno@postle.net","created_at":"2026-07-29T09:07:48Z","created_by":"Bruno Postle","updated_at":"2026-07-30T07:07:09Z","started_at":"2026-07-29T14:05:00Z","closed_at":"2026-07-30T07:07:09Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"homemaker-py-e01","title":"Larger-N harbor-house sweep for c94 beam-width mean improvement","description":"homemaker-py-c94 measured construction_beam_width=4 vs 1 (greedy) end-to-end\non harbor-house at N=5 seeds, budget 1500, n_workers=1: 2 wins / 1 loss / 2\nties, mean fails 56.8 (bw=1) -\u003e 55.4 (bw=4). That's the same small-N,\nmixed-direction shape this log has repeatedly warned produces false signal\n(the \"8sh/1ph/qi6/lj3 pattern\" flagged in DESIGN.md section 23, f1d)\n-- a genuine loss (seed 5) sits alongside the two wins, and N=5 is far\nshort of what f1d's own larger-N confirmation needed (N=15/8) to separate\na real effect from noise.\n\nFollow-up: extend the harbor-house-only comparison to N=15+ seeds at the\nsame protocol (construction_beam_width=4 vs 1, budget 1500, n_workers=1,\ndriver.search from init.dom) to determine whether the mean-improvement\nlean is a real effect or an artefact of seed 2's outlier (67-\u003e52 fails).\nprogramme-house showed zero effect at any width/N tested and does not\nneed re-checking. See DESIGN.md section 29 for full methodology and the\nraw-seed-vs-end-to-end correction this follow-up builds on.","status":"closed","priority":3,"issue_type":"task","assignee":"Bruno Postle","owner":"bruno@postle.net","created_at":"2026-07-28T09:44:38Z","created_by":"Bruno Postle","updated_at":"2026-07-28T23:20:46Z","started_at":"2026-07-28T16:43:56Z","closed_at":"2026-07-28T23:20:46Z","close_reason":"Confirmed null at N=15 (Wilcoxon p=0.84); mean improvement was seed 2's outlier — see DESIGN.md §30","dependencies":[{"issue_id":"homemaker-py-e01","depends_on_id":"homemaker-py-c94","type":"related","created_at":"2026-07-28T10:45:16Z","created_by":"Bruno Postle","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0} diff --git a/DESIGN.md b/DESIGN.md index 64d5b81..a7696b6 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -4968,6 +4968,14 @@ plot perimeter `private`**: | health-centre | 43 m | 41 m | feasible | §32 clean null | — | | programme-house | 24 m | 12 m | 2× surplus | — | **1 fail @ 12k evals** | +**PARTLY RETRACTED — see §39.11.** The "2.7× / 2.9× short" figures below are +computed for a **fully built plot**, which is not what these programmes ask +for. Against actual demand the deficits are far smaller and both are closable: +harbor needs 86 m against 54 m supplied (a 48 m² courtyard, with 304 m² of plot +spare), maple 70 m against 56 m (20 m²). Neither is infeasible. The one +programme that genuinely does not fit is health-centre, for an unrelated and +much simpler reason: it demands 240 m² on a 197 m² plot. + **Frontage deficit predicts the COST of solving, not impossibility.** An earlier draft of this section claimed the deficit predicts the plateau outright, quoting §13.11's 20k-budget figure as harbor's floor; that was @@ -5644,3 +5652,63 @@ was never a construction problem *or* an incentive problem (§39.8). Both flags (`repair_circulation`, `preserve_circulation`) stay default off with these numbers recorded. **Do not revisit either without a new formulation** — the same standing this document gives `bubble.py`. + +### 39.11 The frontage bound, computed correctly (`homemaker-py-tdp`) — shipped as a pre-flight check + +§38.3 derived a real constraint — every interior leaf needs +`L >= A/(1.6202·h)` of daylit wall — and then applied it to the wrong quantity. +It measured what a **fully built plot** would need. These programmes do not ask +for a fully built plot. + +Recomputed against the area each programme actually demands: + +| programme | demanded/storey | % of plot | frontage needed | supplied | gap | courtyard to close | spare plot | | +|---|---|---|---|---|---|---|---|---| +| harbor-house | 418 m² | 60% | 86 m | 53 m | +33 m | 49 m² | 277 m² | **OK** | +| maple-court | 338 m² | 44% | 70 m | 55 m | +15 m | 22 m² | 424 m² | **OK** | +| programme-house | 38 m² | 72% | 8 m | 22 m | −14 m | none | — | **OK** | +| health-centre | 240 m² | **131%** | 49 m | 41 m | +8 m | 12 m² | **−57 m²** | **DOES NOT FIT** | + +Plot area and frontage are measured through `geometry`, not from the raw +`init.dom` corners, so they carry the `wall_outer` inset and the plot rotation — +these are the metres and the square metres the leaves actually get. "Daylit" +means what `Fitness.area_outside` means by it: an external boundary counts +unless its perimeter type is `private` or `fortified`. + +So harbor-house and maple-court are **not** frontage-infeasible; they need a +courtyard of 49 m² and 22 m² respectively, against 277 m² and 424 m² of spare +plot. §38.3's "2.7× short" overstated it by comparing against a building nobody +asked for. + +**The one genuinely infeasible programme is health-centre, and not for daylight +reasons: it demands 240 m² of floor on a 183 m² plot.** That shows up +unmistakably in the geometry — every room comes out at **0.60×** its declared +target, 100% of them undersized, uniformly, no matter what the search does. +Contrast harbor-house and maple-court, where the seeder hits targets almost +exactly (median area / (target × share) = **1.01×**). + +*(An intermediate measurement suggested rooms were systematically inflated to +1.26–1.65× target. That was an artefact of not dividing by a shared leaf's +multiplicity — a leaf covering k rooms is legitimately k× a single target. +Corrected above; the seeder's sizing is accurate where the plot allows.)* + +**Shipped: `evolve._preflight`.** Both checks now run at startup and print a +warning before a multi-hour run bottoms out against something no amount of +searching can fix: + +``` +WARNING: programme demands 240 m2 per storey on a 183 m2 plot (131%). Every room +will be squeezed below its target however long the search runs. +WARNING: 418 m2 per storey needs ~86 m of daylit wall; the plot's non-private +perimeter gives 53 m. Roughly 49 m2 of courtyard closes the gap. +``` + +Advisory only — it never blocks a run, since an author may be deliberately +exploring an over-tight brief. Silent on programme-house. The same numbers are +available in full from `experiments/diag_exposure_frontage.py frontage`. + +**What this means for §38.** The frontage bound survives as a *diagnostic* and +is now correctly calibrated, but it does **not** say the corpus is +unsatisfiable. Of the four programmes, three fit their plots and one does not — +and that one fails a much cruder test than daylight. §38.3's claim that the +plateau programmes are "frontage-infeasible as specified" is withdrawn. diff --git a/experiments/diag_exposure_frontage.py b/experiments/diag_exposure_frontage.py index 5edcec7..d149c80 100644 --- a/experiments/diag_exposure_frontage.py +++ b/experiments/diag_exposure_frontage.py @@ -35,7 +35,6 @@ import math from pathlib import Path import numpy as np -import yaml from homemaker_layout import dom as dom_mod from homemaker_layout import driver, fitness, geometry @@ -176,6 +175,53 @@ def report_value(progdir: str, seed: int, limit: int) -> None: f"({verdict}), fails {len(base_fails)} -> {len(fails)}") +def frontage_budget(progdir: str) -> dict: + """Feasibility of a programme on its plot, before any search runs. + + Two independent checks, in the order they bite: + + 1. **Does the programme fit the plot at all?** ``demand / storeys`` against + the plot area. health-centre asks for 240 m² on a 183 m² plot — 131% — + and every room comes out at 0.60x its target no matter what the search + does. + 2. **Is there enough daylit wall for the area it does demand?** Every + interior leaf needs ``L >= A/(X*h)`` (§38.3), so a storey building + ``A_built`` needs ``A_built/(X*h)`` metres. The plot's non-``private`` + perimeter supplies some; interior courtyard supplies the rest, at roughly + ``2 * area / width`` metres per courtyard slot. + + NB this must be computed against the area the programme actually DEMANDS, + not a fully built plot — see §39.11 for the correction. + """ + root = dom_mod.load(f"{progdir}/init.dom") + per = root.perimeter or {} + height = root.height or 3.0 + # measured exactly as `Fitness.area_outside` does: an external boundary is + # daylit unless its perimeter type is `private` or `fortified`. Going + # through `geometry` rather than the raw YAML corners also picks up the + # `wall_outer` inset and the plot rotation, so these are the metres and the + # square metres the leaves actually get. + daylit = sum(geometry.edge_length(root, e) for e in range(4) + if (per.get(geometry.boundary_id(root, e)) or "").lower() + not in ("private", "fortified")) + plot = geometry.area(root) + reqs = programme.load_programme_dir(progdir) + storeys = max(programme.n_storeys_required(reqs), + programme.storey_minimum(progdir)) + demand = sum(r.size * r.count for r in reqs.values()) + built = demand / storeys + x_buried, _ = fail_bounds() + needed = built / (x_buried * height) + gap = needed - daylit + court = max(0.0, gap) * 3.0 / 2.0 # 3 m courtyard slots + spare = plot - built + return dict(plot=plot, daylit=daylit, height=height, storeys=storeys, + demand=demand, built=built, needed=needed, gap=gap, + court=court, spare=spare, + fits_plot=built <= plot, + frontage_ok=court <= spare) + + def report_frontage(progdirs: list[str]) -> None: x_buried, x_exposed = fail_bounds() print(f"crinkliness fails when 1/crink > {x_buried:.4f} (buried) " @@ -183,35 +229,23 @@ def report_frontage(progdirs: list[str]) -> None: print(f"=> every interior leaf needs exposed wall L >= A / ({x_buried:.4f} * h)\n") for progdir in progdirs: - seed = yaml.safe_load(open(f"{progdir}/init.dom")) - corners, per = seed["node"], (seed.get("perimeter") or {}) - height = seed.get("height") or 3.0 - n = len(corners) - edges = [math.hypot(corners[(i + 1) % n][0] - corners[i][0], - corners[(i + 1) % n][1] - corners[i][1]) for i in range(n)] - daylit = sum(e for k, e in zip("abcd", edges) - if (per.get(k) or "").lower() not in ("private", "fortified")) - area = abs(sum(corners[i][0] * corners[(i + 1) % n][1] - - corners[(i + 1) % n][0] * corners[i][1] - for i in range(n))) / 2 - reqs = programme.load_programme_dir(progdir) - n_storeys = max(programme.n_storeys_required(reqs), - programme.storey_minimum(progdir)) - demand = sum(r.size * r.count for r in reqs.values()) - needed = area / (x_buried * height) - + b = frontage_budget(progdir) print(f"=== {Path(progdir).name}") - print(f" plot {area:.0f} m2, perimeter {sum(edges):.0f} m, " - f"{n_storeys} storeys, h={height}") - print(f" perimeter {per} -> daylit frontage {daylit:.0f} m") - print(f" a fully built storey needs {needed:.0f} m exposed wall; " - f"plot supplies {daylit:.0f} m " - f"-> {needed / max(daylit, 1e-9):.1f}x short" - if needed > daylit else - f" a fully built storey needs {needed:.0f} m exposed wall; " - f"plot supplies {daylit:.0f} m -> FEASIBLE") - print(f" programme demands {demand:.0f} m2 over {n_storeys} storeys " - f"({demand / n_storeys:.0f} m2/storey of {area:.0f} m2 plot)\n") + print(f" plot {b['plot']:.0f} m2, daylit perimeter {b['daylit']:.0f} m, " + f"{b['storeys']} storeys, h={b['height']:g}") + pct = 100 * b["built"] / b["plot"] + verdict = "OK" if b["fits_plot"] else "DOES NOT FIT THE PLOT" + print(f" 1. programme demands {b['demand']:.0f} m2 -> {b['built']:.0f} m2 " + f"per storey = {pct:.0f}% of the plot [{verdict}]") + print(f" 2. that needs {b['needed']:.0f} m of daylit wall; perimeter gives " + f"{b['daylit']:.0f} m -> gap {b['gap']:+.0f} m") + if b["gap"] > 0: + print(f" closing it takes ~{b['court']:.0f} m2 of 3 m courtyard; " + f"spare plot {b['spare']:.0f} m2 " + f"[{'OK' if b['frontage_ok'] else 'NOT ENOUGH ROOM'}]") + else: + print(" perimeter alone is sufficient") + print() def main() -> None: diff --git a/src/homemaker_layout/evolve.py b/src/homemaker_layout/evolve.py index 9058777..80ea5b9 100644 --- a/src/homemaker_layout/evolve.py +++ b/src/homemaker_layout/evolve.py @@ -223,6 +223,60 @@ def _parse_args(argv=None) -> argparse.Namespace: return p.parse_args(argv) +def _preflight(programme_dir) -> None: + """Warn before the run if the programme cannot fit its plot (homemaker-py-tdp). + + Two checks, cheap and closed-form (DESIGN.md §38.3/§39.11). Neither can be + fixed by searching harder, so it is worth saying so up front rather than + letting a multi-hour run bottom out against it: + + 1. does the demanded floor area fit the plot at all; + 2. is there enough daylit wall for that area, given every interior leaf + needs ``L >= A/(1.6202*h)`` before it fails crinkliness. + + "Daylit" is measured exactly as ``Fitness.area_outside`` does: an external + boundary counts unless its perimeter type is ``private`` or ``fortified``. + + Advisory only — it never blocks a run, since an author may deliberately be + exploring an over-tight brief. + """ + from . import geometry + from . import programme as _prog + + try: + root = dom.load(f"{programme_dir}/init.dom") + per = root.perimeter or {} + daylit = sum(geometry.edge_length(root, e) for e in range(4) + if (per.get(geometry.boundary_id(root, e)) or "").lower() + not in ("private", "fortified")) + plot = geometry.area(root) + height = root.height or 3.0 + reqs = _prog.load_programme_dir(str(programme_dir)) + storeys = max(_prog.n_storeys_required(reqs), + _prog.storey_minimum(str(programme_dir))) + built = sum(r.size * r.count for r in reqs.values()) / max(storeys, 1) + except Exception: + return # advisory only; never block a run + + if not plot or not daylit: + return + + if built > plot: + print(f"WARNING: programme demands {built:.0f} m2 per storey on a " + f"{plot:.0f} m2 plot ({100 * built / plot:.0f}%). Every room will " + f"be squeezed below its target however long the search runs. " + f"(DESIGN.md §39.11)", file=sys.stderr) + needed = built / (1.6202 * height) + if needed > daylit: + court = (needed - daylit) * 1.5 + note = (f", but only {plot - built:.0f} m2 of plot is spare" + if court > plot - built else "") + print(f"WARNING: {built:.0f} m2 per storey needs ~{needed:.0f} m of daylit " + f"wall; the plot's non-private perimeter gives {daylit:.0f} m. " + f"Roughly {court:.0f} m2 of courtyard closes the gap{note}. " + f"(DESIGN.md §38.3)", file=sys.stderr) + + def main(argv=None) -> int: args = _parse_args(argv) @@ -246,6 +300,8 @@ def main(argv=None) -> int: else: out = args.output.resolve() + _preflight(programme_dir) + print(f"seed : {seed_file}", file=sys.stderr) print(f"programme : {programme_dir.name}", file=sys.stderr) print(f"budget : {args.budget}", file=sys.stderr)