HardYards/web/world/js/tests/b.test.js
type-two 4095df8b10 Lane B S11: audit the venturi, land setWorld/setBudget, revive the harness
site_audit's headless false-negative is fixed by the browser front-end
(A's loadSite/createWorld landed in main — that was all it waited on), and
fixing it surfaced a worse bug underneath: the sweep built its wind from the
STORM def alone and never called setVenturi, which main.js:424 does at every
site load. The venturi is SITE data, so every site_02 audit ever run — including
the numbers quoted at C in SPRINT10 — was of a corner block with no gap. The
SPRINT6 p1=7.4kN failure inverted: there the tool called a good site unriggable,
here it called a mean site cheap.

- sweep.js: setVenturi on both winds (the gap doesn't switch off for the settle);
  both front-ends pass the site's funnel and print it, so a funnelled run is
  legible as one.
- sweep.selftest.js (new, wired into b.test.js): the auditor gets an auditor.
  Funnel must strictly raise every corner; a funnel-less site must be
  byte-identical. Verified it fails when the setVenturi calls are deleted.
- rigging.js: setWorld(world) + session.setBudget(n)/setAnchors() — A's two asks.
  Markers are rebuilt (the anchor set changes across sites) and disposed; stale
  picks die with the old yard. Deletes A's "one private touch" into this module.
- dev_rigging.html: dead since SPRINT10 (called createWorld without a site, which
  now throws) and nothing noticed, because a dev page has no selftest to go red.
  Fixed, dressed, and given the site switch it should have had (N = next yard).

Verdict for C/A, in THREADS: site_02 PASSES with the funnel on — 64 of 66 lines
affordable, cheapest $20. The funnel kills nothing, so the gain does NOT need
dropping and E's tree stays put. It's a reach problem, not a gain problem: the
throat's radius is 5 m and the bed centre is 6.08 m away, so the gap screams over
the carport at +50% and reaches the garden at +0.0%.

selftest 300/0/0 (296 + 4 new).
2026-07-17 17:42:54 +10:00

39 lines
2.1 KiB
JavaScript

/**
* Lane B selftests — cloth, corner loads, failure cascade, prep economy.
*
* The asserts themselves live next to the code they test, in
* `js/sail.selftest.js` and `js/rigging.selftest.js`, exported as [name, fn]
* pairs. This file is only the adapter that hands them to Lane A's Suite.
*
* The reason for the indirection: those two modules also run under plain
* `node web/world/js/sail.selftest.js` — no browser, no server, no renderer,
* ~6 s — which is how the cloth got proven before M0 landed. Keeping the
* asserts there means the browser suite and the headless suite can never drift,
* because they are literally the same array.
*
* PLAN3D §5-B asked for three asserts. All three are in there, plus a statics
* balance that pins the load meter to real newtons:
* 1. hypar sheds load — scored on WORST CASE over eight wind directions
* rather than one, because Lane C's storms veer and the player never gets
* to pick the wind. Per-direction would be a false assert: a flat sail
* sitting edge-on to the wind genuinely has low drag and beats the hypar
* from that one angle. Worst-case is what the hardware has to survive.
* 2. cascade — break a corner at fixed t, a neighbour's load spikes >= 2x.
* 3. determinism — byte-equal load traces, plus ragged frame dt converging on
* the fixed-dt trace (Lane A's render loop delivers ragged dt, so the
* accumulator has to absorb it or none of this applies to the real game).
*/
import { SAIL_TESTS } from '../sail.selftest.js';
import { RIGGING_TESTS } from '../rigging.selftest.js';
import { SWEEP_TESTS } from '../../../../tools/site_audit/sweep.selftest.js';
/** @param {import('../testkit.js').Suite} t */
export default function run(t) {
for (const [name, fn] of SAIL_TESTS) t.test(name, fn);
for (const [name, fn] of RIGGING_TESTS) t.test(`rigging: ${name}`, fn);
// site_audit's own sweep. SPRINT11: the tool was flying site_02 with the
// venturi switched off — the auditor needed an auditor. See sweep.selftest.js.
for (const [name, fn] of SWEEP_TESTS) t.test(`site_audit: ${name}`, fn);
}