Commit graph

39 commits

Author SHA1 Message Date
7ee51caf62 Parallelise outer search population evaluation (homemaker-py-5l6)
Add n_workers parameter to driver.search(). When n_workers > 1, a
ProcessPoolExecutor evaluates the bootstrap batch and main-loop children
in parallel, giving near-linear speedup with core count. The geometry
module-level cache is cleared in each worker after fork to prevent stale
id-keyed entries. Serial behaviour (n_workers=1, default) is unchanged.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-14 06:55:58 +01:00
304d514573 Add unit tests for geometry and fitness modules
26 tests for geometry (area, angles, aspect, boundary ids, centroid,
offset, etc.) and 35 tests for fitness (gaussian, config lookup,
quality terms, value rates, costs, stair helpers). Suite: 175 passed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-14 00:06:02 +01:00
fadb4f191f Close homemaker-py-hqw in beads 2026-06-13 23:40:00 +01:00
c37f03f1a1 Close homemaker-py-hqw: make project standalone (no Perl/Urb dependency)
Copy programme-house corpus (36 .dom + .score + .fails + patterns.config)
into examples/ and update all 5 test files to use project-relative paths.
Native Python fitness (use_native=True) was already the default; tests now
run without /home/bruno/src/urb present.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 23:39:20 +01:00
33d79be3fe Cold-start bootstrap: diverse initial population for blank-slate search
When the seed is an undivided bare plot (init.dom), auto-generate pop_size
random topologies before the memetic loop starts, each evaluated at
child_budget.  This crosses the zero-feasibility region that single-seed
chaining cannot escape — the programme-house cold start was stalling at 18
fails after 2000 evals vs urb-evolve's 6.

Auto-detection via seed_root.divided preserves the existing single-seed
path for warm starts from existing designs; all previous tests pass unchanged.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 23:29:12 +01:00
6b22e154f8 Close homemaker-py-8fe: Fix Urb programme width default (Perl oracle)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 23:17:07 +01:00
027904c452 Add issues: cold-start, NM inner loop, parallelism, tests, package install
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 22:53:04 +01:00
7f82c03f80 Fix programme width default: derive from sqrt(size/proportion) (homemaker-py-can)
When a programme space has no explicit 'width' key, the fallback to
width_inside [4.0, 1.0] is geometrically impossible for small spaces
(e.g. t3 WC at 3 m²). Now compute target = sqrt(size/proportion),
sigma = max(0.1, target * size_sigma / (2 * size_target)).

Effect on 35-file corpus: 32 files score +1–307% (width quality improves
for correctly-sized small rooms); 5 files lose spurious width fail lines.
Upstream Perl fix tracked as homemaker-py-8fe.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 22:23:03 +01:00
3940746270 Close homemaker-py-ccw: Phase 3 scaled search gate PASS
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 22:13:08 +01:00
7796e795a5 Phase 3 gate (homemaker-py-ccw): scaled search on native fitness
programme-house budget=20000: 1.04e-02 (2 fails), 1.36× over Phase-2
oracle run and 2.60× over urb-evolve p128. Winning topology found via
rotate at eval 10357, unreachable within Phase-2 budget. 71.8 evals/s
(~140× faster than batched oracle).

harbor-house (16 rooms): 3.73e-18 (49 fails) at budget 10000 in 633s.
This programme is beyond the oracle's capability; native fitness makes
it feasible. 638 topologies explored.

Adds experiments/run_search_scaled.py (native-only search runner, no
oracle dependency). DESIGN.md records Phase 3 gate result.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 22:10:38 +01:00
9fde9226ff bd: update sync.remote 2026-06-13 21:47:40 +01:00
69d4dcbf23 Phase 3 gate (homemaker-py-uxz): native fitness 35/35 corpus parity; retire oracle from search
Bug fix: _entrance_bid_for_stair now returns None when the stair leaf has an
outdoor neighbour with public access — Perl's Entrances function picks the
via-outdoor priority (3.5 > 3) which maps the stair to a leaf id rather than
a boundary id, so Boundary_Id(edge) eq leaf_id never matches and no entrance
corners are added.  Without this fix 7 files had an extra 'staircase volume'
failure from corners [3,1,2] giving stair_fit=0.718 instead of [3]→1.095.

New: Fitness._evaluate_full() extracts the shared pipeline so evaluate()
and score_with_fails() both use it.  NativeEvaluator added to innerloop.py
as a drop-in for OracleEvaluator; optimise() defaults to use_native=True.

Gate results: 35/35 score parity (rel_tol=1e-4), 35/35 fail-set identity,
native speed ~45ms/eval vs oracle ~1000ms/eval batched = 23x speedup.
OracleEvaluator kept for validation; oracle.score_batch unchanged.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 21:44:42 +01:00
fc10466966 Fix stair-fit parity: entrance corners + weighted has_circulation (homemaker-py-w1e/q70)
Two root causes found for ca/cb corpus parity failures:

1. _avg_path_len_from used unweighted BFS (hop count) but Perl's
   Graph::average_path_length uses weighted Dijkstra with centroid-to-centroid
   edge distances. This caused wrong edge removal in has_circulation, giving
   wrong stack corner counts (2 instead of 3 for lr in ca9e80c5).

2. Entrance corner logic used _public_access (any street boundary) but Perl's
   Entrances() picks the best entrance route — a stair only gets entrance corners
   if no higher-priority non-stair C leaf has public access.

Also includes homemaker-py-hgg storey/building checks previously uncommitted:
stair fit, circ connectivity, roof-garden, public-access tracking, has_circulation,
corners_in_use, stack_corners_in_use, check_space_counts with failure stacking.

All 4 debug corpus prefixes: ratio=1.000000. 39 tests pass.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 20:55:25 +01:00
8e762b80d8 Phase-2 gate results: 2/3 seeds → REVIEW; fix patterns.config re-score bug
benchmark_vs_urbevolve.py results (2026-06-13, budget=2000, URB_NO_OCCLUSION=1):
- Seeded designs: memetic beats urb-evolve 1.91× (c964435) and 1.63× (2f45907)
- Blank slate init.dom: memetic at 18 fails vs urb-evolve at 6 fails (topology
  diversity gap from single-seed mutation chain vs random-population init)

Bug fixed: run_search.py was calling oracle.score on out.parent without
patterns.config present — causing the re-score to return near-zero instead of
the correct tracked fitness. Added shutil.copy to propagate patterns.config
alongside the output .dom before the standalone re-score.

Gate recorded in DESIGN.md §7. Closes homemaker-py-way.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 09:56:01 +01:00
deb15c3082 Close homemaker-py-d0s: inner-loop optimiser bake-off complete
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 09:48:35 +01:00
bc61f8cb73 Bake-off: CMA-ES confirmed as inner-loop optimiser (homemaker-py-d0s)
4-way comparison (NM / CMA-ES / compass / compass-ms) over 3 corpus files ×
3 seeds at budget 200, cold-start, URB_NO_OCCLUSION=1. CMA-ES wins on
batch-efficiency (18 oracle calls vs 200 for NM, 12x speedup on Perl startup
amortisation per §4.6) with acceptable quality (x1.41 @200 vs NM's x1.56).
Compass stalls on narrow-valley landscapes and introduces fail regressions.
NM flagged as Phase 3+ candidate once native fitness removes oracle call overhead.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 09:47:15 +01:00
c01a8a0887 Native fitness: leaf quality terms + cost model (homemaker-py-gnw)
Port Urb's programme-driven fitness leaf quality factors (perpendicular,
proportion, size, width, crinkliness, daylight, access), value rates,
and cost model (per-leaf area costs, interior/exterior wall edge costs,
boundary costs) to Python.  Passes 0-mismatch parity against the Urb
oracle across all 35 corpus files (407 leaves, 2849 factors), using
URB_NO_OCCLUSION=1 simple crinkliness (illumination factor pinned to 1).

Key fixes: _dist must use math.sqrt not math.hypot (1-ULP difference
flips boundary overlap predicates); leaf-scope fail regex requires ^\d+/
prefix to exclude building-level failure messages.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 07:59:21 +01:00
3bf507a483 Fix benchmark cell arg order and mutate_swap on undivided trees
run_urbevolve took (seed, budget, pop, cell) but cells call
fn(seed, budget, cell, **kw) — every urb-evolve cell died on TypeError,
deferred silently by pool.map. mutate_swap lacked the empty-candidates
noop guard the other operators have, crashing on init.dom-style bare
plots. Regression test: every mutation survives an undivided tree.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 23:26:22 +01:00
e2b3e20070 Phase-2 gate benchmark: memetic loop vs urb-evolve at equal eval budgets
experiments/benchmark_vs_urbevolve.py (homemaker-py-way): 3 seed designs
(init from scratch, c964435 weak, 2f45907 strong) x 2000-eval runs, the
1000-eval tier read from each run's best-so-far log; urb-evolve gets two
population sizes (default 128 = ~16 generations at this budget, and 16 =
~130) and credit for its better one. Counts via the MAX_EVALS counter
patch in urb-evolve.pl (committed in the urb repo); both systems under
URB_NO_OCCLUSION=1; all finals re-scored through urb-fitness.pl as the
common deterministic yardstick.

run_search.py generalised to (budget, rng-seed, seed.dom, out.dom);
innerloop.optimise now handles 0-DOF topologies (an undivided plot like
init.dom scores once instead of crashing CMA).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 22:22:16 +01:00
6459949e41 Close homemaker-py-b39: memetic driver acceptance passed
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 22:10:06 +01:00
9e020f4769 Native fitness: adjacency graph + merge_divided (homemaker-py-3y7)
Two bugs fixed in boundary_id / leaf_graph:
1. 'bid not in "abcd"' used Python substring check, silently dropping the
   root-division boundary (empty-string id).  Fixed to frozenset membership.
2. Upper-storey nodes store their own rotation in the YAML but Urb::Quad::Rotation
   delegates to Below->Rotation.  boundary_id now walks the below-chain to the
   ground-floor rotation, matching Perl exactly.

After fixes all 35 corpus files produce edge counts matching Perl oracle.

Added:
- src/homemaker/graph.py: build_graphs (two-phase pattern), has_adjacency,
  has_vertical_connection (faithful no-overlap stub per DESIGN §8.1),
  find_missing_spaces, check_adjacency, check_level_constraints,
  check_vertical_connectivity
- src/homemaker/dom.py: @dataclass(eq=False) on Node for NetworkX hashability;
  is_outside, is_supported, is_unsupported, merge_divided
- tests/test_graph.py: 7 tests, edge counts vs Perl oracle on all 35 files,
  exact widths for 2f45907, merge_divided smoke, two-phase independence

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-12 21:57:45 +01:00
f160c6dc9e Use Urb's canonical UPPERCASE generic types (C/O); case-insensitive class checks
Bruno's correction: 'C' was never a 'covered' type — Is_Covered is a
geometric predicate. Urb generics are canonically uppercase (get_space_types
qw/C O S/; corpus 100% uppercase). The driver/operator type pool emitted
lowercase 'c'/'o', creating mixed-case designs that fragmented Dom->Ratios
class buckets and fired the latent ratio_type first-match nondeterminism
(which the search promptly reward-hacked). Operators now emit uppercase
generics only and class checks match case-insensitively (t[0].lower() in
'cos', cf. Is_Circulation/Is_Outside). The Urb-side class-sum patch remains
as defensive hardening, zero-impact on canonical designs (35/35 parity).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 19:01:53 +01:00
0beb005a23 Memetic search driver: steady-state GA over topology, warm-started inner loop
driver.py (homemaker-py-b39): tournament selection, operators.mutate (storey
ops down-weighted) + area-matched crossover, every child's geometry
delegated to the warm-started inner loop (Lamarckian write-back; children
use a single local CMA phase - the exploratory ladder phase exists for cold
projections children never face). Budget stated and accounted in oracle
evaluations; near-duplicate fitness guard against population collapse
(neutral mutations are common, per 8cs).

free_with_keys/ratio_map/warm_x0 promoted from the 8cs experiment into
innerloop.py as the Lamarckian inheritance API; alignment with
solver.free_branches asserted across the corpus.

tests/test_driver.py fakes the inner loop: budget accounting, monotone
improvement history, warm-start + sigma plumbing, valid .dom output.
31 tests pass. experiments/run_search.py is the end-to-end acceptance run.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 14:22:26 +01:00
4ed809a47c Update beads issue state (nyb closed, b39 unblocked)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 14:08:44 +01:00
92cc63348e Topology operators: 7 mutations + area-matched subtree crossover
operators.py (homemaker-py-nyb): divide/undivide/retype/swap/rotate/
level_add/level_delete + Urb-style area-matched base-storey crossover.
Operators edit the decoded Node tree; genome.encode absorbs all repair
(dangling deltas, storey misalignment) so every child is a valid genome
by construction. Geometry moves deliberately absent — the inner loop owns
continuous DOF, and 8cs made Lamarckian re-optimisation mandatory.

Fixes dom._link to CLEAR stale below-links when a path vanishes from the
storey below (undividing a base branch left upper nodes pointing at
orphaned quads; oracle scoring unaffected but in-process geometry crashed).

Acceptance (experiments/operator_locality.py, flag-on): 115/115 children
scored without error; geometry perturbation small for core ops (retype
0.07, divide/undivide 0.14, swap/crossover 0.16-0.17), fitness
perturbation large for all (0.68-0.99 rel) — the 0.5^n cliff flags most
raw moves, confirming warm-started re-optimisation + penalty reshaping
as the load-bearing design choices. 27 tests pass.

Closes homemaker-py-nyb.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 14:07:35 +01:00
13f73be771 Topology genome: base tree + per-storey deltas + type assignment
genome.py (homemaker-py-k2g): Genome = base-floor GNode tree + per-storey
StoreyDelta (undivides, divide subtrees, leaf retypes, height) + base
metadata. encode/decode round-trips dom.py Node trees.

Key empirical finding baked into the design: upper-storey nodes carry
heavily drifted DEAD fields (97 inherited-cut divisions, 187 rotations
differ from the owning node below across the corpus) — dead because
geometry delegates to below before reading them. decode canonicalises
them; encode stores only owned state, so genomes from drifted sources
compare equal (fixed-point test).

Acceptance: 35/35 corpus files fitness-identical after round-trip through
the oracle (experiments/genome_parity.py, URB_NO_OCCLUSION=1); owned-cut
projection + genome fixed-point + storey counts in tests/test_genome.py
(16 tests pass).

Closes homemaker-py-k2g.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 13:52:32 +01:00
5f0c159112 Re-baseline under URB_NO_OCCLUSION: new reference gains, DESIGN §4.7
Corpus: all 35 scores shift (x1.0-1.24, daylight pinned), one expected
failure-set change (458aa8b8 +2 crinkliness), oracle ~8% faster batched.
New deterministic-seed reference gains become the accept_innerloop bars:
x1.63 / x1.70 / x1.68 at budget 400, ~35 oracle calls per topology.
urb-evolve respects the flag by construction (in-process fitness reads
ENV at call time). Old flag-off numbers kept in DESIGN as historical.

Closes homemaker-py-gp2 (Urb-side patch lives in /home/bruno/src/urb,
uncommitted there pending review).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 10:31:38 +01:00
1cc86c8a7e Warm-vs-cold experiment: topology mutations + Lamarckian ratio inheritance
experiments/warm_vs_cold.py (homemaker-py-8cs): top-storey divide/undivide
mutations on corpus designs, path-keyed inheritance of the parent's
optimised ratios (surviving cuts keep values, new cuts 0.5), per-evaluation
convergence traces; reports oracle evals to 95% of best final, warm vs
cold. Machinery validated oracle-free (cut survival counts) and one
mutated child scored through the oracle.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 10:00:51 +01:00
511c86c4dc Fix cma convergence test: assert basin convergence, not final-digit polish
The sigma-ladder default splits the budget into restart phases, so a
400-eval run reaches ~0.996 on the smooth test objective rather than
0.999+. Test now matches the component's contract. (Previous commit
landed with this failing because piping pytest to tail masked its exit
code.)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 09:46:13 +01:00
0dcdf1f29f Geometry inner loop: batched full-objective ratio optimiser (CMA-ES)
innerloop.py: optimise(root, programme_dir, x0=None, budget, method) ->
Result, optimising equal-offset free-branch ratios (midpoint projection of
legacy unequal cuts) against full oracle fitness. OracleEvaluator scores
each population in one batched perl call. Methods: cma (default) — multi-
start sigma ladder (0.05 local, 0.15 exploratory) with IPOP-style popsize
doubling and deterministic seeding (pycma treats seed 0 as clock!) — and
compass with Hooke-Jeeves pattern moves, kept for the d0s bake-off.

Acceptance (experiments/accept_innerloop.py, §4.5 bars vs unprojected
originals, within-noise tolerance 1%): x1.65 / x1.66 / x1.58 against bars
x1.24 / x1.67 / x1.59, no new failures, 46 oracle calls vs Nelder-Mead's
200. The two near-bar results are statistically indistinguishable from the
single-NM-draw bars (measured draw spread brackets them); decision approved
by Bruno 2026-06-12.

Also: tests/ scaffold (12 oracle-free unit tests, pytest pythonpath=src),
rebaseline_no_occlusion.py for homemaker-py-gp2, cma>=3.0 dependency
(installed via dnf), dead-variable cleanup in solver.py.

Closes homemaker-py-1p0.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 09:42:24 +01:00
d4266f46dc Descope occlusion/daylight: disable in Urb, port simple crinkliness only
Strategy decision (Bruno): occlusion is orthogonal to building a scalable
optimiser. Instead of porting Sun/Occlusion/CIESky, disable it in Urb
behind an env flag (daylight -> 1, illumination factor -> 1 so crinkliness
is unweighted external wall area / floor area). Python occlusion rebuild
deferred until optimisation is fully native.

Tracker: new homemaker-py-gp2 (flag + re-baseline) gates gnw/way/uxz;
homemaker-py-2g5 re-scoped to the post-Phase-5 Python rebuild (P4).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 08:31:20 +01:00
2ef2b15fe3 Batched oracle: score many .dom files per perl invocation
oracle.score_batch() writes/cleans N outputs and runs urb-fitness.pl once
with all file names; oracle.score() is now a thin wrapper. Adds
Score.fail_lines (sorted) because Perl hash-order randomisation shuffles
.fails line order between runs, and documents Urb's ~1-ULP score
nondeterminism (compare with rel tolerance, never ==).

experiments/bench_batch_oracle.py validates batch-vs-single parity on the
35-file corpus and benchmarks: 0.98 s/dom batched vs 1.27 s/dom single
(x1.30), all files identical (fitness to 1e-12 rel, exact failure sets).

Closes homemaker-py-av5.
2026-06-12 01:13:55 +01:00
16f1704bdc Add beads issues for DESIGN.md phases 1-5 with dependency graph 2026-06-12 00:40:28 +01:00
8efe25601f Update DESIGN.md with findings from source review of Urb
- §4.5: slide() re-randomises cuts (no fine-tuning operator exists) —
  strengthens the geometry-headroom explanation
- §4.6: throughput arithmetic shows native fitness effectively gates
  topology search at scale; oracle suffices for Phase 1 + small Phase 2
- §5: new decision 6 — Lamarckian warm-start of the inner loop; penalty
  reshaping must preserve inner-loop cliff protection
- §6: native-fitness port scope expanded (occlusion/daylight subsystem,
  cost denominator, structural failures, missing-space failure stacking,
  two-phase graph build, has_vertical_connection stub)
- §7: Phase 2 re-scoped as small-scale proof with budgets in oracle
  evaluations; Phase 1 gains warm-vs-cold + optimiser bake-off experiments
- §8: risks updated (reshaping tension, height DOF, confirmed t3 bug)
2026-06-12 00:34:51 +01:00
40b97ac74c bd init: initialize beads issue tracking 2026-06-11 23:27:11 +01:00
7fccc05c0d Add comprehensive DESIGN.md capturing this session's findings
Self-contained design + plan structured for breaking into bd (beads) tasks:
domain constraints that fix the slicing representation; what was built;
full empirical record (geometry port validated 35/35; area-proxy solver
falsified; perpendicular artifact resolved via equal-offset cuts; full-fitness
frozen-topology optimisation validated with 24-67% headroom; 0.5^n cliff);
validated memetic architecture; component plan; phased roadmap; risks/open
questions; repro steps; gotchas.

Oracle throughput measured: ~0.99s/dom batched vs 1.65s single (assessment-
dominated). urb-fitness.pl batches many doms per call, so native fitness is a
later speed/scale optimisation, not a gate; favour population/batch optimisers
and prototype the search on the batched oracle first.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 22:45:44 +01:00
d08d15e4d7 Full-fitness frozen-topology optimisation validates geometry inner loop
Driving equal-offset cut ratios with Nelder-Mead against the REAL oracle
fitness (full objective, no proxy) improves all three test candidates with
zero new failures:

  2f45907 (best evolved)  0.012617 -> 0.015684  x1.24  (2->2 fails)
  candidate-002          0.007375 -> 0.012319  x1.67  (2->2 fails)
  c964435 (baseline)     0.003667 -> 0.005836  x1.59  (3->3 fails)

Headroom widens on weaker designs. The EA under-optimises geometry by
24-67% even on its best result. This validates a full-fitness geometry
inner loop (NOT the earlier area-proxy solver) and motivates a memetic
architecture: topology search outside, full-objective geometry optimise
inside, gated on a native Python fitness (oracle at ~3s/call is too slow).
2026-06-10 22:27:30 +01:00
497d05c343 Add programme/solver/oracle + sizing experiments (negative result)
Adds the bottom-up ratio solver, programme parser, Perl-oracle bridge,
and two experiments. Headline finding: the "isolated size solver on a
frozen topology" hypothesis is NOT validated.

- resolve_ratios.py: re-solving candidate-002 from programme targets
  recovers areas accurately but scores below the original (introduces
  width/perpendicular/crinkliness failures the area objective ignores).
- refine_sweep.py: warm-start refine of all 34 evolved candidates
  regresses 34/34 (fails 124->297 perpendicular-tied; 124->626 area-only
  with free skew). Moving cuts to fix room area breaks the coupled
  adjacency/access/shape constraints those designs balanced.

Conclusion: sizing is not separable from the rest of Urb's fitness;
a geometry inner loop must optimise the full objective, not an area proxy.
Geometry port remains validated byte-identical to Urb.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 21:49:31 +01:00
0366392da4 Scaffold homemaker-py with validated geometry port
Clean-room Python successor to Urb for programme-driven layout search.
This initial commit establishes the .dom bridge format and a faithful
port of Urb's top-down quad geometry, validated byte-identical against
Urb across all 35 programme-house example files (including the wall
inset and multi-storey wall-stacking inheritance).

- dom.py: .dom YAML <-> Node tree, parent/below/position linkage,
  wall_outer inset on load, raw-corner stash for round-tripping
- geometry.py: Coordinate/Coordinate_a/_b/Area/Length + Coordinate_Offset
- experiments/dump_areas.{py,pl}: geometry regression harness
2026-06-10 20:50:20 +01:00