{"release_id":"01a1998d-9802-0000-b0f7-33a23307f4ed","chunk_id":"01a1998d-9819-7ea0-898a-23cfe4d10483","file":"BHOuaeJW.mjs","mappings":"qTAuCO,MAAMA,EAAoB,EAIpBC,EAAkB,GAElBC,EAAiB,GAW9B,SAASC,EAASC,EAAYC,EAAmB,CAChD,GAAID,GAAM,EAAG,MAAO,GAEpB,MAAME,EAAI,KAAK,OAAO,EAAI,KAAK,KAAK,EAAK,EAAIF,EAAMC,CAAC,GAAK,CAAC,EAC1D,OAAO,KAAK,IAAI,EAAGC,CAAC,CACrB,CACA,SAASC,EAAYC,EAAeH,EAAmB,CACtD,OAAOA,EAAI,KAAK,IAAI,EAAGG,EAAQ,CAAC,EAAI,KAAK,IAAI,EAAGA,CAAK,CACtD,CAEO,SAASC,EAAWL,EAAoB,CAC9C,OAAOD,EAASC,EAAI,EAAU,CAC/B,CACO,SAASM,EAAWF,EAAuB,CACjD,OAAOD,EAAYC,EAAO,EAAU,CACrC,CAoBA,SAASG,EAASP,EAAYI,EAAeI,EAAiD,CAC7F,MAAMC,EAAMD,EAAUJ,CAAK,EACrBM,EAAOF,EAAUJ,EAAQ,CAAC,EAC1BO,EAAO,KAAK,IAAI,EAAGD,EAAOD,CAAG,EAC7BG,EAAO,KAAK,IAAI,EAAGZ,EAAKS,CAAG,EAC3BI,EAAS,KAAK,IAAI,EAAGH,EAAOV,CAAE,EAC9Bc,EAAM,KAAK,IAAI,IAAK,KAAK,MAAOF,EAAOD,EAAQ,GAAG,CAAC,EACzD,MAAO,CAAE,MAAAP,EAAO,KAAAQ,EAAM,KAAAD,EAAM,OAAAE,EAAQ,IAAAC,CAAA,CACrC,CAGO,SAASC,EAAiBf,EAA2B,CAC3D,OAAOO,EAASP,EAAIK,EAAWL,CAAE,EAAGM,CAAU,CAC/C","names":["CLAN_CREATE_LEVEL","CLAN_SLOT_BLOCK","CLAN_SLOT_COST","levelFor","xp","k","L","xpForLevelK","level","levelForXp","xpForLevel","progress","threshold","cur","next","span","into","toNext","pct","personalProgress"],"sources":["../../../../../../src/lib/xp.ts"],"sourcesContent":["/**\n * XP & leveling — shared between server and client. No server-only imports here\n * so both the duel-resolution hook (server) and the level bars (client) can use\n * the same curve. All numbers are intentionally tunable in one place.\n *\n * Personal XP is earned from duels (see `duelXp`). Clan XP is the banked sum of\n * its members' duel XP — it never decreases when a member leaves (the clan keeps\n * the level they helped reach). Both use the same triangular curve, scaled by a\n * per-track constant (`PERSONAL_K` vs `CLAN_K`).\n */\n\n// ─── Duel XP rewards ─────────────────────────────────────────────────────────\nexport const DUEL_WIN_XP = 25;\nexport const DUEL_DRAW_XP = 15;\nexport const DUEL_LOSS_XP = 8;\n\nexport type DuelResult = 'win' | 'draw' | 'loss';\n\n/** XP a participant earns from one resolved duel: base by result + their score. */\nexport function duelXp(result: DuelResult, score: number | null | undefined): number {\n\tconst base = result === 'win' ? DUEL_WIN_XP : result === 'draw' ? DUEL_DRAW_XP : DUEL_LOSS_XP;\n\tconst bonus = Math.max(0, Math.floor(score ?? 0));\n\treturn base + bonus;\n}\n\n// ─── Beat (\"beat me\") XP rewards ─────────────────────────────────────────────\n// Flat by result — a beat is a binary crowd vote with no per-entry score, so no\n// score bonus. A touch below the duel bases (lower-stakes, faster format).\nexport const BEAT_WIN_XP = 20;\nexport const BEAT_DRAW_XP = 12;\nexport const BEAT_LOSS_XP = 8;\n\n/** XP a participant earns from one closed beat challenge. */\nexport function beatXp(result: DuelResult): number {\n\treturn result === 'win' ? BEAT_WIN_XP : result === 'draw' ? BEAT_DRAW_XP : BEAT_LOSS_XP;\n}\n\n// ─── Clan economy ────────────────────────────────────────────────────────────\n/** Minimum personal level to found a clan. No token cost (clan wars, 2026-09-16). */\nexport const CLAN_CREATE_LEVEL = 3;\n/** Member slots a clan starts with. */\nexport const CLAN_BASE_CAP = 25;\n/** Slots unlocked per purchase. */\nexport const CLAN_SLOT_BLOCK = 25;\n/** Token cost (from the clan treasury) to unlock one +25 block. */\nexport const CLAN_SLOT_COST = 50;\n/** Max tokens donatable to a treasury in one go. */\nexport const CLAN_DONATE_MAX = 500;\n\n// ─── Level curve ─────────────────────────────────────────────────────────────\n// Cumulative XP required to *reach* level L is `k * (L-1) * L` (triangular).\n// Personal: k=25 → L2=50, L3=150, L4=300, L5=500, L10=2250.\n// Clan:     k=500 → L2=1000, L5=10k, L10=45k, L14=91k.\nconst PERSONAL_K = 25;\nconst CLAN_K = 500;\n\nfunction levelFor(xp: number, k: number): number {\n\tif (xp <= 0) return 1;\n\t// Largest L with k*(L-1)*L <= xp. Closed form from the quadratic.\n\tconst L = Math.floor((1 + Math.sqrt(1 + (4 * xp) / k)) / 2);\n\treturn Math.max(1, L);\n}\nfunction xpForLevelK(level: number, k: number): number {\n\treturn k * Math.max(0, level - 1) * Math.max(0, level);\n}\n\nexport function levelForXp(xp: number): number {\n\treturn levelFor(xp, PERSONAL_K);\n}\nexport function xpForLevel(level: number): number {\n\treturn xpForLevelK(level, PERSONAL_K);\n}\nexport function levelForClanXp(xp: number): number {\n\treturn levelFor(xp, CLAN_K);\n}\nexport function xpForClanLevel(level: number): number {\n\treturn xpForLevelK(level, CLAN_K);\n}\n\nexport interface LevelProgress {\n\tlevel: number;\n\t/** XP accumulated past the current level's threshold. */\n\tinto: number;\n\t/** XP span of the current level (threshold(next) - threshold(current)). */\n\tspan: number;\n\t/** XP remaining to the next level. */\n\ttoNext: number;\n\t/** 0–100 progress through the current level. */\n\tpct: number;\n}\n\nfunction progress(xp: number, level: number, threshold: (l: number) => number): LevelProgress {\n\tconst cur = threshold(level);\n\tconst next = threshold(level + 1);\n\tconst span = Math.max(1, next - cur);\n\tconst into = Math.max(0, xp - cur);\n\tconst toNext = Math.max(0, next - xp);\n\tconst pct = Math.min(100, Math.round((into / span) * 100));\n\treturn { level, into, span, toNext, pct };\n}\n\n/** Personal level + progress to the next, for the XP bar. */\nexport function personalProgress(xp: number): LevelProgress {\n\treturn progress(xp, levelForXp(xp), xpForLevel);\n}\n/** Clan level + progress to the next, for the clan XP bar. */\nexport function clanProgress(xp: number): LevelProgress {\n\treturn progress(xp, levelForClanXp(xp), xpForClanLevel);\n}\n"],"version":3}