feat: v0.12.0 — contracts → packages: load/fly/abandon gameplay (ATR 72-600 capacity 6, manual flight to any airport, time-scaled abandon penalty 15–60%), one-time DB migration preserving wallet/clock; new endpoints /api/packages/:id/load|abandon + /api/planes/:planeId/fly
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
// 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;
|
||||
}
|
||||
Reference in New Issue
Block a user