/* eslint-disable max-lines-per-function -- Test suites naturally have many cases */ /* eslint-disable @typescript-eslint/consistent-type-assertions -- Tests build minimal state objects */ import { describe, expect, it } from "vitest"; import { buildPostTranscendenceState, calculateEchoes, computeTranscendenceMultipliers, isEligibleForTranscendence, } from "../../src/services/transcendence.js"; import type { GameState } from "@elysium/types"; const makePlayer = (overrides: Partial = {}) => ({ avatar: null, characterName: "T", discordId: "t", discriminator: "0", lifetimeAchievementsUnlocked: 0, lifetimeAdventurersRecruited: 0, lifetimeBossesDefeated: 0, lifetimeClicks: 0, lifetimeGoldEarned: 0, lifetimeQuestsCompleted: 0, totalClicks: 0, totalGoldEarned: 0, username: "u", ...overrides, }); const makeMinimalState = (overrides: Partial = {}): GameState => ({ player: makePlayer(), resources: { gold: 0, essence: 0, crystals: 0, runestones: 0 }, adventurers: [], upgrades: [], quests: [], bosses: [], equipment: [], achievements: [], zones: [], exploration: { areas: [], materials: [], craftedRecipeIds: [], craftedGoldMultiplier: 1, craftedEssenceMultiplier: 1, craftedClickMultiplier: 1, craftedCombatMultiplier: 1 }, companions: { unlockedCompanionIds: [], activeCompanionId: null }, prestige: { count: 0, runestones: 0, productionMultiplier: 1, purchasedUpgradeIds: [] }, baseClickPower: 1, lastTickAt: 0, schemaVersion: 1, ...overrides, } as GameState); describe("computeTranscendenceMultipliers", () => { it("returns all 1s with empty ids", () => { const result = computeTranscendenceMultipliers([]); expect(result.echoIncomeMultiplier).toBe(1); expect(result.echoCombatMultiplier).toBe(1); expect(result.echoPrestigeThresholdMultiplier).toBe(1); expect(result.echoPrestigeRunestoneMultiplier).toBe(1); expect(result.echoMetaMultiplier).toBe(1); }); it("applies income upgrade when purchased", () => { const result = computeTranscendenceMultipliers(["echo_income_1"]); expect(result.echoIncomeMultiplier).toBeGreaterThan(1); expect(result.echoCombatMultiplier).toBe(1); }); it("applies combat upgrade when purchased", () => { const result = computeTranscendenceMultipliers(["echo_combat_1"]); expect(result.echoCombatMultiplier).toBeGreaterThan(1); expect(result.echoIncomeMultiplier).toBe(1); }); it("applies prestige_threshold upgrade when purchased", () => { const result = computeTranscendenceMultipliers(["echo_prestige_threshold_1"]); expect(result.echoPrestigeThresholdMultiplier).not.toBe(1); }); it("applies prestige_runestones upgrade when purchased", () => { const result = computeTranscendenceMultipliers(["echo_prestige_runestones_1"]); expect(result.echoPrestigeRunestoneMultiplier).toBeGreaterThan(1); }); it("applies echo_meta upgrade when purchased", () => { const result = computeTranscendenceMultipliers(["echo_meta_1"]); expect(result.echoMetaMultiplier).toBeGreaterThan(1); }); }); describe("isEligibleForTranscendence", () => { it("returns true when final boss is defeated", () => { const state = makeMinimalState({ bosses: [{ id: "the_absolute_one", status: "defeated" }] as GameState["bosses"], }); expect(isEligibleForTranscendence(state)).toBe(true); }); it("returns false when final boss is available but not defeated", () => { const state = makeMinimalState({ bosses: [{ id: "the_absolute_one", status: "available" }] as GameState["bosses"], }); expect(isEligibleForTranscendence(state)).toBe(false); }); it("returns false when final boss is not in the list", () => { const state = makeMinimalState({ bosses: [] }); expect(isEligibleForTranscendence(state)).toBe(false); }); it("returns false when a different boss is defeated", () => { const state = makeMinimalState({ bosses: [{ id: "some_other_boss", status: "defeated" }] as GameState["bosses"], }); expect(isEligibleForTranscendence(state)).toBe(false); }); }); describe("calculateEchoes", () => { it("handles prestige count of 0 by treating it as 1", () => { // safeCount = max(0, 1) = 1; floor(224 / sqrt(1)) = 224 expect(calculateEchoes(0, 1)).toBe(224); }); it("calculates echoes at count 1", () => { // floor(224 / sqrt(1)) = 224 expect(calculateEchoes(1, 1)).toBe(224); }); it("decreases echoes with higher prestige count", () => { const echoesAt1 = calculateEchoes(1, 1); const echoesAt4 = calculateEchoes(4, 1); expect(echoesAt4).toBeLessThan(echoesAt1); // floor(224 / sqrt(4)) = floor(224 / 2) = 112 expect(echoesAt4).toBe(112); }); it("applies echoMetaMultiplier", () => { const base = calculateEchoes(1, 1); const withMult = calculateEchoes(1, 2); expect(withMult).toBe(base * 2); }); it("returns 50 echoes at the target prestige 20", () => { // floor(224 / sqrt(20)) = floor(224 / 4.472) = floor(50.09) = 50 expect(calculateEchoes(20, 1)).toBe(50); }); }); describe("buildPostTranscendenceState", () => { it("increments transcendence count from 0", () => { const state = makeMinimalState(); const { transcendenceData } = buildPostTranscendenceState(state, "T"); expect(transcendenceData.count).toBe(1); }); it("accumulates echoes", () => { const state = makeMinimalState({ transcendence: { count: 1, echoes: 100, purchasedUpgradeIds: [], echoIncomeMultiplier: 1, echoCombatMultiplier: 1, echoPrestigeThresholdMultiplier: 1, echoPrestigeRunestoneMultiplier: 1, echoMetaMultiplier: 1, }, }); const { transcendenceData, echoesEarned } = buildPostTranscendenceState(state, "T"); expect(transcendenceData.echoes).toBe(100 + echoesEarned); }); it("persists codex from current state", () => { const codex = { entries: [{ id: "e1", unlockedAt: 1000, sourceType: "exploration" as const }] }; const state = makeMinimalState({ codex }); const { transcendenceState } = buildPostTranscendenceState(state, "T"); expect(transcendenceState.codex).toEqual(codex); }); it("persists story from current state", () => { const story = { unlockedChapterIds: ["ch1"], completedChapters: [] }; const state = makeMinimalState({ story }); const { transcendenceState } = buildPostTranscendenceState(state, "T"); expect(transcendenceState.story).toEqual(story); }); it("persists apotheosis from current state", () => { const apotheosis = { count: 2 }; const state = makeMinimalState({ apotheosis }); const { transcendenceState } = buildPostTranscendenceState(state, "T"); expect(transcendenceState.apotheosis).toEqual(apotheosis); }); it("persists active companion selection across transcendence", () => { const companions = { unlockedCompanionIds: ["lyra", "vex"], activeCompanionId: "vex" }; const state = makeMinimalState({ companions }); const { transcendenceState } = buildPostTranscendenceState(state, "T"); expect(transcendenceState.companions).toEqual(companions); }); it("falls back to fresh companion state when currentState.companions is undefined", () => { const state = makeMinimalState({ companions: undefined }); const { transcendenceState } = buildPostTranscendenceState(state, "T"); expect(transcendenceState.companions).toEqual({ activeCompanionId: null, unlockedCompanionIds: [] }); }); it("resets prestige to fresh state", () => { const state = makeMinimalState({ prestige: { count: 5, runestones: 500, productionMultiplier: 2, purchasedUpgradeIds: [] }, }); const { transcendenceState } = buildPostTranscendenceState(state, "T"); expect(transcendenceState.prestige.count).toBe(0); expect(transcendenceState.prestige.runestones).toBe(0); }); it("accumulates current-run gold and clicks into lifetime totals", () => { const state = makeMinimalState({ player: makePlayer({ totalGoldEarned: 4_000_000, lifetimeGoldEarned: 1_000_000, totalClicks: 500 }), }); const { transcendenceState } = buildPostTranscendenceState(state, "T"); expect(transcendenceState.player.lifetimeGoldEarned).toBe(5_000_000); expect(transcendenceState.player.lifetimeClicks).toBe(500); }); it("accumulates defeated bosses, completed quests, and recruited adventurers into lifetime totals", () => { const boss = { id: "boss_1", status: "defeated" as const }; const quest = { id: "q_1", status: "completed" as const }; const adventurer = { id: "adv_1", count: 5 }; const state = makeMinimalState({ bosses: [ boss ] as GameState["bosses"], quests: [ quest ] as GameState["quests"], adventurers: [ adventurer ] as GameState["adventurers"], }); const { transcendenceState } = buildPostTranscendenceState(state, "T"); expect(transcendenceState.player.lifetimeBossesDefeated).toBe(1); expect(transcendenceState.player.lifetimeQuestsCompleted).toBe(1); expect(transcendenceState.player.lifetimeAdventurersRecruited).toBe(5); }); it("preserves achievements and accumulates newly-unlocked ones into the lifetime total", () => { const achievement = { id: "ach_1", unlockedAt: Date.now() }; const state = makeMinimalState({ achievements: [ achievement ] as GameState["achievements"], }); const { transcendenceState } = buildPostTranscendenceState(state, "T"); expect(transcendenceState.achievements).toEqual([ achievement ]); expect(transcendenceState.player.lifetimeAchievementsUnlocked).toBe(1); }); it("does not re-count achievements already folded into the lifetime total by a previous reset", () => { const achievement = { id: "ach_1", unlockedAt: Date.now() }; const state = makeMinimalState({ achievements: [ achievement ] as GameState["achievements"], player: makePlayer({ lifetimeAchievementsUnlocked: 1 }), }); const { transcendenceState } = buildPostTranscendenceState(state, "T"); expect(transcendenceState.player.lifetimeAchievementsUnlocked).toBe(1); }); it("preserves equipment ownership history across transcendence", () => { const equipment = { id: "rusty_sword", owned: true, everOwned: true }; const state = makeMinimalState({ equipment: [ equipment ] as GameState["equipment"], }); const { transcendenceState } = buildPostTranscendenceState(state, "T"); const matchingItem = transcendenceState.equipment.find((item) => { return item.id === "rusty_sword"; }); expect(matchingItem?.everOwned).toBe(true); }); });