feat(mgc-config + state): planner.useRendezvous schema + remaining-transition reads
Three coherent additions that the MGC rendezvous planner depends on: - machineGroupControl.json: new `planner.useRendezvous` boolean (default true). Used by both `_optimalControl` and `equalFlowControl` (via the shared `_dispatchFlowDistribution` helper) to gate same-time-landing. - state.js: external aborts (returnToOperational=false) bump a monotonic `sequenceAbortToken`. executeSequence captures it at entry and bails out of its for-loop if it advances mid-sequence, so a shutdown that's past its ramp-down step doesn't barge through stopping → coolingdown when a fresher demand re-engages the pump. - stateManager.js: new `getRemainingTransitionS()` returns the seconds remaining in a timed state by reading the wall-clock entry timestamp. buildProfile() reads it so the planner can compute exact eta for a child that's currently mid-ladder (warmingup / starting / cooling). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -39,6 +39,11 @@
|
||||
class stateManager {
|
||||
constructor(config, logger) {
|
||||
this.currentState = config.state.current;
|
||||
// Wall-clock entry timestamp into currentState. Used by
|
||||
// getRemainingTransitionS() so callers (e.g. MGC movement planner)
|
||||
// can compute exact remaining time for timed states without
|
||||
// approximating from the full configured duration.
|
||||
this.stateEnteredAt = Date.now();
|
||||
this.availableStates = config.state.available;
|
||||
this.descriptions = config.state.descriptions;
|
||||
this.logger = logger;
|
||||
@@ -63,7 +68,18 @@ class stateManager {
|
||||
getCurrentState() {
|
||||
return this.currentState;
|
||||
}
|
||||
|
||||
|
||||
// Seconds remaining in the current timed state (warmingup, coolingdown,
|
||||
// starting, stopping, …). Returns 0 for untimed states or once the
|
||||
// configured duration has elapsed. The MGC movement planner uses this to
|
||||
// compute exact rendezvous time for protected (non-interruptible) states.
|
||||
getRemainingTransitionS() {
|
||||
const d = this.transitionTimes?.[this.currentState] || 0;
|
||||
if (d <= 0) return 0;
|
||||
const elapsed = (Date.now() - this.stateEnteredAt) / 1000;
|
||||
return Math.max(0, d - elapsed);
|
||||
}
|
||||
|
||||
transitionTo(newState,signal) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (signal && signal.aborted) {
|
||||
@@ -89,6 +105,7 @@ class stateManager {
|
||||
if (transitionDuration > 0) {
|
||||
const timeoutId = setTimeout(() => {
|
||||
this.currentState = newState;
|
||||
this.stateEnteredAt = Date.now();
|
||||
resolve(`Transition from ${this.currentState} to ${newState} completed in ${transitionDuration}s.`);
|
||||
}, transitionDuration * 1000);
|
||||
if (signal) {
|
||||
@@ -99,6 +116,7 @@ class stateManager {
|
||||
}
|
||||
} else {
|
||||
this.currentState = newState;
|
||||
this.stateEnteredAt = Date.now();
|
||||
resolve(`Immediate transition to ${this.currentState} completed.`);
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user