This commit is contained in:
NotARoomba 2026-02-20 18:15:20 -05:00
parent a25fe6eae4
commit 9421db9672
5 changed files with 22 additions and 19 deletions

View file

@ -91,23 +91,22 @@ export function calculateShopItemPricing(
};
}
const MIN_ROLL_COST_PERCENT = 0.15;
export function calculateRollCost(
basePrice: number,
effectiveProbability: number,
rollCostOverride?: number | null,
baseProbability?: number,
): number {
// If admin set a manual roll cost, use it
if (rollCostOverride != null && rollCostOverride > 0) {
return rollCostOverride;
}
// Roll cost scales with effective probability (including upgrades).
// Floor at 15% of item price prevents exploitation on rare items where
// base probability is very low (e.g., 2% base → flat cost of 6 scraps).
const scaledCost = Math.round(basePrice * (effectiveProbability / 100));
const floorCost = Math.round(basePrice * MIN_ROLL_COST_PERCENT);
return Math.max(1, scaledCost, floorCost);
// Roll cost is fixed based on base probability, does not scale with upgrades.
// Rare items (baseProbability < 15%) get a 20% floor.
const baseProb = baseProbability ?? effectiveProbability;
if (baseProb < 15) {
return Math.max(1, Math.round(basePrice * 0.20));
}
return Math.max(1, Math.round(basePrice * (baseProb / 100)));
}
export function computeRollThreshold(probability: number): number {

View file

@ -64,8 +64,7 @@ export function computeItemPricing(
);
}
// Roll cost at base probability (includes 15% floor to prevent exploitation on rare items)
const rollCost = calculateRollCost(price, prob);
const rollCost = calculateRollCost(price, prob, undefined, prob);
const threshold = computeRollThreshold(prob);
const expectedRollsAtBase = threshold > 0 ? Math.round((100 / threshold) * 10) / 10 : Infinity;

View file

@ -574,11 +574,11 @@ shop.post("/items/:id/try-luck", async ({ params, headers }) => {
100,
);
// Roll cost scales with effective probability (including upgrades)
const rollCost = calculateRollCost(
currentItem.price,
effectiveProbability,
currentItem.rollCostOverride,
currentItem.baseProbability,
);
// Check if user can afford the roll cost

View file

@ -90,16 +90,20 @@
const SCRAPS_PER_DOLLAR = SCRAPS_PER_HOUR / DOLLARS_PER_HOUR;
// Must match backend calculateRollCost exactly
// Roll cost scales with effective probability (including upgrades)
function calculateRollCost(
basePrice: number,
effectiveProbability: number,
rollCostOverride?: number | null
rollCostOverride?: number | null,
baseProbability?: number
): number {
if (rollCostOverride != null && rollCostOverride > 0) {
return rollCostOverride;
}
return Math.max(1, Math.round(basePrice * (effectiveProbability / 100)));
const baseProb = baseProbability ?? effectiveProbability;
if (baseProb < 15) {
return Math.max(1, Math.round(basePrice * 0.20));
}
return Math.max(1, Math.round(basePrice * (baseProb / 100)));
}
// Must match backend computeRollThreshold exactly
@ -165,8 +169,7 @@
const boostPercent = k * boostAmount;
const effectiveProbability = Math.min(baseProbability + boostPercent, 100);
// Roll cost scales with effective probability (including upgrades)
const rollCost = calculateRollCost(price, effectiveProbability, rollCostOverride);
const rollCost = calculateRollCost(price, effectiveProbability, rollCostOverride, baseProbability);
// Cumulative upgrade cost (geometric series)
let upgradeCostCumulative = 0;

View file

@ -26,8 +26,10 @@
if (item.rollCostOverride != null && item.rollCostOverride > 0) {
return item.rollCostOverride;
}
// Roll cost scales with effective probability (including upgrades)
return Math.max(1, Math.round(item.price * (item.effectiveProbability / 100)));
if (item.baseProbability < 15) {
return Math.max(1, Math.round(item.price * 0.20));
}
return Math.max(1, Math.round(item.price * (item.baseProbability / 100)));
}
let selectedCategories = $state<Set<string>>(new Set());