fix: runestone formula, prestige/transcendence rebalance, exploration fixes, and comprehensive balance audit #135

Merged
naomi merged 53 commits from fix/stones into main 2026-03-31 19:57:53 -07:00
2 changed files with 27 additions and 10 deletions
Showing only changes of commit 959b86fa8b - Show all commits
+15 -4
View File
@@ -20,6 +20,13 @@ const runestonesPerPrestigeLevel = 10;
const milestoneInterval = 5;
const milestoneRunestonesPerInterval = 25;
/*
* Hard cap on the base runestone yield (before multipliers) to prevent
* extreme AFK accumulation from producing game-breaking runestone counts.
* With all upgrades (5.625× max) this caps out at ~281 per prestige.
*/
const maxBaseRunestones = 50;
/**
* Calculates the gold threshold required for the next prestige.
* Formula: BASE * SCALE_FACTOR^prestigeCount — each prestige makes the next threshold harder.
@@ -107,7 +114,9 @@ interface RunestoneParameters {
/**
* Calculates how many runestones the player earns from a prestige.
* Formula: floor(sqrt(totalGoldEarned / threshold)) * RUNESTONES_PER_PRESTIGE_LEVEL * runestoneMultiplier.
* Formula: min(floor(cbrt(totalGoldEarned / threshold)) * RUNESTONES_PER_PRESTIGE_LEVEL, MAX_BASE) * multipliers.
* Uses cube root for stronger diminishing returns than sqrt, and caps the base before multipliers
* to prevent extended AFK sessions from producing runestone windfalls.
* @param parameters - The parameters for the runestone calculation.
* @param parameters.totalGoldEarned - The total gold earned in the current run.
* @param parameters.prestigeCount - The current prestige count.
@@ -123,9 +132,11 @@ const calculateRunestones = (parameters: RunestoneParameters): number => {
echoRunestoneMultiplier = 1,
} = parameters;
const threshold = calculatePrestigeThreshold(prestigeCount);
const base
= Math.floor(Math.sqrt(totalGoldEarned / threshold))
* runestonesPerPrestigeLevel;
const base = Math.min(
Math.floor(Math.cbrt(totalGoldEarned / threshold))
* runestonesPerPrestigeLevel,
maxBaseRunestones,
);
const runestoneMult = getCategoryMultiplier(
purchasedUpgradeIds,
"runestones",
+12 -6
View File
@@ -99,21 +99,27 @@ describe("isEligibleForPrestige", () => {
describe("calculateRunestones", () => {
it("calculates basic runestones formula", () => {
// floor(sqrt(4_000_000 / 1_000_000)) × 10 = floor(2) × 10 = 20
// floor(cbrt(4_000_000 / 1_000_000)) × 10 = floor(cbrt(4)) × 10 = 1 × 10 = 10
const result = calculateRunestones({ totalGoldEarned: 4_000_000, prestigeCount: 0, purchasedUpgradeIds: [] });
expect(result).toBe(20);
expect(result).toBe(10);
});
it("applies echo runestone multiplier", () => {
// floor(sqrt(4) × 10) = 20; × 2 = 40
// floor(cbrt(4)) × 10 = 10; × 2 = 20
const result = calculateRunestones({ totalGoldEarned: 4_000_000, prestigeCount: 0, purchasedUpgradeIds: [], echoRunestoneMultiplier: 2 });
expect(result).toBe(40);
expect(result).toBe(20);
});
it("applies purchased runestone upgrade multiplier", () => {
// With "runestones_1" purchased (multiplier 1.25): floor(20 × 1.25) = 25
// With "runestone_gain_1" purchased (multiplier 1.25): floor(10 × 1.25) = 12
const result = calculateRunestones({ totalGoldEarned: 4_000_000, prestigeCount: 0, purchasedUpgradeIds: ["runestone_gain_1"] });
expect(result).toBeGreaterThan(20);
expect(result).toBe(12);
});
it("caps base runestones before multipliers", () => {
// cbrt(300_000_000 / 1_000_000) = cbrt(300) ≈ 6.67 → floor = 6 → 6 × 10 = 60, capped at 50
const result = calculateRunestones({ totalGoldEarned: 300_000_000, prestigeCount: 0, purchasedUpgradeIds: [] });
expect(result).toBe(50);
});
});