HardYards/web/world/selftest.html
m3ultra 98bc11f033 Add balance.test.js — SPRINT6 gate 1. It is RED, and that is the point
The gate asks the only question no per-lane suite can: can the night be
won through the real $80 shop. It buys loadouts via RiggingSession, flies
the real storm JSON over the real yard, integrates skyfx's real hail/rain
exposure into main.js's real garden drain, and judges with main.js's own
win rule.

Status: storm_01 warm-up PASS, cheap-rig-punished PASS, decision-13
miss-the-bed control PASS, and **storm_02's winnable line FAILS** at
hp=36, 2/4 lost. That reproduces the integrator's finding exactly and is
the gate doing its job — SPRINT6 says everything waits on gate 1.

Two things I got wrong on the way, both worth the comments they now
carry:

- The first draft hardcoded the anchor table from a THREADS entry and
  was badly wrong (the dressed yard has the house at x=±3, not ±5). It
  flew a fictional yard. It now reads the yard from world.js and freezes
  only the sway — the yard IS the balance, so it cannot be a copy. Same
  failure as Sprint 3's 16.7° reference rig.

- The asserts were async. testkit's Suite.test() calls fn() WITHOUT
  awaiting, so every one would have passed forever while proving nothing
  — the exact vacuous-test pattern I've flagged three times. Storms are
  now flown up front in run() (which runAll does await) and the asserts
  are synchronous judgements over the results.

Registers as a sixth 'BAL' entry in selftest.html. A: your "nobody
touches this file" rule guards against five lanes conflicting here; one
jointly-owned entry is the case it makes room for. Revert if you disagree.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 09:25:53 +10:00

109 lines
4.4 KiB
HTML

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>SHADES — selftest</title>
<style>
:root { color-scheme: dark; }
body {
margin: 0; padding: 22px 20px 60px;
background: #10161b; color: #dde5ea;
font: 13px/1.6 ui-monospace, SFMono-Regular, Menlo, monospace;
}
h1 { font-size: 15px; letter-spacing: .16em; margin: 0 0 4px; color: #fff; }
.sub { color: #7f8f9b; margin-bottom: 18px; }
#summary { font-size: 15px; font-weight: 700; padding: 10px 14px; border-radius: 6px; margin-bottom: 18px; }
.ok { background: #12321c; color: #7fce6a; border: 1px solid #2c6b3c; }
.bad { background: #3a1618; color: #ff8f86; border: 1px solid #7d2b2b; }
.lane { margin: 16px 0 6px; color: #7ee0ff; letter-spacing: .1em; }
table { border-collapse: collapse; width: 100%; max-width: 1000px; }
td { padding: 3px 10px 3px 0; vertical-align: top; }
td.s { width: 58px; font-weight: 700; }
td.ms { width: 66px; color: #66757f; text-align: right; }
.pass { color: #7fce6a; } .fail { color: #ff6b6b; } .skip { color: #7f8f9b; }
.err { color: #ffb1ab; padding-left: 68px; display: block; }
tr.f td { background: #2a1214; }
</style>
</head>
<body>
<h1>SHADES SELFTEST</h1>
<div class="sub">
Fixed-dt only — no requestAnimationFrame, so this stays honest in a background tab.
Full report is also on the console as JSON.
</div>
<div id="summary">running…</div>
<div id="out"></div>
<script type="importmap">
{ "imports": { "three": "./vendor/three.module.js",
"three/addons/": "./vendor/addons/" } }
</script>
<script type="module">
import { runAll } from './js/testkit.js';
// One import per lane. Each lane owns js/tests/<letter>.test.js and nobody has
// to touch this file — that's the whole point, it keeps selftest.html out of
// the merge path. (Lane A addition to PLAN3D §3; logged in THREADS.md.)
const lanes = [];
for (const [letter, path] of [
['A', './js/tests/a.test.js'],
['B', './js/tests/b.test.js'],
['C', './js/tests/c.test.js'],
['D', './js/tests/d.test.js'],
['E', './js/tests/e.test.js'],
// SPRINT6 gate 1. Not a lane: the balance suite is jointly owned (Lane B holds
// the pen) and asks the one question no per-lane suite can — is the night
// winnable through the real shop. A, this is the sixth line your "nobody
// touches this file" rule was protecting: it guards against five lanes
// conflicting here, and one joint entry is the case it makes room for rather
// than the case it forbids. Revert it if you'd rather own the wiring. — B
['BAL', './js/tests/balance.test.js'],
]) {
try {
const mod = await import(path);
lanes.push([letter, mod.default]);
} catch (err) {
// A lane whose module doesn't parse or whose dependency isn't landed yet
// must not blank the report for everyone else.
lanes.push([letter, () => { throw new Error(`import failed: ${err.message}`); }]);
}
}
const report = await runAll(lanes);
const summary = document.getElementById('summary');
summary.className = report.ok ? 'ok' : 'bad';
summary.textContent = report.ok
? `PASS — ${report.pass} passed, ${report.skip} skipped`
: `FAIL — ${report.fail} failed, ${report.pass} passed, ${report.skip} skipped`;
const out = document.getElementById('out');
for (const letter of ['A', 'B', 'C', 'D', 'E']) {
const rows = report.results.filter((r) => r.lane === letter);
if (!rows.length) continue;
const h = document.createElement('div');
h.className = 'lane';
h.textContent = `LANE ${letter}`;
out.append(h);
const table = document.createElement('table');
for (const r of rows) {
const tr = table.insertRow();
if (r.status === 'fail') tr.className = 'f';
const s = tr.insertCell(); s.className = `s ${r.status}`; s.textContent = r.status.toUpperCase();
const l = tr.insertCell(); l.textContent = r.label;
if (r.err) { const e = document.createElement('span'); e.className = 'err'; e.textContent = `${r.err}`; l.append(e); }
const m = tr.insertCell(); m.className = 'ms'; m.textContent = r.ms >= 0.05 ? `${r.ms.toFixed(1)}ms` : '';
}
out.append(table);
}
console.log(JSON.stringify(report, null, 2));
// Scrapeable by anything that wants a verdict without parsing the DOM.
globalThis.SHADES_SELFTEST = report;
document.title = `SHADES selftest — ${report.ok ? 'PASS' : 'FAIL'}`;
</script>
</body>
</html>