// Abandon penalty (v0.12.0) — dropping a loaded package costs a fraction of its // payout that grows with time pressure: 15% right after creation, up to 60% at // the deadline. Pure function shared by the server action (lib/game/actions.ts) // and the client-side preview in components/RightPane.tsx so both always agree. export const ABANDON_PENALTY_MIN = 0.15; // fraction of the payout, at creation time export const ABANDON_PENALTY_SPAN = 0.45; // added across the package's lifetime → max 60% at the deadline // The penalty fraction for a given elapsedFraction ∈ [0, 1] (clamped). export function abandonPenaltyPct(elapsedFraction: number): number { const f = Math.min(1, Math.max(0, elapsedFraction)); return ABANDON_PENALTY_MIN + ABANDON_PENALTY_SPAN * f; } // How much of the package's lifetime has elapsed at clockMs (clamped to [0, 1]). export function elapsedFractionFor(createdSimTime: number, deadlineSimTime: number, clockMs: number): number { const span = deadlineSimTime - createdSimTime; if (span <= 0) return 1; // degenerate — treat as fully elapsed (max penalty) return Math.min(1, Math.max(0, (clockMs - createdSimTime) / span)); } // The exact € penalty for abandoning `pkg` at game time clockMs. An already- // EXPIRED package abandons for free; cash is floored at 0 by the caller // (account.cash = max(0, cash − penalty)). Rounded to cents like all money math. export function abandonPenalty( pkg: { status: string; reward: number; createdSimTime: number; deadlineSimTime: number }, clockMs: number, ): number { if (pkg.status === 'expired') return 0; const pct = abandonPenaltyPct(elapsedFractionFor(pkg.createdSimTime, pkg.deadlineSimTime, clockMs)); return Math.round(pkg.reward * pct * 100) / 100; }