Files
EVOLV/test/realistic-startup-timing.integration.test.js

59 lines
2.8 KiB
JavaScript
Raw Normal View History

2026-05-08 18:07:11 +02:00
// Race-window guard with PRODUCTION-default state.time:
// starting: 10 s, warmingup: 5 s, stopping: 5 s, coolingdown: 10 s
//
// All previous deadlock tests use 1-2 s timing for speed. The race that
// actually killed the live demo is about ordering during a long startup
// window where many MGC.handleInput calls land while pumps are still
// transitioning. This test re-runs the load-bearing demand-cycle scenario
// against schema defaults so the test wall time matches the failure mode.
const test = require('node:test');
const assert = require('node:assert/strict');
const { buildPlant, injectPumpPressure } = require('./lib/wiring');
const TICK_MS = 1000;
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
test('realistic startup (start=10s, warm=5s) — varying demand during 15-second startup window', async () => {
const plant = buildPlant({ initialBasinLevel: 2.6 });
const { ps, mgc, pumps, restore } = plant;
try {
// Apply production-default times.
for (const p of pumps) {
p.state.config.time = { starting: 10, warmingup: 5, stopping: 5, coolingdown: 10 };
}
// Inject realistic pressures so predicts have a head.
for (const p of pumps) injectPumpPressure(p, 19620, 117720);
// Drive demand sequence at 1 Hz (mirroring PS tick rate). The first
// 15 calls land during pump startup window; the last 15 land after.
const sequence = [25, 75, 50, 100, 30, 90, 60, 100, 50, 80, 40, 100, 70, 100, 100,
100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100];
for (const pct of sequence) {
mgc.handleInput('parent', pct).catch((e) => console.log(`call ${pct}% rejected: ${e.message}`));
await sleep(1000);
}
// Drain: give the slowest pump time to finish its startup + ramp.
await sleep(6000);
const states = pumps.map((p) => p.state.getCurrentState());
const ctrls = pumps.map((p) => Number(p.state.getCurrentPosition?.()) || 0);
console.log(` states=[${states.join(', ')}] ctrls=[${ctrls.map((c) => c.toFixed(1)).join(', ')}]`);
console.log(` delayedMove=[${pumps.map((p) => String(p.state.delayedMove)).join(', ')}]`);
// After settling, the LAST demand was 100 % so all 3 pumps must be
// high. This is the same invariant idle-startup-deadlock Scenario 4
// checks, but with production timing.
for (let i = 0; i < pumps.length; i++) {
const id = pumps[i].config.general.id;
assert.equal(states[i], 'operational',
`${id}: expected operational, got '${states[i]}' (delayedMove=${pumps[i].state.delayedMove})`);
assert.ok(ctrls[i] > 70,
`${id}: expected ctrl > 70 % at final demand 100 %, got ${ctrls[i].toFixed(1)} % — startup race regression with production timing`);
}
} finally {
restore();
}
});