// Pure math helpers. No state, no DOM. export function clamp(value, min, max) { return Math.min(max, Math.max(min, value)); } /** Price of the next item when `owned` items are already owned. */ export function unitCost(base, growth, owned) { return base * growth ** owned; } /** Total price of buying `count` more items (geometric series, rounded up). */ export function bulkCost(base, growth, owned, count) { if (count <= 0) return 0; return Math.ceil((unitCost(base, growth, owned) * (growth ** count - 1)) / (growth - 1)); } /** How many items `budget` can pay for, robust against float error. */ export function maxAffordable(base, growth, owned, budget) { const first = unitCost(base, growth, owned); if (Number.isNaN(budget) || budget < first) return 0; let n = Math.floor(Math.log((budget * (growth - 1)) / first + 1) / Math.log(growth)); while (n > 0 && bulkCost(base, growth, owned, n) > budget) n--; while (bulkCost(base, growth, owned, n + 1) <= budget) n++; return Math.max(n, 0); } /** * Units customers want to buy per second at `price`. * Demand is maxed at or below the fair price and drops steeply above it, so * extra production is only worth selling at a higher price while supply is * short; to sell more at the fair price, the player needs more demand. */ export function demandPerSecond(market, price, multiplier) { const overpriced = Math.max(1, price / market.fairPrice); return (market.baseDemand * multiplier) / overpriced ** market.elasticity; }