Implements the contracts.js wind surface (PLAN3D §4): sample(pos,t) and gustTelegraph(t), plus storm defs as data. The prototype scheduled gusts by integrating (wind.gustT += dt). We can't: sample(pos,t) is called by sail/player/debris/rain at arbitrary t, so gusts are precomputed into a timeline from a seeded PRNG at load and read from t. Envelope shape is a faithful port — telegraph 1.5s / ramp 0.8s / hold 1.7s / fade 1.0s. Maths lives in weather.core.js with zero imports (no THREE, no DOM, no Date.now), so the determinism rule is structural and the suite runs in node without waiting on Lane A's M0. storm_01_gentle peaks at 11 m/s; storm_02_wildnight sustains 20 m/s and gusts to 32 (BOM 'destructive'), with the southerly change landing just before the worst of it — the corners that were slack all storm are the ones that cop it. 15 asserts green: telegraph lead >=1.2s, wind continuity in time and space, storm JSON validator (incl. 14 deliberately-broken defs it must reject), determinism, sample-order independence, tree wind shadow. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
33 lines
1.2 KiB
JavaScript
33 lines
1.2 KiB
JavaScript
#!/usr/bin/env node
|
|
'use strict';
|
|
// SHADES — Lane C — headless runner for the weather suite.
|
|
//
|
|
// node web/world/js/tests/run-node.mjs
|
|
//
|
|
// The same suite Lane A's selftest.html runs, but with no browser and no server,
|
|
// so tuning a storm curve is a one-second loop. Exits non-zero on failure.
|
|
|
|
import { readFileSync, readdirSync } from 'node:fs';
|
|
import { fileURLToPath } from 'node:url';
|
|
import { dirname, join } from 'node:path';
|
|
import { runWeatherSuite } from './weather.selftest.js';
|
|
|
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
const stormDir = join(here, '..', '..', 'data', 'storms');
|
|
|
|
const storms = {};
|
|
for (const f of readdirSync(stormDir).filter((f) => f.endsWith('.json')).sort()) {
|
|
storms[f.replace(/\.json$/, '')] = JSON.parse(readFileSync(join(stormDir, f), 'utf8'));
|
|
}
|
|
|
|
const r = runWeatherSuite(storms);
|
|
|
|
for (const res of r.results) {
|
|
console.log(res.ok ? ` ok ${res.name}` : ` FAIL ${res.name}\n ${res.err}`);
|
|
}
|
|
console.log('\n metrics (Lane B: tune cloth rho against these):');
|
|
for (const [k, v] of Object.entries(r.metrics)) console.log(` ${k.padEnd(32)} ${v}`);
|
|
console.log(`\n ${r.pass} passed, ${r.fail} failed\n`);
|
|
|
|
process.exit(r.fail ? 1 : 0);
|