Lane B S16: flyGarden grows a porosity param — the audit engine can fly membrane
Default stays the pinned 0.30 shade cloth (asserted: omitting porosity === explicit 0.30 to the digit, hp and every corner peak), so no pinned number moves. porosity 0 flies genuinely heavier (per-corner strictly greater, measured 1.23x average on the synthetic funnel yard). C's storm_06 gate-3.1 membrane-vs-cloth pin and B's membrane-balance tables ride this seam. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
5bfd843b77
commit
edce5c2758
@ -89,9 +89,17 @@ export function hardwareByName(name) {
|
|||||||
* @param {Array} [o.ids] 4 anchor ids; null/omitted = bare bed (the control)
|
* @param {Array} [o.ids] 4 anchor ids; null/omitted = bare bed (the control)
|
||||||
* @param {Array|object} [o.hw] hardware per pick (aligned with ids), or one tier for all 4
|
* @param {Array|object} [o.hw] hardware per pick (aligned with ids), or one tier for all 4
|
||||||
* @param {number} [o.tension]
|
* @param {number} [o.tension]
|
||||||
|
* @param {number} [o.porosity] fabric flown. DEFAULT 0.30 (shade cloth — the
|
||||||
|
* fabric a competent player flies, same as sweep.js,
|
||||||
|
* and every pinned number to date was measured on
|
||||||
|
* it); pass 0 for membrane. SPRINT16: the F key is
|
||||||
|
* live and storm_06's whole bet is membrane-vs-
|
||||||
|
* cloth, so the audit engine must be able to fly
|
||||||
|
* both — C's gate-3.1 pin and B's membrane-balance
|
||||||
|
* tables ride this parameter.
|
||||||
* @returns {{ hp, state, byHail, byRain, cornersLost, peaks, marginal, cost }}
|
* @returns {{ hp, state, byHail, byRain, cornersLost, peaks, marginal, cost }}
|
||||||
*/
|
*/
|
||||||
export function flyGarden({ anchors, bed, stormDef, siteDef = null, use = null, ids = null, hw = null, tension = 1.0 }) {
|
export function flyGarden({ anchors, bed, stormDef, siteDef = null, use = null, ids = null, hw = null, tension = 1.0, porosity = 0.30 }) {
|
||||||
const wind = windForSite(stormDef, siteDef, anchors);
|
const wind = windForSite(stormDef, siteDef, anchors);
|
||||||
use?.(wind);
|
use?.(wind);
|
||||||
|
|
||||||
@ -99,8 +107,7 @@ export function flyGarden({ anchors, bed, stormDef, siteDef = null, use = null,
|
|||||||
if (ids) {
|
if (ids) {
|
||||||
const hwArr = Array.isArray(hw) ? hw : Array(4).fill(hw ?? hardwareByName('rated shackle'));
|
const hwArr = Array.isArray(hw) ? hw : Array(4).fill(hw ?? hardwareByName('rated shackle'));
|
||||||
cost = hwArr.reduce((s, h) => s + (h.cost || 0), 0);
|
cost = hwArr.reduce((s, h) => s + (h.cost || 0), 0);
|
||||||
// shade cloth (porosity 0.30): the fabric a competent player flies — same as sweep.js
|
rig = new SailRig({ anchors, gridN: 10, porosity }).attach(ids, hwArr, tension);
|
||||||
rig = new SailRig({ anchors, gridN: 10, porosity: 0.30 }).attach(ids, hwArr, tension);
|
|
||||||
// D's skipped-attach trap: a rig without its shadow mesh scores as a bare
|
// D's skipped-attach trap: a rig without its shadow mesh scores as a bare
|
||||||
// bed and the number LOOKS plausible. Refuse to fly it.
|
// bed and the number LOOKS plausible. Refuse to fly it.
|
||||||
if (!rig.rigged || !rig.pos || !rig.tris || !rig.tris.length) {
|
if (!rig.rigged || !rig.pos || !rig.tris || !rig.tris.length) {
|
||||||
|
|||||||
@ -122,6 +122,22 @@ export async function buildGardenflyTests() {
|
|||||||
venRun = { bare: fly([]), funnelled: fly(SYN_FUNNEL) };
|
venRun = { bare: fly([]), funnelled: fly(SYN_FUNNEL) };
|
||||||
} catch (err) { venErr = String((err && err.stack) || err); }
|
} catch (err) { venErr = String((err && err.stack) || err); }
|
||||||
|
|
||||||
|
// SPRINT16 [B]: flyGarden grew a `porosity` param (storm_06's membrane bet
|
||||||
|
// needs the audit engine to fly both fabrics). Two things must hold or every
|
||||||
|
// pinned number in THREADS quietly changes meaning: the DEFAULT is still the
|
||||||
|
// 0.30 shade cloth every pin was measured on, and membrane genuinely flies
|
||||||
|
// as a different fabric (double the pressure term → heavier corners).
|
||||||
|
let fabRun = null, fabErr = null;
|
||||||
|
try {
|
||||||
|
const flyFab = (porosity) => flyGarden({
|
||||||
|
anchors: SYN_ANCHORS, bed: SYN_BED, stormDef: SYN_STORM,
|
||||||
|
siteDef: { wind: { venturi: SYN_FUNNEL } },
|
||||||
|
ids: ['a1', 'a2', 'a3', 'a4'], hw: hardwareByName('rated shackle'),
|
||||||
|
...(porosity === undefined ? {} : { porosity }),
|
||||||
|
});
|
||||||
|
fabRun = { byDefault: flyFab(undefined), cloth: flyFab(0.30), membrane: flyFab(0) };
|
||||||
|
} catch (err) { fabErr = String((err && err.stack) || err); }
|
||||||
|
|
||||||
return [
|
return [
|
||||||
['gardenfly: the pinned separation line, flown in BOTH chains — the 12s-skew disagreement, pinned', () => {
|
['gardenfly: the pinned separation line, flown in BOTH chains — the 12s-skew disagreement, pinned', () => {
|
||||||
// ⚠ THE STATE OF PLAY (2026-07-18, [B] THREADS, receipts in the entry):
|
// ⚠ THE STATE OF PLAY (2026-07-18, [B] THREADS, receipts in the entry):
|
||||||
@ -168,6 +184,31 @@ export async function buildGardenflyTests() {
|
|||||||
`the siteDef's venturi is not reaching gardenfly's wind (windForSite must receive the SITE def)`);
|
`the siteDef's venturi is not reaching gardenfly's wind (windForSite must receive the SITE def)`);
|
||||||
}
|
}
|
||||||
}],
|
}],
|
||||||
|
['gardenfly: default fabric is still the pinned 0.30 cloth, and membrane flies heavier', () => {
|
||||||
|
if (fabErr) throw new Error(`fabric flight died: ${fabErr}`);
|
||||||
|
const { byDefault, cloth, membrane } = fabRun;
|
||||||
|
// omitting porosity === saying 0.30, to the digit — every pinned number
|
||||||
|
// in THREADS was measured on the default and must not move under it
|
||||||
|
assert(byDefault.hp === cloth.hp, `default flew hp ${byDefault.hp} vs explicit cloth ${cloth.hp} — the default fabric drifted`);
|
||||||
|
for (const d of byDefault.peaks) {
|
||||||
|
const c = cloth.peaks.find((p) => p.id === d.id);
|
||||||
|
assert(c && c.peakN === d.peakN, `corner ${d.id}: default peak ${d.peakN} != explicit-cloth ${c?.peakN} — the default fabric drifted`);
|
||||||
|
}
|
||||||
|
// porosity 0 must be a genuinely different fabric: the solid membrane
|
||||||
|
// catches the full pressure term, so every corner flies heavier
|
||||||
|
for (const m of membrane.peaks) {
|
||||||
|
const c = cloth.peaks.find((p) => p.id === m.id);
|
||||||
|
assert(m.peakN > c.peakN,
|
||||||
|
`corner ${m.id}: membrane peak ${m.peakN} N not above cloth ${c.peakN} N — the porosity param is not reaching the rig`);
|
||||||
|
}
|
||||||
|
// Measured 1.23x on this synthetic yard (browser, 2026-07-20) — less
|
||||||
|
// than the raw 1/0.7 pressure ratio because relative wind bleeds load as
|
||||||
|
// the heavier-loaded membrane accelerates. Floor at 1.1: the per-corner
|
||||||
|
// strictly-greater asserts above prove the wiring; this only guards
|
||||||
|
// against the difference collapsing to epsilon.
|
||||||
|
const up = membrane.peaks.reduce((s, m) => s + m.peakN / cloth.peaks.find((p) => p.id === m.id).peakN, 0) / 4;
|
||||||
|
assert(up > 1.1, `membrane only ${up.toFixed(2)}x cloth load on average — porosity is wired but not biting`);
|
||||||
|
}],
|
||||||
['gardenfly: unknown hardware names throw — the shop has no mystery tier', () => {
|
['gardenfly: unknown hardware names throw — the shop has no mystery tier', () => {
|
||||||
let threw = false;
|
let threw = false;
|
||||||
try { hardwareByName('titanium dream'); } catch { threw = true; }
|
try { hardwareByName('titanium dream'); } catch { threw = true; }
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user