Fix progressive outflow formula: ensure monotonic increase

Fixed critical bug where outflow would decrease when crossing from
building to capacity zone. The original formula caused a discontinuity.

**Problem:**
Old formula had outflow jumping from (inflow × ratio) in building zone
to (inflow - max) in capacity zone, causing outflow to DROP when
crossing the threshold.

Example (min=100, max=300):
- At inflow=300: outflow = 300 (100% sharing)
- At inflow=301: outflow = 1 (sudden drop!)

**Solution:**
1. Changed capacity threshold from max to 1.5 × max
2. Building zone now uses linear interpolation from 0 to 0.5 × max
3. Capacity zone retains max and shares all excess

New formula guarantees:
✓ Monotonically increasing outflow as inflow increases
✓ Continuous at all zone boundaries
✓ At inflow = min → outflow = 0
✓ At inflow = 1.5 × max → outflow = 0.5 × max
✓ At inflow > 1.5 × max → retain max, share excess

Zones:
- Deficit (inflow < min): outflow = 0
- Building (min ≤ inflow < 1.5×max): progressive linear growth
- Capacity (inflow ≥ 1.5×max): outflow = inflow - max

Changes:
- lib/flow-v2/engine-v2.ts: Updated getFlowZone() and calculateOutflow()

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Shawn Anderson 2025-11-10 11:44:19 -08:00
parent a55cec9958
commit cccb6cc20e
1 changed files with 29 additions and 12 deletions

View File

@ -50,13 +50,16 @@ export function perSecondToPerMonth(amountPerSecond: number): number {
/**
* Determine which zone a node is in based on total inflow
*
* Capacity threshold is 1.5x the max threshold
*/
export function getFlowZone(node: FlowNode): FlowZone {
const totalInflow = node.totalInflow || 0
const capacityThreshold = 1.5 * node.maxThreshold
if (totalInflow < node.minThreshold) {
return 'deficit'
} else if (totalInflow <= node.maxThreshold) {
} else if (totalInflow < capacityThreshold) {
return 'building'
} else {
return 'capacity'
@ -66,33 +69,47 @@ export function getFlowZone(node: FlowNode): FlowZone {
/**
* Calculate progressive outflow based on zone
*
* Deficit Zone (f < min): outflow = 0 (keep everything)
* Building Zone (min f max): outflow = f × ((f - min) / (max - min))
* Capacity Zone (f > max): outflow = f - max (redirect excess)
* Deficit Zone (inflow < min):
* outflow = 0 (keep everything)
*
* Building Zone (min inflow < 1.5 * max):
* outflow = (inflow - min) × (0.5 × max) / (1.5 × max - min)
* Progressive sharing that smoothly increases
*
* Capacity Zone (inflow 1.5 * max):
* outflow = inflow - max
* Retain max, share all excess
*
* This ensures monotonically increasing outflow and smooth transitions.
*/
export function calculateOutflow(node: FlowNode): number {
const totalInflow = node.totalInflow || 0
const { minThreshold, maxThreshold } = node
// Capacity threshold: when you start sharing all excess above max
const capacityThreshold = 1.5 * maxThreshold
// Deficit zone: keep everything
if (totalInflow < minThreshold) {
return 0
}
// Capacity zone: redirect excess
if (totalInflow > maxThreshold) {
// Capacity zone: retain max, share all excess
if (totalInflow >= capacityThreshold) {
return totalInflow - maxThreshold
}
// Building zone: progressive sharing
const range = maxThreshold - minThreshold
if (range === 0) {
// Edge case: min === max
return totalInflow > maxThreshold ? totalInflow - maxThreshold : 0
const buildingRange = capacityThreshold - minThreshold
if (buildingRange === 0) {
// Edge case: min === 1.5 * max (shouldn't happen in practice)
return totalInflow - maxThreshold
}
const ratio = (totalInflow - minThreshold) / range
return totalInflow * ratio
const excess = totalInflow - minThreshold
const targetOutflow = 0.5 * maxThreshold // What we'll share at capacity threshold
return (excess / buildingRange) * targetOutflow
}
/**