Gutter priced and wrecked: GUTTER_COLLATERAL=90 baked with reasoning; torn-gutter house wreck, carport pattern

The fascia anchors have said collateral:"gutter" since Sprint 6 and nobody
priced one — collateralFor('gutter') returns null, backyard_01's house is a
free failure. Proposal rides in the asset like the carport's did: 90, between
the gnome (25) and the carport (180), with the band reasoning next to the
constant. A rules the number.

house_yardside_wrecked_v1: same origin/footprint/facade (shared _house_facade
helper — intact house BIN chunk byte-identical to the committed GLB, only the
three new price extras differ), fascia torn through the anchored span, left
run hanging by its last bracket, right run kinked ON the grass, downpipe out
of plumb, offcuts flat on the lawn. No fascia_anchor_* empties survive — you
cannot re-tie to a ripped eave.

collateral_key is new and explicit: unlike the carport, the structure and the
thing you take have different names, so the key names which collateral string
the value prices.

All 33 GLBs byte-identical across two full factory runs; the house pair also
byte-identical between --only and full runs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
type-two 2026-07-17 20:21:24 +10:00
parent 8decac0626
commit 4d1a2d7aa9
5 changed files with 184 additions and 43 deletions

View File

@ -133,6 +133,31 @@
"status": "PASS",
"problems": []
},
{
"name": "house_yardside_wrecked",
"dims": [
9.4552,
2.1021,
2.9172
],
"tris": 340,
"nodes": [
"debris_fascia",
"door",
"downpipe_loose",
"fascia_torn",
"gutter_down",
"gutter_torn",
"house_yardside_wrecked",
"roof",
"wall",
"window",
"window_glow",
"window_light_anchor"
],
"status": "PASS",
"problems": []
},
{
"name": "carport_01",
"dims": [
@ -468,42 +493,5 @@
"problems": []
}
],
"debris": [
{
"file": "BlueCrate_v2.glb",
"dims": [
0.36,
0.36,
0.29
],
"sane": true
},
{
"file": "BlackTub_v2.glb",
"dims": [
0.36,
0.54,
0.2
],
"sane": true
},
{
"file": "WhiteTub_v2.glb",
"dims": [
0.36,
0.54,
0.2
],
"sane": true
},
{
"file": "WoodenBin_v2.glb",
"dims": [
0.35,
0.36,
0.31
],
"sane": true
}
]
"debris": []
}

View File

@ -684,10 +684,16 @@ def build_gate(name):
return root
def build_house_yardside(name):
"""Rear façade only — no interior. The fascia is the point: DESIGN.md says
it holds until the first real gust, then leaves with the gutter."""
root = add_empty(name)
def _house_facade(name, root):
"""The rear façade shared VERBATIM by the intact house and its torn-gutter
wreck wall ring, door, window, night glow, roof. Split out in Sprint 12
because a house does not fall down when a sail rips its eave line off (the
carport folds; brick veneer shrugs), so the two variants have to be the SAME
building or the aftermath swap reads as the house moving. One set of numbers,
two eave lines.
Returns the measurements and materials the eave line is built from, because
the eave is exactly the part that differs between the two callers."""
wall_m = get_material("Mat_Render", PAL["render_wall"], 0.9)
brick_m = get_material("Mat_Brick", PAL["brick"], 0.9)
trim = get_material("Mat_Timber", PAL["timber_dark"], 0.8)
@ -754,12 +760,47 @@ def build_house_yardside(name):
root, size=0.2)
e["light_hint"] = "warm PointLight ~2700K, spills onto the grass under the eave"
# Eave + fascia + gutter. The eave overhangs 0.55 into the yard (-Y).
# The roof and its eave. The eave overhangs 0.55 into the yard (-Y).
eave_y = -0.55
fascia_z = H + 0.10
join_group([add_box(f"{name}_roof", (W + 0.2, D + 0.75, 0.10),
(0, (eave_y + D / 2) / 2, H + 0.05), roof_m,
rot=(math.radians(-6), 0, 0))], "roof", root)
return dict(W=W, H=H, D=D, eave_y=eave_y, fascia_z=fascia_z,
trim=trim, gutter_m=gutter_m)
# What it costs to take the client's gutter — and the fascia it hangs off —
# with you. Same deal as CARPORT_COLLATERAL below, second time around: Lane A
# owns this number, it's a design call, not an art one. But the fascia anchors
# have said collateral:"gutter" since Sprint 6 and NOBODY has ever priced one —
# collateralFor('gutter') returns null, so backyard_01's house is a free
# failure: tie 25 m² to a 0.35 fascia, rip it off, pay for a shackle. My
# proposal and the reasoning, to argue with rather than adopt:
# · the band is established now: gnome 25 at the floor, carport 180, my
# stated ceiling ~250. The gutter goes between the ornament and the
# structure, because that is what it is;
# · a night's shop budget is 80 and the wild night's fee is ~76 (feeFor at a
# 32 m/s peak). At 90 the gutter costs slightly more than EITHER — the
# night it fires on is wiped, kit and fee together. That is the fascia's
# lesson in one number: the "free" anchors were the dearest kit in the yard;
# · under ~50 it's a shrug — less than the fee alone, so the trap couldn't
# even wipe its own night;
# · over ~130 the backyard outbids half a carport for a smaller lie: the
# fascia (0.35) is honestly better steel than the beam (0.22), it's three
# anchors not four, and it's the tutorial yard — the first trap a player
# can hit should cost them a night, not the week.
GUTTER_COLLATERAL = 90
def build_house_yardside(name):
"""Rear façade only — no interior. The fascia is the point: DESIGN.md says
it holds until the first real gust, then leaves with the gutter."""
root = add_empty(name)
f = _house_facade(name, root)
W, H, eave_y, fascia_z = f["W"], f["H"], f["eave_y"], f["fascia_z"]
trim, gutter_m = f["trim"], f["gutter_m"]
fascia = add_box("fascia", (W + 0.2, 0.035, 0.20), (0, eave_y, fascia_z),
trim, parent=root)
gutter = join_group([
@ -780,6 +821,109 @@ def build_house_yardside(name):
e["collateral"] = "gutter"
stamp(root, name, "structure")
root["facade_width"] = W
# The proposal rides in the asset, exactly like the carport's did (and A
# adopted that one into site JSON, where the canonical number now lives —
# sites are data; this is the fallback and the argument). collateral_key is
# NEW and explicit because the carport never needed it: there, the priced
# thing and the structure share a name. Here the structure is "house" and
# the thing you take is the GUTTER — the key names which collateral string
# this value prices, so Lane A's wiring never has to guess.
root["collateral_key"] = "gutter"
root["collateral_value"] = GUTTER_COLLATERAL
root["collateral_label"] = "the gutter"
return root
def build_house_yardside_wrecked(name):
"""The house after the fascia let go (SPRINT12 §gate 3.3) — DESIGN.md's line
made geometry: "holds until the first real gust, then rips off taking the
gutter with it".
Same origin, same footprint, same façade _house_facade, shared verbatim
because a house does not fall down when a sail takes its eave line; only
what the anchors betrayed changes. The fascia is torn through the span the
fixings held, and the gutter it carried is in two states at once: the left
run still hangs off the surviving board by its last bracket, torn end sagged
toward the grass, and the right run is ON the grass out in the yard, kinked
where it landed, with the downpipe leaning out of plumb after it. A gutter
doesn't shatter — it unzips, springs, and lands where the wind was going.
NO fascia_anchor_* empties survive into this file. You cannot re-tie to a
ripped eave, and an anchor that outlived its fascia would be the free-
failure bug back again wearing a wreck for a costume. e.test.js pins that,
and that the gutter is DOWN here while its intact twin's is UP at the eave.
"""
rng = rng_for(name)
root = add_empty(name)
f = _house_facade(name, root)
W, eave_y, fascia_z = f["W"], f["eave_y"], f["fascia_z"]
trim, gutter_m = f["trim"], f["gutter_m"]
# The fascia, minus what the sail took: a 3.4 m board survives on the left,
# a stub clings to the far right corner, and both torn ends splinter past
# the break. The gap in between is where fascia_anchor_02 used to be.
fascia = [
add_box(f"{name}_fascia_left", (3.4, 0.035, 0.20),
(-2.9, eave_y, fascia_z), trim),
add_box(f"{name}_fascia_stub", (0.6, 0.035, 0.20),
(4.3, eave_y, fascia_z), trim),
]
for i, (bx, ang) in enumerate(((-1.05, 14), (3.85, -18))):
fascia.append(add_box(f"{name}_fascia_splinter_{i}", (0.38, 0.028, 0.09),
(bx, eave_y, fascia_z + 0.02), trim,
rot=(0, math.radians(ang + rng.uniform(-3, 3)), 0)))
join_group(fascia, "fascia_torn", root)
# The left run: still bracketed at the healthy end, torn free at the break,
# hanging diagonally with the freed end sprung out over the grass.
join_group([add_tube_between(
f"{name}_gutter_hang",
(-4.4, eave_y - 0.05, fascia_z - 0.12),
(-0.55, eave_y - 0.34, 1.28), 0.055, gutter_m, verts=8)],
"gutter_torn", root)
# The right run went where the wind was going: out over the yard, down on
# the grass, kinked where it hit. Flat ON the grass — the carport wreck's
# first-pass lesson (a corner sunk to z=-0.41) is why nothing here sinks.
kx = 2.4 + rng.uniform(-0.3, 0.3)
join_group([
add_tube_between(f"{name}_gutter_ground_a",
(0.35, -1.35, 0.055), (kx, -1.72, 0.055),
0.055, gutter_m, verts=8),
add_tube_between(f"{name}_gutter_ground_b",
(kx, -1.72, 0.055), (4.85, -1.48, 0.055),
0.055, gutter_m, verts=8),
], "gutter_down", root)
# The downpipe keeps its bottom strap and loses the top one with the run —
# so it leans out of plumb into the yard. Cheapest possible "this eave is
# finished" tell: it reads from across the lawn at night.
join_group([add_tube_between(
f"{name}_downpipe_loose",
(W / 2 - 0.25, eave_y - 0.05, 0.02),
(W / 2 - 0.12, eave_y - 0.62, 2.58), 0.04, gutter_m, verts=8)],
"downpipe_loose", root)
# What a ripped fascia leaves on the lawn: offcuts scattered near where the
# run came down, every board flat on the grass.
debris = []
for i, (dx, dy, ln, ang) in enumerate(((1.35, -1.15, 0.85, 22),
(3.25, -1.58, 0.55, -34),
(2.15, -0.92, 0.34, 63))):
debris.append(add_box(f"{name}_fascia_bit_{i}", (ln, 0.20, 0.035),
(dx + rng.uniform(-0.08, 0.08),
dy + rng.uniform(-0.08, 0.08), 0.0175), trim,
rot=(0, 0, math.radians(ang + rng.uniform(-6, 6)))))
join_group(debris, "debris_fascia", root)
stamp(root, name, "structure")
root["broken_variant_of"] = "house_yardside"
root["facade_width"] = W
# Same price as the state it used to be, so scoring can read either —
# the carport wreck's rule, verbatim.
root["collateral_key"] = "gutter"
root["collateral_value"] = GUTTER_COLLATERAL
root["collateral_label"] = "the gutter"
return root
@ -2277,6 +2421,15 @@ ASSETS = [
nodes=["wall", "door", "window", "roof", "fascia", "gutter",
"window_glow", "window_light_anchor",
"fascia_anchor_01", "fascia_anchor_02", "fascia_anchor_03"]),
# The torn-gutter aftermath (SPRINT12 §gate 3.3). Same height as the intact
# house — a house doesn't fall, only its eave line does — but DEEPER (y),
# because the right gutter run and the fascia offcuts are on the grass out
# in the yard. If y ever measures ~1.2 again someone un-tore it.
dict(name="house_yardside_wrecked", fn=build_house_yardside_wrecked,
dims=((9.0, 9.9), (1.7, 2.6), (2.8, 3.3)),
nodes=["wall", "door", "window", "roof", "window_glow",
"fascia_torn", "gutter_torn", "gutter_down",
"downpipe_loose", "debris_fascia"]),
# site_02 (corner block): the anchor trap, not just dressing.
dict(name="carport_01", fn=build_carport_01,
dims=((3.0, 3.5), (5.2, 5.8), (2.4, 2.8)),

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 MiB

After

Width:  |  Height:  |  Size: 3.5 MiB

Binary file not shown.