From cccb6cc20e617b9d8b0d71c3ce7bbff846c21ffb Mon Sep 17 00:00:00 2001 From: Shawn Anderson Date: Mon, 10 Nov 2025 11:44:19 -0800 Subject: [PATCH] Fix progressive outflow formula: ensure monotonic increase MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- lib/flow-v2/engine-v2.ts | 41 ++++++++++++++++++++++++++++------------ 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/lib/flow-v2/engine-v2.ts b/lib/flow-v2/engine-v2.ts index a241fbe..b27cbcd 100644 --- a/lib/flow-v2/engine-v2.ts +++ b/lib/flow-v2/engine-v2.ts @@ -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 } /**