/** * XP/Level Engine for OmniRoute Gamification * * Pure functions for XP calculations, level progression, and reward definitions. * No side effects or DB calls — all stateful logic lives in the persistence layer. */ // ─── XP Curve ──────────────────────────────────────────────────────────────── /** * XP required for a specific level (delta, not cumulative). * Polynomial curve: `xp_for_level(n) = floor(100 * n^1.5)` * * @param level - Target level (must be >= 1) * @returns XP needed to gain this level (0 for level 1) * * @example * xpForLevel(1) // 0 * xpForLevel(10) // 3162 * xpForLevel(50) // 35355 */ export function xpForLevel(level: number): number { if (level <= 1) return 0; return Math.floor(100 * Math.pow(level, 1.5)); } /** * Cumulative XP required to reach a given level (sum of all prior level deltas). * * @param level - Target level (must be >= 1) * @returns Total XP accumulated by that level * * @example * cumulativeXpForLevel(1) // 0 * cumulativeXpForLevel(10) // sum of xpForLevel(2..10) */ export function cumulativeXpForLevel(level: number): number { if (level <= 1) return 0; let total = 0; for (let i = 2; i <= level; i++) { total += xpForLevel(i); } return total; } /** * Calculate level from total XP using the inverse of the cumulative XP curve. * * The cumulative XP for level L approximates to `100 * L^2.5 / 2.5`. * Solving for L gives `L ≈ (totalXp * 2.5 / 100) ^ 0.4`. * * @param totalXp - Total accumulated XP * @returns Current level (minimum 1) * * @example * calculateLevel(0) // 1 * calculateLevel(5000) // ~8 * calculateLevel(100000) // ~100 */ export function calculateLevel(totalXp: number): number { if (totalXp <= 0) return 1; return Math.max(1, Math.floor(Math.pow((totalXp * 2.5) / 100, 0.4))); } /** * Get XP needed to reach the next level from the current total XP. * * @param totalXp - Current total XP * @returns Remaining XP until next level-up * * @example * xpToNextLevel(0) // XP needed to reach level 2 * xpToNextLevel(5000) // XP needed from 5000 to next level */ export function xpToNextLevel(totalXp: number): number { const currentLevel = calculateLevel(totalXp); const nextLevelXp = cumulativeXpForLevel(currentLevel + 1); return nextLevelXp - totalXp; } // ─── Level Titles & Tiers ──────────────────────────────────────────────────── /** * Get a human-readable title for a given level. * * @param level - Current level * @returns Level title string * * @example * getLevelTitle(5) // "Beginner" * getLevelTitle(15) // "Explorer" * getLevelTitle(30) // "Expert" * getLevelTitle(60) // "Master" * getLevelTitle(80) // "Legend" */ export function getLevelTitle(level: number): string { if (level >= 76) return "Legend"; if (level >= 51) return "Master"; if (level >= 26) return "Expert"; if (level >= 11) return "Explorer"; return "Beginner"; } /** * Get the badge tier for a given level. * * @param level - Current level * @returns Tier identifier suitable for badge display * * @example * getLevelTier(5) // "bronze" * getLevelTier(20) // "silver" * getLevelTier(40) // "gold" * getLevelTier(60) // "platinum" * getLevelTier(90) // "diamond" */ export function getLevelTier(level: number): "bronze" | "silver" | "gold" | "platinum" | "diamond" { if (level >= 76) return "diamond"; if (level >= 51) return "platinum"; if (level >= 26) return "gold"; if (level >= 11) return "silver"; return "bronze"; } // ─── XP Rewards ────────────────────────────────────────────────────────────── /** Base XP rewards for each gamified action. */ export const XP_REWARDS = { /** Per API request routed through OmniRoute */ request: 1, /** Switching to a different provider */ provider_switch: 5, /** Switching to a different model */ model_switch: 3, /** Creating a new combo */ combo_create: 10, /** Using a combo for a request */ combo_use: 2, /** Per 1 000 tokens shared with another user */ token_share: 1, /** Redeeming an invite code */ invite_redeem: 50, /** Daily active usage (once per day) */ daily_login: 5, /** Per consecutive streak day (multiplied by streak length) */ streak_bonus: 2, /** Unlocking a badge */ badge_unlock: 10, } as const; /** Union type of all XP action keys. */ export type XpAction = keyof typeof XP_REWARDS;