Files
pumpingStation/src/editor/bounds.js

111 lines
4.3 KiB
JavaScript
Raw Normal View History

Editor: dynamic input bounds + full hierarchy validation, layout polish Bounds (new src/editor/bounds.js): - Sets HTML5 min/max on every level + percent input each redraw, derived from the current values of related inputs so the spinner stops at the basin hierarchy: 0 < outflowLevel < dryRunLevel < startLevel ≤ inflowLevel ≤ shiftLevel ≤ maxLevel ≤ overflowLevel ≤ basinHeight - dryRunPercent capped so dryRunLevel ≤ startLevel given current outflow. - shiftArmPercent ∈ [1, 100]; highVolumeSafety% ∈ [1, 100]. Validation: - New visible ribbon above the basin diagram (#ps-basin-validation) listing every hierarchy violation. The in-SVG warning text is now a small reminder ("⚠ N ordering issues"). - basin-diagram.js owns hierarchy issues; mode-preview.js trimmed to only own shift-specific issues (shift > start, shift ≤ max, shiftArmPercent range, shiftLevel required-when-enabled). - oneditsave blocks Deploy on the union of _psBasinValidationIssues and _psModeValidationIssues with a RED.notify listing all problems. Layout polish: - Side panel widened to 220 px with minmax(0, 1fr) first column so long labels can no longer push the rows past the panel edge. - Basin SVG max-width 380 → 360, gap between side panel and SVG bumped 14 → 28 px. Tank shifted right (x=145 width=110) so the inlet "bottom of pipe" sub-label is no longer clipped on the left edge. - "0 m (datum)" moved below the tank (y=395, centred) so it can't collide with "Outlet / top of pipe" when outflowLevel is near floor. - Zone labels shortened (Spare / Sewage + buffer / Buffer / Dead vol) and only show when the bracketing thresholds are ≥ 28 px apart, so they never sit on a threshold label. - Mode preview axis labels under the chart removed — line colour + side-panel labels + hover-couple already identify each line. Stub <text> elements left hidden to keep the redraw loop simple. Arm-% line + label trimmed in 10 px on the right so they're not clipped. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 14:10:22 +02:00
// PumpingStation editor — dynamic input bounds.
// Sets HTML5 min/max attributes on every level and percent input based on
// the current values of related inputs, so the up/down arrows stop at
// values that respect the basin hierarchy:
//
fix(levelBased): drop hold zone, route through MGC.setDemand, add holdLevel + integrator variant pick; slim npm pack levelBased ramp + engagement: - Ramp foot is now max(startLevel, holdLevel) — was max(startLevel, inflowLevel). inflowLevel is basin geometry, not a control setpoint; the implicit hold zone it created was causing pumps to "start at inflowLevel" instead of startLevel. - New optional `holdLevel` config (defaults to startLevel = no hold band). When raised, pumps engage at startLevel and hold at 0 % = MGC flow.min across [startLevel, holdLevel], then ramp 0..100 % to maxLevel. - Engagement decided in run() (not in `_applyMachineGroupLevelControl`): rising-edge hysteresis arming gates a clean turnOff early-return. Once armed, the helper always forwards setDemand(pct, '%') — 0 % legitimately means "engaged at min flow", no more soft-turnOff at the boundary. - Disengagement paths (minLevel hard-stop, stopLevel falling-edge, pre-arming idle) now all clear the shifted-ramp hysteresis state too. - Threshold validator drops the startLevel ≤ inflowLevel rule; adds startLevel ≤ holdLevel < maxLevel (only checked when holdLevel is explicitly set, so default-null doesn't false-flag). MGC unit math: - Replace direct group.handleInput(percent) with group.setDemand(pct, '%') in _applyMachineGroupLevelControl. The percent → m³/s resolution now lives in MGC.setDemand (committed separately in the MGC submodule). FlowAggregator variant picking: - New _pickFlowSum() helper mirrors selectBestNetFlow's variant precedence (measured first, then predicted) and resolves each side independently. Realistic mixed case — real measured upstream sensor + predicted pump outflow — now feeds the predicted-volume integrator. Was reading only `flow.predicted.*` so a real upstream sensor (which writes `flow.measured.*`) never moved the level. Editor: - New `holdLevel` and `deadZoneKeepAlivePercent` defaults + side-panel input rows in the levelbased mode preview. - Add the missing `ps-mode-line-holdLevel` SVG marker (was declared in the side-panel coupling but the SVG element didn't exist, so the dashed line never rendered). - Relax stopLevel marker gate so it renders for any non-negative typed value — start/stop ordering is the ribbon's job, not the marker's (was hiding the line whenever startLevel was momentarily smaller). - Add holdLevel to the marker loop in mode-preview so changes track. - Add stopLevel + holdLevel + maxLevel to all three bindRedraw lists (basin-diagram, mode-preview, bounds.apply) so the SVG, validation ribbon, and HTML5 min/max attrs update on every edit. - Initialise stopLevel + holdLevel + deadZoneKeepAlivePercent inputs in oneditprepare so reopening the editor shows the saved values. - nodeClass passes holdLevel + deadZoneKeepAlivePercent into the domain config. Tests: - New test/basic/_probe_upstream_emit.test.js: confirms the parent surfaces flow.measured.upstream.* on Port 0 after a measurement child write — pins the previously-invisible measured variant flow. - flowAggregator.basic.test.js: two new regression cases — measured inflow when predicted side is empty, and the measured-in / predicted-out mixed case. - control-levelBased.basic.test.js: new cases for the holdLevel hold band, the [stopLevel, startLevel] keep-alive, the engagement gate, and the "0 % at startLevel = setDemand" contract. - specificClass.test.js: zone tests adjusted to the new ramp foot. Shifted-ramp tests pin holdLevel = 3 explicitly so their legacy arithmetic (ramp foot at inflowLevel) stays self-consistent. - shifted-ramp-end-to-end.test.js: same holdLevel pin for the same reason. Packaging: - Add .gitignore + .npmignore so the published tarball drops the wiki/, simulations/, test/, tools/, .claude/ etc. The pack went from 1.5 MB (72 files) to ~57 KB (30 files). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 21:36:29 +02:00
// 0 < outflowLevel < dryRunLevel < startLevel < maxLevel ≤ overflowLevel ≤ basinHeight
// 0 < outflowLevel < inflowLevel < overflowLevel ≤ basinHeight
//
// startLevel is intentionally NOT clamped against inflowLevel: pushing
// startLevel above the gravity-feed inlet is the "buffer in the sewer"
// configuration where upstream pipe storage absorbs flow before pumping
// engages. The level-based ramp foot is max(startLevel, inflowLevel) so
// either ordering is valid.
Editor: dynamic input bounds + full hierarchy validation, layout polish Bounds (new src/editor/bounds.js): - Sets HTML5 min/max on every level + percent input each redraw, derived from the current values of related inputs so the spinner stops at the basin hierarchy: 0 < outflowLevel < dryRunLevel < startLevel ≤ inflowLevel ≤ shiftLevel ≤ maxLevel ≤ overflowLevel ≤ basinHeight - dryRunPercent capped so dryRunLevel ≤ startLevel given current outflow. - shiftArmPercent ∈ [1, 100]; highVolumeSafety% ∈ [1, 100]. Validation: - New visible ribbon above the basin diagram (#ps-basin-validation) listing every hierarchy violation. The in-SVG warning text is now a small reminder ("⚠ N ordering issues"). - basin-diagram.js owns hierarchy issues; mode-preview.js trimmed to only own shift-specific issues (shift > start, shift ≤ max, shiftArmPercent range, shiftLevel required-when-enabled). - oneditsave blocks Deploy on the union of _psBasinValidationIssues and _psModeValidationIssues with a RED.notify listing all problems. Layout polish: - Side panel widened to 220 px with minmax(0, 1fr) first column so long labels can no longer push the rows past the panel edge. - Basin SVG max-width 380 → 360, gap between side panel and SVG bumped 14 → 28 px. Tank shifted right (x=145 width=110) so the inlet "bottom of pipe" sub-label is no longer clipped on the left edge. - "0 m (datum)" moved below the tank (y=395, centred) so it can't collide with "Outlet / top of pipe" when outflowLevel is near floor. - Zone labels shortened (Spare / Sewage + buffer / Buffer / Dead vol) and only show when the bracketing thresholds are ≥ 28 px apart, so they never sit on a threshold label. - Mode preview axis labels under the chart removed — line colour + side-panel labels + hover-couple already identify each line. Stub <text> elements left hidden to keep the redraw loop simple. Arm-% line + label trimmed in 10 px on the right so they're not clipped. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 14:10:22 +02:00
//
// The user can still type out-of-range values via the keyboard (HTML5
// min/max only constrain the spinner). The validation ribbons in
// basin-diagram.js and mode-preview.js catch typed violations and the
// oneditsave handler blocks Deploy until they're resolved.
(function () {
const ns = window.PSEditor = window.PSEditor || {};
const fNum = (id) => ns.fNum(id);
const EPS = 0.001; // smallest meaningful step (mm-precision)
const setBounds = (id, min, max) => {
const el = document.getElementById(`node-input-${id}`);
if (!el) return;
if (Number.isFinite(min)) el.setAttribute('min', String(min));
else el.removeAttribute('min');
if (Number.isFinite(max)) el.setAttribute('max', String(max));
else el.removeAttribute('max');
};
ns.bounds = {
apply() {
const basinHeight = fNum('basinHeight');
const outflow = fNum('outflowLevel');
const dryPct = fNum('dryRunThresholdPercent');
const start = fNum('startLevel');
const inlet = fNum('inflowLevel');
const max = fNum('maxLevel');
const overflow = fNum('overflowLevel');
const shiftEnabled = !!document.getElementById('node-input-enableShiftedRamp')?.checked;
// Derived dryRunLevel (lower bound for startLevel).
const dryRun = (Number.isFinite(outflow) && Number.isFinite(dryPct))
? outflow * (1 + dryPct / 100) : null;
// Geometry — basin envelope.
setBounds('basinHeight', EPS, undefined);
setBounds('basinVolume', EPS, undefined);
// Levels (each capped by the next-higher level if defined).
setBounds('outflowLevel', EPS,
Number.isFinite(start) && Number.isFinite(dryPct)
? start / (1 + dryPct / 100) - EPS // keep dryRun < start
: (start ?? inlet ?? max ?? overflow ?? basinHeight));
setBounds('startLevel',
Number.isFinite(dryRun) ? dryRun + EPS : EPS,
fix(levelBased): drop hold zone, route through MGC.setDemand, add holdLevel + integrator variant pick; slim npm pack levelBased ramp + engagement: - Ramp foot is now max(startLevel, holdLevel) — was max(startLevel, inflowLevel). inflowLevel is basin geometry, not a control setpoint; the implicit hold zone it created was causing pumps to "start at inflowLevel" instead of startLevel. - New optional `holdLevel` config (defaults to startLevel = no hold band). When raised, pumps engage at startLevel and hold at 0 % = MGC flow.min across [startLevel, holdLevel], then ramp 0..100 % to maxLevel. - Engagement decided in run() (not in `_applyMachineGroupLevelControl`): rising-edge hysteresis arming gates a clean turnOff early-return. Once armed, the helper always forwards setDemand(pct, '%') — 0 % legitimately means "engaged at min flow", no more soft-turnOff at the boundary. - Disengagement paths (minLevel hard-stop, stopLevel falling-edge, pre-arming idle) now all clear the shifted-ramp hysteresis state too. - Threshold validator drops the startLevel ≤ inflowLevel rule; adds startLevel ≤ holdLevel < maxLevel (only checked when holdLevel is explicitly set, so default-null doesn't false-flag). MGC unit math: - Replace direct group.handleInput(percent) with group.setDemand(pct, '%') in _applyMachineGroupLevelControl. The percent → m³/s resolution now lives in MGC.setDemand (committed separately in the MGC submodule). FlowAggregator variant picking: - New _pickFlowSum() helper mirrors selectBestNetFlow's variant precedence (measured first, then predicted) and resolves each side independently. Realistic mixed case — real measured upstream sensor + predicted pump outflow — now feeds the predicted-volume integrator. Was reading only `flow.predicted.*` so a real upstream sensor (which writes `flow.measured.*`) never moved the level. Editor: - New `holdLevel` and `deadZoneKeepAlivePercent` defaults + side-panel input rows in the levelbased mode preview. - Add the missing `ps-mode-line-holdLevel` SVG marker (was declared in the side-panel coupling but the SVG element didn't exist, so the dashed line never rendered). - Relax stopLevel marker gate so it renders for any non-negative typed value — start/stop ordering is the ribbon's job, not the marker's (was hiding the line whenever startLevel was momentarily smaller). - Add holdLevel to the marker loop in mode-preview so changes track. - Add stopLevel + holdLevel + maxLevel to all three bindRedraw lists (basin-diagram, mode-preview, bounds.apply) so the SVG, validation ribbon, and HTML5 min/max attrs update on every edit. - Initialise stopLevel + holdLevel + deadZoneKeepAlivePercent inputs in oneditprepare so reopening the editor shows the saved values. - nodeClass passes holdLevel + deadZoneKeepAlivePercent into the domain config. Tests: - New test/basic/_probe_upstream_emit.test.js: confirms the parent surfaces flow.measured.upstream.* on Port 0 after a measurement child write — pins the previously-invisible measured variant flow. - flowAggregator.basic.test.js: two new regression cases — measured inflow when predicted side is empty, and the measured-in / predicted-out mixed case. - control-levelBased.basic.test.js: new cases for the holdLevel hold band, the [stopLevel, startLevel] keep-alive, the engagement gate, and the "0 % at startLevel = setDemand" contract. - specificClass.test.js: zone tests adjusted to the new ramp foot. Shifted-ramp tests pin holdLevel = 3 explicitly so their legacy arithmetic (ramp foot at inflowLevel) stays self-consistent. - shifted-ramp-end-to-end.test.js: same holdLevel pin for the same reason. Packaging: - Add .gitignore + .npmignore so the published tarball drops the wiki/, simulations/, test/, tools/, .claude/ etc. The pack went from 1.5 MB (72 files) to ~57 KB (30 files). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 21:36:29 +02:00
max ?? overflow ?? basinHeight);
Editor: dynamic input bounds + full hierarchy validation, layout polish Bounds (new src/editor/bounds.js): - Sets HTML5 min/max on every level + percent input each redraw, derived from the current values of related inputs so the spinner stops at the basin hierarchy: 0 < outflowLevel < dryRunLevel < startLevel ≤ inflowLevel ≤ shiftLevel ≤ maxLevel ≤ overflowLevel ≤ basinHeight - dryRunPercent capped so dryRunLevel ≤ startLevel given current outflow. - shiftArmPercent ∈ [1, 100]; highVolumeSafety% ∈ [1, 100]. Validation: - New visible ribbon above the basin diagram (#ps-basin-validation) listing every hierarchy violation. The in-SVG warning text is now a small reminder ("⚠ N ordering issues"). - basin-diagram.js owns hierarchy issues; mode-preview.js trimmed to only own shift-specific issues (shift > start, shift ≤ max, shiftArmPercent range, shiftLevel required-when-enabled). - oneditsave blocks Deploy on the union of _psBasinValidationIssues and _psModeValidationIssues with a RED.notify listing all problems. Layout polish: - Side panel widened to 220 px with minmax(0, 1fr) first column so long labels can no longer push the rows past the panel edge. - Basin SVG max-width 380 → 360, gap between side panel and SVG bumped 14 → 28 px. Tank shifted right (x=145 width=110) so the inlet "bottom of pipe" sub-label is no longer clipped on the left edge. - "0 m (datum)" moved below the tank (y=395, centred) so it can't collide with "Outlet / top of pipe" when outflowLevel is near floor. - Zone labels shortened (Spare / Sewage + buffer / Buffer / Dead vol) and only show when the bracketing thresholds are ≥ 28 px apart, so they never sit on a threshold label. - Mode preview axis labels under the chart removed — line colour + side-panel labels + hover-couple already identify each line. Stub <text> elements left hidden to keep the redraw loop simple. Arm-% line + label trimmed in 10 px on the right so they're not clipped. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 14:10:22 +02:00
setBounds('inflowLevel',
fix(levelBased): drop hold zone, route through MGC.setDemand, add holdLevel + integrator variant pick; slim npm pack levelBased ramp + engagement: - Ramp foot is now max(startLevel, holdLevel) — was max(startLevel, inflowLevel). inflowLevel is basin geometry, not a control setpoint; the implicit hold zone it created was causing pumps to "start at inflowLevel" instead of startLevel. - New optional `holdLevel` config (defaults to startLevel = no hold band). When raised, pumps engage at startLevel and hold at 0 % = MGC flow.min across [startLevel, holdLevel], then ramp 0..100 % to maxLevel. - Engagement decided in run() (not in `_applyMachineGroupLevelControl`): rising-edge hysteresis arming gates a clean turnOff early-return. Once armed, the helper always forwards setDemand(pct, '%') — 0 % legitimately means "engaged at min flow", no more soft-turnOff at the boundary. - Disengagement paths (minLevel hard-stop, stopLevel falling-edge, pre-arming idle) now all clear the shifted-ramp hysteresis state too. - Threshold validator drops the startLevel ≤ inflowLevel rule; adds startLevel ≤ holdLevel < maxLevel (only checked when holdLevel is explicitly set, so default-null doesn't false-flag). MGC unit math: - Replace direct group.handleInput(percent) with group.setDemand(pct, '%') in _applyMachineGroupLevelControl. The percent → m³/s resolution now lives in MGC.setDemand (committed separately in the MGC submodule). FlowAggregator variant picking: - New _pickFlowSum() helper mirrors selectBestNetFlow's variant precedence (measured first, then predicted) and resolves each side independently. Realistic mixed case — real measured upstream sensor + predicted pump outflow — now feeds the predicted-volume integrator. Was reading only `flow.predicted.*` so a real upstream sensor (which writes `flow.measured.*`) never moved the level. Editor: - New `holdLevel` and `deadZoneKeepAlivePercent` defaults + side-panel input rows in the levelbased mode preview. - Add the missing `ps-mode-line-holdLevel` SVG marker (was declared in the side-panel coupling but the SVG element didn't exist, so the dashed line never rendered). - Relax stopLevel marker gate so it renders for any non-negative typed value — start/stop ordering is the ribbon's job, not the marker's (was hiding the line whenever startLevel was momentarily smaller). - Add holdLevel to the marker loop in mode-preview so changes track. - Add stopLevel + holdLevel + maxLevel to all three bindRedraw lists (basin-diagram, mode-preview, bounds.apply) so the SVG, validation ribbon, and HTML5 min/max attrs update on every edit. - Initialise stopLevel + holdLevel + deadZoneKeepAlivePercent inputs in oneditprepare so reopening the editor shows the saved values. - nodeClass passes holdLevel + deadZoneKeepAlivePercent into the domain config. Tests: - New test/basic/_probe_upstream_emit.test.js: confirms the parent surfaces flow.measured.upstream.* on Port 0 after a measurement child write — pins the previously-invisible measured variant flow. - flowAggregator.basic.test.js: two new regression cases — measured inflow when predicted side is empty, and the measured-in / predicted-out mixed case. - control-levelBased.basic.test.js: new cases for the holdLevel hold band, the [stopLevel, startLevel] keep-alive, the engagement gate, and the "0 % at startLevel = setDemand" contract. - specificClass.test.js: zone tests adjusted to the new ramp foot. Shifted-ramp tests pin holdLevel = 3 explicitly so their legacy arithmetic (ramp foot at inflowLevel) stays self-consistent. - shifted-ramp-end-to-end.test.js: same holdLevel pin for the same reason. Packaging: - Add .gitignore + .npmignore so the published tarball drops the wiki/, simulations/, test/, tools/, .claude/ etc. The pack went from 1.5 MB (72 files) to ~57 KB (30 files). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 21:36:29 +02:00
EPS,
Editor: dynamic input bounds + full hierarchy validation, layout polish Bounds (new src/editor/bounds.js): - Sets HTML5 min/max on every level + percent input each redraw, derived from the current values of related inputs so the spinner stops at the basin hierarchy: 0 < outflowLevel < dryRunLevel < startLevel ≤ inflowLevel ≤ shiftLevel ≤ maxLevel ≤ overflowLevel ≤ basinHeight - dryRunPercent capped so dryRunLevel ≤ startLevel given current outflow. - shiftArmPercent ∈ [1, 100]; highVolumeSafety% ∈ [1, 100]. Validation: - New visible ribbon above the basin diagram (#ps-basin-validation) listing every hierarchy violation. The in-SVG warning text is now a small reminder ("⚠ N ordering issues"). - basin-diagram.js owns hierarchy issues; mode-preview.js trimmed to only own shift-specific issues (shift > start, shift ≤ max, shiftArmPercent range, shiftLevel required-when-enabled). - oneditsave blocks Deploy on the union of _psBasinValidationIssues and _psModeValidationIssues with a RED.notify listing all problems. Layout polish: - Side panel widened to 220 px with minmax(0, 1fr) first column so long labels can no longer push the rows past the panel edge. - Basin SVG max-width 380 → 360, gap between side panel and SVG bumped 14 → 28 px. Tank shifted right (x=145 width=110) so the inlet "bottom of pipe" sub-label is no longer clipped on the left edge. - "0 m (datum)" moved below the tank (y=395, centred) so it can't collide with "Outlet / top of pipe" when outflowLevel is near floor. - Zone labels shortened (Spare / Sewage + buffer / Buffer / Dead vol) and only show when the bracketing thresholds are ≥ 28 px apart, so they never sit on a threshold label. - Mode preview axis labels under the chart removed — line colour + side-panel labels + hover-couple already identify each line. Stub <text> elements left hidden to keep the redraw loop simple. Arm-% line + label trimmed in 10 px on the right so they're not clipped. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 14:10:22 +02:00
max ?? overflow ?? basinHeight);
setBounds('maxLevel',
inlet ?? start ?? EPS,
overflow ?? basinHeight);
setBounds('overflowLevel',
max ?? inlet ?? start ?? EPS,
basinHeight);
// stopLevel — explicit pump-off threshold. Must sit between
// dryRunLevel and startLevel (so it can be reached during draining
// before pumps re-engage).
setBounds('stopLevel',
Number.isFinite(dryRun) ? dryRun + EPS : EPS,
start ?? inlet ?? max ?? overflow ?? basinHeight);
fix(levelBased): drop hold zone, route through MGC.setDemand, add holdLevel + integrator variant pick; slim npm pack levelBased ramp + engagement: - Ramp foot is now max(startLevel, holdLevel) — was max(startLevel, inflowLevel). inflowLevel is basin geometry, not a control setpoint; the implicit hold zone it created was causing pumps to "start at inflowLevel" instead of startLevel. - New optional `holdLevel` config (defaults to startLevel = no hold band). When raised, pumps engage at startLevel and hold at 0 % = MGC flow.min across [startLevel, holdLevel], then ramp 0..100 % to maxLevel. - Engagement decided in run() (not in `_applyMachineGroupLevelControl`): rising-edge hysteresis arming gates a clean turnOff early-return. Once armed, the helper always forwards setDemand(pct, '%') — 0 % legitimately means "engaged at min flow", no more soft-turnOff at the boundary. - Disengagement paths (minLevel hard-stop, stopLevel falling-edge, pre-arming idle) now all clear the shifted-ramp hysteresis state too. - Threshold validator drops the startLevel ≤ inflowLevel rule; adds startLevel ≤ holdLevel < maxLevel (only checked when holdLevel is explicitly set, so default-null doesn't false-flag). MGC unit math: - Replace direct group.handleInput(percent) with group.setDemand(pct, '%') in _applyMachineGroupLevelControl. The percent → m³/s resolution now lives in MGC.setDemand (committed separately in the MGC submodule). FlowAggregator variant picking: - New _pickFlowSum() helper mirrors selectBestNetFlow's variant precedence (measured first, then predicted) and resolves each side independently. Realistic mixed case — real measured upstream sensor + predicted pump outflow — now feeds the predicted-volume integrator. Was reading only `flow.predicted.*` so a real upstream sensor (which writes `flow.measured.*`) never moved the level. Editor: - New `holdLevel` and `deadZoneKeepAlivePercent` defaults + side-panel input rows in the levelbased mode preview. - Add the missing `ps-mode-line-holdLevel` SVG marker (was declared in the side-panel coupling but the SVG element didn't exist, so the dashed line never rendered). - Relax stopLevel marker gate so it renders for any non-negative typed value — start/stop ordering is the ribbon's job, not the marker's (was hiding the line whenever startLevel was momentarily smaller). - Add holdLevel to the marker loop in mode-preview so changes track. - Add stopLevel + holdLevel + maxLevel to all three bindRedraw lists (basin-diagram, mode-preview, bounds.apply) so the SVG, validation ribbon, and HTML5 min/max attrs update on every edit. - Initialise stopLevel + holdLevel + deadZoneKeepAlivePercent inputs in oneditprepare so reopening the editor shows the saved values. - nodeClass passes holdLevel + deadZoneKeepAlivePercent into the domain config. Tests: - New test/basic/_probe_upstream_emit.test.js: confirms the parent surfaces flow.measured.upstream.* on Port 0 after a measurement child write — pins the previously-invisible measured variant flow. - flowAggregator.basic.test.js: two new regression cases — measured inflow when predicted side is empty, and the measured-in / predicted-out mixed case. - control-levelBased.basic.test.js: new cases for the holdLevel hold band, the [stopLevel, startLevel] keep-alive, the engagement gate, and the "0 % at startLevel = setDemand" contract. - specificClass.test.js: zone tests adjusted to the new ramp foot. Shifted-ramp tests pin holdLevel = 3 explicitly so their legacy arithmetic (ramp foot at inflowLevel) stays self-consistent. - shifted-ramp-end-to-end.test.js: same holdLevel pin for the same reason. Packaging: - Add .gitignore + .npmignore so the published tarball drops the wiki/, simulations/, test/, tools/, .claude/ etc. The pack went from 1.5 MB (72 files) to ~57 KB (30 files). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 21:36:29 +02:00
// holdLevel — 0 % ramp foot. Defaults to startLevel (no hold band);
// when raised above startLevel, pumps engage at startLevel but emit
// 0 % across [startLevel, holdLevel] before the ramp begins. Bounds:
// startLevel ≤ holdLevel < maxLevel.
setBounds('holdLevel',
Number.isFinite(start) ? start : EPS,
max ?? overflow ?? basinHeight);
Editor: dynamic input bounds + full hierarchy validation, layout polish Bounds (new src/editor/bounds.js): - Sets HTML5 min/max on every level + percent input each redraw, derived from the current values of related inputs so the spinner stops at the basin hierarchy: 0 < outflowLevel < dryRunLevel < startLevel ≤ inflowLevel ≤ shiftLevel ≤ maxLevel ≤ overflowLevel ≤ basinHeight - dryRunPercent capped so dryRunLevel ≤ startLevel given current outflow. - shiftArmPercent ∈ [1, 100]; highVolumeSafety% ∈ [1, 100]. Validation: - New visible ribbon above the basin diagram (#ps-basin-validation) listing every hierarchy violation. The in-SVG warning text is now a small reminder ("⚠ N ordering issues"). - basin-diagram.js owns hierarchy issues; mode-preview.js trimmed to only own shift-specific issues (shift > start, shift ≤ max, shiftArmPercent range, shiftLevel required-when-enabled). - oneditsave blocks Deploy on the union of _psBasinValidationIssues and _psModeValidationIssues with a RED.notify listing all problems. Layout polish: - Side panel widened to 220 px with minmax(0, 1fr) first column so long labels can no longer push the rows past the panel edge. - Basin SVG max-width 380 → 360, gap between side panel and SVG bumped 14 → 28 px. Tank shifted right (x=145 width=110) so the inlet "bottom of pipe" sub-label is no longer clipped on the left edge. - "0 m (datum)" moved below the tank (y=395, centred) so it can't collide with "Outlet / top of pipe" when outflowLevel is near floor. - Zone labels shortened (Spare / Sewage + buffer / Buffer / Dead vol) and only show when the bracketing thresholds are ≥ 28 px apart, so they never sit on a threshold label. - Mode preview axis labels under the chart removed — line colour + side-panel labels + hover-couple already identify each line. Stub <text> elements left hidden to keep the redraw loop simple. Arm-% line + label trimmed in 10 px on the right so they're not clipped. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 14:10:22 +02:00
// Shift inputs (only relevant when shifted ramp enabled).
if (shiftEnabled) {
setBounds('shiftLevel',
Number.isFinite(start) ? start : EPS,
max ?? overflow ?? basinHeight);
setBounds('shiftArmPercent', 1, 100);
}
// Percentages.
// dryRun% capped so dryRunLevel ≤ startLevel.
let dryMax = 99;
if (Number.isFinite(start) && Number.isFinite(outflow) && outflow > 0) {
dryMax = Math.max(0, Math.min(99, ((start / outflow) - 1) * 100));
}
setBounds('dryRunThresholdPercent', 0, dryMax);
// highVol% bounded (1, 100). Equal to 100 means no margin to overflow.
setBounds('highVolumeSafetyThresholdPercent', 1, 100);
},
};
})();