HardYards/tools/garden_bench/sweep2.html
type-two 45bdc2d079 Lane C S13 correction: the bench flew site_02 funnel-OFF and tree-FROZEN; wild-night headline dies
D's live replay caught landmine 1: bench.js read def.wind.venturi off the
STORM def where a venturi never lives (it is SITE data, main.js:453), and the
probes never attempted it — every site_02 bench number was funnel-off, the
third harness to make B's Sprint-11 mistake. Verifying the fix exposed
landmine 2: the tools remapped anchors to a static sway:()=>pos, freezing the
gum tree whose sway is the dynamic load a tr1 rig eats (ablation on the $80
line's q4: 1.02 funnel-off/static, 1.03 funnel-on/static, 1.24 funnel-on/real
anchors). Backyard numbers matched D's UI to the decimal precisely because
p1..p4 are posts — both landmines are no-ops there.

Fix: windForSite(stormDef, siteDef, anchors) in weather.js is the one shared
wind builder (venturi + shelters, exactly main.js's wiring); all garden_bench
tools build wind through it, fly world.anchors themselves via a re-pointable
wind proxy, print the venturi in their headers, and drop the phantom 12 s
settle (commit->attach->storm is one keypress, and the settle skewed storm
sampling via SailRig's internal clock). c.test.js pins the helper with B's
strictly-raises assert against the real shipped funnel (mutation-checked).

Corrected numbers: $80 line wildnight 91.9 with q4 at 1.24-vs-1.2 rating - a
knife edge the real game falls off (D UI: q3+q4 break, 39.8 TATTERED; D's UI
is canonical there, and a corner over its rating is not a safe line either
way). Buster verdict stands (95.2 bench / 97.9 D live). Icenight 9 DEAD 2/4.
Bench still under-reads UI peaks ~5-15% on site_02 - residual in the pool,
stated in THREADS. Separation-target site_02 example withdrawn to A.

Selftest 341/0/0.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 13:08:21 +10:00

126 lines
6.9 KiB
HTML

<!doctype html>
<html>
<head><meta charset="utf-8"><title>sweep2 — hold AND cover?</title>
<style>body{background:#11161a;color:#dde5ea;font:13px/1.5 ui-monospace,Menlo,monospace;padding:18px}pre{white-space:pre}</style></head>
<body>
<pre id="out">running…</pre>
<script type="importmap">
{ "imports": {
"three": "../../web/world/vendor/three.module.js",
"three/addons/": "../../web/world/vendor/addons/"
} }
</script>
<script type="module">
import * as THREE from '../../web/world/vendor/three.module.js';
import { RiggingSession } from '../../web/world/js/rigging.js';
import { SailRig } from '../../web/world/js/sail.js';
import { RainShadow } from '../../web/world/js/skyfx.js';
import { loadStorm, windForSite } from '../../web/world/js/weather.js';
import { createWorld, loadSite } from '../../web/world/js/world.js';
import { FIXED_DT, HARDWARE } from '../../web/world/js/contracts.js';
const [CARABINER, SHACKLE, RATED] = HARDWARE;
const el = document.getElementById('out');
const lines = []; const log = (s) => { lines.push(s); el.textContent = lines.join('\n'); };
el.textContent = '';
// THE PARETO QUESTION, and the whole of gate 1.
//
// probe2 proved the shadow model is sound (a sail over the bed reads 1.000 by
// raycast AND by the grid). probe3 found the variable: _checkFailure compares
// load against hw.rating * anchor.ratingHint, and the HOUSE fascia's hint is
// 0.35 — so a $30 RATED shackle is worth 2.3 kN on the house and 6.5 kN on a
// post. Every quad that covers the bed hangs off the house.
//
// So: sweep every 4-anchor line on the BEST steel in the game, settle it the way
// prep does, and ask both questions at once — does it HOLD, and does it COVER?
// A quad that covers 100% while tearing itself apart in prep covers nothing.
// This is the table the garden's whole design rests on and nobody has ever
// printed it.
try {
for (const siteId of ['backyard_01', 'site_02_corner_block']) {
const scene = new THREE.Scene();
const zero = { sample:(p,t,o)=>(o||new THREE.Vector3()).set(0,0,0), speedAt:()=>0, rainAt:()=>0,
rainMmPerHour:()=>0, gustTelegraph:()=>null, setSheltersFromTrees(){}, eventsBetween:()=>[] };
const siteDef = await loadSite(siteId);
// re-pointable wind, main.js-router style: the anchors are world.anchors
// THEMSELVES, so tree sway is live — the old static remap froze the gum
// tree and under-read every tr1 corner (correction, 2026-07-18).
let current = zero;
const proxy = {
sample: (p,t,o) => current.sample(p, t, o || new THREE.Vector3()),
speedAt: (p,t) => current.speedAt(p, t),
rainAt: (t) => current.rainAt(t),
rainMmPerHour: (t) => (current.rainMmPerHour ? current.rainMmPerHour(t) : 0),
gustTelegraph: (t) => current.gustTelegraph(t),
eventsBetween: (a,b) => current.eventsBetween(a, b),
setSheltersFromTrees() {},
get seed() { return current.seed ?? 1; },
get def() { return current.def ?? {}; },
};
const world = createWorld(scene, { wind: proxy, site: siteDef });
if (world.dress) { try { await world.dress(); } catch {} }
const anchors = world.anchors;
const bed = world.gardenBed;
const vl = siteDef.wind?.venturi ?? [];
const vtxt = vl.length ? vl.map((v)=>`throat (${v.x},${v.z}) gain ${v.gain} r${v.radius}`).join(' · ') : 'none';
log(`\n\n######## ${siteId} bed ${bed.w}x${bed.d} @ (${bed.x}, ${bed.z}) venturi: ${vtxt}`);
log(` anchors and what the BEST steel ($30 RATED, 6.5 kN) is actually worth on them:`);
for (const a of anchors) {
log(` ${a.id.padEnd(5)} ${String(a.type).padEnd(8)} hint ${(a.ratingHint ?? 1).toFixed(2)} -> ${((a.ratingHint ?? 1) * 6.5).toFixed(2)} kN`);
}
// prep's own wind: main.js hands the rig the calm day outside a storm.
// windForSite so the SITE's venturi flies too — before 2026-07-18 this
// sweep settled site_02's quads with the funnel OFF (the third harness to
// make B's Sprint-11 mistake; see windForSite's comment). The funnel does
// not switch off for prep.
const calmWind = windForSite(await loadStorm('storm_01_gentle'), siteDef, anchors);
current = calmWind; // the tree-sway closures fly the same calm day
const rows = [];
const n = anchors.length;
for (let a=0;a<n;a++) for (let b=a+1;b<n;b++) for (let c=b+1;c<n;c++) for (let d=c+1;d<n;d++) {
const ids = [anchors[a].id, anchors[b].id, anchors[c].id, anchors[d].id];
const s = new RiggingSession({ anchors, budget: 9999 });
let ok = true;
for (const id of ids) if (!s.rig(id).ok) { ok = false; break; }
if (!ok) continue;
for (const id of ids) { const r = s.setHardware(id, RATED);
if (!r.ok) throw new Error(`setHardware ${id} RATED refused: ${r.reason} — the bench is flying hardware it did not buy`); }
s.setTension(1.0);
let rig;
try { rig = s.commit(new SailRig({ anchors, gridN: 10 })); } catch { continue; }
// settle exactly like prep: calm day, running clock, 12 s
let prepT = 0, peak = 0;
for (let i=0;i<Math.round(12/FIXED_DT);i++){
rig.step(FIXED_DT, calmWind, prepT % 3); prepT += FIXED_DT;
for (const co of rig.corners) if ((co.load||0) > peak) peak = co.load;
}
const broke = rig.corners.filter((co)=>co.broken).length;
const cov = rig.coverageOver(bed, {x:0,y:1,z:0}, null); // vertical: what hail sees
const sh = new RainShadow({ groundY: 0 }); sh.update(rig, 0, -1, 0);
const frac = sh.fractionOver(bed);
rows.push({ ids, broke, cov, frac, peak: peak/1000 });
}
const survived = rows.filter((r) => r.broke === 0);
log(`\n ${rows.length} quads swept on RATED steel. ${survived.length} survive PREP on the calm day.`);
log(`\n --- best COVER among quads that actually HOLD (this is the real ceiling) ---`);
log(` line vert.cover shadow peak kN broke`);
for (const r of survived.sort((p,q)=>q.cov-p.cov).slice(0,10)) {
log(` ${r.ids.join('+').padEnd(22)} ${(r.cov*100).toFixed(0).padStart(7)}% ${r.frac.toFixed(3).padStart(6)} ${r.peak.toFixed(2).padStart(7)} ${r.broke}`);
}
log(`\n --- best COVER overall, holding or not (what the sprint assumed was available) ---`);
log(` line vert.cover shadow peak kN broke`);
for (const r of rows.slice().sort((p,q)=>q.cov-p.cov).slice(0,6)) {
log(` ${r.ids.join('+').padEnd(22)} ${(r.cov*100).toFixed(0).padStart(7)}% ${r.frac.toFixed(3).padStart(6)} ${r.peak.toFixed(2).padStart(7)} ${r.broke}${r.broke?' <-- tears in PREP':''}`);
}
const canonical = siteId === 'backyard_01' ? 'p1+p2+p3+p4' : 'q1+q2+q3+tr1';
const cn = rows.find((r)=>r.ids.join('+')===canonical);
if (cn) log(`\n the canonical line ${canonical}: cover ${(cn.cov*100).toFixed(0)}% shadow ${cn.frac.toFixed(3)} peak ${cn.peak.toFixed(2)} kN broke ${cn.broke}`);
}
} catch (e) { log('THREW: ' + (e && e.stack || e)); }
document.title = 'sweep2 done';
</script>
</body></html>