gods-eye-view/scripts/qa-flyroute-mutations.mjs

164 lines
6.6 KiB
JavaScript

#!/usr/bin/env node
/**
* Are the fly_route pins actually load-bearing?
*
* A pin that is red only when you delete the whole feature proves very little.
* This reverts each fix INDIVIDUALLY — the smallest edit that reintroduces the
* original defect — and requires src/routeCinematics.test.mjs to go red for it.
* Every entry names the defect it restores, so the count is reproducible rather
* than asserted in a commit message.
*
* node scripts/qa-flyroute-mutations.mjs
*
* Restores the file on exit, including on failure.
*/
import { execFileSync } from 'node:child_process';
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
const SOURCE = path.join(ROOT, 'src', 'cameraVerbs.js');
const TESTS = 'src/routeCinematics.test.mjs';
/** @type {Array<{defect: string, from: string, to: string}>} */
const MUTATIONS = [
{
defect: 'interrupt leaves the horizon tilted (round-1 behaviour)',
from: 'const leveled = wasRoute && !tracking ? levelCameraRoll() : false;',
to: 'const leveled = false;',
},
{
defect: 'completion freezes a bank into the final frame',
from: 'const appliedBankDeg = state.bankDeg * profile.speed;',
to: 'const appliedBankDeg = state.bankDeg;',
},
{
defect: 'heading lerps in Cartesian space and cannot cross a U-turn',
from: ' const turned = Cesium.Matrix3.multiplyByVector(rotation, state.headingDir, _frameDir);',
to: ' const turned = Cesium.Cartesian3.lerp(state.headingDir, gaze, k, _frameDir);',
},
{
defect: 'the exact-180° bank direction becomes a floating-point coin toss',
from: ' if (cosTurn <= -1 + ANTIPODAL_EPS && Math.abs(sinLeft) < ANTIPODAL_EPS) return Math.PI;',
to: ' // branch removed',
},
{
defect: 'a cold cell is read as sea level (the 1.3 km underground bug)',
from: ' state.floorM = Number.isFinite(state.floorM) ? state.floorM : state.coldSeedFloorM;',
to: ' state.floorM = carto.height;',
},
{
defect: 'the flight never warms its own corridor',
from: ' warmRouteCorridor(state, 0, ROUTE_WARM_START_M);',
to: ' // warm removed',
},
{
defect: 'the dolly races the fire-and-forget warm instead of arming for it',
from: ' if (canAnswer && !state.floorKnown && !state.departed && state.armS < ROUTE_ARM_S) {',
to: ' if (false) {',
},
{
defect: 'no mesh probe, so a permanently cold DEM is flown blind',
from: ' if (cold.length && !state.meshProbeSpent && typeof state.probeFn === \'function\') {',
to: ' if (false) {',
},
{
defect: 'the launch-altitude hold is dropped and the eye descends blind',
from: ' if (!state.floorKnown && Number.isFinite(state.safeHoldHeightM)) {',
to: ' if (false) {',
},
{
defect: 'releasing the hold onto late data drops the eye without a limit',
from: ' if (Number.isFinite(state.appliedHeightM)) {',
to: ' if (false) {',
},
{
defect: 'a late mid-flight floor is taken whole and snaps the eye',
from: ' state.floorM = routeFloorHoldM(state.floorM, sampledFloor, step);\n state.floorKnown = true;',
to: ' state.floorM = sampledFloor;\n state.floorKnown = true;',
},
{
defect: 'the lookahead floor read is dropped (clearance only underfoot)',
from: ' const warm = [here, ahead].filter((value) => Number.isFinite(value));',
to: ' const warm = [here].filter((value) => Number.isFinite(value));',
},
{
defect: 'the shaping floor adopts rises instantly and pops at cell edges',
from: ' if (!Number.isFinite(previousM)) return sampledM;',
to: ' if (!Number.isFinite(previousM) || sampledM >= previousM) return sampledM;',
},
{
defect: 'the mesh probe fires every arming frame instead of once per route',
from: ' if (cold.length && !state.meshProbeSpent && typeof state.probeFn === \'function\') {\n state.meshProbeSpent = true;',
to: ' if (cold.length && typeof state.probeFn === \'function\') {\n state.meshProbeSpent = true;',
},
{
defect: 'one warm cell resolves a corridor whose other cells are unknown',
from: ' if (cold.length && !state.meshProbeCoveredCold) return false;',
to: ' // partial-corridor guard removed',
},
{
defect: 'the probe re-reads cells the cache already answered',
from: ' const probe = state.probeFn(cold) || {};',
to: ' const probe = state.probeFn(cells) || {};',
},
{
defect: 'the hard clearance clamp reads the SMOOTHED floor, not the raw sample',
from: ' (Number.isFinite(sampledFloor) ? sampledFloor : state.floorM) + ROUTE_MIN_CLEARANCE_M,',
to: ' state.floorM + ROUTE_MIN_CLEARANCE_M,',
},
{
defect: 'levelling also RE-FRAMES the camera (destination passed with the HPR)',
from: ' cam.setView({ orientation: { heading: cam.heading, pitch: cam.pitch, roll: 0 } });',
to: ' cam.setView({ destination: Cesium.Cartesian3.fromDegrees(0, 0, 5000), orientation: { heading: cam.heading, pitch: cam.pitch, roll: 0 } });',
},
{
defect: 'the OS reduced-motion preference is never consulted',
from: ' reducedMotion: prefersReducedMotion(),',
to: ' reducedMotion: false,',
},
];
const original = fs.readFileSync(SOURCE, 'utf8');
let caught = 0;
const missed = [];
process.on('exit', () => fs.writeFileSync(SOURCE, original));
console.log(`\nfly_route pin strength — ${MUTATIONS.length} individual reverts\n`);
for (const { defect, from, to } of MUTATIONS) {
if (!original.includes(from)) {
missed.push(`${defect} (ANCHOR MISSING — the mutation no longer applies)`);
console.log(` \x1b[31mSTALE\x1b[0m ${defect}`);
continue;
}
fs.writeFileSync(SOURCE, original.replace(from, to));
let red = false;
let by = '';
try {
execFileSync('node', ['--test', TESTS], { cwd: ROOT, encoding: 'utf8', stdio: 'pipe' });
} catch (error) {
red = true;
const failed = String(error.stdout || '').split('\n')
.filter((line) => line.trim().startsWith('✖') && line.includes('('))
.map((line) => line.trim().slice(2).split(' (')[0]);
by = [...new Set(failed)].slice(0, 2).join('; ');
}
if (red) {
caught += 1;
console.log(` \x1b[32mRED \x1b[0m ${defect}\n caught by: ${by}`);
} else {
missed.push(defect);
console.log(` \x1b[31mGREEN\x1b[0m ${defect} <-- no pin covers this`);
}
}
fs.writeFileSync(SOURCE, original);
console.log(`\n ${caught}/${MUTATIONS.length} defects caught by the pins`);
if (missed.length) {
console.log('\n uncovered:');
for (const item of missed) console.log(` - ${item}`);
}
process.exitCode = missed.length ? 1 : 0;