/** Base simultaneous opens at key 1 (x10). Each +1 key adds +1 open until x20, then continues +1 per key. */ export const BASE_MAX_OPENS = 10; /** Keys 1–10 → next key always costs this many opens. */ export const OPENS_PER_KEY_EARLY = 100; /** From key 11 onward: cost = OPENS_PER_KEY_EARLY × (keys - 9). */ export const ESCALATION_KEY_THRESHOLD = 11; export function maxOpensForKeys(keys, openBonus = 0) { const k = Math.max(1, keys); const bonus = Math.max(0, Math.floor(Number(openBonus) || 0)); return k + (BASE_MAX_OPENS - 1) + bonus; } export function opensRequiredForNextKey(keys, keyOpenReduction = 0) { const k = Math.max(1, keys); const base = k < ESCALATION_KEY_THRESHOLD ? OPENS_PER_KEY_EARLY : OPENS_PER_KEY_EARLY * (k - (ESCALATION_KEY_THRESHOLD - 2)); const reduction = Math.max(0, Number(keyOpenReduction) || 0); return Math.max(1, base - reduction); } export function serializeKeyProgress(progress, keyOpenReduction = 0, openBonus = 0) { const keys = progress?.keys ?? 1; const opensSinceLastKey = progress?.opensSinceLastKey ?? 0; const totalOpens = progress?.totalOpens ?? 0; const opensRequired = opensRequiredForNextKey(keys, keyOpenReduction); return { keys, maxOpens: maxOpensForKeys(keys, openBonus), opensSinceLastKey, opensRequired, totalOpens, }; } /** * Apply opens to progress state. Returns updated fields + keysGained. */ export function applyOpens(progress, count, keyOpenReduction = 0) { let keys = Math.max(1, progress.keys ?? 1); let opensSinceLastKey = progress.opensSinceLastKey ?? 0; let totalOpens = progress.totalOpens ?? 0; let keysGained = 0; opensSinceLastKey += count; totalOpens += count; while (opensSinceLastKey >= opensRequiredForNextKey(keys, keyOpenReduction)) { opensSinceLastKey -= opensRequiredForNextKey(keys, keyOpenReduction); keys += 1; keysGained += 1; } return { keys, opensSinceLastKey, totalOpens, keysGained }; } /** * Rebuild keys/opensSinceLastKey from a lifetime open count (backfill). * Prestige reduction is ignored here — backfill uses base costs. */ export function progressFromTotalOpens(totalOpens) { let keys = 1; let remaining = totalOpens; while (remaining >= opensRequiredForNextKey(keys, 0)) { remaining -= opensRequiredForNextKey(keys, 0); keys += 1; } return { keys, opensSinceLastKey: remaining, totalOpens, }; } export async function getOrCreateProgress(tx, userId, caseId) { const existing = await tx.caseKeyProgress.findUnique({ where: { userId_caseId: { userId, caseId } }, }); if (existing) return existing; return tx.caseKeyProgress.create({ data: { userId, caseId, keys: 1, opensSinceLastKey: 0, totalOpens: 0 }, }); }