Files
machineGroupControl/wiki/Home.md

149 lines
7.6 KiB
Markdown
Raw Normal View History

# machineGroupControl
feat(mgc): rendezvous planner — same-time landing across all modes Routes every dispatch through a tick-aware planner so all pumps reach their setpoint at the same wall-clock instant t* = max(eta_i), regardless of control strategy or per-pump reaction speed. Architecture (src/movement/): - machineProfile.js – pure snapshot of a registered child (state, position, velocityPctPerS, ladder timings, flowAt / positionForFlow). Reads timings from child.state.config.time (the actual storage location — previous fallback paths silently produced 0 s, collapsing every eta to ramp-only). - moveTrajectory.js – seconds-to-target per machine; handles idle / starting / warmingup / operational / cooling. - movementScheduler.js – t* = max eta over ALL non-noop moves. Every command is delayed so its move finishes at t*. Startup execsequence fires at 0; its flowmovement is gated by max(ladderS, t* − rampS) so a fast pump waits before ramping rather than landing early. useRendezvous=false collapses to all fireAtTickN=0 (legacy fire-and-forget). - movementExecutor.js – wall-clock virtual cursor: each tick fires every command whose fireAtTickN ≤ floor(elapsed/tickS). tick() no longer awaits pending fireCommand promises — the synchronous prologue of handleInput claims the latest-wins gate, which is what race-favouring relies on. Shared dispatch path (src/specificClass.js): - _dispatchFlowDistribution(distribution) — extracted from _optimalControl. Builds profiles, calls movementScheduler.plan, replans the executor, ticks once. Reads config.planner.useRendezvous (default true). - _optimalControl computes its bestCombination and hands off. - equalFlowControl (priorityControl mode) computes its flowDistribution and hands off via ctx.mgc._dispatchFlowDistribution. Same-time landing now applies in BOTH modes. Editor toggle (mgc.html + src/nodeClass.js): - New "Same-time landing" checkbox under Control Strategy. - nodeClass.buildDomainConfig bridges uiConfig.useRendezvous → config.planner.useRendezvous. Default ON. Tests: - New: planner-convergence.integration.test.js (real-time end-to-end diagnostic — drives a 3-pump mixed-state dispatch and asserts both convergence to the demand setpoint AND same-time landing within one tick). - New: planner-rendezvous.integration.test.js (schedule-shape assertions against real pump objects). - New: movementScheduler.basic.test.js — includes a mixed-speed multi-startup case proving the fast pumps wait so all three land together (the regression that prompted this work). - New: movementExecutor.basic.test.js + moveTrajectory.basic.test.js. - Updated executor contract test: tick() must NOT await pending fires. Commands + wiki: - handlers.js: source/mode allow-list gate moved into a shared _gate() helper; every command now checks isValidActionForMode + isValidSourceForMode before dispatching. Status-level commands (set.mode, set.scaling) are allowed in every mode. - commands.basic.test.js: coverage for the new gate behaviour. - wiki regen: Home.md visual-first rewrite + Reference-{Architecture, Contracts,Examples,Limitations}.md split with _Sidebar.md index. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 19:43:55 +02:00
![code-ref](https://img.shields.io/badge/code--ref-26e92b5-blue) ![s88](https://img.shields.io/badge/S88-Unit-50a8d9) ![status](https://img.shields.io/badge/status-trial--ready-brightgreen)
feat(mgc): rendezvous planner — same-time landing across all modes Routes every dispatch through a tick-aware planner so all pumps reach their setpoint at the same wall-clock instant t* = max(eta_i), regardless of control strategy or per-pump reaction speed. Architecture (src/movement/): - machineProfile.js – pure snapshot of a registered child (state, position, velocityPctPerS, ladder timings, flowAt / positionForFlow). Reads timings from child.state.config.time (the actual storage location — previous fallback paths silently produced 0 s, collapsing every eta to ramp-only). - moveTrajectory.js – seconds-to-target per machine; handles idle / starting / warmingup / operational / cooling. - movementScheduler.js – t* = max eta over ALL non-noop moves. Every command is delayed so its move finishes at t*. Startup execsequence fires at 0; its flowmovement is gated by max(ladderS, t* − rampS) so a fast pump waits before ramping rather than landing early. useRendezvous=false collapses to all fireAtTickN=0 (legacy fire-and-forget). - movementExecutor.js – wall-clock virtual cursor: each tick fires every command whose fireAtTickN ≤ floor(elapsed/tickS). tick() no longer awaits pending fireCommand promises — the synchronous prologue of handleInput claims the latest-wins gate, which is what race-favouring relies on. Shared dispatch path (src/specificClass.js): - _dispatchFlowDistribution(distribution) — extracted from _optimalControl. Builds profiles, calls movementScheduler.plan, replans the executor, ticks once. Reads config.planner.useRendezvous (default true). - _optimalControl computes its bestCombination and hands off. - equalFlowControl (priorityControl mode) computes its flowDistribution and hands off via ctx.mgc._dispatchFlowDistribution. Same-time landing now applies in BOTH modes. Editor toggle (mgc.html + src/nodeClass.js): - New "Same-time landing" checkbox under Control Strategy. - nodeClass.buildDomainConfig bridges uiConfig.useRendezvous → config.planner.useRendezvous. Default ON. Tests: - New: planner-convergence.integration.test.js (real-time end-to-end diagnostic — drives a 3-pump mixed-state dispatch and asserts both convergence to the demand setpoint AND same-time landing within one tick). - New: planner-rendezvous.integration.test.js (schedule-shape assertions against real pump objects). - New: movementScheduler.basic.test.js — includes a mixed-speed multi-startup case proving the fast pumps wait so all three land together (the regression that prompted this work). - New: movementExecutor.basic.test.js + moveTrajectory.basic.test.js. - Updated executor contract test: tick() must NOT await pending fires. Commands + wiki: - handlers.js: source/mode allow-list gate moved into a shared _gate() helper; every command now checks isValidActionForMode + isValidSourceForMode before dispatching. Status-level commands (set.mode, set.scaling) are allowed in every mode. - commands.basic.test.js: coverage for the new gate behaviour. - wiki regen: Home.md visual-first rewrite + Reference-{Architecture, Contracts,Examples,Limitations}.md split with _Sidebar.md index. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 19:43:55 +02:00
A `machineGroupControl` (MGC) coordinates two or more `rotatingMachine` children that share a common header. It accepts an operator demand setpoint, enumerates the valid pump combinations against the group's live flow/power envelope, picks the best operating point (BEP-Gravitation by default), and schedules per-machine flow setpoints + start/stop commands with **timing-aware rendezvous** so the running aggregate stays close to demand during transitions.
feat(mgc): rendezvous planner — same-time landing across all modes Routes every dispatch through a tick-aware planner so all pumps reach their setpoint at the same wall-clock instant t* = max(eta_i), regardless of control strategy or per-pump reaction speed. Architecture (src/movement/): - machineProfile.js – pure snapshot of a registered child (state, position, velocityPctPerS, ladder timings, flowAt / positionForFlow). Reads timings from child.state.config.time (the actual storage location — previous fallback paths silently produced 0 s, collapsing every eta to ramp-only). - moveTrajectory.js – seconds-to-target per machine; handles idle / starting / warmingup / operational / cooling. - movementScheduler.js – t* = max eta over ALL non-noop moves. Every command is delayed so its move finishes at t*. Startup execsequence fires at 0; its flowmovement is gated by max(ladderS, t* − rampS) so a fast pump waits before ramping rather than landing early. useRendezvous=false collapses to all fireAtTickN=0 (legacy fire-and-forget). - movementExecutor.js – wall-clock virtual cursor: each tick fires every command whose fireAtTickN ≤ floor(elapsed/tickS). tick() no longer awaits pending fireCommand promises — the synchronous prologue of handleInput claims the latest-wins gate, which is what race-favouring relies on. Shared dispatch path (src/specificClass.js): - _dispatchFlowDistribution(distribution) — extracted from _optimalControl. Builds profiles, calls movementScheduler.plan, replans the executor, ticks once. Reads config.planner.useRendezvous (default true). - _optimalControl computes its bestCombination and hands off. - equalFlowControl (priorityControl mode) computes its flowDistribution and hands off via ctx.mgc._dispatchFlowDistribution. Same-time landing now applies in BOTH modes. Editor toggle (mgc.html + src/nodeClass.js): - New "Same-time landing" checkbox under Control Strategy. - nodeClass.buildDomainConfig bridges uiConfig.useRendezvous → config.planner.useRendezvous. Default ON. Tests: - New: planner-convergence.integration.test.js (real-time end-to-end diagnostic — drives a 3-pump mixed-state dispatch and asserts both convergence to the demand setpoint AND same-time landing within one tick). - New: planner-rendezvous.integration.test.js (schedule-shape assertions against real pump objects). - New: movementScheduler.basic.test.js — includes a mixed-speed multi-startup case proving the fast pumps wait so all three land together (the regression that prompted this work). - New: movementExecutor.basic.test.js + moveTrajectory.basic.test.js. - Updated executor contract test: tick() must NOT await pending fires. Commands + wiki: - handlers.js: source/mode allow-list gate moved into a shared _gate() helper; every command now checks isValidActionForMode + isValidSourceForMode before dispatching. Status-level commands (set.mode, set.scaling) are allowed in every mode. - commands.basic.test.js: coverage for the new gate behaviour. - wiki regen: Home.md visual-first rewrite + Reference-{Architecture, Contracts,Examples,Limitations}.md split with _Sidebar.md index. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 19:43:55 +02:00
---
feat(mgc): rendezvous planner — same-time landing across all modes Routes every dispatch through a tick-aware planner so all pumps reach their setpoint at the same wall-clock instant t* = max(eta_i), regardless of control strategy or per-pump reaction speed. Architecture (src/movement/): - machineProfile.js – pure snapshot of a registered child (state, position, velocityPctPerS, ladder timings, flowAt / positionForFlow). Reads timings from child.state.config.time (the actual storage location — previous fallback paths silently produced 0 s, collapsing every eta to ramp-only). - moveTrajectory.js – seconds-to-target per machine; handles idle / starting / warmingup / operational / cooling. - movementScheduler.js – t* = max eta over ALL non-noop moves. Every command is delayed so its move finishes at t*. Startup execsequence fires at 0; its flowmovement is gated by max(ladderS, t* − rampS) so a fast pump waits before ramping rather than landing early. useRendezvous=false collapses to all fireAtTickN=0 (legacy fire-and-forget). - movementExecutor.js – wall-clock virtual cursor: each tick fires every command whose fireAtTickN ≤ floor(elapsed/tickS). tick() no longer awaits pending fireCommand promises — the synchronous prologue of handleInput claims the latest-wins gate, which is what race-favouring relies on. Shared dispatch path (src/specificClass.js): - _dispatchFlowDistribution(distribution) — extracted from _optimalControl. Builds profiles, calls movementScheduler.plan, replans the executor, ticks once. Reads config.planner.useRendezvous (default true). - _optimalControl computes its bestCombination and hands off. - equalFlowControl (priorityControl mode) computes its flowDistribution and hands off via ctx.mgc._dispatchFlowDistribution. Same-time landing now applies in BOTH modes. Editor toggle (mgc.html + src/nodeClass.js): - New "Same-time landing" checkbox under Control Strategy. - nodeClass.buildDomainConfig bridges uiConfig.useRendezvous → config.planner.useRendezvous. Default ON. Tests: - New: planner-convergence.integration.test.js (real-time end-to-end diagnostic — drives a 3-pump mixed-state dispatch and asserts both convergence to the demand setpoint AND same-time landing within one tick). - New: planner-rendezvous.integration.test.js (schedule-shape assertions against real pump objects). - New: movementScheduler.basic.test.js — includes a mixed-speed multi-startup case proving the fast pumps wait so all three land together (the regression that prompted this work). - New: movementExecutor.basic.test.js + moveTrajectory.basic.test.js. - Updated executor contract test: tick() must NOT await pending fires. Commands + wiki: - handlers.js: source/mode allow-list gate moved into a shared _gate() helper; every command now checks isValidActionForMode + isValidSourceForMode before dispatching. Status-level commands (set.mode, set.scaling) are allowed in every mode. - commands.basic.test.js: coverage for the new gate behaviour. - wiki regen: Home.md visual-first rewrite + Reference-{Architecture, Contracts,Examples,Limitations}.md split with _Sidebar.md index. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 19:43:55 +02:00
## At a glance
| Thing | Value |
|:---|:---|
| What it represents | A pump group sharing one suction + one discharge header |
| S88 level | Unit |
| Use it when | You have 2&nbsp;+ pumps that can substitute for each other on the same header and you want efficient load-sharing |
| Don't use it for | A single pump (wire `rotatingMachine` directly), valves (use `valveGroupControl`), or pumps living behind independent headers |
| Children it accepts | `machine` (rotatingMachine), `measurement` (pressure / others) |
| Parent it talks to | `pumpingStation` (typical), or any node that issues `set.demand` |
---
## How it fits
```mermaid
flowchart LR
parent[pumpingStation<br/>Process Cell]:::pc -->|set.demand| mgc[machineGroupControl<br/>Unit]:::unit
feat(mgc): rendezvous planner — same-time landing across all modes Routes every dispatch through a tick-aware planner so all pumps reach their setpoint at the same wall-clock instant t* = max(eta_i), regardless of control strategy or per-pump reaction speed. Architecture (src/movement/): - machineProfile.js – pure snapshot of a registered child (state, position, velocityPctPerS, ladder timings, flowAt / positionForFlow). Reads timings from child.state.config.time (the actual storage location — previous fallback paths silently produced 0 s, collapsing every eta to ramp-only). - moveTrajectory.js – seconds-to-target per machine; handles idle / starting / warmingup / operational / cooling. - movementScheduler.js – t* = max eta over ALL non-noop moves. Every command is delayed so its move finishes at t*. Startup execsequence fires at 0; its flowmovement is gated by max(ladderS, t* − rampS) so a fast pump waits before ramping rather than landing early. useRendezvous=false collapses to all fireAtTickN=0 (legacy fire-and-forget). - movementExecutor.js – wall-clock virtual cursor: each tick fires every command whose fireAtTickN ≤ floor(elapsed/tickS). tick() no longer awaits pending fireCommand promises — the synchronous prologue of handleInput claims the latest-wins gate, which is what race-favouring relies on. Shared dispatch path (src/specificClass.js): - _dispatchFlowDistribution(distribution) — extracted from _optimalControl. Builds profiles, calls movementScheduler.plan, replans the executor, ticks once. Reads config.planner.useRendezvous (default true). - _optimalControl computes its bestCombination and hands off. - equalFlowControl (priorityControl mode) computes its flowDistribution and hands off via ctx.mgc._dispatchFlowDistribution. Same-time landing now applies in BOTH modes. Editor toggle (mgc.html + src/nodeClass.js): - New "Same-time landing" checkbox under Control Strategy. - nodeClass.buildDomainConfig bridges uiConfig.useRendezvous → config.planner.useRendezvous. Default ON. Tests: - New: planner-convergence.integration.test.js (real-time end-to-end diagnostic — drives a 3-pump mixed-state dispatch and asserts both convergence to the demand setpoint AND same-time landing within one tick). - New: planner-rendezvous.integration.test.js (schedule-shape assertions against real pump objects). - New: movementScheduler.basic.test.js — includes a mixed-speed multi-startup case proving the fast pumps wait so all three land together (the regression that prompted this work). - New: movementExecutor.basic.test.js + moveTrajectory.basic.test.js. - Updated executor contract test: tick() must NOT await pending fires. Commands + wiki: - handlers.js: source/mode allow-list gate moved into a shared _gate() helper; every command now checks isValidActionForMode + isValidSourceForMode before dispatching. Status-level commands (set.mode, set.scaling) are allowed in every mode. - commands.basic.test.js: coverage for the new gate behaviour. - wiki regen: Home.md visual-first rewrite + Reference-{Architecture, Contracts,Examples,Limitations}.md split with _Sidebar.md index. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 19:43:55 +02:00
header[measurement<br/>header pressure]:::ctrl -.measured.-> mgc
mgc -->|flowmovement / execsequence| m_a[rotatingMachine A]:::equip
mgc -->|flowmovement / execsequence| m_b[rotatingMachine B]:::equip
mgc -->|flowmovement / execsequence| m_c[rotatingMachine C]:::equip
mgc -->|child.register| parent
m_a -->|child.register| mgc
m_b -->|child.register| mgc
m_c -->|child.register| mgc
classDef pc fill:#0c99d9,color:#fff
classDef unit fill:#50a8d9,color:#000
classDef equip fill:#86bbdd,color:#000
classDef ctrl fill:#a9daee,color:#000
```
feat(mgc): rendezvous planner — same-time landing across all modes Routes every dispatch through a tick-aware planner so all pumps reach their setpoint at the same wall-clock instant t* = max(eta_i), regardless of control strategy or per-pump reaction speed. Architecture (src/movement/): - machineProfile.js – pure snapshot of a registered child (state, position, velocityPctPerS, ladder timings, flowAt / positionForFlow). Reads timings from child.state.config.time (the actual storage location — previous fallback paths silently produced 0 s, collapsing every eta to ramp-only). - moveTrajectory.js – seconds-to-target per machine; handles idle / starting / warmingup / operational / cooling. - movementScheduler.js – t* = max eta over ALL non-noop moves. Every command is delayed so its move finishes at t*. Startup execsequence fires at 0; its flowmovement is gated by max(ladderS, t* − rampS) so a fast pump waits before ramping rather than landing early. useRendezvous=false collapses to all fireAtTickN=0 (legacy fire-and-forget). - movementExecutor.js – wall-clock virtual cursor: each tick fires every command whose fireAtTickN ≤ floor(elapsed/tickS). tick() no longer awaits pending fireCommand promises — the synchronous prologue of handleInput claims the latest-wins gate, which is what race-favouring relies on. Shared dispatch path (src/specificClass.js): - _dispatchFlowDistribution(distribution) — extracted from _optimalControl. Builds profiles, calls movementScheduler.plan, replans the executor, ticks once. Reads config.planner.useRendezvous (default true). - _optimalControl computes its bestCombination and hands off. - equalFlowControl (priorityControl mode) computes its flowDistribution and hands off via ctx.mgc._dispatchFlowDistribution. Same-time landing now applies in BOTH modes. Editor toggle (mgc.html + src/nodeClass.js): - New "Same-time landing" checkbox under Control Strategy. - nodeClass.buildDomainConfig bridges uiConfig.useRendezvous → config.planner.useRendezvous. Default ON. Tests: - New: planner-convergence.integration.test.js (real-time end-to-end diagnostic — drives a 3-pump mixed-state dispatch and asserts both convergence to the demand setpoint AND same-time landing within one tick). - New: planner-rendezvous.integration.test.js (schedule-shape assertions against real pump objects). - New: movementScheduler.basic.test.js — includes a mixed-speed multi-startup case proving the fast pumps wait so all three land together (the regression that prompted this work). - New: movementExecutor.basic.test.js + moveTrajectory.basic.test.js. - Updated executor contract test: tick() must NOT await pending fires. Commands + wiki: - handlers.js: source/mode allow-list gate moved into a shared _gate() helper; every command now checks isValidActionForMode + isValidSourceForMode before dispatching. Status-level commands (set.mode, set.scaling) are allowed in every mode. - commands.basic.test.js: coverage for the new gate behaviour. - wiki regen: Home.md visual-first rewrite + Reference-{Architecture, Contracts,Examples,Limitations}.md split with _Sidebar.md index. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 19:43:55 +02:00
S88 colours are anchored in `.claude/rules/node-red-flow-layout.md`.
feat(mgc): rendezvous planner — same-time landing across all modes Routes every dispatch through a tick-aware planner so all pumps reach their setpoint at the same wall-clock instant t* = max(eta_i), regardless of control strategy or per-pump reaction speed. Architecture (src/movement/): - machineProfile.js – pure snapshot of a registered child (state, position, velocityPctPerS, ladder timings, flowAt / positionForFlow). Reads timings from child.state.config.time (the actual storage location — previous fallback paths silently produced 0 s, collapsing every eta to ramp-only). - moveTrajectory.js – seconds-to-target per machine; handles idle / starting / warmingup / operational / cooling. - movementScheduler.js – t* = max eta over ALL non-noop moves. Every command is delayed so its move finishes at t*. Startup execsequence fires at 0; its flowmovement is gated by max(ladderS, t* − rampS) so a fast pump waits before ramping rather than landing early. useRendezvous=false collapses to all fireAtTickN=0 (legacy fire-and-forget). - movementExecutor.js – wall-clock virtual cursor: each tick fires every command whose fireAtTickN ≤ floor(elapsed/tickS). tick() no longer awaits pending fireCommand promises — the synchronous prologue of handleInput claims the latest-wins gate, which is what race-favouring relies on. Shared dispatch path (src/specificClass.js): - _dispatchFlowDistribution(distribution) — extracted from _optimalControl. Builds profiles, calls movementScheduler.plan, replans the executor, ticks once. Reads config.planner.useRendezvous (default true). - _optimalControl computes its bestCombination and hands off. - equalFlowControl (priorityControl mode) computes its flowDistribution and hands off via ctx.mgc._dispatchFlowDistribution. Same-time landing now applies in BOTH modes. Editor toggle (mgc.html + src/nodeClass.js): - New "Same-time landing" checkbox under Control Strategy. - nodeClass.buildDomainConfig bridges uiConfig.useRendezvous → config.planner.useRendezvous. Default ON. Tests: - New: planner-convergence.integration.test.js (real-time end-to-end diagnostic — drives a 3-pump mixed-state dispatch and asserts both convergence to the demand setpoint AND same-time landing within one tick). - New: planner-rendezvous.integration.test.js (schedule-shape assertions against real pump objects). - New: movementScheduler.basic.test.js — includes a mixed-speed multi-startup case proving the fast pumps wait so all three land together (the regression that prompted this work). - New: movementExecutor.basic.test.js + moveTrajectory.basic.test.js. - Updated executor contract test: tick() must NOT await pending fires. Commands + wiki: - handlers.js: source/mode allow-list gate moved into a shared _gate() helper; every command now checks isValidActionForMode + isValidSourceForMode before dispatching. Status-level commands (set.mode, set.scaling) are allowed in every mode. - commands.basic.test.js: coverage for the new gate behaviour. - wiki regen: Home.md visual-first rewrite + Reference-{Architecture, Contracts,Examples,Limitations}.md split with _Sidebar.md index. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 19:43:55 +02:00
---
feat(mgc): rendezvous planner — same-time landing across all modes Routes every dispatch through a tick-aware planner so all pumps reach their setpoint at the same wall-clock instant t* = max(eta_i), regardless of control strategy or per-pump reaction speed. Architecture (src/movement/): - machineProfile.js – pure snapshot of a registered child (state, position, velocityPctPerS, ladder timings, flowAt / positionForFlow). Reads timings from child.state.config.time (the actual storage location — previous fallback paths silently produced 0 s, collapsing every eta to ramp-only). - moveTrajectory.js – seconds-to-target per machine; handles idle / starting / warmingup / operational / cooling. - movementScheduler.js – t* = max eta over ALL non-noop moves. Every command is delayed so its move finishes at t*. Startup execsequence fires at 0; its flowmovement is gated by max(ladderS, t* − rampS) so a fast pump waits before ramping rather than landing early. useRendezvous=false collapses to all fireAtTickN=0 (legacy fire-and-forget). - movementExecutor.js – wall-clock virtual cursor: each tick fires every command whose fireAtTickN ≤ floor(elapsed/tickS). tick() no longer awaits pending fireCommand promises — the synchronous prologue of handleInput claims the latest-wins gate, which is what race-favouring relies on. Shared dispatch path (src/specificClass.js): - _dispatchFlowDistribution(distribution) — extracted from _optimalControl. Builds profiles, calls movementScheduler.plan, replans the executor, ticks once. Reads config.planner.useRendezvous (default true). - _optimalControl computes its bestCombination and hands off. - equalFlowControl (priorityControl mode) computes its flowDistribution and hands off via ctx.mgc._dispatchFlowDistribution. Same-time landing now applies in BOTH modes. Editor toggle (mgc.html + src/nodeClass.js): - New "Same-time landing" checkbox under Control Strategy. - nodeClass.buildDomainConfig bridges uiConfig.useRendezvous → config.planner.useRendezvous. Default ON. Tests: - New: planner-convergence.integration.test.js (real-time end-to-end diagnostic — drives a 3-pump mixed-state dispatch and asserts both convergence to the demand setpoint AND same-time landing within one tick). - New: planner-rendezvous.integration.test.js (schedule-shape assertions against real pump objects). - New: movementScheduler.basic.test.js — includes a mixed-speed multi-startup case proving the fast pumps wait so all three land together (the regression that prompted this work). - New: movementExecutor.basic.test.js + moveTrajectory.basic.test.js. - Updated executor contract test: tick() must NOT await pending fires. Commands + wiki: - handlers.js: source/mode allow-list gate moved into a shared _gate() helper; every command now checks isValidActionForMode + isValidSourceForMode before dispatching. Status-level commands (set.mode, set.scaling) are allowed in every mode. - commands.basic.test.js: coverage for the new gate behaviour. - wiki regen: Home.md visual-first rewrite + Reference-{Architecture, Contracts,Examples,Limitations}.md split with _Sidebar.md index. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 19:43:55 +02:00
## Try it &mdash; 3-minute demo
feat(mgc): rendezvous planner — same-time landing across all modes Routes every dispatch through a tick-aware planner so all pumps reach their setpoint at the same wall-clock instant t* = max(eta_i), regardless of control strategy or per-pump reaction speed. Architecture (src/movement/): - machineProfile.js – pure snapshot of a registered child (state, position, velocityPctPerS, ladder timings, flowAt / positionForFlow). Reads timings from child.state.config.time (the actual storage location — previous fallback paths silently produced 0 s, collapsing every eta to ramp-only). - moveTrajectory.js – seconds-to-target per machine; handles idle / starting / warmingup / operational / cooling. - movementScheduler.js – t* = max eta over ALL non-noop moves. Every command is delayed so its move finishes at t*. Startup execsequence fires at 0; its flowmovement is gated by max(ladderS, t* − rampS) so a fast pump waits before ramping rather than landing early. useRendezvous=false collapses to all fireAtTickN=0 (legacy fire-and-forget). - movementExecutor.js – wall-clock virtual cursor: each tick fires every command whose fireAtTickN ≤ floor(elapsed/tickS). tick() no longer awaits pending fireCommand promises — the synchronous prologue of handleInput claims the latest-wins gate, which is what race-favouring relies on. Shared dispatch path (src/specificClass.js): - _dispatchFlowDistribution(distribution) — extracted from _optimalControl. Builds profiles, calls movementScheduler.plan, replans the executor, ticks once. Reads config.planner.useRendezvous (default true). - _optimalControl computes its bestCombination and hands off. - equalFlowControl (priorityControl mode) computes its flowDistribution and hands off via ctx.mgc._dispatchFlowDistribution. Same-time landing now applies in BOTH modes. Editor toggle (mgc.html + src/nodeClass.js): - New "Same-time landing" checkbox under Control Strategy. - nodeClass.buildDomainConfig bridges uiConfig.useRendezvous → config.planner.useRendezvous. Default ON. Tests: - New: planner-convergence.integration.test.js (real-time end-to-end diagnostic — drives a 3-pump mixed-state dispatch and asserts both convergence to the demand setpoint AND same-time landing within one tick). - New: planner-rendezvous.integration.test.js (schedule-shape assertions against real pump objects). - New: movementScheduler.basic.test.js — includes a mixed-speed multi-startup case proving the fast pumps wait so all three land together (the regression that prompted this work). - New: movementExecutor.basic.test.js + moveTrajectory.basic.test.js. - Updated executor contract test: tick() must NOT await pending fires. Commands + wiki: - handlers.js: source/mode allow-list gate moved into a shared _gate() helper; every command now checks isValidActionForMode + isValidSourceForMode before dispatching. Status-level commands (set.mode, set.scaling) are allowed in every mode. - commands.basic.test.js: coverage for the new gate behaviour. - wiki regen: Home.md visual-first rewrite + Reference-{Architecture, Contracts,Examples,Limitations}.md split with _Sidebar.md index. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 19:43:55 +02:00
Import the basic example flow, deploy, and watch three pumps come online together when demand rises.
feat(mgc): rendezvous planner — same-time landing across all modes Routes every dispatch through a tick-aware planner so all pumps reach their setpoint at the same wall-clock instant t* = max(eta_i), regardless of control strategy or per-pump reaction speed. Architecture (src/movement/): - machineProfile.js – pure snapshot of a registered child (state, position, velocityPctPerS, ladder timings, flowAt / positionForFlow). Reads timings from child.state.config.time (the actual storage location — previous fallback paths silently produced 0 s, collapsing every eta to ramp-only). - moveTrajectory.js – seconds-to-target per machine; handles idle / starting / warmingup / operational / cooling. - movementScheduler.js – t* = max eta over ALL non-noop moves. Every command is delayed so its move finishes at t*. Startup execsequence fires at 0; its flowmovement is gated by max(ladderS, t* − rampS) so a fast pump waits before ramping rather than landing early. useRendezvous=false collapses to all fireAtTickN=0 (legacy fire-and-forget). - movementExecutor.js – wall-clock virtual cursor: each tick fires every command whose fireAtTickN ≤ floor(elapsed/tickS). tick() no longer awaits pending fireCommand promises — the synchronous prologue of handleInput claims the latest-wins gate, which is what race-favouring relies on. Shared dispatch path (src/specificClass.js): - _dispatchFlowDistribution(distribution) — extracted from _optimalControl. Builds profiles, calls movementScheduler.plan, replans the executor, ticks once. Reads config.planner.useRendezvous (default true). - _optimalControl computes its bestCombination and hands off. - equalFlowControl (priorityControl mode) computes its flowDistribution and hands off via ctx.mgc._dispatchFlowDistribution. Same-time landing now applies in BOTH modes. Editor toggle (mgc.html + src/nodeClass.js): - New "Same-time landing" checkbox under Control Strategy. - nodeClass.buildDomainConfig bridges uiConfig.useRendezvous → config.planner.useRendezvous. Default ON. Tests: - New: planner-convergence.integration.test.js (real-time end-to-end diagnostic — drives a 3-pump mixed-state dispatch and asserts both convergence to the demand setpoint AND same-time landing within one tick). - New: planner-rendezvous.integration.test.js (schedule-shape assertions against real pump objects). - New: movementScheduler.basic.test.js — includes a mixed-speed multi-startup case proving the fast pumps wait so all three land together (the regression that prompted this work). - New: movementExecutor.basic.test.js + moveTrajectory.basic.test.js. - Updated executor contract test: tick() must NOT await pending fires. Commands + wiki: - handlers.js: source/mode allow-list gate moved into a shared _gate() helper; every command now checks isValidActionForMode + isValidSourceForMode before dispatching. Status-level commands (set.mode, set.scaling) are allowed in every mode. - commands.basic.test.js: coverage for the new gate behaviour. - wiki regen: Home.md visual-first rewrite + Reference-{Architecture, Contracts,Examples,Limitations}.md split with _Sidebar.md index. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 19:43:55 +02:00
```bash
curl -X POST -H 'Content-Type: application/json' \
--data @nodes/machineGroupControl/examples/01-Basic.json \
http://localhost:1880/flow
```
feat(mgc): rendezvous planner — same-time landing across all modes Routes every dispatch through a tick-aware planner so all pumps reach their setpoint at the same wall-clock instant t* = max(eta_i), regardless of control strategy or per-pump reaction speed. Architecture (src/movement/): - machineProfile.js – pure snapshot of a registered child (state, position, velocityPctPerS, ladder timings, flowAt / positionForFlow). Reads timings from child.state.config.time (the actual storage location — previous fallback paths silently produced 0 s, collapsing every eta to ramp-only). - moveTrajectory.js – seconds-to-target per machine; handles idle / starting / warmingup / operational / cooling. - movementScheduler.js – t* = max eta over ALL non-noop moves. Every command is delayed so its move finishes at t*. Startup execsequence fires at 0; its flowmovement is gated by max(ladderS, t* − rampS) so a fast pump waits before ramping rather than landing early. useRendezvous=false collapses to all fireAtTickN=0 (legacy fire-and-forget). - movementExecutor.js – wall-clock virtual cursor: each tick fires every command whose fireAtTickN ≤ floor(elapsed/tickS). tick() no longer awaits pending fireCommand promises — the synchronous prologue of handleInput claims the latest-wins gate, which is what race-favouring relies on. Shared dispatch path (src/specificClass.js): - _dispatchFlowDistribution(distribution) — extracted from _optimalControl. Builds profiles, calls movementScheduler.plan, replans the executor, ticks once. Reads config.planner.useRendezvous (default true). - _optimalControl computes its bestCombination and hands off. - equalFlowControl (priorityControl mode) computes its flowDistribution and hands off via ctx.mgc._dispatchFlowDistribution. Same-time landing now applies in BOTH modes. Editor toggle (mgc.html + src/nodeClass.js): - New "Same-time landing" checkbox under Control Strategy. - nodeClass.buildDomainConfig bridges uiConfig.useRendezvous → config.planner.useRendezvous. Default ON. Tests: - New: planner-convergence.integration.test.js (real-time end-to-end diagnostic — drives a 3-pump mixed-state dispatch and asserts both convergence to the demand setpoint AND same-time landing within one tick). - New: planner-rendezvous.integration.test.js (schedule-shape assertions against real pump objects). - New: movementScheduler.basic.test.js — includes a mixed-speed multi-startup case proving the fast pumps wait so all three land together (the regression that prompted this work). - New: movementExecutor.basic.test.js + moveTrajectory.basic.test.js. - Updated executor contract test: tick() must NOT await pending fires. Commands + wiki: - handlers.js: source/mode allow-list gate moved into a shared _gate() helper; every command now checks isValidActionForMode + isValidSourceForMode before dispatching. Status-level commands (set.mode, set.scaling) are allowed in every mode. - commands.basic.test.js: coverage for the new gate behaviour. - wiki regen: Home.md visual-first rewrite + Reference-{Architecture, Contracts,Examples,Limitations}.md split with _Sidebar.md index. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 19:43:55 +02:00
What to click in the dashboard after deploy:
feat(mgc): rendezvous planner — same-time landing across all modes Routes every dispatch through a tick-aware planner so all pumps reach their setpoint at the same wall-clock instant t* = max(eta_i), regardless of control strategy or per-pump reaction speed. Architecture (src/movement/): - machineProfile.js – pure snapshot of a registered child (state, position, velocityPctPerS, ladder timings, flowAt / positionForFlow). Reads timings from child.state.config.time (the actual storage location — previous fallback paths silently produced 0 s, collapsing every eta to ramp-only). - moveTrajectory.js – seconds-to-target per machine; handles idle / starting / warmingup / operational / cooling. - movementScheduler.js – t* = max eta over ALL non-noop moves. Every command is delayed so its move finishes at t*. Startup execsequence fires at 0; its flowmovement is gated by max(ladderS, t* − rampS) so a fast pump waits before ramping rather than landing early. useRendezvous=false collapses to all fireAtTickN=0 (legacy fire-and-forget). - movementExecutor.js – wall-clock virtual cursor: each tick fires every command whose fireAtTickN ≤ floor(elapsed/tickS). tick() no longer awaits pending fireCommand promises — the synchronous prologue of handleInput claims the latest-wins gate, which is what race-favouring relies on. Shared dispatch path (src/specificClass.js): - _dispatchFlowDistribution(distribution) — extracted from _optimalControl. Builds profiles, calls movementScheduler.plan, replans the executor, ticks once. Reads config.planner.useRendezvous (default true). - _optimalControl computes its bestCombination and hands off. - equalFlowControl (priorityControl mode) computes its flowDistribution and hands off via ctx.mgc._dispatchFlowDistribution. Same-time landing now applies in BOTH modes. Editor toggle (mgc.html + src/nodeClass.js): - New "Same-time landing" checkbox under Control Strategy. - nodeClass.buildDomainConfig bridges uiConfig.useRendezvous → config.planner.useRendezvous. Default ON. Tests: - New: planner-convergence.integration.test.js (real-time end-to-end diagnostic — drives a 3-pump mixed-state dispatch and asserts both convergence to the demand setpoint AND same-time landing within one tick). - New: planner-rendezvous.integration.test.js (schedule-shape assertions against real pump objects). - New: movementScheduler.basic.test.js — includes a mixed-speed multi-startup case proving the fast pumps wait so all three land together (the regression that prompted this work). - New: movementExecutor.basic.test.js + moveTrajectory.basic.test.js. - Updated executor contract test: tick() must NOT await pending fires. Commands + wiki: - handlers.js: source/mode allow-list gate moved into a shared _gate() helper; every command now checks isValidActionForMode + isValidSourceForMode before dispatching. Status-level commands (set.mode, set.scaling) are allowed in every mode. - commands.basic.test.js: coverage for the new gate behaviour. - wiki regen: Home.md visual-first rewrite + Reference-{Architecture, Contracts,Examples,Limitations}.md split with _Sidebar.md index. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 19:43:55 +02:00
1. The Setup group auto-fires `virtualControl` + `cmd.startup` on each child pump after ~1.5&nbsp;s.
2. `set.demand = 50` (bare number = percent of group capacity) &rarr; MGC picks the best 1- or 2-pump combination by BEP-Gravitation.
3. `set.demand = { value: 80, unit: "m3/h" }` &rarr; absolute-flow setpoint.
4. `set.mode = priorityControl` &rarr; equal-flow distribution by priority order.
5. `set.demand = -1` &rarr; operator stop-all; `turnOffAllMachines` cancels any pending dispatch and shuts every active pump down.
feat(mgc): rendezvous planner — same-time landing across all modes Routes every dispatch through a tick-aware planner so all pumps reach their setpoint at the same wall-clock instant t* = max(eta_i), regardless of control strategy or per-pump reaction speed. Architecture (src/movement/): - machineProfile.js – pure snapshot of a registered child (state, position, velocityPctPerS, ladder timings, flowAt / positionForFlow). Reads timings from child.state.config.time (the actual storage location — previous fallback paths silently produced 0 s, collapsing every eta to ramp-only). - moveTrajectory.js – seconds-to-target per machine; handles idle / starting / warmingup / operational / cooling. - movementScheduler.js – t* = max eta over ALL non-noop moves. Every command is delayed so its move finishes at t*. Startup execsequence fires at 0; its flowmovement is gated by max(ladderS, t* − rampS) so a fast pump waits before ramping rather than landing early. useRendezvous=false collapses to all fireAtTickN=0 (legacy fire-and-forget). - movementExecutor.js – wall-clock virtual cursor: each tick fires every command whose fireAtTickN ≤ floor(elapsed/tickS). tick() no longer awaits pending fireCommand promises — the synchronous prologue of handleInput claims the latest-wins gate, which is what race-favouring relies on. Shared dispatch path (src/specificClass.js): - _dispatchFlowDistribution(distribution) — extracted from _optimalControl. Builds profiles, calls movementScheduler.plan, replans the executor, ticks once. Reads config.planner.useRendezvous (default true). - _optimalControl computes its bestCombination and hands off. - equalFlowControl (priorityControl mode) computes its flowDistribution and hands off via ctx.mgc._dispatchFlowDistribution. Same-time landing now applies in BOTH modes. Editor toggle (mgc.html + src/nodeClass.js): - New "Same-time landing" checkbox under Control Strategy. - nodeClass.buildDomainConfig bridges uiConfig.useRendezvous → config.planner.useRendezvous. Default ON. Tests: - New: planner-convergence.integration.test.js (real-time end-to-end diagnostic — drives a 3-pump mixed-state dispatch and asserts both convergence to the demand setpoint AND same-time landing within one tick). - New: planner-rendezvous.integration.test.js (schedule-shape assertions against real pump objects). - New: movementScheduler.basic.test.js — includes a mixed-speed multi-startup case proving the fast pumps wait so all three land together (the regression that prompted this work). - New: movementExecutor.basic.test.js + moveTrajectory.basic.test.js. - Updated executor contract test: tick() must NOT await pending fires. Commands + wiki: - handlers.js: source/mode allow-list gate moved into a shared _gate() helper; every command now checks isValidActionForMode + isValidSourceForMode before dispatching. Status-level commands (set.mode, set.scaling) are allowed in every mode. - commands.basic.test.js: coverage for the new gate behaviour. - wiki regen: Home.md visual-first rewrite + Reference-{Architecture, Contracts,Examples,Limitations}.md split with _Sidebar.md index. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 19:43:55 +02:00
> [!IMPORTANT]
> **GIF needed.** Demo recording of demand 50 % &rarr; 100 % &rarr; -1 with the live status panel. Save as `wiki/_partial-gifs/machineGroupControl/01-basic-demo.gif`, target &le; 1&nbsp;MB after `gifsicle -O3 --lossy=80`.
feat(mgc): rendezvous planner — same-time landing across all modes Routes every dispatch through a tick-aware planner so all pumps reach their setpoint at the same wall-clock instant t* = max(eta_i), regardless of control strategy or per-pump reaction speed. Architecture (src/movement/): - machineProfile.js – pure snapshot of a registered child (state, position, velocityPctPerS, ladder timings, flowAt / positionForFlow). Reads timings from child.state.config.time (the actual storage location — previous fallback paths silently produced 0 s, collapsing every eta to ramp-only). - moveTrajectory.js – seconds-to-target per machine; handles idle / starting / warmingup / operational / cooling. - movementScheduler.js – t* = max eta over ALL non-noop moves. Every command is delayed so its move finishes at t*. Startup execsequence fires at 0; its flowmovement is gated by max(ladderS, t* − rampS) so a fast pump waits before ramping rather than landing early. useRendezvous=false collapses to all fireAtTickN=0 (legacy fire-and-forget). - movementExecutor.js – wall-clock virtual cursor: each tick fires every command whose fireAtTickN ≤ floor(elapsed/tickS). tick() no longer awaits pending fireCommand promises — the synchronous prologue of handleInput claims the latest-wins gate, which is what race-favouring relies on. Shared dispatch path (src/specificClass.js): - _dispatchFlowDistribution(distribution) — extracted from _optimalControl. Builds profiles, calls movementScheduler.plan, replans the executor, ticks once. Reads config.planner.useRendezvous (default true). - _optimalControl computes its bestCombination and hands off. - equalFlowControl (priorityControl mode) computes its flowDistribution and hands off via ctx.mgc._dispatchFlowDistribution. Same-time landing now applies in BOTH modes. Editor toggle (mgc.html + src/nodeClass.js): - New "Same-time landing" checkbox under Control Strategy. - nodeClass.buildDomainConfig bridges uiConfig.useRendezvous → config.planner.useRendezvous. Default ON. Tests: - New: planner-convergence.integration.test.js (real-time end-to-end diagnostic — drives a 3-pump mixed-state dispatch and asserts both convergence to the demand setpoint AND same-time landing within one tick). - New: planner-rendezvous.integration.test.js (schedule-shape assertions against real pump objects). - New: movementScheduler.basic.test.js — includes a mixed-speed multi-startup case proving the fast pumps wait so all three land together (the regression that prompted this work). - New: movementExecutor.basic.test.js + moveTrajectory.basic.test.js. - Updated executor contract test: tick() must NOT await pending fires. Commands + wiki: - handlers.js: source/mode allow-list gate moved into a shared _gate() helper; every command now checks isValidActionForMode + isValidSourceForMode before dispatching. Status-level commands (set.mode, set.scaling) are allowed in every mode. - commands.basic.test.js: coverage for the new gate behaviour. - wiki regen: Home.md visual-first rewrite + Reference-{Architecture, Contracts,Examples,Limitations}.md split with _Sidebar.md index. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 19:43:55 +02:00
---
feat(mgc): rendezvous planner — same-time landing across all modes Routes every dispatch through a tick-aware planner so all pumps reach their setpoint at the same wall-clock instant t* = max(eta_i), regardless of control strategy or per-pump reaction speed. Architecture (src/movement/): - machineProfile.js – pure snapshot of a registered child (state, position, velocityPctPerS, ladder timings, flowAt / positionForFlow). Reads timings from child.state.config.time (the actual storage location — previous fallback paths silently produced 0 s, collapsing every eta to ramp-only). - moveTrajectory.js – seconds-to-target per machine; handles idle / starting / warmingup / operational / cooling. - movementScheduler.js – t* = max eta over ALL non-noop moves. Every command is delayed so its move finishes at t*. Startup execsequence fires at 0; its flowmovement is gated by max(ladderS, t* − rampS) so a fast pump waits before ramping rather than landing early. useRendezvous=false collapses to all fireAtTickN=0 (legacy fire-and-forget). - movementExecutor.js – wall-clock virtual cursor: each tick fires every command whose fireAtTickN ≤ floor(elapsed/tickS). tick() no longer awaits pending fireCommand promises — the synchronous prologue of handleInput claims the latest-wins gate, which is what race-favouring relies on. Shared dispatch path (src/specificClass.js): - _dispatchFlowDistribution(distribution) — extracted from _optimalControl. Builds profiles, calls movementScheduler.plan, replans the executor, ticks once. Reads config.planner.useRendezvous (default true). - _optimalControl computes its bestCombination and hands off. - equalFlowControl (priorityControl mode) computes its flowDistribution and hands off via ctx.mgc._dispatchFlowDistribution. Same-time landing now applies in BOTH modes. Editor toggle (mgc.html + src/nodeClass.js): - New "Same-time landing" checkbox under Control Strategy. - nodeClass.buildDomainConfig bridges uiConfig.useRendezvous → config.planner.useRendezvous. Default ON. Tests: - New: planner-convergence.integration.test.js (real-time end-to-end diagnostic — drives a 3-pump mixed-state dispatch and asserts both convergence to the demand setpoint AND same-time landing within one tick). - New: planner-rendezvous.integration.test.js (schedule-shape assertions against real pump objects). - New: movementScheduler.basic.test.js — includes a mixed-speed multi-startup case proving the fast pumps wait so all three land together (the regression that prompted this work). - New: movementExecutor.basic.test.js + moveTrajectory.basic.test.js. - Updated executor contract test: tick() must NOT await pending fires. Commands + wiki: - handlers.js: source/mode allow-list gate moved into a shared _gate() helper; every command now checks isValidActionForMode + isValidSourceForMode before dispatching. Status-level commands (set.mode, set.scaling) are allowed in every mode. - commands.basic.test.js: coverage for the new gate behaviour. - wiki regen: Home.md visual-first rewrite + Reference-{Architecture, Contracts,Examples,Limitations}.md split with _Sidebar.md index. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 19:43:55 +02:00
## The three things you'll send
feat(mgc): rendezvous planner — same-time landing across all modes Routes every dispatch through a tick-aware planner so all pumps reach their setpoint at the same wall-clock instant t* = max(eta_i), regardless of control strategy or per-pump reaction speed. Architecture (src/movement/): - machineProfile.js – pure snapshot of a registered child (state, position, velocityPctPerS, ladder timings, flowAt / positionForFlow). Reads timings from child.state.config.time (the actual storage location — previous fallback paths silently produced 0 s, collapsing every eta to ramp-only). - moveTrajectory.js – seconds-to-target per machine; handles idle / starting / warmingup / operational / cooling. - movementScheduler.js – t* = max eta over ALL non-noop moves. Every command is delayed so its move finishes at t*. Startup execsequence fires at 0; its flowmovement is gated by max(ladderS, t* − rampS) so a fast pump waits before ramping rather than landing early. useRendezvous=false collapses to all fireAtTickN=0 (legacy fire-and-forget). - movementExecutor.js – wall-clock virtual cursor: each tick fires every command whose fireAtTickN ≤ floor(elapsed/tickS). tick() no longer awaits pending fireCommand promises — the synchronous prologue of handleInput claims the latest-wins gate, which is what race-favouring relies on. Shared dispatch path (src/specificClass.js): - _dispatchFlowDistribution(distribution) — extracted from _optimalControl. Builds profiles, calls movementScheduler.plan, replans the executor, ticks once. Reads config.planner.useRendezvous (default true). - _optimalControl computes its bestCombination and hands off. - equalFlowControl (priorityControl mode) computes its flowDistribution and hands off via ctx.mgc._dispatchFlowDistribution. Same-time landing now applies in BOTH modes. Editor toggle (mgc.html + src/nodeClass.js): - New "Same-time landing" checkbox under Control Strategy. - nodeClass.buildDomainConfig bridges uiConfig.useRendezvous → config.planner.useRendezvous. Default ON. Tests: - New: planner-convergence.integration.test.js (real-time end-to-end diagnostic — drives a 3-pump mixed-state dispatch and asserts both convergence to the demand setpoint AND same-time landing within one tick). - New: planner-rendezvous.integration.test.js (schedule-shape assertions against real pump objects). - New: movementScheduler.basic.test.js — includes a mixed-speed multi-startup case proving the fast pumps wait so all three land together (the regression that prompted this work). - New: movementExecutor.basic.test.js + moveTrajectory.basic.test.js. - Updated executor contract test: tick() must NOT await pending fires. Commands + wiki: - handlers.js: source/mode allow-list gate moved into a shared _gate() helper; every command now checks isValidActionForMode + isValidSourceForMode before dispatching. Status-level commands (set.mode, set.scaling) are allowed in every mode. - commands.basic.test.js: coverage for the new gate behaviour. - wiki regen: Home.md visual-first rewrite + Reference-{Architecture, Contracts,Examples,Limitations}.md split with _Sidebar.md index. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 19:43:55 +02:00
`set.demand` is **unit-self-describing** &mdash; the payload itself decides how the value is interpreted. There is no persistent `scaling` state on the orchestrator.
feat(mgc): rendezvous planner — same-time landing across all modes Routes every dispatch through a tick-aware planner so all pumps reach their setpoint at the same wall-clock instant t* = max(eta_i), regardless of control strategy or per-pump reaction speed. Architecture (src/movement/): - machineProfile.js – pure snapshot of a registered child (state, position, velocityPctPerS, ladder timings, flowAt / positionForFlow). Reads timings from child.state.config.time (the actual storage location — previous fallback paths silently produced 0 s, collapsing every eta to ramp-only). - moveTrajectory.js – seconds-to-target per machine; handles idle / starting / warmingup / operational / cooling. - movementScheduler.js – t* = max eta over ALL non-noop moves. Every command is delayed so its move finishes at t*. Startup execsequence fires at 0; its flowmovement is gated by max(ladderS, t* − rampS) so a fast pump waits before ramping rather than landing early. useRendezvous=false collapses to all fireAtTickN=0 (legacy fire-and-forget). - movementExecutor.js – wall-clock virtual cursor: each tick fires every command whose fireAtTickN ≤ floor(elapsed/tickS). tick() no longer awaits pending fireCommand promises — the synchronous prologue of handleInput claims the latest-wins gate, which is what race-favouring relies on. Shared dispatch path (src/specificClass.js): - _dispatchFlowDistribution(distribution) — extracted from _optimalControl. Builds profiles, calls movementScheduler.plan, replans the executor, ticks once. Reads config.planner.useRendezvous (default true). - _optimalControl computes its bestCombination and hands off. - equalFlowControl (priorityControl mode) computes its flowDistribution and hands off via ctx.mgc._dispatchFlowDistribution. Same-time landing now applies in BOTH modes. Editor toggle (mgc.html + src/nodeClass.js): - New "Same-time landing" checkbox under Control Strategy. - nodeClass.buildDomainConfig bridges uiConfig.useRendezvous → config.planner.useRendezvous. Default ON. Tests: - New: planner-convergence.integration.test.js (real-time end-to-end diagnostic — drives a 3-pump mixed-state dispatch and asserts both convergence to the demand setpoint AND same-time landing within one tick). - New: planner-rendezvous.integration.test.js (schedule-shape assertions against real pump objects). - New: movementScheduler.basic.test.js — includes a mixed-speed multi-startup case proving the fast pumps wait so all three land together (the regression that prompted this work). - New: movementExecutor.basic.test.js + moveTrajectory.basic.test.js. - Updated executor contract test: tick() must NOT await pending fires. Commands + wiki: - handlers.js: source/mode allow-list gate moved into a shared _gate() helper; every command now checks isValidActionForMode + isValidSourceForMode before dispatching. Status-level commands (set.mode, set.scaling) are allowed in every mode. - commands.basic.test.js: coverage for the new gate behaviour. - wiki regen: Home.md visual-first rewrite + Reference-{Architecture, Contracts,Examples,Limitations}.md split with _Sidebar.md index. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 19:43:55 +02:00
| Topic | Aliases | Payload | What it does |
|:---|:---|:---|:---|
| `set.mode` | `setMode` | `"optimalControl"` \| `"priorityControl"` \| `"maintenance"` | Switches dispatch strategy. `maintenance` is monitoring-only. |
| `set.demand` | `Qd` | bare number = %; `{value, unit}` for absolute units (`m3/h`, `l/s`, `m3/s`, &hellip;); negative = stop all | Operator demand setpoint. Resolves to canonical m³/s before dispatch. |
| `child.register` | `registerChild` | child node id (string) | Manually register a child (Port 2 wiring does this automatically in most flows). |
feat(mgc): rendezvous planner — same-time landing across all modes Routes every dispatch through a tick-aware planner so all pumps reach their setpoint at the same wall-clock instant t* = max(eta_i), regardless of control strategy or per-pump reaction speed. Architecture (src/movement/): - machineProfile.js – pure snapshot of a registered child (state, position, velocityPctPerS, ladder timings, flowAt / positionForFlow). Reads timings from child.state.config.time (the actual storage location — previous fallback paths silently produced 0 s, collapsing every eta to ramp-only). - moveTrajectory.js – seconds-to-target per machine; handles idle / starting / warmingup / operational / cooling. - movementScheduler.js – t* = max eta over ALL non-noop moves. Every command is delayed so its move finishes at t*. Startup execsequence fires at 0; its flowmovement is gated by max(ladderS, t* − rampS) so a fast pump waits before ramping rather than landing early. useRendezvous=false collapses to all fireAtTickN=0 (legacy fire-and-forget). - movementExecutor.js – wall-clock virtual cursor: each tick fires every command whose fireAtTickN ≤ floor(elapsed/tickS). tick() no longer awaits pending fireCommand promises — the synchronous prologue of handleInput claims the latest-wins gate, which is what race-favouring relies on. Shared dispatch path (src/specificClass.js): - _dispatchFlowDistribution(distribution) — extracted from _optimalControl. Builds profiles, calls movementScheduler.plan, replans the executor, ticks once. Reads config.planner.useRendezvous (default true). - _optimalControl computes its bestCombination and hands off. - equalFlowControl (priorityControl mode) computes its flowDistribution and hands off via ctx.mgc._dispatchFlowDistribution. Same-time landing now applies in BOTH modes. Editor toggle (mgc.html + src/nodeClass.js): - New "Same-time landing" checkbox under Control Strategy. - nodeClass.buildDomainConfig bridges uiConfig.useRendezvous → config.planner.useRendezvous. Default ON. Tests: - New: planner-convergence.integration.test.js (real-time end-to-end diagnostic — drives a 3-pump mixed-state dispatch and asserts both convergence to the demand setpoint AND same-time landing within one tick). - New: planner-rendezvous.integration.test.js (schedule-shape assertions against real pump objects). - New: movementScheduler.basic.test.js — includes a mixed-speed multi-startup case proving the fast pumps wait so all three land together (the regression that prompted this work). - New: movementExecutor.basic.test.js + moveTrajectory.basic.test.js. - Updated executor contract test: tick() must NOT await pending fires. Commands + wiki: - handlers.js: source/mode allow-list gate moved into a shared _gate() helper; every command now checks isValidActionForMode + isValidSourceForMode before dispatching. Status-level commands (set.mode, set.scaling) are allowed in every mode. - commands.basic.test.js: coverage for the new gate behaviour. - wiki regen: Home.md visual-first rewrite + Reference-{Architecture, Contracts,Examples,Limitations}.md split with _Sidebar.md index. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 19:43:55 +02:00
---
feat(mgc): rendezvous planner — same-time landing across all modes Routes every dispatch through a tick-aware planner so all pumps reach their setpoint at the same wall-clock instant t* = max(eta_i), regardless of control strategy or per-pump reaction speed. Architecture (src/movement/): - machineProfile.js – pure snapshot of a registered child (state, position, velocityPctPerS, ladder timings, flowAt / positionForFlow). Reads timings from child.state.config.time (the actual storage location — previous fallback paths silently produced 0 s, collapsing every eta to ramp-only). - moveTrajectory.js – seconds-to-target per machine; handles idle / starting / warmingup / operational / cooling. - movementScheduler.js – t* = max eta over ALL non-noop moves. Every command is delayed so its move finishes at t*. Startup execsequence fires at 0; its flowmovement is gated by max(ladderS, t* − rampS) so a fast pump waits before ramping rather than landing early. useRendezvous=false collapses to all fireAtTickN=0 (legacy fire-and-forget). - movementExecutor.js – wall-clock virtual cursor: each tick fires every command whose fireAtTickN ≤ floor(elapsed/tickS). tick() no longer awaits pending fireCommand promises — the synchronous prologue of handleInput claims the latest-wins gate, which is what race-favouring relies on. Shared dispatch path (src/specificClass.js): - _dispatchFlowDistribution(distribution) — extracted from _optimalControl. Builds profiles, calls movementScheduler.plan, replans the executor, ticks once. Reads config.planner.useRendezvous (default true). - _optimalControl computes its bestCombination and hands off. - equalFlowControl (priorityControl mode) computes its flowDistribution and hands off via ctx.mgc._dispatchFlowDistribution. Same-time landing now applies in BOTH modes. Editor toggle (mgc.html + src/nodeClass.js): - New "Same-time landing" checkbox under Control Strategy. - nodeClass.buildDomainConfig bridges uiConfig.useRendezvous → config.planner.useRendezvous. Default ON. Tests: - New: planner-convergence.integration.test.js (real-time end-to-end diagnostic — drives a 3-pump mixed-state dispatch and asserts both convergence to the demand setpoint AND same-time landing within one tick). - New: planner-rendezvous.integration.test.js (schedule-shape assertions against real pump objects). - New: movementScheduler.basic.test.js — includes a mixed-speed multi-startup case proving the fast pumps wait so all three land together (the regression that prompted this work). - New: movementExecutor.basic.test.js + moveTrajectory.basic.test.js. - Updated executor contract test: tick() must NOT await pending fires. Commands + wiki: - handlers.js: source/mode allow-list gate moved into a shared _gate() helper; every command now checks isValidActionForMode + isValidSourceForMode before dispatching. Status-level commands (set.mode, set.scaling) are allowed in every mode. - commands.basic.test.js: coverage for the new gate behaviour. - wiki regen: Home.md visual-first rewrite + Reference-{Architecture, Contracts,Examples,Limitations}.md split with _Sidebar.md index. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 19:43:55 +02:00
## What you'll see come out
feat(mgc): rendezvous planner — same-time landing across all modes Routes every dispatch through a tick-aware planner so all pumps reach their setpoint at the same wall-clock instant t* = max(eta_i), regardless of control strategy or per-pump reaction speed. Architecture (src/movement/): - machineProfile.js – pure snapshot of a registered child (state, position, velocityPctPerS, ladder timings, flowAt / positionForFlow). Reads timings from child.state.config.time (the actual storage location — previous fallback paths silently produced 0 s, collapsing every eta to ramp-only). - moveTrajectory.js – seconds-to-target per machine; handles idle / starting / warmingup / operational / cooling. - movementScheduler.js – t* = max eta over ALL non-noop moves. Every command is delayed so its move finishes at t*. Startup execsequence fires at 0; its flowmovement is gated by max(ladderS, t* − rampS) so a fast pump waits before ramping rather than landing early. useRendezvous=false collapses to all fireAtTickN=0 (legacy fire-and-forget). - movementExecutor.js – wall-clock virtual cursor: each tick fires every command whose fireAtTickN ≤ floor(elapsed/tickS). tick() no longer awaits pending fireCommand promises — the synchronous prologue of handleInput claims the latest-wins gate, which is what race-favouring relies on. Shared dispatch path (src/specificClass.js): - _dispatchFlowDistribution(distribution) — extracted from _optimalControl. Builds profiles, calls movementScheduler.plan, replans the executor, ticks once. Reads config.planner.useRendezvous (default true). - _optimalControl computes its bestCombination and hands off. - equalFlowControl (priorityControl mode) computes its flowDistribution and hands off via ctx.mgc._dispatchFlowDistribution. Same-time landing now applies in BOTH modes. Editor toggle (mgc.html + src/nodeClass.js): - New "Same-time landing" checkbox under Control Strategy. - nodeClass.buildDomainConfig bridges uiConfig.useRendezvous → config.planner.useRendezvous. Default ON. Tests: - New: planner-convergence.integration.test.js (real-time end-to-end diagnostic — drives a 3-pump mixed-state dispatch and asserts both convergence to the demand setpoint AND same-time landing within one tick). - New: planner-rendezvous.integration.test.js (schedule-shape assertions against real pump objects). - New: movementScheduler.basic.test.js — includes a mixed-speed multi-startup case proving the fast pumps wait so all three land together (the regression that prompted this work). - New: movementExecutor.basic.test.js + moveTrajectory.basic.test.js. - Updated executor contract test: tick() must NOT await pending fires. Commands + wiki: - handlers.js: source/mode allow-list gate moved into a shared _gate() helper; every command now checks isValidActionForMode + isValidSourceForMode before dispatching. Status-level commands (set.mode, set.scaling) are allowed in every mode. - commands.basic.test.js: coverage for the new gate behaviour. - wiki regen: Home.md visual-first rewrite + Reference-{Architecture, Contracts,Examples,Limitations}.md split with _Sidebar.md index. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 19:43:55 +02:00
Sample Port 0 message (delta-compressed &mdash; only changed fields each tick):
feat(mgc): rendezvous planner — same-time landing across all modes Routes every dispatch through a tick-aware planner so all pumps reach their setpoint at the same wall-clock instant t* = max(eta_i), regardless of control strategy or per-pump reaction speed. Architecture (src/movement/): - machineProfile.js – pure snapshot of a registered child (state, position, velocityPctPerS, ladder timings, flowAt / positionForFlow). Reads timings from child.state.config.time (the actual storage location — previous fallback paths silently produced 0 s, collapsing every eta to ramp-only). - moveTrajectory.js – seconds-to-target per machine; handles idle / starting / warmingup / operational / cooling. - movementScheduler.js – t* = max eta over ALL non-noop moves. Every command is delayed so its move finishes at t*. Startup execsequence fires at 0; its flowmovement is gated by max(ladderS, t* − rampS) so a fast pump waits before ramping rather than landing early. useRendezvous=false collapses to all fireAtTickN=0 (legacy fire-and-forget). - movementExecutor.js – wall-clock virtual cursor: each tick fires every command whose fireAtTickN ≤ floor(elapsed/tickS). tick() no longer awaits pending fireCommand promises — the synchronous prologue of handleInput claims the latest-wins gate, which is what race-favouring relies on. Shared dispatch path (src/specificClass.js): - _dispatchFlowDistribution(distribution) — extracted from _optimalControl. Builds profiles, calls movementScheduler.plan, replans the executor, ticks once. Reads config.planner.useRendezvous (default true). - _optimalControl computes its bestCombination and hands off. - equalFlowControl (priorityControl mode) computes its flowDistribution and hands off via ctx.mgc._dispatchFlowDistribution. Same-time landing now applies in BOTH modes. Editor toggle (mgc.html + src/nodeClass.js): - New "Same-time landing" checkbox under Control Strategy. - nodeClass.buildDomainConfig bridges uiConfig.useRendezvous → config.planner.useRendezvous. Default ON. Tests: - New: planner-convergence.integration.test.js (real-time end-to-end diagnostic — drives a 3-pump mixed-state dispatch and asserts both convergence to the demand setpoint AND same-time landing within one tick). - New: planner-rendezvous.integration.test.js (schedule-shape assertions against real pump objects). - New: movementScheduler.basic.test.js — includes a mixed-speed multi-startup case proving the fast pumps wait so all three land together (the regression that prompted this work). - New: movementExecutor.basic.test.js + moveTrajectory.basic.test.js. - Updated executor contract test: tick() must NOT await pending fires. Commands + wiki: - handlers.js: source/mode allow-list gate moved into a shared _gate() helper; every command now checks isValidActionForMode + isValidSourceForMode before dispatching. Status-level commands (set.mode, set.scaling) are allowed in every mode. - commands.basic.test.js: coverage for the new gate behaviour. - wiki regen: Home.md visual-first rewrite + Reference-{Architecture, Contracts,Examples,Limitations}.md split with _Sidebar.md index. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 19:43:55 +02:00
```json
{
feat(mgc): rendezvous planner — same-time landing across all modes Routes every dispatch through a tick-aware planner so all pumps reach their setpoint at the same wall-clock instant t* = max(eta_i), regardless of control strategy or per-pump reaction speed. Architecture (src/movement/): - machineProfile.js – pure snapshot of a registered child (state, position, velocityPctPerS, ladder timings, flowAt / positionForFlow). Reads timings from child.state.config.time (the actual storage location — previous fallback paths silently produced 0 s, collapsing every eta to ramp-only). - moveTrajectory.js – seconds-to-target per machine; handles idle / starting / warmingup / operational / cooling. - movementScheduler.js – t* = max eta over ALL non-noop moves. Every command is delayed so its move finishes at t*. Startup execsequence fires at 0; its flowmovement is gated by max(ladderS, t* − rampS) so a fast pump waits before ramping rather than landing early. useRendezvous=false collapses to all fireAtTickN=0 (legacy fire-and-forget). - movementExecutor.js – wall-clock virtual cursor: each tick fires every command whose fireAtTickN ≤ floor(elapsed/tickS). tick() no longer awaits pending fireCommand promises — the synchronous prologue of handleInput claims the latest-wins gate, which is what race-favouring relies on. Shared dispatch path (src/specificClass.js): - _dispatchFlowDistribution(distribution) — extracted from _optimalControl. Builds profiles, calls movementScheduler.plan, replans the executor, ticks once. Reads config.planner.useRendezvous (default true). - _optimalControl computes its bestCombination and hands off. - equalFlowControl (priorityControl mode) computes its flowDistribution and hands off via ctx.mgc._dispatchFlowDistribution. Same-time landing now applies in BOTH modes. Editor toggle (mgc.html + src/nodeClass.js): - New "Same-time landing" checkbox under Control Strategy. - nodeClass.buildDomainConfig bridges uiConfig.useRendezvous → config.planner.useRendezvous. Default ON. Tests: - New: planner-convergence.integration.test.js (real-time end-to-end diagnostic — drives a 3-pump mixed-state dispatch and asserts both convergence to the demand setpoint AND same-time landing within one tick). - New: planner-rendezvous.integration.test.js (schedule-shape assertions against real pump objects). - New: movementScheduler.basic.test.js — includes a mixed-speed multi-startup case proving the fast pumps wait so all three land together (the regression that prompted this work). - New: movementExecutor.basic.test.js + moveTrajectory.basic.test.js. - Updated executor contract test: tick() must NOT await pending fires. Commands + wiki: - handlers.js: source/mode allow-list gate moved into a shared _gate() helper; every command now checks isValidActionForMode + isValidSourceForMode before dispatching. Status-level commands (set.mode, set.scaling) are allowed in every mode. - commands.basic.test.js: coverage for the new gate behaviour. - wiki regen: Home.md visual-first rewrite + Reference-{Architecture, Contracts,Examples,Limitations}.md split with _Sidebar.md index. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 19:43:55 +02:00
"topic": "machineGroupControl#MGC1",
"payload": {
"mode": "optimalControl",
"atEquipment_predicted_flow": 42.5,
"downstream_predicted_flow": 42.5,
"atEquipment_predicted_power": 18.0,
"headerDiffPa": 145000,
"headerDiffMbar": 1450,
"flowCapacityMax": 90,
"flowCapacityMin": 6,
"machineCount": 3,
"machineCountActive": 2,
"absDistFromPeak": 0.02,
"relDistFromPeak": 0.10
}
}
feat(mgc): rendezvous planner — same-time landing across all modes Routes every dispatch through a tick-aware planner so all pumps reach their setpoint at the same wall-clock instant t* = max(eta_i), regardless of control strategy or per-pump reaction speed. Architecture (src/movement/): - machineProfile.js – pure snapshot of a registered child (state, position, velocityPctPerS, ladder timings, flowAt / positionForFlow). Reads timings from child.state.config.time (the actual storage location — previous fallback paths silently produced 0 s, collapsing every eta to ramp-only). - moveTrajectory.js – seconds-to-target per machine; handles idle / starting / warmingup / operational / cooling. - movementScheduler.js – t* = max eta over ALL non-noop moves. Every command is delayed so its move finishes at t*. Startup execsequence fires at 0; its flowmovement is gated by max(ladderS, t* − rampS) so a fast pump waits before ramping rather than landing early. useRendezvous=false collapses to all fireAtTickN=0 (legacy fire-and-forget). - movementExecutor.js – wall-clock virtual cursor: each tick fires every command whose fireAtTickN ≤ floor(elapsed/tickS). tick() no longer awaits pending fireCommand promises — the synchronous prologue of handleInput claims the latest-wins gate, which is what race-favouring relies on. Shared dispatch path (src/specificClass.js): - _dispatchFlowDistribution(distribution) — extracted from _optimalControl. Builds profiles, calls movementScheduler.plan, replans the executor, ticks once. Reads config.planner.useRendezvous (default true). - _optimalControl computes its bestCombination and hands off. - equalFlowControl (priorityControl mode) computes its flowDistribution and hands off via ctx.mgc._dispatchFlowDistribution. Same-time landing now applies in BOTH modes. Editor toggle (mgc.html + src/nodeClass.js): - New "Same-time landing" checkbox under Control Strategy. - nodeClass.buildDomainConfig bridges uiConfig.useRendezvous → config.planner.useRendezvous. Default ON. Tests: - New: planner-convergence.integration.test.js (real-time end-to-end diagnostic — drives a 3-pump mixed-state dispatch and asserts both convergence to the demand setpoint AND same-time landing within one tick). - New: planner-rendezvous.integration.test.js (schedule-shape assertions against real pump objects). - New: movementScheduler.basic.test.js — includes a mixed-speed multi-startup case proving the fast pumps wait so all three land together (the regression that prompted this work). - New: movementExecutor.basic.test.js + moveTrajectory.basic.test.js. - Updated executor contract test: tick() must NOT await pending fires. Commands + wiki: - handlers.js: source/mode allow-list gate moved into a shared _gate() helper; every command now checks isValidActionForMode + isValidSourceForMode before dispatching. Status-level commands (set.mode, set.scaling) are allowed in every mode. - commands.basic.test.js: coverage for the new gate behaviour. - wiki regen: Home.md visual-first rewrite + Reference-{Architecture, Contracts,Examples,Limitations}.md split with _Sidebar.md index. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 19:43:55 +02:00
```
feat(mgc): rendezvous planner — same-time landing across all modes Routes every dispatch through a tick-aware planner so all pumps reach their setpoint at the same wall-clock instant t* = max(eta_i), regardless of control strategy or per-pump reaction speed. Architecture (src/movement/): - machineProfile.js – pure snapshot of a registered child (state, position, velocityPctPerS, ladder timings, flowAt / positionForFlow). Reads timings from child.state.config.time (the actual storage location — previous fallback paths silently produced 0 s, collapsing every eta to ramp-only). - moveTrajectory.js – seconds-to-target per machine; handles idle / starting / warmingup / operational / cooling. - movementScheduler.js – t* = max eta over ALL non-noop moves. Every command is delayed so its move finishes at t*. Startup execsequence fires at 0; its flowmovement is gated by max(ladderS, t* − rampS) so a fast pump waits before ramping rather than landing early. useRendezvous=false collapses to all fireAtTickN=0 (legacy fire-and-forget). - movementExecutor.js – wall-clock virtual cursor: each tick fires every command whose fireAtTickN ≤ floor(elapsed/tickS). tick() no longer awaits pending fireCommand promises — the synchronous prologue of handleInput claims the latest-wins gate, which is what race-favouring relies on. Shared dispatch path (src/specificClass.js): - _dispatchFlowDistribution(distribution) — extracted from _optimalControl. Builds profiles, calls movementScheduler.plan, replans the executor, ticks once. Reads config.planner.useRendezvous (default true). - _optimalControl computes its bestCombination and hands off. - equalFlowControl (priorityControl mode) computes its flowDistribution and hands off via ctx.mgc._dispatchFlowDistribution. Same-time landing now applies in BOTH modes. Editor toggle (mgc.html + src/nodeClass.js): - New "Same-time landing" checkbox under Control Strategy. - nodeClass.buildDomainConfig bridges uiConfig.useRendezvous → config.planner.useRendezvous. Default ON. Tests: - New: planner-convergence.integration.test.js (real-time end-to-end diagnostic — drives a 3-pump mixed-state dispatch and asserts both convergence to the demand setpoint AND same-time landing within one tick). - New: planner-rendezvous.integration.test.js (schedule-shape assertions against real pump objects). - New: movementScheduler.basic.test.js — includes a mixed-speed multi-startup case proving the fast pumps wait so all three land together (the regression that prompted this work). - New: movementExecutor.basic.test.js + moveTrajectory.basic.test.js. - Updated executor contract test: tick() must NOT await pending fires. Commands + wiki: - handlers.js: source/mode allow-list gate moved into a shared _gate() helper; every command now checks isValidActionForMode + isValidSourceForMode before dispatching. Status-level commands (set.mode, set.scaling) are allowed in every mode. - commands.basic.test.js: coverage for the new gate behaviour. - wiki regen: Home.md visual-first rewrite + Reference-{Architecture, Contracts,Examples,Limitations}.md split with _Sidebar.md index. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 19:43:55 +02:00
| Field | Meaning |
|:---|:---|
| `mode` | Current dispatch mode. |
| `atEquipment_predicted_flow` / `_power` | Group aggregate at the pump shafts. The optimizer writes intent here; `handlePressureChange` keeps it in sync with the live totals. |
| `downstream_predicted_flow` | Live aggregate mirrored onto DOWNSTREAM &mdash; pumpingStation parents subscribe here. |
| `headerDiffPa` / `headerDiffMbar` | Last header differential the equalizer resolved. Dashboards use it for Q-H plots without re-reading every child. |
| `flowCapacityMax` / `flowCapacityMin` | The group's dynamic envelope at the current header pressure. Defines where `set.demand` (as %) maps to. |
| `machineCount` / `machineCountActive` | All registered children, and how many are in a state other than `off` / `maintenance`. |
| `absDistFromPeak` / `relDistFromPeak` | Distance from group BEP. `relDistFromPeak` is `undefined` when the η spread collapses (homogeneous pump group). |
feat(mgc): rendezvous planner — same-time landing across all modes Routes every dispatch through a tick-aware planner so all pumps reach their setpoint at the same wall-clock instant t* = max(eta_i), regardless of control strategy or per-pump reaction speed. Architecture (src/movement/): - machineProfile.js – pure snapshot of a registered child (state, position, velocityPctPerS, ladder timings, flowAt / positionForFlow). Reads timings from child.state.config.time (the actual storage location — previous fallback paths silently produced 0 s, collapsing every eta to ramp-only). - moveTrajectory.js – seconds-to-target per machine; handles idle / starting / warmingup / operational / cooling. - movementScheduler.js – t* = max eta over ALL non-noop moves. Every command is delayed so its move finishes at t*. Startup execsequence fires at 0; its flowmovement is gated by max(ladderS, t* − rampS) so a fast pump waits before ramping rather than landing early. useRendezvous=false collapses to all fireAtTickN=0 (legacy fire-and-forget). - movementExecutor.js – wall-clock virtual cursor: each tick fires every command whose fireAtTickN ≤ floor(elapsed/tickS). tick() no longer awaits pending fireCommand promises — the synchronous prologue of handleInput claims the latest-wins gate, which is what race-favouring relies on. Shared dispatch path (src/specificClass.js): - _dispatchFlowDistribution(distribution) — extracted from _optimalControl. Builds profiles, calls movementScheduler.plan, replans the executor, ticks once. Reads config.planner.useRendezvous (default true). - _optimalControl computes its bestCombination and hands off. - equalFlowControl (priorityControl mode) computes its flowDistribution and hands off via ctx.mgc._dispatchFlowDistribution. Same-time landing now applies in BOTH modes. Editor toggle (mgc.html + src/nodeClass.js): - New "Same-time landing" checkbox under Control Strategy. - nodeClass.buildDomainConfig bridges uiConfig.useRendezvous → config.planner.useRendezvous. Default ON. Tests: - New: planner-convergence.integration.test.js (real-time end-to-end diagnostic — drives a 3-pump mixed-state dispatch and asserts both convergence to the demand setpoint AND same-time landing within one tick). - New: planner-rendezvous.integration.test.js (schedule-shape assertions against real pump objects). - New: movementScheduler.basic.test.js — includes a mixed-speed multi-startup case proving the fast pumps wait so all three land together (the regression that prompted this work). - New: movementExecutor.basic.test.js + moveTrajectory.basic.test.js. - Updated executor contract test: tick() must NOT await pending fires. Commands + wiki: - handlers.js: source/mode allow-list gate moved into a shared _gate() helper; every command now checks isValidActionForMode + isValidSourceForMode before dispatching. Status-level commands (set.mode, set.scaling) are allowed in every mode. - commands.basic.test.js: coverage for the new gate behaviour. - wiki regen: Home.md visual-first rewrite + Reference-{Architecture, Contracts,Examples,Limitations}.md split with _Sidebar.md index. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 19:43:55 +02:00
The key shape is `<position>_<variant>_<type>` &mdash; the inverse of `rotatingMachine`'s `<type>.<variant>.<position>.<childId>` key shape, because MGC's output is the group aggregate, not a per-child snapshot.
feat(mgc): rendezvous planner — same-time landing across all modes Routes every dispatch through a tick-aware planner so all pumps reach their setpoint at the same wall-clock instant t* = max(eta_i), regardless of control strategy or per-pump reaction speed. Architecture (src/movement/): - machineProfile.js – pure snapshot of a registered child (state, position, velocityPctPerS, ladder timings, flowAt / positionForFlow). Reads timings from child.state.config.time (the actual storage location — previous fallback paths silently produced 0 s, collapsing every eta to ramp-only). - moveTrajectory.js – seconds-to-target per machine; handles idle / starting / warmingup / operational / cooling. - movementScheduler.js – t* = max eta over ALL non-noop moves. Every command is delayed so its move finishes at t*. Startup execsequence fires at 0; its flowmovement is gated by max(ladderS, t* − rampS) so a fast pump waits before ramping rather than landing early. useRendezvous=false collapses to all fireAtTickN=0 (legacy fire-and-forget). - movementExecutor.js – wall-clock virtual cursor: each tick fires every command whose fireAtTickN ≤ floor(elapsed/tickS). tick() no longer awaits pending fireCommand promises — the synchronous prologue of handleInput claims the latest-wins gate, which is what race-favouring relies on. Shared dispatch path (src/specificClass.js): - _dispatchFlowDistribution(distribution) — extracted from _optimalControl. Builds profiles, calls movementScheduler.plan, replans the executor, ticks once. Reads config.planner.useRendezvous (default true). - _optimalControl computes its bestCombination and hands off. - equalFlowControl (priorityControl mode) computes its flowDistribution and hands off via ctx.mgc._dispatchFlowDistribution. Same-time landing now applies in BOTH modes. Editor toggle (mgc.html + src/nodeClass.js): - New "Same-time landing" checkbox under Control Strategy. - nodeClass.buildDomainConfig bridges uiConfig.useRendezvous → config.planner.useRendezvous. Default ON. Tests: - New: planner-convergence.integration.test.js (real-time end-to-end diagnostic — drives a 3-pump mixed-state dispatch and asserts both convergence to the demand setpoint AND same-time landing within one tick). - New: planner-rendezvous.integration.test.js (schedule-shape assertions against real pump objects). - New: movementScheduler.basic.test.js — includes a mixed-speed multi-startup case proving the fast pumps wait so all three land together (the regression that prompted this work). - New: movementExecutor.basic.test.js + moveTrajectory.basic.test.js. - Updated executor contract test: tick() must NOT await pending fires. Commands + wiki: - handlers.js: source/mode allow-list gate moved into a shared _gate() helper; every command now checks isValidActionForMode + isValidSourceForMode before dispatching. Status-level commands (set.mode, set.scaling) are allowed in every mode. - commands.basic.test.js: coverage for the new gate behaviour. - wiki regen: Home.md visual-first rewrite + Reference-{Architecture, Contracts,Examples,Limitations}.md split with _Sidebar.md index. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 19:43:55 +02:00
---
feat(mgc): rendezvous planner — same-time landing across all modes Routes every dispatch through a tick-aware planner so all pumps reach their setpoint at the same wall-clock instant t* = max(eta_i), regardless of control strategy or per-pump reaction speed. Architecture (src/movement/): - machineProfile.js – pure snapshot of a registered child (state, position, velocityPctPerS, ladder timings, flowAt / positionForFlow). Reads timings from child.state.config.time (the actual storage location — previous fallback paths silently produced 0 s, collapsing every eta to ramp-only). - moveTrajectory.js – seconds-to-target per machine; handles idle / starting / warmingup / operational / cooling. - movementScheduler.js – t* = max eta over ALL non-noop moves. Every command is delayed so its move finishes at t*. Startup execsequence fires at 0; its flowmovement is gated by max(ladderS, t* − rampS) so a fast pump waits before ramping rather than landing early. useRendezvous=false collapses to all fireAtTickN=0 (legacy fire-and-forget). - movementExecutor.js – wall-clock virtual cursor: each tick fires every command whose fireAtTickN ≤ floor(elapsed/tickS). tick() no longer awaits pending fireCommand promises — the synchronous prologue of handleInput claims the latest-wins gate, which is what race-favouring relies on. Shared dispatch path (src/specificClass.js): - _dispatchFlowDistribution(distribution) — extracted from _optimalControl. Builds profiles, calls movementScheduler.plan, replans the executor, ticks once. Reads config.planner.useRendezvous (default true). - _optimalControl computes its bestCombination and hands off. - equalFlowControl (priorityControl mode) computes its flowDistribution and hands off via ctx.mgc._dispatchFlowDistribution. Same-time landing now applies in BOTH modes. Editor toggle (mgc.html + src/nodeClass.js): - New "Same-time landing" checkbox under Control Strategy. - nodeClass.buildDomainConfig bridges uiConfig.useRendezvous → config.planner.useRendezvous. Default ON. Tests: - New: planner-convergence.integration.test.js (real-time end-to-end diagnostic — drives a 3-pump mixed-state dispatch and asserts both convergence to the demand setpoint AND same-time landing within one tick). - New: planner-rendezvous.integration.test.js (schedule-shape assertions against real pump objects). - New: movementScheduler.basic.test.js — includes a mixed-speed multi-startup case proving the fast pumps wait so all three land together (the regression that prompted this work). - New: movementExecutor.basic.test.js + moveTrajectory.basic.test.js. - Updated executor contract test: tick() must NOT await pending fires. Commands + wiki: - handlers.js: source/mode allow-list gate moved into a shared _gate() helper; every command now checks isValidActionForMode + isValidSourceForMode before dispatching. Status-level commands (set.mode, set.scaling) are allowed in every mode. - commands.basic.test.js: coverage for the new gate behaviour. - wiki regen: Home.md visual-first rewrite + Reference-{Architecture, Contracts,Examples,Limitations}.md split with _Sidebar.md index. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 19:43:55 +02:00
## The new bit &mdash; the movement planner
feat(mgc): rendezvous planner — same-time landing across all modes Routes every dispatch through a tick-aware planner so all pumps reach their setpoint at the same wall-clock instant t* = max(eta_i), regardless of control strategy or per-pump reaction speed. Architecture (src/movement/): - machineProfile.js – pure snapshot of a registered child (state, position, velocityPctPerS, ladder timings, flowAt / positionForFlow). Reads timings from child.state.config.time (the actual storage location — previous fallback paths silently produced 0 s, collapsing every eta to ramp-only). - moveTrajectory.js – seconds-to-target per machine; handles idle / starting / warmingup / operational / cooling. - movementScheduler.js – t* = max eta over ALL non-noop moves. Every command is delayed so its move finishes at t*. Startup execsequence fires at 0; its flowmovement is gated by max(ladderS, t* − rampS) so a fast pump waits before ramping rather than landing early. useRendezvous=false collapses to all fireAtTickN=0 (legacy fire-and-forget). - movementExecutor.js – wall-clock virtual cursor: each tick fires every command whose fireAtTickN ≤ floor(elapsed/tickS). tick() no longer awaits pending fireCommand promises — the synchronous prologue of handleInput claims the latest-wins gate, which is what race-favouring relies on. Shared dispatch path (src/specificClass.js): - _dispatchFlowDistribution(distribution) — extracted from _optimalControl. Builds profiles, calls movementScheduler.plan, replans the executor, ticks once. Reads config.planner.useRendezvous (default true). - _optimalControl computes its bestCombination and hands off. - equalFlowControl (priorityControl mode) computes its flowDistribution and hands off via ctx.mgc._dispatchFlowDistribution. Same-time landing now applies in BOTH modes. Editor toggle (mgc.html + src/nodeClass.js): - New "Same-time landing" checkbox under Control Strategy. - nodeClass.buildDomainConfig bridges uiConfig.useRendezvous → config.planner.useRendezvous. Default ON. Tests: - New: planner-convergence.integration.test.js (real-time end-to-end diagnostic — drives a 3-pump mixed-state dispatch and asserts both convergence to the demand setpoint AND same-time landing within one tick). - New: planner-rendezvous.integration.test.js (schedule-shape assertions against real pump objects). - New: movementScheduler.basic.test.js — includes a mixed-speed multi-startup case proving the fast pumps wait so all three land together (the regression that prompted this work). - New: movementExecutor.basic.test.js + moveTrajectory.basic.test.js. - Updated executor contract test: tick() must NOT await pending fires. Commands + wiki: - handlers.js: source/mode allow-list gate moved into a shared _gate() helper; every command now checks isValidActionForMode + isValidSourceForMode before dispatching. Status-level commands (set.mode, set.scaling) are allowed in every mode. - commands.basic.test.js: coverage for the new gate behaviour. - wiki regen: Home.md visual-first rewrite + Reference-{Architecture, Contracts,Examples,Limitations}.md split with _Sidebar.md index. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 19:43:55 +02:00
When MGC computes a new optimal combination it doesn't fan the commands out instantly. It builds a **schedule** that times each command so the running aggregate stays close to demand during the transition.
```mermaid
feat(mgc): rendezvous planner — same-time landing across all modes Routes every dispatch through a tick-aware planner so all pumps reach their setpoint at the same wall-clock instant t* = max(eta_i), regardless of control strategy or per-pump reaction speed. Architecture (src/movement/): - machineProfile.js – pure snapshot of a registered child (state, position, velocityPctPerS, ladder timings, flowAt / positionForFlow). Reads timings from child.state.config.time (the actual storage location — previous fallback paths silently produced 0 s, collapsing every eta to ramp-only). - moveTrajectory.js – seconds-to-target per machine; handles idle / starting / warmingup / operational / cooling. - movementScheduler.js – t* = max eta over ALL non-noop moves. Every command is delayed so its move finishes at t*. Startup execsequence fires at 0; its flowmovement is gated by max(ladderS, t* − rampS) so a fast pump waits before ramping rather than landing early. useRendezvous=false collapses to all fireAtTickN=0 (legacy fire-and-forget). - movementExecutor.js – wall-clock virtual cursor: each tick fires every command whose fireAtTickN ≤ floor(elapsed/tickS). tick() no longer awaits pending fireCommand promises — the synchronous prologue of handleInput claims the latest-wins gate, which is what race-favouring relies on. Shared dispatch path (src/specificClass.js): - _dispatchFlowDistribution(distribution) — extracted from _optimalControl. Builds profiles, calls movementScheduler.plan, replans the executor, ticks once. Reads config.planner.useRendezvous (default true). - _optimalControl computes its bestCombination and hands off. - equalFlowControl (priorityControl mode) computes its flowDistribution and hands off via ctx.mgc._dispatchFlowDistribution. Same-time landing now applies in BOTH modes. Editor toggle (mgc.html + src/nodeClass.js): - New "Same-time landing" checkbox under Control Strategy. - nodeClass.buildDomainConfig bridges uiConfig.useRendezvous → config.planner.useRendezvous. Default ON. Tests: - New: planner-convergence.integration.test.js (real-time end-to-end diagnostic — drives a 3-pump mixed-state dispatch and asserts both convergence to the demand setpoint AND same-time landing within one tick). - New: planner-rendezvous.integration.test.js (schedule-shape assertions against real pump objects). - New: movementScheduler.basic.test.js — includes a mixed-speed multi-startup case proving the fast pumps wait so all three land together (the regression that prompted this work). - New: movementExecutor.basic.test.js + moveTrajectory.basic.test.js. - Updated executor contract test: tick() must NOT await pending fires. Commands + wiki: - handlers.js: source/mode allow-list gate moved into a shared _gate() helper; every command now checks isValidActionForMode + isValidSourceForMode before dispatching. Status-level commands (set.mode, set.scaling) are allowed in every mode. - commands.basic.test.js: coverage for the new gate behaviour. - wiki regen: Home.md visual-first rewrite + Reference-{Architecture, Contracts,Examples,Limitations}.md split with _Sidebar.md index. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 19:43:55 +02:00
flowchart LR
demand[set.demand] --> dispatch[_runDispatch<br/>latest-wins]
dispatch --> abort[abortActiveMovements]
abort --> opt[optimizer.calcBestCombination*]
opt --> profiles[buildProfile<br/>x children]
profiles --> plan[movementScheduler.plan<br/>rendezvous t* = max&#40;eta_i&#41;]
plan --> exec[movementExecutor.replan<br/>+ await tick&#40;&#41;]
exec --> kids[rotatingMachine x N<br/>flowmovement / execsequence]
```
feat(mgc): rendezvous planner — same-time landing across all modes Routes every dispatch through a tick-aware planner so all pumps reach their setpoint at the same wall-clock instant t* = max(eta_i), regardless of control strategy or per-pump reaction speed. Architecture (src/movement/): - machineProfile.js – pure snapshot of a registered child (state, position, velocityPctPerS, ladder timings, flowAt / positionForFlow). Reads timings from child.state.config.time (the actual storage location — previous fallback paths silently produced 0 s, collapsing every eta to ramp-only). - moveTrajectory.js – seconds-to-target per machine; handles idle / starting / warmingup / operational / cooling. - movementScheduler.js – t* = max eta over ALL non-noop moves. Every command is delayed so its move finishes at t*. Startup execsequence fires at 0; its flowmovement is gated by max(ladderS, t* − rampS) so a fast pump waits before ramping rather than landing early. useRendezvous=false collapses to all fireAtTickN=0 (legacy fire-and-forget). - movementExecutor.js – wall-clock virtual cursor: each tick fires every command whose fireAtTickN ≤ floor(elapsed/tickS). tick() no longer awaits pending fireCommand promises — the synchronous prologue of handleInput claims the latest-wins gate, which is what race-favouring relies on. Shared dispatch path (src/specificClass.js): - _dispatchFlowDistribution(distribution) — extracted from _optimalControl. Builds profiles, calls movementScheduler.plan, replans the executor, ticks once. Reads config.planner.useRendezvous (default true). - _optimalControl computes its bestCombination and hands off. - equalFlowControl (priorityControl mode) computes its flowDistribution and hands off via ctx.mgc._dispatchFlowDistribution. Same-time landing now applies in BOTH modes. Editor toggle (mgc.html + src/nodeClass.js): - New "Same-time landing" checkbox under Control Strategy. - nodeClass.buildDomainConfig bridges uiConfig.useRendezvous → config.planner.useRendezvous. Default ON. Tests: - New: planner-convergence.integration.test.js (real-time end-to-end diagnostic — drives a 3-pump mixed-state dispatch and asserts both convergence to the demand setpoint AND same-time landing within one tick). - New: planner-rendezvous.integration.test.js (schedule-shape assertions against real pump objects). - New: movementScheduler.basic.test.js — includes a mixed-speed multi-startup case proving the fast pumps wait so all three land together (the regression that prompted this work). - New: movementExecutor.basic.test.js + moveTrajectory.basic.test.js. - Updated executor contract test: tick() must NOT await pending fires. Commands + wiki: - handlers.js: source/mode allow-list gate moved into a shared _gate() helper; every command now checks isValidActionForMode + isValidSourceForMode before dispatching. Status-level commands (set.mode, set.scaling) are allowed in every mode. - commands.basic.test.js: coverage for the new gate behaviour. - wiki regen: Home.md visual-first rewrite + Reference-{Architecture, Contracts,Examples,Limitations}.md split with _Sidebar.md index. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 19:43:55 +02:00
The planner classifies each pump's required move (`startup` / `flowmove` / `shutdown` / `noop`), computes an ETA per move via `MoveTrajectory`, sets the rendezvous time `t* = max(eta_i)` over flow-INCREASING moves, and delays flow-DECREASING moves so they FINISH at `t*`. Net effect: the sum of flows tracks the demand smoothly during the transition; on overshoot the header pressure rises and self-corrects.
feat(mgc): rendezvous planner — same-time landing across all modes Routes every dispatch through a tick-aware planner so all pumps reach their setpoint at the same wall-clock instant t* = max(eta_i), regardless of control strategy or per-pump reaction speed. Architecture (src/movement/): - machineProfile.js – pure snapshot of a registered child (state, position, velocityPctPerS, ladder timings, flowAt / positionForFlow). Reads timings from child.state.config.time (the actual storage location — previous fallback paths silently produced 0 s, collapsing every eta to ramp-only). - moveTrajectory.js – seconds-to-target per machine; handles idle / starting / warmingup / operational / cooling. - movementScheduler.js – t* = max eta over ALL non-noop moves. Every command is delayed so its move finishes at t*. Startup execsequence fires at 0; its flowmovement is gated by max(ladderS, t* − rampS) so a fast pump waits before ramping rather than landing early. useRendezvous=false collapses to all fireAtTickN=0 (legacy fire-and-forget). - movementExecutor.js – wall-clock virtual cursor: each tick fires every command whose fireAtTickN ≤ floor(elapsed/tickS). tick() no longer awaits pending fireCommand promises — the synchronous prologue of handleInput claims the latest-wins gate, which is what race-favouring relies on. Shared dispatch path (src/specificClass.js): - _dispatchFlowDistribution(distribution) — extracted from _optimalControl. Builds profiles, calls movementScheduler.plan, replans the executor, ticks once. Reads config.planner.useRendezvous (default true). - _optimalControl computes its bestCombination and hands off. - equalFlowControl (priorityControl mode) computes its flowDistribution and hands off via ctx.mgc._dispatchFlowDistribution. Same-time landing now applies in BOTH modes. Editor toggle (mgc.html + src/nodeClass.js): - New "Same-time landing" checkbox under Control Strategy. - nodeClass.buildDomainConfig bridges uiConfig.useRendezvous → config.planner.useRendezvous. Default ON. Tests: - New: planner-convergence.integration.test.js (real-time end-to-end diagnostic — drives a 3-pump mixed-state dispatch and asserts both convergence to the demand setpoint AND same-time landing within one tick). - New: planner-rendezvous.integration.test.js (schedule-shape assertions against real pump objects). - New: movementScheduler.basic.test.js — includes a mixed-speed multi-startup case proving the fast pumps wait so all three land together (the regression that prompted this work). - New: movementExecutor.basic.test.js + moveTrajectory.basic.test.js. - Updated executor contract test: tick() must NOT await pending fires. Commands + wiki: - handlers.js: source/mode allow-list gate moved into a shared _gate() helper; every command now checks isValidActionForMode + isValidSourceForMode before dispatching. Status-level commands (set.mode, set.scaling) are allowed in every mode. - commands.basic.test.js: coverage for the new gate behaviour. - wiki regen: Home.md visual-first rewrite + Reference-{Architecture, Contracts,Examples,Limitations}.md split with _Sidebar.md index. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 19:43:55 +02:00
This path is exercised in `optimalControl` mode. `priorityControl` mode still uses the legacy direct-dispatch path (`control.equalFlowControl`) &mdash; the planner has not been wired through there yet.
feat(mgc): rendezvous planner — same-time landing across all modes Routes every dispatch through a tick-aware planner so all pumps reach their setpoint at the same wall-clock instant t* = max(eta_i), regardless of control strategy or per-pump reaction speed. Architecture (src/movement/): - machineProfile.js – pure snapshot of a registered child (state, position, velocityPctPerS, ladder timings, flowAt / positionForFlow). Reads timings from child.state.config.time (the actual storage location — previous fallback paths silently produced 0 s, collapsing every eta to ramp-only). - moveTrajectory.js – seconds-to-target per machine; handles idle / starting / warmingup / operational / cooling. - movementScheduler.js – t* = max eta over ALL non-noop moves. Every command is delayed so its move finishes at t*. Startup execsequence fires at 0; its flowmovement is gated by max(ladderS, t* − rampS) so a fast pump waits before ramping rather than landing early. useRendezvous=false collapses to all fireAtTickN=0 (legacy fire-and-forget). - movementExecutor.js – wall-clock virtual cursor: each tick fires every command whose fireAtTickN ≤ floor(elapsed/tickS). tick() no longer awaits pending fireCommand promises — the synchronous prologue of handleInput claims the latest-wins gate, which is what race-favouring relies on. Shared dispatch path (src/specificClass.js): - _dispatchFlowDistribution(distribution) — extracted from _optimalControl. Builds profiles, calls movementScheduler.plan, replans the executor, ticks once. Reads config.planner.useRendezvous (default true). - _optimalControl computes its bestCombination and hands off. - equalFlowControl (priorityControl mode) computes its flowDistribution and hands off via ctx.mgc._dispatchFlowDistribution. Same-time landing now applies in BOTH modes. Editor toggle (mgc.html + src/nodeClass.js): - New "Same-time landing" checkbox under Control Strategy. - nodeClass.buildDomainConfig bridges uiConfig.useRendezvous → config.planner.useRendezvous. Default ON. Tests: - New: planner-convergence.integration.test.js (real-time end-to-end diagnostic — drives a 3-pump mixed-state dispatch and asserts both convergence to the demand setpoint AND same-time landing within one tick). - New: planner-rendezvous.integration.test.js (schedule-shape assertions against real pump objects). - New: movementScheduler.basic.test.js — includes a mixed-speed multi-startup case proving the fast pumps wait so all three land together (the regression that prompted this work). - New: movementExecutor.basic.test.js + moveTrajectory.basic.test.js. - Updated executor contract test: tick() must NOT await pending fires. Commands + wiki: - handlers.js: source/mode allow-list gate moved into a shared _gate() helper; every command now checks isValidActionForMode + isValidSourceForMode before dispatching. Status-level commands (set.mode, set.scaling) are allowed in every mode. - commands.basic.test.js: coverage for the new gate behaviour. - wiki regen: Home.md visual-first rewrite + Reference-{Architecture, Contracts,Examples,Limitations}.md split with _Sidebar.md index. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 19:43:55 +02:00
---
feat(mgc): rendezvous planner — same-time landing across all modes Routes every dispatch through a tick-aware planner so all pumps reach their setpoint at the same wall-clock instant t* = max(eta_i), regardless of control strategy or per-pump reaction speed. Architecture (src/movement/): - machineProfile.js – pure snapshot of a registered child (state, position, velocityPctPerS, ladder timings, flowAt / positionForFlow). Reads timings from child.state.config.time (the actual storage location — previous fallback paths silently produced 0 s, collapsing every eta to ramp-only). - moveTrajectory.js – seconds-to-target per machine; handles idle / starting / warmingup / operational / cooling. - movementScheduler.js – t* = max eta over ALL non-noop moves. Every command is delayed so its move finishes at t*. Startup execsequence fires at 0; its flowmovement is gated by max(ladderS, t* − rampS) so a fast pump waits before ramping rather than landing early. useRendezvous=false collapses to all fireAtTickN=0 (legacy fire-and-forget). - movementExecutor.js – wall-clock virtual cursor: each tick fires every command whose fireAtTickN ≤ floor(elapsed/tickS). tick() no longer awaits pending fireCommand promises — the synchronous prologue of handleInput claims the latest-wins gate, which is what race-favouring relies on. Shared dispatch path (src/specificClass.js): - _dispatchFlowDistribution(distribution) — extracted from _optimalControl. Builds profiles, calls movementScheduler.plan, replans the executor, ticks once. Reads config.planner.useRendezvous (default true). - _optimalControl computes its bestCombination and hands off. - equalFlowControl (priorityControl mode) computes its flowDistribution and hands off via ctx.mgc._dispatchFlowDistribution. Same-time landing now applies in BOTH modes. Editor toggle (mgc.html + src/nodeClass.js): - New "Same-time landing" checkbox under Control Strategy. - nodeClass.buildDomainConfig bridges uiConfig.useRendezvous → config.planner.useRendezvous. Default ON. Tests: - New: planner-convergence.integration.test.js (real-time end-to-end diagnostic — drives a 3-pump mixed-state dispatch and asserts both convergence to the demand setpoint AND same-time landing within one tick). - New: planner-rendezvous.integration.test.js (schedule-shape assertions against real pump objects). - New: movementScheduler.basic.test.js — includes a mixed-speed multi-startup case proving the fast pumps wait so all three land together (the regression that prompted this work). - New: movementExecutor.basic.test.js + moveTrajectory.basic.test.js. - Updated executor contract test: tick() must NOT await pending fires. Commands + wiki: - handlers.js: source/mode allow-list gate moved into a shared _gate() helper; every command now checks isValidActionForMode + isValidSourceForMode before dispatching. Status-level commands (set.mode, set.scaling) are allowed in every mode. - commands.basic.test.js: coverage for the new gate behaviour. - wiki regen: Home.md visual-first rewrite + Reference-{Architecture, Contracts,Examples,Limitations}.md split with _Sidebar.md index. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 19:43:55 +02:00
## Need more?
feat(mgc): rendezvous planner — same-time landing across all modes Routes every dispatch through a tick-aware planner so all pumps reach their setpoint at the same wall-clock instant t* = max(eta_i), regardless of control strategy or per-pump reaction speed. Architecture (src/movement/): - machineProfile.js – pure snapshot of a registered child (state, position, velocityPctPerS, ladder timings, flowAt / positionForFlow). Reads timings from child.state.config.time (the actual storage location — previous fallback paths silently produced 0 s, collapsing every eta to ramp-only). - moveTrajectory.js – seconds-to-target per machine; handles idle / starting / warmingup / operational / cooling. - movementScheduler.js – t* = max eta over ALL non-noop moves. Every command is delayed so its move finishes at t*. Startup execsequence fires at 0; its flowmovement is gated by max(ladderS, t* − rampS) so a fast pump waits before ramping rather than landing early. useRendezvous=false collapses to all fireAtTickN=0 (legacy fire-and-forget). - movementExecutor.js – wall-clock virtual cursor: each tick fires every command whose fireAtTickN ≤ floor(elapsed/tickS). tick() no longer awaits pending fireCommand promises — the synchronous prologue of handleInput claims the latest-wins gate, which is what race-favouring relies on. Shared dispatch path (src/specificClass.js): - _dispatchFlowDistribution(distribution) — extracted from _optimalControl. Builds profiles, calls movementScheduler.plan, replans the executor, ticks once. Reads config.planner.useRendezvous (default true). - _optimalControl computes its bestCombination and hands off. - equalFlowControl (priorityControl mode) computes its flowDistribution and hands off via ctx.mgc._dispatchFlowDistribution. Same-time landing now applies in BOTH modes. Editor toggle (mgc.html + src/nodeClass.js): - New "Same-time landing" checkbox under Control Strategy. - nodeClass.buildDomainConfig bridges uiConfig.useRendezvous → config.planner.useRendezvous. Default ON. Tests: - New: planner-convergence.integration.test.js (real-time end-to-end diagnostic — drives a 3-pump mixed-state dispatch and asserts both convergence to the demand setpoint AND same-time landing within one tick). - New: planner-rendezvous.integration.test.js (schedule-shape assertions against real pump objects). - New: movementScheduler.basic.test.js — includes a mixed-speed multi-startup case proving the fast pumps wait so all three land together (the regression that prompted this work). - New: movementExecutor.basic.test.js + moveTrajectory.basic.test.js. - Updated executor contract test: tick() must NOT await pending fires. Commands + wiki: - handlers.js: source/mode allow-list gate moved into a shared _gate() helper; every command now checks isValidActionForMode + isValidSourceForMode before dispatching. Status-level commands (set.mode, set.scaling) are allowed in every mode. - commands.basic.test.js: coverage for the new gate behaviour. - wiki regen: Home.md visual-first rewrite + Reference-{Architecture, Contracts,Examples,Limitations}.md split with _Sidebar.md index. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 19:43:55 +02:00
| Page | What you'll find |
|:---|:---|
| [Reference &mdash; Contracts](Reference-Contracts) | Topic registry, config schema, child registration filters |
| [Reference &mdash; Architecture](Reference-Architecture) | Code map, dispatch lifecycle, planner internals, output ports |
| [Reference &mdash; Examples](Reference-Examples) | Shipped flows, debug recipes |
| [Reference &mdash; Limitations](Reference-Limitations) | When not to use, known issues, open questions |
feat(mgc): rendezvous planner — same-time landing across all modes Routes every dispatch through a tick-aware planner so all pumps reach their setpoint at the same wall-clock instant t* = max(eta_i), regardless of control strategy or per-pump reaction speed. Architecture (src/movement/): - machineProfile.js – pure snapshot of a registered child (state, position, velocityPctPerS, ladder timings, flowAt / positionForFlow). Reads timings from child.state.config.time (the actual storage location — previous fallback paths silently produced 0 s, collapsing every eta to ramp-only). - moveTrajectory.js – seconds-to-target per machine; handles idle / starting / warmingup / operational / cooling. - movementScheduler.js – t* = max eta over ALL non-noop moves. Every command is delayed so its move finishes at t*. Startup execsequence fires at 0; its flowmovement is gated by max(ladderS, t* − rampS) so a fast pump waits before ramping rather than landing early. useRendezvous=false collapses to all fireAtTickN=0 (legacy fire-and-forget). - movementExecutor.js – wall-clock virtual cursor: each tick fires every command whose fireAtTickN ≤ floor(elapsed/tickS). tick() no longer awaits pending fireCommand promises — the synchronous prologue of handleInput claims the latest-wins gate, which is what race-favouring relies on. Shared dispatch path (src/specificClass.js): - _dispatchFlowDistribution(distribution) — extracted from _optimalControl. Builds profiles, calls movementScheduler.plan, replans the executor, ticks once. Reads config.planner.useRendezvous (default true). - _optimalControl computes its bestCombination and hands off. - equalFlowControl (priorityControl mode) computes its flowDistribution and hands off via ctx.mgc._dispatchFlowDistribution. Same-time landing now applies in BOTH modes. Editor toggle (mgc.html + src/nodeClass.js): - New "Same-time landing" checkbox under Control Strategy. - nodeClass.buildDomainConfig bridges uiConfig.useRendezvous → config.planner.useRendezvous. Default ON. Tests: - New: planner-convergence.integration.test.js (real-time end-to-end diagnostic — drives a 3-pump mixed-state dispatch and asserts both convergence to the demand setpoint AND same-time landing within one tick). - New: planner-rendezvous.integration.test.js (schedule-shape assertions against real pump objects). - New: movementScheduler.basic.test.js — includes a mixed-speed multi-startup case proving the fast pumps wait so all three land together (the regression that prompted this work). - New: movementExecutor.basic.test.js + moveTrajectory.basic.test.js. - Updated executor contract test: tick() must NOT await pending fires. Commands + wiki: - handlers.js: source/mode allow-list gate moved into a shared _gate() helper; every command now checks isValidActionForMode + isValidSourceForMode before dispatching. Status-level commands (set.mode, set.scaling) are allowed in every mode. - commands.basic.test.js: coverage for the new gate behaviour. - wiki regen: Home.md visual-first rewrite + Reference-{Architecture, Contracts,Examples,Limitations}.md split with _Sidebar.md index. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 19:43:55 +02:00
[EVOLV master wiki](https://gitea.wbd-rd.nl/RnD/EVOLV/wiki/Home) &middot; [Topology Patterns](https://gitea.wbd-rd.nl/RnD/EVOLV/wiki/Topology-Patterns) &middot; [Topic Conventions](https://gitea.wbd-rd.nl/RnD/EVOLV/wiki/Topic-Conventions)