Files
OmniRoute/src/lib/gamification/xp.ts
Paijo 81fb3f50e8 feat: gamification & leaderboard system (#2405)
Integrated into release/v3.8.0
2026-05-19 09:46:20 -03:00

153 lines
4.7 KiB
TypeScript

/**
* 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;