Merge remote-tracking branch 'origin/lane/e'
This commit is contained in:
commit
a71d67e390
43
THREADS.md
43
THREADS.md
@ -2987,3 +2987,46 @@ Selftest: **277 passed, 0 failed, 0 skipped**, LANE BAL now visible.
|
|||||||
open on carports until landed), C's venturi (landed, waiting on site JSON shape), E's carport
|
open on carports until landed), C's venturi (landed, waiting on site JSON shape), E's carport
|
||||||
(landed as a data-driven trap, waiting on site_02), B's site_audit tool (from the quad sweep).
|
(landed as a data-driven trap, waiting on site_02), B's site_audit tool (from the quad sweep).
|
||||||
Gate 3 (John plays the week) remains open — the game has never been more ready for it.
|
Gate 3 (John plays the week) remains open — the game has never been more ready for it.
|
||||||
|
|
||||||
|
[E] 2026-07-18 — 💰 **LANE A — the carport trap was UNSCOREABLE and that's on me. Fixed.** I shipped the
|
||||||
|
anchors last sprint carrying `collateral="carport"`, but the root carried no `collateral_value` — so
|
||||||
|
the thing your aftermath reaches for (`world.gnome.collateralValue`, main.js:548) simply wasn't there
|
||||||
|
for the carport. A trap with no price isn't a trap, it's a warning label. It now carries
|
||||||
|
`collateral_value` + `collateral_label` on the root, same shape and same place as the gnome, and
|
||||||
|
e.test pins it above the gnome's 25.
|
||||||
|
**The number is YOURS — this is a design call, not an art one.** My proposal is **180**, and the
|
||||||
|
reasoning is in the code beside it so you can argue with it: the gnome is 25, a night's shop is 80, a
|
||||||
|
good week banks ~475. At 180 it's 2.25 nights' budget — it turns a good week into a broke one and is
|
||||||
|
felt for the rest of the run, without instantly ending a strong one. Below ~120 the trap is a shrug;
|
||||||
|
above ~250 tying off the carport once is a silent game over, which teaches nothing because the player
|
||||||
|
never gets to act on the lesson. It should be the worst bill on the site and still a week you can dig
|
||||||
|
out of. Change the constant, not the asset.
|
||||||
|
|
||||||
|
[E] 2026-07-18 — **`carport_01_wrecked_v1.glb`** — the payoff for the trap, same origin and footprint as
|
||||||
|
`carport_01` so you swap mesh-for-mesh like the gnome and the fence. 6.28 × 5.57 × **2.20 m** (the
|
||||||
|
intact one is 2.69 — a wreck must stand SHORTER than the thing it was, and there's an assert for that
|
||||||
|
now because my first pass came out taller). It fails the way a light structure actually does: the pads
|
||||||
|
stay put — they were never the weak part, which is the joke — the frame racks over like a
|
||||||
|
parallelogram, and the roof sheet peels off downwind in one piece. A carport doesn't shatter; it leans,
|
||||||
|
and then it's somewhere else. Copy for the line you asked about:
|
||||||
|
· collateral row: **"the carport — $180"**
|
||||||
|
· verdict/ticker: **"you took the carport with you"**
|
||||||
|
· if you want the knife in: *"They'll want that back up by Thursday."*
|
||||||
|
|
||||||
|
[E] 2026-07-18 — 🐞 two measured bugs on the way in, both caught by measuring instead of reasoning — the
|
||||||
|
exact lesson from my Sprint 9 phantom-post entry, applied earlier this time and it cost minutes rather
|
||||||
|
than an afternoon: (1) I rotated the wreck's beams 31° about Y, but a beam spans the short axis and the
|
||||||
|
frame racks in that SAME axis — both post tops travel together, so the beam rides across level. Tilting
|
||||||
|
it swung one end to 2.96 m and made the wreck taller than the carport. (2) The roof sheets dipped to
|
||||||
|
z = −0.41, i.e. through the ground, which is what inflated the measured height. Both are now comments
|
||||||
|
where the numbers are.
|
||||||
|
|
||||||
|
[E] 2026-07-18 — status: selftest **291/0/0**, Lane E is 64 asserts, **40/40 output files byte-identical**
|
||||||
|
across two full runs. §E dressing (fence lengths, bed size) is **blocked on gate 1** — `data/sites/`
|
||||||
|
doesn't exist yet, and that's correct, it's yours from day one. **Ping me the moment
|
||||||
|
`site_02_corner_block.json` lands** and I'll fit the dressing to the real numbers and refresh the
|
||||||
|
sheets same session; the factory is site-agnostic and the carport was the only new geometry it needed.
|
||||||
|
**Lane B — audit the carport before it ships.** If the one tree plus ground anchors can't make an
|
||||||
|
in-band quad at $80, say so and **I'll move the tree, not the ratings** — the ratings are the site.
|
||||||
|
Carried, still yours either way: grass_atlas (**nine sprints, 0 refs** — bin it), the screenshot POST,
|
||||||
|
`moon.png`.
|
||||||
|
|||||||
@ -155,6 +155,24 @@
|
|||||||
"status": "PASS",
|
"status": "PASS",
|
||||||
"problems": []
|
"problems": []
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "carport_01_wrecked",
|
||||||
|
"dims": [
|
||||||
|
6.2819,
|
||||||
|
5.5704,
|
||||||
|
2.1983
|
||||||
|
],
|
||||||
|
"tris": 144,
|
||||||
|
"nodes": [
|
||||||
|
"beams",
|
||||||
|
"carport_01_wrecked",
|
||||||
|
"footings",
|
||||||
|
"posts",
|
||||||
|
"roof_down"
|
||||||
|
],
|
||||||
|
"status": "PASS",
|
||||||
|
"problems": []
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "shed_01",
|
"name": "shed_01",
|
||||||
"dims": [
|
"dims": [
|
||||||
|
|||||||
@ -781,6 +781,21 @@ def build_house_yardside(name):
|
|||||||
return root
|
return root
|
||||||
|
|
||||||
|
|
||||||
|
# What it costs to take the client's carport with you. Lane A owns this number —
|
||||||
|
# it's a design call, not an art one — but the trap needs A number or it isn't a
|
||||||
|
# trap, and shipping the asset without one is why nothing scored it. My proposal
|
||||||
|
# and the reasoning, to argue with rather than adopt:
|
||||||
|
# · the gnome is 25, a night's shop budget is 80, a good week banks ~475;
|
||||||
|
# · at 180 it's 2.25 nights' budget — it turns a good week into a broke one and
|
||||||
|
# is felt for the rest of the run, without instantly ending a strong one;
|
||||||
|
# · cheaper than ~120 and the trap is a shrug; dearer than ~250 and tying off
|
||||||
|
# the carport once is a silent game over, which teaches nothing because the
|
||||||
|
# player never gets to act on the lesson.
|
||||||
|
# The point is that it should be the worst thing on the site's bill and still be
|
||||||
|
# a week you can dig out of.
|
||||||
|
CARPORT_COLLATERAL = 180
|
||||||
|
|
||||||
|
|
||||||
def build_carport_01(name):
|
def build_carport_01(name):
|
||||||
"""A single-car carport — the corner block's whole personality (SPRINT9 §E).
|
"""A single-car carport — the corner block's whole personality (SPRINT9 §E).
|
||||||
|
|
||||||
@ -858,6 +873,87 @@ def build_carport_01(name):
|
|||||||
stamp(root, name, "structure")
|
stamp(root, name, "structure")
|
||||||
root["site_hint"] = "corner_block"
|
root["site_hint"] = "corner_block"
|
||||||
root["is_anchor_trap"] = True
|
root["is_anchor_trap"] = True
|
||||||
|
# Without this the trap is unscoreable: the anchors say collateral="carport"
|
||||||
|
# but nothing said what a carport COSTS, so Lane A's aftermath had no number
|
||||||
|
# to reach for. main.js already reads world.gnome.collateralValue — same
|
||||||
|
# shape, same place.
|
||||||
|
root["collateral_value"] = CARPORT_COLLATERAL
|
||||||
|
root["collateral_label"] = "the carport"
|
||||||
|
return root
|
||||||
|
|
||||||
|
|
||||||
|
def build_carport_01_wrecked(name):
|
||||||
|
"""The carport after you tied a sail to it (SPRINT10 §E).
|
||||||
|
|
||||||
|
Same origin and footprint as `carport_01`, so Lane A swaps mesh-for-mesh the
|
||||||
|
way the gnome and fence already do. This is the payoff for the trap: the
|
||||||
|
site's lesson only lands if taking the beam LOOKS like taking the carport,
|
||||||
|
not like a number going down on a card.
|
||||||
|
|
||||||
|
It fails the way a light structure actually fails — not flattened. The
|
||||||
|
windward pair of posts stays in its pads and the leeward pair folds, so the
|
||||||
|
whole frame racks over like a parallelogram and the roof sheet peels off
|
||||||
|
downwind in one piece. That's the tell: a carport doesn't shatter, it leans
|
||||||
|
and then it's somewhere else.
|
||||||
|
"""
|
||||||
|
rng = rng_for(name)
|
||||||
|
root = add_empty(name)
|
||||||
|
steel = get_material("Mat_Steel", PAL["steel_gal"], 0.4, metallic=0.85)
|
||||||
|
dark = get_material("Mat_SteelDark", PAL["steel_dark"], 0.45, metallic=0.8)
|
||||||
|
sheet = get_material("Mat_Colorbond", PAL["colorbond"], 0.45, metallic=0.5)
|
||||||
|
conc = get_material("Mat_Concrete", PAL["concrete"], 0.95)
|
||||||
|
|
||||||
|
W, D = 3.0, 5.4
|
||||||
|
H_HI, H_LO = 2.45, 2.20
|
||||||
|
RACK = math.radians(34) # how far the frame leaned before it stopped
|
||||||
|
|
||||||
|
pads, posts = [], []
|
||||||
|
for sx in (-1, 1):
|
||||||
|
for sy in (-1, 1):
|
||||||
|
x, y = sx * (W / 2 - 0.09), sy * (D / 2 - 0.09)
|
||||||
|
h = H_HI if sy < 0 else H_LO
|
||||||
|
# The pads stay: they were never the weak part. That's the joke.
|
||||||
|
pads.append(add_box(f"{name}_pad_{sx}_{sy}", (0.26, 0.26, 0.09),
|
||||||
|
(x, y, 0.045), conc))
|
||||||
|
lean = RACK * rng.uniform(0.86, 1.0)
|
||||||
|
# Racked over toward +X, hinging at the pad — so the top travels and
|
||||||
|
# the base doesn't.
|
||||||
|
posts.append(add_box(f"{name}_post_{sx}_{sy}", (0.09, 0.09, h),
|
||||||
|
(x + math.sin(lean) * h / 2, y,
|
||||||
|
0.09 + math.cos(lean) * h / 2), steel,
|
||||||
|
rot=(0, lean, 0)))
|
||||||
|
join_group(pads, "footings", root)
|
||||||
|
join_group(posts, "posts", root)
|
||||||
|
|
||||||
|
# The beams stay LEVEL. A beam spans the short axis and the frame racks in
|
||||||
|
# that same axis, so both post tops travel together and the beam rides across
|
||||||
|
# — it does not tilt. Rotating it swung one end to 2.96 m, i.e. the wreck came
|
||||||
|
# out TALLER than the carport it used to be. Measured, not reasoned.
|
||||||
|
beams = []
|
||||||
|
for sy in (-1, 1):
|
||||||
|
h = H_HI if sy < 0 else H_LO
|
||||||
|
beams.append(add_box(f"{name}_beam_{sy}", (W, 0.05, 0.14),
|
||||||
|
(math.sin(RACK) * h, sy * (D / 2 - 0.09),
|
||||||
|
0.09 + math.cos(RACK) * h), dark))
|
||||||
|
join_group(beams, "beams", root)
|
||||||
|
|
||||||
|
# The sheet let go and went downwind — folded, not flat, and clear of the
|
||||||
|
# frame. Nobody's parking under this again.
|
||||||
|
# Flat-ish and ON the grass. The first pass tilted them enough to sink a
|
||||||
|
# corner to z=-0.41 — through the ground — which is both wrong and what
|
||||||
|
# inflated the wreck's measured height. Kept nearer the frame too: a 7 m
|
||||||
|
# footprint would overlap whatever Lane A has parked next door.
|
||||||
|
join_group([add_box(f"{name}_sheet_a", (W + 0.2, D * 0.55, 0.04),
|
||||||
|
(W * 0.95, -D * 0.18, 0.12), sheet,
|
||||||
|
rot=(0, math.radians(3), math.radians(-13))),
|
||||||
|
add_box(f"{name}_sheet_b", (W * 0.8, D * 0.38, 0.04),
|
||||||
|
(W * 0.88, D * 0.30, 0.28), sheet,
|
||||||
|
rot=(math.radians(9), math.radians(-4), math.radians(8)))],
|
||||||
|
"roof_down", root)
|
||||||
|
|
||||||
|
stamp(root, name, "structure")
|
||||||
|
root["broken_variant_of"] = "carport_01"
|
||||||
|
root["collateral_value"] = CARPORT_COLLATERAL
|
||||||
return root
|
return root
|
||||||
|
|
||||||
|
|
||||||
@ -2054,6 +2150,11 @@ ASSETS = [
|
|||||||
nodes=["footings", "posts", "beams", "roof",
|
nodes=["footings", "posts", "beams", "roof",
|
||||||
"beam_anchor_01", "beam_anchor_02",
|
"beam_anchor_01", "beam_anchor_02",
|
||||||
"post_anchor_01", "post_anchor_02"]),
|
"post_anchor_01", "post_anchor_02"]),
|
||||||
|
# Wider than the intact carport (the sheet goes downwind) but SHORTER — a
|
||||||
|
# wreck that stands taller than the thing it was is a bug, not a wreck.
|
||||||
|
dict(name="carport_01_wrecked", fn=build_carport_01_wrecked,
|
||||||
|
dims=((4.5, 6.6), (5.2, 5.8), (1.9, 2.45)),
|
||||||
|
nodes=["footings", "posts", "beams", "roof_down"]),
|
||||||
dict(name="shed_01", fn=build_shed_01,
|
dict(name="shed_01", fn=build_shed_01,
|
||||||
dims=((2.4, 2.7), (1.8, 2.1), (1.95, 2.25)),
|
dims=((2.4, 2.7), (1.8, 2.1), (1.95, 2.25)),
|
||||||
nodes=["shell", "roof", "doors", "door_anchor"]),
|
nodes=["shell", "roof", "doors", "door_anchor"]),
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 3.0 MiB After Width: | Height: | Size: 3.2 MiB |
@ -83,6 +83,8 @@ const ASSETS = [
|
|||||||
{ name: 'carport_01', h: [2.4, 2.8],
|
{ name: 'carport_01', h: [2.4, 2.8],
|
||||||
nodes: ['footings', 'posts', 'beams', 'roof',
|
nodes: ['footings', 'posts', 'beams', 'roof',
|
||||||
'beam_anchor_01', 'beam_anchor_02', 'post_anchor_01', 'post_anchor_02'] },
|
'beam_anchor_01', 'beam_anchor_02', 'post_anchor_01', 'post_anchor_02'] },
|
||||||
|
{ name: 'carport_01_wrecked', h: [1.9, 2.45],
|
||||||
|
nodes: ['footings', 'posts', 'beams', 'roof_down'] },
|
||||||
];
|
];
|
||||||
|
|
||||||
function sizeOf(gltf) {
|
function sizeOf(gltf) {
|
||||||
@ -153,6 +155,36 @@ export default async function run(t) {
|
|||||||
// house fascia, which DESIGN.md already calls a lie. If someone quietly
|
// house fascia, which DESIGN.md already calls a lie. If someone quietly
|
||||||
// "fixes" these ratings upward the site stops teaching anything and just
|
// "fixes" these ratings upward the site stops teaching anything and just
|
||||||
// becomes a smaller yard, so they're pinned here with the reason attached.
|
// becomes a smaller yard, so they're pinned here with the reason attached.
|
||||||
|
// The trap has to be scoreable or it isn't a trap, only a warning. The anchors
|
||||||
|
// said collateral="carport" from Sprint 9 but nothing said what a carport
|
||||||
|
// COSTS, so Lane A's aftermath had no number to reach for and the site's whole
|
||||||
|
// lesson was unpriced. main.js already reads world.gnome.collateralValue — this
|
||||||
|
// is the same shape in the same place.
|
||||||
|
t.test('the carport is priced, and it is the worst bill on the site', () => {
|
||||||
|
const c = loaded.get('carport_01')?.scene.getObjectByName('carport_01');
|
||||||
|
assert(c, 'carport_01 root missing');
|
||||||
|
const cost = c.userData?.collateral_value;
|
||||||
|
assert(typeof cost === 'number', `carport carries no collateral_value (${JSON.stringify(c.userData)})`);
|
||||||
|
const gnome = loaded.get('garden_gnome_01')?.scene
|
||||||
|
.getObjectByName('garden_gnome_01')?.userData?.collateral_value;
|
||||||
|
assert(cost > gnome, `carport (${cost}) must cost more than the gnome (${gnome})`);
|
||||||
|
// Wreckage carries the same price, so scoring can read either state.
|
||||||
|
const w = loaded.get('carport_01_wrecked')?.scene.getObjectByName('carport_01_wrecked');
|
||||||
|
assert(w?.userData?.collateral_value === cost,
|
||||||
|
'the wrecked carport must be priced the same as the one it used to be');
|
||||||
|
assert(w?.userData?.broken_variant_of === 'carport_01',
|
||||||
|
'wrecked carport must name its intact twin so A can pair the swap');
|
||||||
|
});
|
||||||
|
|
||||||
|
// A wreck that stands taller than the thing it was is a bug, not a wreck.
|
||||||
|
t.test('the wrecked carport is shorter than the carport', () => {
|
||||||
|
const a = new THREE.Box3().setFromObject(loaded.get('carport_01').scene);
|
||||||
|
const b = new THREE.Box3().setFromObject(loaded.get('carport_01_wrecked').scene);
|
||||||
|
const hi = a.max.y - a.min.y, lo = b.max.y - b.min.y;
|
||||||
|
assert(lo < hi, `wreck stands ${lo.toFixed(2)} m vs the intact ${hi.toFixed(2)} m`);
|
||||||
|
assert(Math.abs(b.min.y) < 0.05, `wreck sits at y=${b.min.y.toFixed(2)} — roof sheet through the ground?`);
|
||||||
|
});
|
||||||
|
|
||||||
t.test('carport anchors stay a trap — the corner block has no good tie-off', () => {
|
t.test('carport anchors stay a trap — the corner block has no good tie-off', () => {
|
||||||
const g = loaded.get('carport_01');
|
const g = loaded.get('carport_01');
|
||||||
assert(g, 'carport_01 did not load');
|
assert(g, 'carport_01 did not load');
|
||||||
|
|||||||
Binary file not shown.
BIN
web/world/models/carport_01_wrecked_v1.glb
Normal file
BIN
web/world/models/carport_01_wrecked_v1.glb
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user