P3 wave 1: extract measurement simulator/calibration/commands + CONTRACT

src/simulation/simulator.js  random-walk generator (was simulateInput inline)
  src/calibration/calibrator.js  calibrate + isStable + evaluateRepeatability,
                                using generalFunctions/stats. NB: isStable
                                tautology preserved verbatim — see
                                OPEN_QUESTIONS.md 2026-05-10 for the bug.
  src/commands/                  registry + handlers (canonical names from start)
  CONTRACT.md                    inputs/outputs/events surface

77 basic tests pass (62 pre-refactor + 15 new across the three new files).
specificClass.js / nodeClass.js untouched — integration is P3 wave 2.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
znetsixe
2026-05-10 20:32:26 +02:00
parent 998b2002e9
commit b990f67df1
8 changed files with 725 additions and 0 deletions

View File

@@ -0,0 +1,60 @@
/**
* Simulator — random-walk driver for the measurement input.
*
* Lifted verbatim from Measurement.simulateInput. The orchestrator decides
* what to do with the returned value (originally written to `inputValue`),
* so this module owns nothing but the walk and its bounds.
*/
class Simulator {
constructor({ config, logger } = {}) {
if (!config || !config.scaling) {
throw new Error('Simulator requires { config.scaling }');
}
this.config = config;
this.logger = logger || { warn() {}, info() {}, debug() {}, error() {} };
const s = config.scaling;
this.inputRange = Math.abs(s.inputMax - s.inputMin);
this.processRange = Math.abs(s.absMax - s.absMin);
this.simValue = 0;
}
step() {
const s = this.config.scaling;
const sign = Math.random() < 0.5 ? -1 : 1;
let maxStep;
if (s.enabled) {
// Step size scales with the live input window; fall back to 1 so a
// collapsed range still wanders instead of freezing at zero.
maxStep = this.inputRange > 0 ? this.inputRange * 0.05 : 1;
if (this.simValue < s.inputMin || this.simValue > s.inputMax) {
this.logger.warn(`Simulated value ${this.simValue} is outside of input range constraining between min=${s.inputMin} and max=${s.inputMax}`);
this.simValue = _constrain(this.simValue, s.inputMin, s.inputMax);
}
} else {
maxStep = this.processRange > 0 ? this.processRange * 0.05 : 1;
if (this.simValue < s.absMin || this.simValue > s.absMax) {
this.logger.warn(`Simulated value ${this.simValue} is outside of abs range constraining between min=${s.absMin} and max=${s.absMax}`);
this.simValue = _constrain(this.simValue, s.absMin, s.absMax);
}
}
this.simValue += sign * Math.random() * maxStep;
return this.simValue;
}
reset() {
this.simValue = 0;
}
get current() {
return this.simValue;
}
}
function _constrain(v, lo, hi) {
return Math.min(Math.max(v, lo), hi);
}
module.exports = Simulator;