From 78697fb1630fb97b718b6efb88ebac72b0549093 Mon Sep 17 00:00:00 2001 From: Felipe Domingues <53182096+domfelipe@users.noreply.github.com> Date: Sun, 26 Apr 2026 15:57:41 -0300 Subject: [PATCH] feat: add XP progression engine --- src/progression.js | 70 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 src/progression.js diff --git a/src/progression.js b/src/progression.js new file mode 100644 index 0000000..b43fac9 --- /dev/null +++ b/src/progression.js @@ -0,0 +1,70 @@ +export const XP_REWARDS = Object.freeze({ + task_started: 2, + task_done: 25, + task_cancelled: 1, + command_success: 6, + command_failure: 2, + tests_passed: 18, + tests_failed: 4, + build_passed: 16, + build_failed: 3, + lint_passed: 12, + lint_failed: 3, + typecheck_passed: 12, + typecheck_failed: 3, + commit_created: 20, + merge_completed: 28, + push_attempted: 8, + docs_written: 15, + watch_changed: 4, + manual_focus: 10 +}); + +export const LEVEL_TITLES = Object.freeze([ + { min: 1, title: 'Hatchling' }, + { min: 2, title: 'Junior Companion' }, + { min: 5, title: 'Debug Partner' }, + { min: 10, title: 'Terminal Guardian' }, + { min: 20, title: 'Senior Beast' }, + { min: 35, title: 'Mythic Companion' }, + { min: 50, title: 'Legend of the Prompt' } +]); + +export function xpForNextLevel(level) { + return Math.max(1, level) * Math.max(1, level) * 100; +} + +export function levelFromXp(xp = 0) { + let level = 1; + while (xp >= xpForNextLevel(level)) { + level += 1; + } + return level; +} + +export function titleForLevel(level = 1) { + return LEVEL_TITLES.filter((entry) => level >= entry.min).at(-1)?.title ?? 'Hatchling'; +} + +export function progressToNextLevel(xp = 0) { + const level = levelFromXp(xp); + const previous = level <= 1 ? 0 : xpForNextLevel(level - 1); + const next = xpForNextLevel(level); + return { + level, + title: titleForLevel(level), + current: Math.max(0, xp - previous), + needed: Math.max(1, next - previous), + previous, + next + }; +} + +export function applyXp(entry, amount) { + const beforeLevel = entry.level ?? levelFromXp(entry.xp ?? 0); + entry.xp = Math.max(0, (entry.xp ?? 0) + amount); + entry.level = levelFromXp(entry.xp); + entry.energy = Math.min(100, Math.max(0, (entry.energy ?? 50) + Math.min(12, Math.ceil(amount / 3)))); + entry.mood = entry.level > beforeLevel ? 'level-up' : amount >= 15 ? 'proud' : 'focused'; + return { beforeLevel, afterLevel: entry.level, leveledUp: entry.level > beforeLevel }; +}