generated from nhcarrigan/template
balance: smooth prestige income cliff, quadratic milestones, exponential combat scaling (#170, #171)
Reduce income_10 cost 30k→22.5k and income_11 80k→60k (25% cut each) to ease the late-prestige runestone cliff without collapsing the timeline. Change prestige milestone bonus from linear (n×25) to quadratic (n²×25) so high-prestige milestones feel meaningful (P100 = 10k stones). Replace linear prestige combat multiplier (1 + count×0.1) with exponential (4^count) in both the tick engine and server-side boss route. Without this the final boss (2×10^145 HP) was unreachable by ~112 orders of magnitude; base-4 makes it achievable around P190, consistent with the 6-month target.
This commit is contained in:
@@ -96,7 +96,7 @@ export const defaultPrestigeUpgrades: Array<PrestigeUpgrade> = [
|
|||||||
id: "income_10",
|
id: "income_10",
|
||||||
multiplier: 200,
|
multiplier: 200,
|
||||||
name: "Eternal Rune I",
|
name: "Eternal Rune I",
|
||||||
runestonesCost: 30_000,
|
runestonesCost: 22_500,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
category: "income",
|
category: "income",
|
||||||
@@ -105,7 +105,7 @@ export const defaultPrestigeUpgrades: Array<PrestigeUpgrade> = [
|
|||||||
id: "income_11",
|
id: "income_11",
|
||||||
multiplier: 500,
|
multiplier: 500,
|
||||||
name: "Eternal Rune II",
|
name: "Eternal Rune II",
|
||||||
runestonesCost: 80_000,
|
runestonesCost: 60_000,
|
||||||
},
|
},
|
||||||
// ── Click Power ───────────────────────────────────────────────────────────
|
// ── Click Power ───────────────────────────────────────────────────────────
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -24,6 +24,13 @@ import { updateChallengeProgress } from "../services/dailyChallenges.js";
|
|||||||
import { logger } from "../services/logger.js";
|
import { logger } from "../services/logger.js";
|
||||||
import type { HonoEnvironment } from "../types/hono.js";
|
import type { HonoEnvironment } from "../types/hono.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exponential base for the prestige combat multiplier: Math.pow(base, prestigeCount).
|
||||||
|
* Replaces the former linear formula (1 + count * 0.1) to enable late-game zone progression.
|
||||||
|
* Must be kept in sync with prestigeCombatBase in apps/web/src/engine/tick.ts.
|
||||||
|
*/
|
||||||
|
const prestigeCombatBase = 4;
|
||||||
|
|
||||||
const bossRouter = new Hono<HonoEnvironment>();
|
const bossRouter = new Hono<HonoEnvironment>();
|
||||||
|
|
||||||
bossRouter.use("*", authMiddleware);
|
bossRouter.use("*", authMiddleware);
|
||||||
@@ -38,8 +45,7 @@ const calculatePartyStats = (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line stylistic/no-mixed-operators -- prestige count * factor is clear
|
const prestigeMultiplier = Math.pow(prestigeCombatBase, state.prestige.count);
|
||||||
const prestigeMultiplier = 1 + state.prestige.count * 0.1;
|
|
||||||
|
|
||||||
// Apply equipped weapon's combat bonus
|
// Apply equipped weapon's combat bonus
|
||||||
// eslint-disable-next-line capitalized-comments -- v8 ignore
|
// eslint-disable-next-line capitalized-comments -- v8 ignore
|
||||||
|
|||||||
@@ -159,7 +159,7 @@ const calculateProductionMultiplier = (
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the milestone runestone bonus for the given prestige count.
|
* Returns the milestone runestone bonus for the given prestige count.
|
||||||
* Every MILESTONE_INTERVAL prestiges awards milestone_number * MILESTONE_RUNESTONES_PER_INTERVAL stones.
|
* Every MILESTONE_INTERVAL prestiges awards milestone_number² * MILESTONE_RUNESTONES_PER_INTERVAL stones.
|
||||||
* @param prestigeCount - The prestige count after the current prestige.
|
* @param prestigeCount - The prestige count after the current prestige.
|
||||||
* @returns The milestone runestone bonus, or 0 if not a milestone prestige.
|
* @returns The milestone runestone bonus, or 0 if not a milestone prestige.
|
||||||
*/
|
*/
|
||||||
@@ -168,7 +168,7 @@ const calculateMilestoneBonus = (prestigeCount: number): number => {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
const milestoneNumber = prestigeCount / milestoneInterval;
|
const milestoneNumber = prestigeCount / milestoneInterval;
|
||||||
return milestoneNumber * milestoneRunestonesPerInterval;
|
return milestoneNumber * milestoneNumber * milestoneRunestonesPerInterval;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -151,12 +151,12 @@ describe("calculateMilestoneBonus", () => {
|
|||||||
expect(calculateMilestoneBonus(5)).toBe(25);
|
expect(calculateMilestoneBonus(5)).toBe(25);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns 50 at prestige 10", () => {
|
it("returns 100 at prestige 10", () => {
|
||||||
expect(calculateMilestoneBonus(10)).toBe(50);
|
expect(calculateMilestoneBonus(10)).toBe(100);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns 75 at prestige 15", () => {
|
it("returns 225 at prestige 15", () => {
|
||||||
expect(calculateMilestoneBonus(15)).toBe(75);
|
expect(calculateMilestoneBonus(15)).toBe(225);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -84,6 +84,12 @@ const checkAchievements = (state: GameState): Array<Achievement> => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exponential base for the prestige combat multiplier: Math.pow(base, prestigeCount).
|
||||||
|
* Replaces the former linear formula (1 + count * 0.1) to enable late-game zone progression.
|
||||||
|
*/
|
||||||
|
export const PRESTIGE_COMBAT_BASE = 4;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Maximum value any resource can accumulate to. Beyond this JS floats lose all useful precision.
|
* Maximum value any resource can accumulate to. Beyond this JS floats lose all useful precision.
|
||||||
*/
|
*/
|
||||||
@@ -296,8 +302,7 @@ export const computeEffectiveAdventurerStats = (
|
|||||||
|
|
||||||
const runestonesIncome = state.prestige.runestonesIncomeMultiplier ?? 1;
|
const runestonesIncome = state.prestige.runestonesIncomeMultiplier ?? 1;
|
||||||
const runestonesEssence = state.prestige.runestonesEssenceMultiplier ?? 1;
|
const runestonesEssence = state.prestige.runestonesEssenceMultiplier ?? 1;
|
||||||
// eslint-disable-next-line stylistic/no-mixed-operators -- prestige count * factor is clear
|
const prestigeCombatMultiplier = Math.pow(PRESTIGE_COMBAT_BASE, state.prestige.count);
|
||||||
const prestigeCombatMultiplier = 1 + state.prestige.count * 0.1;
|
|
||||||
const echoIncome = state.transcendence?.echoIncomeMultiplier ?? 1;
|
const echoIncome = state.transcendence?.echoIncomeMultiplier ?? 1;
|
||||||
const echoCombatMultiplier = state.transcendence?.echoCombatMultiplier ?? 1;
|
const echoCombatMultiplier = state.transcendence?.echoCombatMultiplier ?? 1;
|
||||||
const craftedGoldMultiplier
|
const craftedGoldMultiplier
|
||||||
@@ -378,8 +383,7 @@ export const computePartyCombatPower = (state: GameState): number => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line stylistic/no-mixed-operators -- prestige count * factor is clear
|
const prestigeMultiplier = Math.pow(PRESTIGE_COMBAT_BASE, state.prestige.count);
|
||||||
const prestigeMultiplier = 1 + state.prestige.count * 0.1;
|
|
||||||
|
|
||||||
const equipmentCombatMultiplier = state.equipment.
|
const equipmentCombatMultiplier = state.equipment.
|
||||||
filter((item) => {
|
filter((item) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user