File size: 4,769 Bytes
cd8bd0a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
/**
 * 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;