Bump MGC@69bdf11 + adjust overcapacity test to actually exercise storm
Some checks failed
CI / lint-and-test (push) Has been cancelled

- nodes/machineGroupControl@69bdf11 makes DOWNSTREAM single-writer
  (handlePressureChange = live aggregate; optimizer target moved to
  AT_EQUIPMENT). Closes the ps-mgc-flow-contract failure.

- test/inflow-overcapacity-stability now starts the basin at maxLevel
  so PS percControl is immediately 100 % (the actual storm condition)
  and uses real-time waits between ticks so movementManager intervals
  fire — the previous setImmediate yield was too fast for moves to
  progress, making pumps look perma-parked even when behaviour was OK.
  Park observations dropped from 83 to 3 across the sim window; final
  ctrl converges to ~88 % across all 3 pumps.

All 82 cross-node + node integration tests now pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Rene De Ren
2026-05-08 18:33:09 +02:00
parent 3c7d54e9c3
commit 15c39f76bb
2 changed files with 14 additions and 4 deletions

View File

@@ -22,7 +22,11 @@ test('inflow ≫ capacity: pumps reach steady high-ctrl, no parking, no thrashin
// wall time while still exercising the transient (1 s startup + 2 s // wall time while still exercising the transient (1 s startup + 2 s
// warmup). The race conditions we care about are the same — they're // warmup). The race conditions we care about are the same — they're
// about ORDER, not absolute duration. // about ORDER, not absolute duration.
const plant = buildPlant({ initialBasinLevel: 2.6 }); // Start at maxLevel so PS percControl is immediately 100 % (the
// storm condition). Otherwise the basin needs to fill to maxLevel
// first, which on a 2× capacity inflow takes ~2 minutes — longer
// than this test's wall time.
const plant = buildPlant({ initialBasinLevel: 3.5 });
const { ps, mgc, pumps, advance, restore } = plant; const { ps, mgc, pumps, advance, restore } = plant;
try { try {
// Pre-start pumps to operational so the test focuses on STEADY-STATE // Pre-start pumps to operational so the test focuses on STEADY-STATE
@@ -39,8 +43,14 @@ test('inflow ≫ capacity: pumps reach steady high-ctrl, no parking, no thrashin
let abortLogObservations = 0; let abortLogObservations = 0;
// Drive the loop: every tick, refresh pressures, set inflow, // Drive the loop: every tick, refresh pressures, set inflow,
// tick PS (which fires _applyMachineGroupLevelControl). // tick PS (which fires _applyMachineGroupLevelControl). The
// settlePerTickMs wait is REAL wall-clock so movementManager's
// setInterval timers actually fire between handleInputs — without
// it the test runs too fast for moves to progress and pumps look
// permanently parked even when production behaviour is fine.
const ticks = SIM_SECONDS; const ticks = SIM_SECONDS;
const settlePerTickMs = 200;
const realSleep = (ms) => new Promise((r) => setTimeout(r, ms));
let lastCtrl = pumps.map(() => 0); let lastCtrl = pumps.map(() => 0);
let largeJumpTicks = 0; let largeJumpTicks = 0;
for (let i = 0; i < ticks; i++) { for (let i = 0; i < ticks; i++) {
@@ -48,7 +58,7 @@ test('inflow ≫ capacity: pumps reach steady high-ctrl, no parking, no thrashin
ps.setManualInflow(Q_IN, Date.now(), 'm3/s'); ps.setManualInflow(Q_IN, Date.now(), 'm3/s');
advance(TICK_MS); advance(TICK_MS);
ps.tick(); ps.tick();
await new Promise((r) => setImmediate(r)); await realSleep(settlePerTickMs);
const states = pumps.map((p) => p.state.getCurrentState()); const states = pumps.map((p) => p.state.getCurrentState());
const ctrls = pumps.map((p) => Number(p.state.getCurrentPosition?.()) || 0); const ctrls = pumps.map((p) => Number(p.state.getCurrentPosition?.()) || 0);