fix: preserve lifetime player stats across prestige

Fixes #37. After prestige, the GameState's player.lifetime* fields were
stale — they did not include the current run's contributions. The Prisma
Player record was updated correctly, but the saved GameState had old values,
so the UI showed stale all-time totals on reload.

buildPostPrestigeState now computes run-stat contributions and folds them
into the fresh player object before writing the prestige state, ensuring
the GameState is always consistent with the DB Player record.
This commit is contained in:
2026-03-09 21:28:16 -07:00
committed by Naomi Carrigan
parent 4d7e624358
commit 062e5b59a6
2 changed files with 141 additions and 9 deletions
+99 -8
View File
@@ -13,14 +13,24 @@ import {
} from "../../src/services/prestige.js";
import type { GameState } from "@elysium/types";
const makePlayer = (totalGoldEarned: number) => ({
discordId: "test_id",
username: "testuser",
discriminator: "0",
avatar: null,
totalGoldEarned,
totalClicks: 0,
characterName: "Tester",
const makePlayer = (
totalGoldEarned: number,
lifetimeGoldEarned = 0,
totalClicks = 0,
) => ({
avatar: null,
characterName: "Tester",
discordId: "test_id",
discriminator: "0",
lifetimeAchievementsUnlocked: 0,
lifetimeAdventurersRecruited: 0,
lifetimeBossesDefeated: 0,
lifetimeClicks: 0,
lifetimeGoldEarned: lifetimeGoldEarned,
lifetimeQuestsCompleted: 0,
totalClicks: totalClicks,
totalGoldEarned: totalGoldEarned,
username: "testuser",
});
const makeMinimalState = (overrides: Partial<GameState> = {}): GameState =>
@@ -242,4 +252,85 @@ describe("buildPostPrestigeState", () => {
const { prestigeState } = buildPostPrestigeState(state, "Tester");
expect(prestigeState.apotheosis).toEqual(apotheosis);
});
it("accumulates current-run gold into lifetime total", () => {
const state = makeMinimalState({
player: makePlayer(4_000_000, 1_000_000),
});
const { prestigeState } = buildPostPrestigeState(state, "Tester");
expect(prestigeState.player.lifetimeGoldEarned).toBe(5_000_000);
});
it("accumulates current-run clicks into lifetime total", () => {
const state = makeMinimalState({
player: makePlayer(4_000_000, 0, 500),
});
const { prestigeState } = buildPostPrestigeState(state, "Tester");
expect(prestigeState.player.lifetimeClicks).toBe(500);
});
it("accumulates defeated bosses into lifetime total", () => {
const defeatedBoss = {
bountyRunestones: 0,
crystalReward: 0,
currentHp: 0,
damagePerSecond: 10,
description: "A boss",
equipmentRewards: [] as string[],
essenceReward: 0,
goldReward: 100,
id: "boss_1",
maxHp: 100,
name: "Boss One",
prestigeRequirement: 0,
status: "defeated" as const,
upgradeRewards: [] as string[],
zoneId: "zone_1",
};
const state = makeMinimalState({ bosses: [ defeatedBoss ] });
const { prestigeState } = buildPostPrestigeState(state, "Tester");
expect(prestigeState.player.lifetimeBossesDefeated).toBe(1);
});
it("accumulates completed quests into lifetime total", () => {
const quest = {
id: "q_1",
name: "A Quest",
description: "Do the thing",
status: "completed" as const,
zoneId: "zone_1",
};
const state = makeMinimalState({ quests: [ quest ] as GameState["quests"] });
const { prestigeState } = buildPostPrestigeState(state, "Tester");
expect(prestigeState.player.lifetimeQuestsCompleted).toBe(1);
});
it("accumulates recruited adventurers into lifetime total", () => {
const adventurer = {
combatPower: 10,
count: 5,
essencePerSecond: 0,
goldPerSecond: 1,
id: "adv_1",
level: 1,
unlocked: true,
};
const state = makeMinimalState({ adventurers: [ adventurer ] as GameState["adventurers"] });
const { prestigeState } = buildPostPrestigeState(state, "Tester");
expect(prestigeState.player.lifetimeAdventurersRecruited).toBe(5);
});
it("accumulates unlocked achievements into lifetime total", () => {
const achievement = {
description: "Did a thing",
id: "ach_1",
name: "Achiever",
requirement: 1,
type: "totalClicks" as const,
unlockedAt: Date.now(),
};
const state = makeMinimalState({ achievements: [ achievement ] as GameState["achievements"] });
const { prestigeState } = buildPostPrestigeState(state, "Tester");
expect(prestigeState.player.lifetimeAchievementsUnlocked).toBe(1);
});
});