feat(simulation): add the overflow rule, a one-sided gap for hard ceilings

A fourth Rule kind: overflow = max(0, factor × (level − threshold)). It stays
shut until its `+` level passes its `−` threshold, so an outflow on it spills only
the excess — a spillway / hard ceiling the smooth rules can't draw.

Gap can't stand in for it: it's signed and bidirectional (coffee cooling relies on
that), so below the threshold a gap outflow runs backwards. Clamping at zero is the
whole point, and it can't be applied globally without breaking gap. This is the
additive vocabulary growth ADR-0004 anticipates — a new kind in the union plus a
case in the evaluator — alongside touch-ups to the rule validator and the inspector.
This commit is contained in:
Julien Calixte
2026-06-21 18:31:39 +02:00
parent 46d3279a3c
commit f691b99ca5
4 changed files with 22 additions and 1 deletions

View File

@@ -107,6 +107,19 @@ function evalRule(rule: Rule, links: InformationLink[], valueOf: (id: string) =>
rule.factor * ((level ? valueOf(level.source) : 0) - (target ? valueOf(target.source) : 0))
)
}
case "overflow": {
// max(0, factor × (level threshold)): a one-sided gap. The `+` input is the
// level, the `` the threshold; it stays shut until the level passes it, so an
// overflow Flow spills only the excess. Clamping at 0 is what stops it running
// backwards below the threshold — gap can't, by design (it's bidirectional).
const level = links.find((link) => link.polarity === "+")
const threshold = links.find((link) => link.polarity === "-")
return Math.max(
0,
rule.factor *
((level ? valueOf(level.source) : 0) - (threshold ? valueOf(threshold.source) : 0)),
)
}
}
}