Fix mutate_level_add: use generic C/O floor instead of room duplicate

Previously level_add copied the top storey exactly, duplicating all
named programme rooms and immediately triggering space-count failures
for every room on the new floor. The lex outer-search comparison
(-n_fails, score) then always rejected the multi-storey child because
its fail count was far higher than the single-storey parent.

Fix: retype all named-room leaves on the new storey to generic C or O
before admitting the child. The outer search then retypes them
incrementally via the normal retype operator. This allows level_add to
produce designs with the same fail count as the parent (storey_minimum
fail removed, no duplication fails added), making the multi-storey
transition visible to the lex selector.

Result on programme-house cold start (init.dom, 100k evals, 4 workers):
  before: 6 fails, single-storey, stuck after 40k evals
  after:  4 fails, two-storey, still improving at 100k

Also adds examples/harbor-house/ from urb/examples for future runs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Bruno Postle 2026-06-14 10:33:05 +01:00
parent 3c8f7aba07
commit 517a825505
4 changed files with 434 additions and 0 deletions

View file

@ -0,0 +1,148 @@
node:
- - 0.0
- 0.0
- - 25.0
- 2.0
- - 23.0
- 31.0
- - 0.0
- 31.0
perimeter:
a: private
b: private
c: null
d: null
rotation: 0
division:
- 0.5206890368316089
- 0.5206890368316089
height: 3.0
elevation: 0.0
wall_inner: 0.08
wall_outer: 0.25
l:
rotation: 1
division:
- 0.4900271770215713
- 0.4900271770215713
l:
rotation: 2
division:
- 0.28955830710300473
- 0.28955830710300473
l:
rotation: 1
division:
- 0.5900577549923657
- 0.5900577549923657
l:
rotation: 0
division:
- 0.5042565018475718
- 0.5042565018475718
l:
type: st2
rotation: 0
r:
type: st1
rotation: 0
r:
rotation: 1
division:
- 0.5042565018475718
- 0.5042565018475718
l:
type: O
rotation: 0
r:
type: ut1
rotation: 0
r:
rotation: 2
division:
- 0.5254855092815014
- 0.5254855092815014
l:
rotation: 1
division:
- 0.7453088885860744
- 0.7453088885860744
l:
rotation: 2
division:
- 0.5622884610865619
- 0.5622884610865619
l:
type: k1
rotation: 0
r:
rotation: 3
division:
- 0.545507016217933
- 0.545507016217933
l:
type: ws1
rotation: 0
r:
type: cr1
rotation: 0
r:
type: O
rotation: 0
r:
type: da1
rotation: 0
r:
rotation: 2
division:
- 0.5017908930114854
- 0.5017908930114854
l:
type: r
rotation: 0
r:
type: li1
rotation: 0
r:
rotation: 0
division:
- 0.4006976137653344
- 0.4006976137653344
l:
rotation: 3
division:
- 0.48703066466032097
- 0.48703066466032097
l:
rotation: 2
division:
- 0.5562346554544078
- 0.5562346554544078
l:
type: m
rotation: 0
r:
rotation: 2
division:
- 0.5042565018475718
- 0.5042565018475718
l:
type: me1
rotation: 0
r:
type: C
rotation: 0
r:
rotation: 2
division:
- 0.6464404401824229
- 0.6464404401824229
l:
type: O
rotation: 0
r:
type: t
rotation: 0
r:
type: r
rotation: 3

View file

@ -0,0 +1,25 @@
---
node:
-
- 0.0
- 0.0
-
- 25.0
- 2.0
-
- 23.0
- 31.0
-
- 0.0
- 31.0
perimeter:
a: private
b: private
c: ~
d: ~
type: O
rotation: 0
height: 3
elevation: 0.0
wall_inner: 0.08
wall_outer: 0.25

View file

@ -0,0 +1,253 @@
---
# Harbor House - Programme-driven building evolution (Simplified)
# Two-story community living facility for 50+ residents
# Using anonymous interchangeable rooms to reduce complexity
# Building programme: community living spaces
spaces:
# GROUND FLOOR - Main Community Areas
cr1:
name: Common Room with Fireplace
size:
- 80.0
- 10.0
width:
- 6.0
- 1.5
proportion:
- 2.0
- 0.5
level: 0
adjacency:
- c
- o
ef1:
name: Entrance Foyer
size:
- 15.0
- 3.0
width:
- 3.0
- 0.5
level: 0
adjacency:
- c
da1:
name: Dining Area
size:
- 60.0
- 8.0
width:
- 5.0
- 1.0
proportion:
- 2.5
- 0.8
level: 0
adjacency:
- c
- k1
- o
k1:
name: Kitchen
size:
- 30.0
- 5.0
width:
- 4.0
- 1.0
level: 0
adjacency:
- da1
- c
# SLEEPING AREAS - Anonymous neighborhoods (5 total, 2 ground + 3 first)
n:
name: Neighborhood
size:
- 60.0
- 8.0
width:
- 5.0
- 1.0
proportion:
- 2.0
- 0.6
adjacency:
- c
count: 5
# GROUND FLOOR - Activity Spaces
ws1:
name: Workshop Space
size:
- 40.0
- 6.0
width:
- 5.0
- 1.0
proportion:
- 1.8
- 0.5
level: 0
adjacency:
- c
- o
# MEETING ROOMS - Anonymous interchangeable (3 total)
m:
name: Meeting Room
size:
- 10.0
- 2.0
width:
- 2.5
- 0.5
level: 0
adjacency:
- c
count: 3
# GROUND FLOOR - Service Areas
# BATHROOMS - Ground floor (3 total)
t:
name: Bathroom
size:
- 6.0
- 1.5
width:
- 2.0
- 0.5
adjacency:
- c
count: 6 # 3 ground + 3 first floor
la1:
name: Laundry Room
size:
- 20.0
- 4.0
width:
- 3.0
- 0.8
level: 0
adjacency:
- c
st1:
name: Ground Floor Storage
size:
- 22.0
- 4.0
width:
- 3.0
- 0.8
level: 0
adjacency:
- c
me1:
name: Mechanical/Electrical Room
size:
- 25.0
- 4.0
width:
- 3.5
- 0.8
level: 0
adjacency:
- c
# STAFF OFFICES - Anonymous (2 total)
of:
name: Staff Office
size:
- 12.5
- 2.5
width:
- 2.5
- 0.5
level: 0
adjacency:
- c
count: 2
# FIRST FLOOR - Community Areas
li1:
name: Library Corner
size:
- 20.0
- 4.0
width:
- 3.5
- 0.8
level: 1
adjacency:
- c
# FIRST FLOOR - Individual Rooms (10 anonymous rooms)
r:
name: Individual Room
size:
- 10.0
- 2.0
width:
- 2.5
- 0.5
level: 1
adjacency:
- c
count: 10
# FIRST FLOOR - Storage
st2:
name: First Floor Storage
size:
- 18.0
- 3.5
width:
- 3.0
- 0.8
level: 1
adjacency:
- c
ut1:
name: Utilities Closet
size:
- 14.0
- 3.0
width:
- 2.5
- 0.6
level: 1
adjacency:
- c
# Building constraints
storey_minimum: 2
storey_limit: 3
force_roof_garden: 0
# Circulation ratio (allow more circulation for larger building)
ratio_circulation:
- 0.08
- 0.15
# Outside space ratio (courtyard requirement)
ratio_outside:
- 0.15
- 0.10
# Staircase requirements (main + secondary)
staircase_min: 2
staircase_max: 2
# Economic parameters
value_inside: 300.0
value_circulation: 50.0
value_outside: 100.0
value_supported: 300.0

View file

@ -130,6 +130,14 @@ def mutate_level_add(root: dom.Node, rng: np.random.Generator,
top = dom.levels(child)[-1]
dup = _g._copy_storey(top)
dup.height = top.height
# Retype all named-room leaves to generic C/O so the new storey carries no
# duplicated programme rooms. The outer search retypes them incrementally.
generic = [t for t in types if t.upper() in ("C", "O")]
if not generic:
generic = ["C"]
for leaf in dup.leaves():
if leaf.type not in ("C", "O", None):
leaf.type = str(rng.choice(generic))
top.above = dup
return _finalise(child), f"level_add ({len(dom.levels(child))} storeys)"