33 lines
1.7 KiB
TypeScript
33 lines
1.7 KiB
TypeScript
// 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;
|
||
}
|