diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl index 160717a..d7ae1f6 100644 --- a/.beads/issues.jsonl +++ b/.beads/issues.jsonl @@ -1,6 +1,6 @@ {"_type":"issue","id":"homemaker-py-ju3","title":"Programme codes share a namespace with the generic c/o/s type prefixes: 14% of harbor-house is silently optional and cr1's declared targets are all discarded","description":"Urb's type system is prefix-based (a type starting with 'c' is circulation, 'o'/'s' is outside) and programme room codes live in the SAME namespace, so any code whose name happens to start with c, o or s is silently reinterpreted as a generic type. Three separate consequences, none announced anywhere in the output:\\n\\n1. graph.check_space_counts line ~530 does 'if code[0].lower() in (\"c\",\"o\",\"s\"): continue' -- the code is SKIPPED ENTIRELY. Never required, never counted, no missing fail, no too-many fail.\\n2. Fitness.get_space_params returns the generic *_circulation / *_outside params BEFORE consulting self.spaces, so declared size/width/proportion are overridden.\\n3. dom.is_circulation / is_outside become true, changing the leaf's value rate, exempting it from crinkliness, and making it supply daylight to neighbours.\\n\\nharbor-house is affected; maple-court, health-centre and programme-house are namespace-clean.\\n\\n cr1 'Common Room with Fireplace' (c): size 80.0 -\u003e 0.0/14.0, width 6.0 -\u003e 2.4, proportion 2.0 -\u003e 1.5, ALL THREE overridden; is_circulation=True so value_rate 50 not 300.\\n of 'Staff Office' x2 (o): width/proportion overridden; is_outside=True; value_rate 100.\\n st1/st2 'Storage' (s): width/proportion overridden; is_outside AND is_circulation True; value_rate 100.\\n\\n5 of 37 room instances (14%) are silently optional. MEASURED CONSEQUENCE: in a 20k-eval run the two cr1 leaves converged to 32.9 and 17.1 m2 against a declared 80 m2, and produced no too-many-spaces fail despite count:1; of/st1/st2 are absent from the result entirely with zero fails, because nothing ever asked for them. Compounds with homemaker-py-2v1: cr1 is the single largest room in the programme and is classified circulation, so the x6 value gap pays the search to shrink it.","design":"Separate the namespaces. Cleanest is an explicit per-space 'class:' key in patterns.config (inside/circulation/outside) defaulting to inside, with the prefix rule used ONLY for untyped generic leaves the search creates -- programme codes then never collide regardless of spelling. A cheaper stopgap is a load-time validation error in programme.load_programme_dir that refuses a programme code starting with c/o/s, which at least converts a silent misread into a loud one. Renaming harbor's four codes would fix that one programme but leaves the trap armed for the next author.","acceptance_criteria":"A programme declaring a code starting with c/o/s either honours its declared params and count, or fails loudly at load. harbor-house re-baselined against its real 37-instance programme, and every DESIGN.md harbor fail count re-stated or annotated as measured against the 32-instance effective programme.","status":"closed","priority":0,"issue_type":"bug","assignee":"Claude","owner":"noreply@anthropic.com","created_at":"2026-08-26T08:26:37Z","created_by":"Claude","updated_at":"2026-08-26T09:05:11Z","started_at":"2026-08-26T08:55:30Z","closed_at":"2026-08-26T09:05:11Z","close_reason":"Shipped as loud validation + harbor rename rather than the class: key (DESIGN.md §39.3). The class: key was deliberately not built: auditing the prefix rule showed l/k/b/t carry adjacency semantics too, so re-plumbing the type system would invalidate the whole corpus for a problem whose damage is the silence, not the convention. programme.validate_codes raises on c/o/s codes from both parse paths; harbor cr1/of/st1/st2 renamed to fr1/ao/gs1/gs2 (neutral unused prefixes, prefix-sharing preserved); migrate_ju3_rename.py migrates pre-rename .dom files. Re-baselined at seed 1/20k: 57 fails on the 32-instance effective programme -\u003e 55 on the real 37-instance one, with all five previously-lost room instances now placed inside their declared sigma bands and zero fails naming them.","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"homemaker-py-2v1","title":"Connectivity is under-priced ~3x against the circulation-\u003ehabitable value gap: the objective is net-positive on severing the spine","description":"Sharper root cause than homemaker-py-ssz, isolated by the 38.6 A/B (which showed none of the three crinkliness modes removes the deletion incentive). Deleting a circulation leaf merges it into its sibling, converting corridor into habitable area. value_circulation=50 vs value_inside=300, so that is a flat 6x value gain. The only counter-pressure is the 'level N not connected' fail, worth 0.5x under value *= 0.5**len(failures). Break-even needs 0.5^k \u003c 50/300, i.e. k \u003e 2.58 -- severing must cost AT LEAST 3 fails to be net-negative, and it costs 1. Net incentive to sever = 6 * 0.5 = 3.0x in favour; measured 4.06x on a well-lit (q_crink=0.736) circulation leaf, so this is NOT the zero-exposure effect and is not fixable inside quality_uncrinkliness. This is the cleanest explanation of why 'level 0 not connected' and 'level 1 not connected' are still present in evolved-3M-nols-3, the best layout found after 1.7M evals: the search is being paid 3-4x to create them.","design":"Options: (a) emit connectivity fails with a multiplicity \u003e= 3 (cheapest, but stacks with the 1i8 cascade-weighting problem and is a magic number); (b) make the connectivity penalty multiplicative and explicit rather than riding the generic 0.5^n (a dedicated building_factor term, sized from the value-rate gap so it tracks value_circulation/value_inside instead of being hard-coded); (c) revalue circulation as infrastructure -- its worth is that it makes other rooms reachable, which the current per-leaf value rate cannot express; the principled version credits circulation with the access it provides rather than its own floor area. (c) is the architecturally correct one and the biggest change. Recommend measuring (a) first purely to confirm the mechanism (does the 3x threshold flip the deletion test?), then designing (b) or (c) properly.","acceptance_criteria":"Deletion test (experiments/diag_exposure_frontage.py value + the ssz A/B harness) shows lit and buried C/O deletions are no longer rewarded; then harbor-house reaches the 15-fail floor in materially fewer than 1.7M evals AND without 'level 0/1 not connected' in the result.","status":"open","priority":0,"issue_type":"bug","owner":"noreply@anthropic.com","created_at":"2026-08-26T07:37:39Z","created_by":"Claude","updated_at":"2026-08-26T07:37:39Z","dependency_count":0,"dependent_count":2,"comment_count":0} -{"_type":"issue","id":"homemaker-py-sel","title":"Usage prefixes (b/t/l/k) are a second implicit namespace: 4 corpus rooms get another room's connectivity rules from their spelling","description":"§39.4 separated programme codes from the GENERIC structural types (C/O/S). It did not touch the other namespace sharing the first character: the USAGE prefixes b=bedroom, t=toilet, l=living, k=kitchen. These classify programme codes by first letter and are still prefix-based BY DESIGN (it is how Urb encodes room usage, and unlike the generic rule they never discard a requirement) -- but they are not inert.\\n\\ngraph.has_circulation deletes graph edges from them: a 'bedroom' loses its edges to living/kitchen/bedroom/toilet, a 'toilet' loses its edges to outside/living/kitchen/toilet, and b/t keep their LEAST popular circulation neighbour while l/k keep their MOST popular. fitness.access and the public-access check read them too. So a code that picks one up by accident is silently given another room's connectivity rules -- and connectivity is exactly where §38 located the residual.\\n\\nFour corpus rooms are misclassified by spelling alone:\\n la1 'Laundry Room' -\u003e living (harbor-house, harbor-house-l0, maple-court)\\n li1 'Library Corner' -\u003e living (harbor-house, maple-court)\\n br1 'Staff Room' -\u003e bedroom (health-centre)\\n tr1 'Treatment Room' -\u003e toilet (health-centre)\\n\\nMeasured on a constructed health-centre seed: tr1 (as a toilet) has its edge to the adjacent outside space O stripped from the circulation graph; br1 (as a bedroom) has its edge to t10 'Staff WC' stripped. Both feed has_circulation and therefore the 'N inaccessible usable space' / 'level N not connected' fails.\\n\\nReport it with: python experiments/audit_programme_config.py (usage-prefix section).","design":"REVISED after review (the 'parallel key vs mutating leaf name' objection).\n\nTWO DISTINCT PROBLEMS, only one of which the key creates:\n\n1. Mutation staleness — DISSOLVES if usage is CODE-level, not leaf-level. mutate_retype sets leaf.type='b1'; usage is usage_of('b1'), looked up fresh from patterns.config on every read, exactly as size/width/proportion/adjacency/level/count already are. Verified: every leaf type in the corpus resolves to a declared code or a generic, so the lookup never dangles. The broken design is usage STAMPED ON THE LEAF: there are 51 sites assigning leaf.type, and this project has been bitten by that class twice already — 'share' needed a companion 'share_type' so a retype invalidates it, and even with that guard homemaker-py-r5a was a real bug where a stale stamp got RESURRECTED by a relabel restoring the old code. Never stamp usage on a leaf.\n\n2. Name/key divergence — REAL, and not fixed by (1). If b1 declares usage:living, the name says bedroom and the key says living: two sources of truth for one fact, the exact bug class §39 removed.\n\nRESOLUTION (supersedes the earlier per-code-override design): make usage MANDATORY on every declared space code, with NO prefix fallback. The prefix rule is retired as a semantic mechanism; room codes become free-form identifiers. A missing usage is a load error naming the code (loud, like validate_codes), not a silent 'none'. Closed vocabulary {bedroom, toilet, living, kitchen, none} — exactly what the engine reads (graph.has_circulation edge stripping, fitness.access, public-access) — so an unknown value errors rather than silently no-opping. Generic C/O/S are unaffected: they are never declared in spaces:, and their behaviour comes from the generic rule.\n\nMEASURED MIGRATION COST: 10 corpus files, 107 code entries. 100 of 107 (93%) are pure annotation reproducing current behaviour. Declarations would be none=60, toilet=23, bedroom=12, living=8, kitchen=4. Only 7 entries change behaviour (la1 x3, li1 x2, br1, tr1) and those ARE the fix. write_stage1_programme already does dict(spec) so derived programmes carry usage through.\n\nOPEN — ARCHITECTURAL, NOT MECHANICAL: what the four flagged rooms should actually declare. la1 'Laundry Room' (currently living) and tr1 'Treatment Room' (currently toilet) look plainly wrong. But li1 'Library Corner' as 'living' is arguably CORRECT — a reading corner is a sitting space — and br1 'Staff Room' is a break room, so 'living' may fit better than 'none'. These are the programme author's calls; the audit tool's 'name disagrees' flag is a prompt, not a verdict.\n\nCOST TO OUT-OF-REPO PROGRAMMES: mandatory means any config outside this repo fails to load until annotated. Mitigate with an error listing each missing code and its prefix-implied usage so migration is copy-paste, plus a one-shot migration script.\n\nStill needs its own A/B and re-baseline (the 7 changed entries move harbor/maple/health-centre).","acceptance_criteria":"A programme can declare a room's usage explicitly; the prefix rule applies only where nothing is declared; audit_programme_config reports no unintended usage classification across the corpus; re-baselined with an A/B.","notes":"SCOPE CORRECTION (2026-08-26). My earlier '93% pure annotation, only 7 entries change' estimate was misleading. It was mechanically true — declaring the prefix-implied usage reproduces current behaviour — but it missed that 'none' is not neutral: nothing is stripped, so has_circulation MAY ROUTE THROUGH the room.\n\n34 of 52 distinct corpus codes currently get no usage class, i.e. are modelled as permeable circulation routes. That list includes Dental Surgery, GP Consulting Room, Minor Surgery, Physiotherapy, Counselling, Therapy Room, Quiet Room, Records Room, Sterilisation Room, General Storage, Utilities Closet, Mechanical/Electrical, six storage rooms, four offices, and 'Individual Room' (a resident's own room). By Bruno's own criterion — 'it isn't going to be used as circulation' — most should be terminal.\n\nCONSEQUENCE FOR §38: the model currently OVERSTATES connectivity, because has_circulation can connect a level through rooms that are dead ends in reality. So 'level N not connected' fails are rarer than they should be and layouts score better than they deserve. This compounds §38.2 (the objective already pays x60-85 to delete circulation): the corridors that do get deleted are then not missed, because consulting rooms and store cupboards stand in for them. Worth re-reading the §38 residual after this lands.\n\nProposed assignment for all 52 codes is in experiments/usage_map_proposal.yaml (47 assigned, 5 unresolved, none missing): living 10, kitchen 2, bedroom 15, toilet 7, utility 10, none 3. Awaiting review before any code changes.\n\nBruno's rulings so far: li1 Library Corner and br1 Staff Room -\u003e living (same access requirements); tr1 Treatment Room -\u003e bedroom (terminal, not circulation); la1 Laundry Room -\u003e utility (not a circulation route). Ground-floor-external utility cases (fuel stores, bins) need no class — level: 0 plus adjacency: [o] already covers it.\n\nVERIFIED ASIDE (Q2 in the proposal): a dedicated toilet off a bedroom-class room does not survive scoring. has_circulation runs its bedroom loop before its toilet loop and the bedroom loop removes bedroom-\u003etoilet edges, so the ensuite is severed — for real bedrooms too. Existing Urb behaviour; neither caused nor fixed by the usage key.","status":"open","priority":1,"issue_type":"bug","owner":"noreply@anthropic.com","created_at":"2026-08-26T10:06:54Z","created_by":"Claude","updated_at":"2026-08-26T10:47:11Z","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"homemaker-py-sel","title":"Usage prefixes (b/t/l/k) are a second implicit namespace: 4 corpus rooms get another room's connectivity rules from their spelling","description":"§39.4 separated programme codes from the GENERIC structural types (C/O/S). It did not touch the other namespace sharing the first character: the USAGE prefixes b=bedroom, t=toilet, l=living, k=kitchen. These classify programme codes by first letter and are still prefix-based BY DESIGN (it is how Urb encodes room usage, and unlike the generic rule they never discard a requirement) -- but they are not inert.\\n\\ngraph.has_circulation deletes graph edges from them: a 'bedroom' loses its edges to living/kitchen/bedroom/toilet, a 'toilet' loses its edges to outside/living/kitchen/toilet, and b/t keep their LEAST popular circulation neighbour while l/k keep their MOST popular. fitness.access and the public-access check read them too. So a code that picks one up by accident is silently given another room's connectivity rules -- and connectivity is exactly where §38 located the residual.\\n\\nFour corpus rooms are misclassified by spelling alone:\\n la1 'Laundry Room' -\u003e living (harbor-house, harbor-house-l0, maple-court)\\n li1 'Library Corner' -\u003e living (harbor-house, maple-court)\\n br1 'Staff Room' -\u003e bedroom (health-centre)\\n tr1 'Treatment Room' -\u003e toilet (health-centre)\\n\\nMeasured on a constructed health-centre seed: tr1 (as a toilet) has its edge to the adjacent outside space O stripped from the circulation graph; br1 (as a bedroom) has its edge to t10 'Staff WC' stripped. Both feed has_circulation and therefore the 'N inaccessible usable space' / 'level N not connected' fails.\\n\\nReport it with: python experiments/audit_programme_config.py (usage-prefix section).","design":"REVISED after review (the 'parallel key vs mutating leaf name' objection).\n\nTWO DISTINCT PROBLEMS, only one of which the key creates:\n\n1. Mutation staleness — DISSOLVES if usage is CODE-level, not leaf-level. mutate_retype sets leaf.type='b1'; usage is usage_of('b1'), looked up fresh from patterns.config on every read, exactly as size/width/proportion/adjacency/level/count already are. Verified: every leaf type in the corpus resolves to a declared code or a generic, so the lookup never dangles. The broken design is usage STAMPED ON THE LEAF: there are 51 sites assigning leaf.type, and this project has been bitten by that class twice already — 'share' needed a companion 'share_type' so a retype invalidates it, and even with that guard homemaker-py-r5a was a real bug where a stale stamp got RESURRECTED by a relabel restoring the old code. Never stamp usage on a leaf.\n\n2. Name/key divergence — REAL, and not fixed by (1). If b1 declares usage:living, the name says bedroom and the key says living: two sources of truth for one fact, the exact bug class §39 removed.\n\nRESOLUTION (supersedes the earlier per-code-override design): make usage MANDATORY on every declared space code, with NO prefix fallback. The prefix rule is retired as a semantic mechanism; room codes become free-form identifiers. A missing usage is a load error naming the code (loud, like validate_codes), not a silent 'none'. Closed vocabulary {bedroom, toilet, living, kitchen, none} — exactly what the engine reads (graph.has_circulation edge stripping, fitness.access, public-access) — so an unknown value errors rather than silently no-opping. Generic C/O/S are unaffected: they are never declared in spaces:, and their behaviour comes from the generic rule.\n\nMEASURED MIGRATION COST: 10 corpus files, 107 code entries. 100 of 107 (93%) are pure annotation reproducing current behaviour. Declarations would be none=60, toilet=23, bedroom=12, living=8, kitchen=4. Only 7 entries change behaviour (la1 x3, li1 x2, br1, tr1) and those ARE the fix. write_stage1_programme already does dict(spec) so derived programmes carry usage through.\n\nOPEN — ARCHITECTURAL, NOT MECHANICAL: what the four flagged rooms should actually declare. la1 'Laundry Room' (currently living) and tr1 'Treatment Room' (currently toilet) look plainly wrong. But li1 'Library Corner' as 'living' is arguably CORRECT — a reading corner is a sitting space — and br1 'Staff Room' is a break room, so 'living' may fit better than 'none'. These are the programme author's calls; the audit tool's 'name disagrees' flag is a prompt, not a verdict.\n\nCOST TO OUT-OF-REPO PROGRAMMES: mandatory means any config outside this repo fails to load until annotated. Mitigate with an error listing each missing code and its prefix-implied usage so migration is copy-paste, plus a one-shot migration script.\n\nStill needs its own A/B and re-baseline (the 7 changed entries move harbor/maple/health-centre).","acceptance_criteria":"A programme can declare a room's usage explicitly; the prefix rule applies only where nothing is declared; audit_programme_config reports no unintended usage classification across the corpus; re-baselined with an A/B.","notes":"ALL 52 CORPUS CODES NOW ASSIGNED (experiments/usage_map_proposal.yaml): living 12, bedroom 17, utility 11, toilet 7, kitchen 2, none 3. Zero unresolved.\n\nRulings received: li1 Library Corner + br1 Staff Room -\u003e living (same access requirements); tr1 Treatment Room -\u003e bedroom; la1 Laundry -\u003e utility; zt1 Sterilisation -\u003e utility; ph1 Pharmacy -\u003e bedroom (private room, not public, like an office); n 'Neighborhood' -\u003e bedroom (a communal sleeping area, much like a dormitory); gh1 Greenhouse -\u003e living (grows plants but a living area like a conservatory); ws1 Workshop -\u003e living CONDITIONALLY (building-specific: communal craft room vs maintenance area; both corpus programmes carrying it are communal living facilities).\n\nDESIGN DECISION 1 — utility is a DISTINCT value, not an alias for bedroom. Same edge-stripping today (identical access requirements) but separately declared, because (a) they are very different uses and the config should read truthfully, and (b) it gives programme.derive_interchange_classes (§13/§26, homemaker-py-9o5) a natural axis: bedroom-class and utility-class leaves could be interchangeable DURING search and collapse to their real use at scoring time — exactly the superposition relaxation that machinery already implements. Worth building the usage key with that hook in mind.\n\nDESIGN DECISION 2 — vocabulary controlled but NOT closed. The earlier 'closed enum, unknown value errors' recommendation is withdrawn ('programmes are building specific, so it would be nice to have a controlled vocabulary, but not entirely necessary'). Proposed: the six known names drive engine behaviour; a programme may coin its own (usage: craft) provided it binds it in the same file via usage_classes: {craft: living}. An unbound name is a load error, so naming stays free without ever letting an unrecognised usage fall through to a silent default.\n\nVERIFIED — Brand adjacency point is NOT an engine bug. fitness._evaluate_full runs check_adjacency against graph_base_pre, the UNFILTERED graph, so a declared adjacency requirement is satisfied by a neighbouring room regardless of what has_circulation strips. The stripping only denies ROUTING, which is correct. So 'a toilet adjacent to a bedroom is a positive even if there is no door' is already expressible -- but it is only scored where DECLARED, and 3 of 4 real programmes never declare it: only programme-house has t1 -\u003e adjacency [b1]. harbor-house (t vs r/n), maple-court (t/tt vs r/n) and health-centre (t9/t10 vs tr1) do not. Filed separately as homemaker-py-adj.","status":"open","priority":1,"issue_type":"bug","owner":"noreply@anthropic.com","created_at":"2026-08-26T10:06:54Z","created_by":"Claude","updated_at":"2026-08-26T12:33:38Z","dependency_count":0,"dependent_count":0,"comment_count":0} {"_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} @@ -34,6 +34,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-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} {"_type":"issue","id":"homemaker-py-1i8","title":"Missing-space fail cascade weights rooms by patterns.config verbosity, not by design intent","description":"graph.check_space_counts emits, per missing room instance: 2 base fails ('missing required space: X' + '(critical)') plus one 'would need \u003ccheck\u003e' placeholder for each of size/width/proportion the programme HAPPENS to declare (has_size/has_width/has_proportion are literally 'size' in c etc. from the YAML). So a missing room costs 3 to 5 fails depending only on how many optional keys the author typed. Under value *= 0.5**len(failures) that is a 4x difference in fitness weight between two single rooms. Concretely in programme-house: missing b1 (declares size+width+proportion) = 5 fails = 1/32 penalty; missing t2 (declares size only) = 3 fails = 1/8 penalty. Same for harbor-house: n and cr1 cost 5 each, r and t cost 4 each. The tiered comparator inherits this -- n_hard is dominated by these cascades, so the primary search key is weighted by YAML verbosity.","design":"Either emit exactly one fail per missing instance (and let the cascade placeholders be informational, not counted), or normalise the cascade to a fixed count per instance independent of declared keys. Note this changes every historical fail-count baseline in DESIGN.md, so it needs its own A/B and a recorded re-baseline.","status":"open","priority":2,"issue_type":"bug","owner":"noreply@anthropic.com","created_at":"2026-08-25T23:15:00Z","created_by":"Claude","updated_at":"2026-08-25T23:15:00Z","dependency_count":0,"dependent_count":0,"comment_count":0} diff --git a/experiments/usage_map_proposal.yaml b/experiments/usage_map_proposal.yaml index 2324121..6a8060f 100644 --- a/experiments/usage_map_proposal.yaml +++ b/experiments/usage_map_proposal.yaml @@ -1,11 +1,11 @@ -# Proposed `usage:` assignments for every corpus room code (homemaker-py-sel). +# `usage:` assignments for every corpus room code (homemaker-py-sel). # # FOR REVIEW — nothing reads this yet. Once agreed it becomes the input to the # migration that writes `usage:` into each `patterns.config`. # -# Usage is an ACCESS-REQUIREMENT class, not a room-name category (Bruno's -# framing: "library corner and staff room are living rooms as they have the same -# access requirements"). What each class actually does, in `graph.has_circulation`: +# Usage is an ACCESS-REQUIREMENT class, not a room-name category (Bruno: +# "library corner and staff room are living rooms as they have the same access +# requirements"). What each class does today, in `graph.has_circulation`: # # living keeps its MOST popular circulation neighbour; not stripped from # other rooms, so living spaces may chain to one another. @@ -14,15 +14,15 @@ # reachable only from circulation or outside — a terminal private # room, never a route. # toilet edges to outside/living/kitchen/toilet removed. -# utility NEW. Terminal, not a route — the same requirement as `bedroom`. -# See OPEN QUESTION 1 below. +# utility NEW. Same access requirements as `bedroom` today (terminal, never a +# route) but a DISTINCT declared use — see DESIGN NOTE 1. # none nothing is stripped: the room is fully permeable and the # connectivity graph MAY ROUTE THROUGH IT. Correct only for spaces # that genuinely are part of the circulation system. # -# The `none` semantics are the reason this is a bigger change than it looks: -# 34 of 52 corpus codes currently get no class, so a Dental Surgery, a Records -# Room and a Utilities Closet can all serve as corridors today. +# `none` is why this is a bigger change than it looks: 34 of 52 corpus codes +# currently have no class, so a Dental Surgery, a Records Room and a Utilities +# Closet can all serve as corridors today. living: cr1: Common Room with Fireplace @@ -30,11 +30,13 @@ living: en1: TV / Media Room gy1: Exercise Room l1: Living/Dining/Kitchen - li1: Library Corner # confirmed: same access requirements as a living room + li1: Library Corner # ruled: same access requirements as a living room lo1: Lounge lr1: Common Living Room - br1: Staff Room # confirmed: same access requirements as a living room + br1: Staff Room # ruled: same access requirements as a living room m: Meeting Room + gh1: Greenhouse # ruled: grows plants, but a living area like a conservatory + ws1: Workshop Space # ruled CONDITIONALLY — see DESIGN NOTE 3 kitchen: k1: Kitchen @@ -44,7 +46,8 @@ bedroom: # terminal private room, entered from circulat b1: Master Bedroom b2: Second Bedroom r: Individual Room - tr1: Treatment Room # confirmed: access requirements of a bedroom + n: Neighborhood # ruled: a communal sleeping area, much like a dormitory + tr1: Treatment Room # ruled: access requirements of a bedroom de1: Dental Surgery gp1: GP Consulting Room ms1: Minor Surgery Room @@ -56,6 +59,7 @@ bedroom: # terminal private room, entered from circulat ao1: Admin Office em: Staff Office of: Staff Office + ph1: Pharmacy / Dispensary # ruled: a private room, not public, like an office toilet: t: Bathroom @@ -67,7 +71,7 @@ toilet: tt: Upper Bathroom utility: # terminal, never a circulation route - la1: Laundry Room # confirmed: utility, not a circulation route + la1: Laundry Room # ruled: utility, not a circulation route ut1: Utilities Closet me1: Mechanical/Electrical Room # "Plant / Mechanical Room" in health-centre dp1: General Storage @@ -77,46 +81,78 @@ utility: # terminal, never a circulation route ur2: First Floor Storage ur3: Second Floor Storage re1: Records Room + zt1: Sterilisation Room # ruled: utility — a room for sterilising tools and machinery none: # genuinely part of the circulation system ef1: Entrance Foyer - rc1: Reception # "Reception Office" in maple-court — see Q3 + rc1: Reception # "Reception Office" in maple-court — see DESIGN NOTE 4 wa1: Waiting Room # --------------------------------------------------------------------------- # -# UNRESOLVED — please rule on these -# --------------------------------------------------------------------------- # -unresolved: - ws1: Workshop Space # living (shared activity) or utility (terminal)? - zt1: Sterilisation Room # utility, or bedroom-class off a surgery? - ph1: Pharmacy / Dispensary # health-centre; bedroom-class? and see Q3 - n: Neighborhood # unclear what this models at all - gh1: Greenhouse # utility, or outside-ish? - -# --------------------------------------------------------------------------- # -# OPEN QUESTIONS +# DESIGN NOTES # --------------------------------------------------------------------------- # # -# Q1 Does `utility` need behaviour distinct from `bedroom`? Both are "terminal, -# not a route", and the engine has no term today that would tell them apart. -# Declaring it as a distinct name that maps onto bedroom's edge-stripping -# costs nothing and makes the config read truthfully, leaving somewhere to -# hang a real difference later. The alternative is to drop it and write -# `bedroom`, which reads badly for a bin store. +# 1. `utility` IS a distinct value, not an alias for `bedroom`. +# Ruled: "a utility may have similar requirements to a bedroom, and it may be +# useful during evolution superposition to treat them as interchangeable +# before wave function collapse, but they are very different uses." +# So it maps onto bedroom's edge-stripping today (identical access +# requirements) while staying separately declared, which gives two things: +# the config reads truthfully for a bin store, and `usage` becomes a natural +# axis for `programme.derive_interchange_classes` (§13/§26, homemaker-py-9o5) +# — bedroom-class and utility-class leaves could be declared interchangeable +# DURING search and collapse to their real use at scoring time, which is +# exactly the superposition relaxation that machinery already implements. # -# The "ground floor external access" cases (fuel stores, bins) do NOT need a -# class: `level: 0` plus `adjacency: [o]` already expresses that, and it is -# per-room programme data rather than an access class. +# 2. Adjacency value survives the connectivity stripping — VERIFIED, no bug. +# Ruled: "a toilet adjacent to a bedroom or treatment room is a positive even +# if there is no door, see How Buildings Learn by Stewart Brand, at some point +# there may be a need to knock it through." +# `fitness._evaluate_full` runs `check_adjacency` against `graph_base_pre`, +# the UNFILTERED graph, so a declared `adjacency:` requirement is satisfied by +# a neighbouring room regardless of what `has_circulation` later strips. The +# stripping only denies ROUTING (you cannot walk through a bedroom to reach +# the loo), which is correct. Brand's adaptability positive is therefore +# already expressible. # -# Q2 A dedicated toilet off a bedroom-class room does NOT survive scoring. -# Verified: `has_circulation` runs its bedroom loop before its toilet loop, -# and the bedroom loop removes bedroom->toilet edges, so the ensuite is -# severed — for real bedrooms too, not just for treatment rooms. This is -# existing Urb behaviour that the usage key neither causes nor fixes; filed -# separately if it should change. +# BUT it is only ever scored where a programme DECLARES it, and three of the +# four real programmes do not: +# programme-house t1 -> adjacency: [b1] declared +# harbor-house t (Bathroom, count 6) vs r / n (sleeping) NOT declared +# maple-court t, tt vs r / n NOT declared +# health-centre t9 / t10 vs tr1 (Treatment Room) NOT declared +# Adding those declarations is programme authoring, independent of this bead +# (filed as homemaker-py-3qj), and would let the search earn the adaptability credit it +# currently cannot. # -# Q3 Three codes mean different rooms in different programmes: `ph1` -# (Pharmacy/Dispensary in health-centre, Clinic Room in maple-court), `me1` -# (Mechanical/Electrical vs Plant/Mechanical), `rc1` (Reception vs Reception -# Office). Usage is declared per `patterns.config`, so they can differ — but -# it is worth confirming that is intended rather than a copy-paste drift. +# 3. `ws1` Workshop Space is building-specific. +# Ruled: "could be a communal craft room, a living space, or it could be a +# maintenance area, a utility room, it depends on the building." +# Both corpus programmes carrying it (harbor-house, maple-court) are communal +# living facilities, so `living` is the reading here — but this is exactly the +# case that argues for per-programme declaration rather than a global map. +# Flip it to `utility` in either file if the intent was a maintenance shop. +# +# 4. Three codes mean different rooms in different programmes: `ph1` +# (Pharmacy/Dispensary in health-centre, Clinic Room in maple-court), `me1` +# (Mechanical/Electrical vs Plant/Mechanical), `rc1` (Reception vs Reception +# Office). Usage is declared per `patterns.config`, so they may legitimately +# differ — worth confirming it is intended rather than copy-paste drift. +# NB the assignments above are listed per CODE for review convenience; the +# migration writes them per FILE, so a code can differ between programmes. +# +# 5. Vocabulary: controlled but not closed. +# Ruled: "programmes are building specific, so it would be nice to have a +# controlled vocabulary, but not entirely necessary." +# So the earlier "closed enum, unknown value is an error" recommendation is +# withdrawn. Proposed instead: the six names above are the known vocabulary +# and drive engine behaviour directly; a programme may coin its own name +# (`usage: craft`) provided it binds it to a known class in the same file: +# +# usage_classes: +# craft: living +# bin_store: utility +# +# An unbound name is a load error. That keeps naming free and building- +# specific while never letting an unrecognised usage fall through to a silent +# default — the failure mode §39 exists to remove.