'use strict'; // Sampling-cabinet boundary + target math + rain-scaled flow prediction. // All operations are pure given a domain handle — the domain owns the // mutable fields (maxVolume, targetPuls, …) so legacy tests that read // `monster.maxVolume` keep working. const RAIN_STALE_MS = 2 * 60 * 60 * 1000; function applyBoundsAndTargets(m) { m.maxVolume = m.maxWeight - m.emptyWeightBucket; m.minPuls = Math.round(m.minVolume / m.volume_pulse); m.maxPuls = Math.round(m.maxVolume / m.volume_pulse); m.absMaxPuls = Math.round(m.cap_volume / m.volume_pulse); m.targetVolume = m.minVolume * Math.sqrt(m.maxVolume / m.minVolume); m.targetPuls = Math.round(m.targetVolume / m.volume_pulse); } function validateFlowBounds(m) { // nominalFlowMin / flowMax must be declared in monster.json's `constraints` // section. If they aren't, configUtils strips them as unknown keys and the // bounds collapse to undefined → NaN → invalid here, which silently blocks // every sampling run. (Fixed 2026-05-11.) const min = Number(m.nominalFlowMin); const max = Number(m.flowMax); const valid = Number.isFinite(min) && Number.isFinite(max) && min >= 0 && max > 0 && min < max; m.invalidFlowBounds = !valid; if (!valid) m.logger.warn(`Invalid flow bounds. nominalFlowMin=${m.nominalFlowMin}, flowMax=${m.flowMax}`); return valid; } function getRainIndex(m) { if (!m.lastRainUpdate) return 0; if (Date.now() - m.lastRainUpdate > RAIN_STALE_MS) return 0; return Number.isFinite(m.avgRain) ? m.avgRain : 0; } function getPredictedFlowRate(m) { const min = Number(m.nominalFlowMin); const max = Number(m.flowMax); if (!Number.isFinite(min) || !Number.isFinite(max) || min < 0 || max <= 0 || min >= max) return 0; const rainIndex = getRainIndex(m); const scale = Math.max(0, Math.min(1, m.rainMaxRef > 0 ? rainIndex / m.rainMaxRef : 0)); return min + (max - min) * scale; } function getSampleCooldownMs(m) { if (!m.lastSampleTime) return 0; const remaining = (m.minSampleIntervalSec * 1000) - (Date.now() - m.lastSampleTime); return Math.max(0, remaining); } module.exports = { applyBoundsAndTargets, validateFlowBounds, getRainIndex, getPredictedFlowRate, getSampleCooldownMs, RAIN_STALE_MS, };