The gate-1 diagnosis the sprint chartered, inverted by measurement: the shadow geometry is exact (probe2: raycast and rasteriser agree 1.0000/1.0000), the canonical measuring lines just don't cover the bed against vertical hail, and site_02's tr1+q1+q3+q4 at exactly $80 already separates 91.5 FULL vs 35.7 bare (sep 55.8, 0/4 lost) with no model change. Full findings + separation-target proposal in THREADS. Re-verified after the authoring session died un-pushed: all headline numbers reproduce; the bench drives RiggingSession.commit(rig) -> rig.attach(), not the bypassed-attach seam D's landmine names; balance.test's canonical shade-cloth peaks reproduce per-anchor to the last digit. One fix over the original staging: probe3/probe4 labelled per-corner peaks in plan order, but rig.corners is ring order — labels now come from corners[k].anchorId (values were always true; the $80 line's two RATED corners read swapped). Selftest 335/0/0. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
80 lines
3.8 KiB
HTML
80 lines
3.8 KiB
HTML
<!doctype html>
|
|
<html>
|
|
<head><meta charset="utf-8"><title>shadow probe 2 — synthetic</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 { FIXED_DT, HARDWARE } from '../../web/world/js/contracts.js';
|
|
const [,, RATED] = HARDWARE;
|
|
const el = document.getElementById('out');
|
|
const lines = []; const log = (s) => { lines.push(s); el.textContent = lines.join('\n'); };
|
|
el.textContent = '';
|
|
|
|
// ISOLATE THE RASTERISER FROM THE LEVEL.
|
|
// probe.html showed the real yard's best quad sits over only 25% of the bed, so
|
|
// its shadow numbers can't tell a rasteriser bug from an anchor-placement fact.
|
|
// So: a SYNTHETIC square of anchors dead over the bed, at 4 m. This sail
|
|
// unambiguously covers the whole bed. Grid and raycast must BOTH read ~1.0.
|
|
// Anything less is the shadow model losing cloth it is standing under.
|
|
try {
|
|
const bed = { x: 0, z: 0, w: 6, d: 4 };
|
|
const mk = (id, x, z) => { const pos = { x, y: 4, z }; return { id, type: 'post', pos, sway: () => pos }; };
|
|
// a generous 10x8 square around a 6x4 bed — no edge effects, no excuses
|
|
const anchors = [mk('s1', -5, -4), mk('s2', 5, -4), mk('s3', 5, 4), mk('s4', -5, 4)];
|
|
const calm = { sample:(p,t,o)=>(o||new THREE.Vector3()).set(0,0,0), speedAt:()=>0, rainAt:()=>0,
|
|
rainMmPerHour:()=>0, gustTelegraph:()=>null, setSheltersFromTrees(){}, eventsBetween:()=>[] };
|
|
|
|
for (const gridN of [10, 16]) {
|
|
const s = new RiggingSession({ anchors, budget: 9999 });
|
|
for (const a of anchors) s.rig(a.id);
|
|
for (const a of anchors) { const r = s.setHardware(a.id, RATED);
|
|
if (!r.ok) throw new Error(`setHardware ${a.id} refused: ${r.reason}`); }
|
|
s.setTension(1.0);
|
|
const rig = s.commit(new SailRig({ anchors, gridN }));
|
|
for (let i = 0; i < Math.round(12/FIXED_DT); i++) rig.step(FIXED_DT, calm, 0);
|
|
|
|
let minY=1e9,maxY=-1e9;
|
|
for (let i=1;i<rig.pos.length;i+=3){ minY=Math.min(minY,rig.pos[i]); maxY=Math.max(maxY,rig.pos[i]); }
|
|
|
|
const cov = rig.coverageOver(bed, { x:0, y:1, z:0 }, null);
|
|
const sh = new RainShadow({ groundY: 0 });
|
|
sh.update(rig, 0, -1, 0);
|
|
const frac = sh.fractionOver(bed);
|
|
|
|
log(`\n=== gridN ${gridN} (${rig.tris.length/3} tris, ${rig.pos.length/3} nodes) cloth y ${minY.toFixed(2)}..${maxY.toFixed(2)} ===`);
|
|
log(` a 10x4 sail sitting dead over a 6x4 bed. Truth for both numbers is 1.00.`);
|
|
log(` rig.coverageOver(bed, straight up) = ${cov.toFixed(4)} ${cov > 0.99 ? 'ok' : '<-- LOSES CLOTH'}`);
|
|
log(` RainShadow.fractionOver(bed), down = ${frac.toFixed(4)} ${frac > 0.99 ? 'ok' : '<-- LOSES CLOTH'}`);
|
|
|
|
// where are the holes? print the grid cells across the bed's footprint
|
|
log(' grid cells over the bed (1 = shadowed):');
|
|
for (let j=0;j<7;j++){
|
|
let row='';
|
|
for (let i=0;i<15;i++){
|
|
const x = bed.x + ((i+0.5)/15 - 0.5)*bed.w;
|
|
const z = bed.z + ((j+0.5)/7 - 0.5)*bed.d;
|
|
const c = sh._idx(x,z);
|
|
row += (c>=0 && sh.ceil[c]>0) ? '1' : '.';
|
|
}
|
|
log(` ${row}`);
|
|
}
|
|
let filled=0; for (let i=0;i<sh.ceil.length;i++) if (sh.ceil[i]>0) filled++;
|
|
const cellA = (sh.half*2/sh.n)**2;
|
|
log(` cells filled ${filled} => ${(filled*cellA).toFixed(1)} m2 shadowed; the sail is 10x8 = 80 m2`);
|
|
}
|
|
} catch (e) { log('THREW: ' + (e && e.stack || e)); }
|
|
document.title = 'probe2 done';
|
|
</script>
|
|
</body></html>
|