fix: rebalance rawCost and componentCost formulas to be achievable with realistic regen
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m30s
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m30s
- Changed rawCost from exponential 10^(d+1) to linear 2*distance - Base (d=1): 100 → 2 - Composite (d=2): 1,000 → 4 - Exotic (d=3): 10,000 → 6 - Time (d=4): 100,000 → 8 - Changed componentCost from 10*(d+1) to 3*distance - Composite (d=2): 30 → 6 per component - Exotic (d=3): 40 → 9 per component - Time (d=4): 50 → 12 per component - Updated test comments and expectations in conversion-pause-bug-regression.test.ts and mana-conversion-component-deduction.test.ts to match new values Root cause: The exponential rawCost formula produced values 100-10000x too high, making mana conversion permanently paused since drain (rate × cost) always exceeded even late-game raw regen (~20-50/hr). The new linear formula allows conversions to be sustainable at all game stages. Fixes #378
This commit is contained in:
@@ -41,9 +41,9 @@ describe('Bug #348 — conversion pause regression', () => {
|
||||
rawGrossRegen,
|
||||
});
|
||||
|
||||
// Earth conversion: baseRate=22.04, rawCost=100
|
||||
// rawDrain = 22.04 * 1.0 * 1.0 * 1.0 * 100 = 2204/hr
|
||||
// 2204 < 3342.95 => should NOT be paused
|
||||
// Earth conversion: baseRate=22.04, rawCost=2
|
||||
// rawDrain = 22.04 * 1.0 * 1.0 * 1.0 * 2 = 44.08/hr
|
||||
// 44.08 < 3342.95 => should NOT be paused
|
||||
const earthEntry = result.rates['earth'];
|
||||
expect(earthEntry).toBeDefined();
|
||||
expect(earthEntry.paused).toBe(false);
|
||||
|
||||
@@ -32,14 +32,14 @@ describe('Mana Conversion — component consumption deduction (bug #293)', () =>
|
||||
});
|
||||
|
||||
// Fire is used as a component for metal conversion
|
||||
// metal rate = 0.35, fire component cost = 30 per unit
|
||||
// elementDrain[fire] = 0.35 * 30 = 10.5/hr
|
||||
// metal rate = 0.35, fire component cost = 6 per unit (3 * distance 2)
|
||||
// elementDrain[fire] = 0.35 * 6 = 2.1/hr
|
||||
expect(result.elementDrain['fire']).toBeGreaterThan(0);
|
||||
expect(result.elementDrain['fire']).toBeCloseTo(0.35 * 30, 5);
|
||||
expect(result.elementDrain['fire']).toBeCloseTo(0.35 * 6, 5);
|
||||
|
||||
// Earth is also used as a component for metal conversion
|
||||
expect(result.elementDrain['earth']).toBeGreaterThan(0);
|
||||
expect(result.elementDrain['earth']).toBeCloseTo(0.35 * 30, 5);
|
||||
expect(result.elementDrain['earth']).toBeCloseTo(0.35 * 6, 5);
|
||||
});
|
||||
|
||||
it('should compute elementRegen as produced - drained', () => {
|
||||
@@ -65,7 +65,7 @@ describe('Mana Conversion — component consumption deduction (bug #293)', () =>
|
||||
rawGrossRegen: 10000,
|
||||
});
|
||||
|
||||
// Fire: produced = 0.5/hr, drained = 0.35 * 30 = 10.5/hr
|
||||
// Fire: produced = 0.5/hr, drained = 0.35 * 6 = 2.1/hr
|
||||
const fireEntry = result.rates['fire'];
|
||||
expect(fireEntry.finalRate).toBeCloseTo(0.5, 5);
|
||||
|
||||
@@ -76,7 +76,7 @@ describe('Mana Conversion — component consumption deduction (bug #293)', () =>
|
||||
// Verify net: produced - drained
|
||||
const fireProduced = fireEntry.finalRate;
|
||||
const fireDrained = result.elementDrain['fire'] || 0;
|
||||
expect(fireProduced - fireDrained).toBeCloseTo(0.5 - 10.5, 5);
|
||||
expect(fireProduced - fireDrained).toBeCloseTo(0.5 - 2.1, 5);
|
||||
});
|
||||
|
||||
it('should have zero elementDrain when element is not used as component', () => {
|
||||
@@ -135,15 +135,15 @@ describe('Mana Conversion — component consumption deduction (bug #293)', () =>
|
||||
rawGrossRegen: 10000,
|
||||
});
|
||||
|
||||
// Fire: produced = 0.5, drained by metal (0.35 * 30 = 10.5) and lightning (0.35 * 30 = 10.5)
|
||||
// Total fire drain = 10.5 + 10.5 = 21.0
|
||||
expect(result.elementDrain['fire']).toBeCloseTo(21.0, 5);
|
||||
// Fire: produced = 0.5, drained by metal (0.35 * 6 = 2.1) and lightning (0.35 * 6 = 2.1)
|
||||
// Total fire drain = 2.1 + 2.1 = 4.2
|
||||
expect(result.elementDrain['fire']).toBeCloseTo(4.2, 5);
|
||||
|
||||
// Earth: produced = 0 (no earth discipline), drained by metal (0.35 * 30 = 10.5)
|
||||
expect(result.elementDrain['earth']).toBeCloseTo(10.5, 5);
|
||||
// Earth: produced = 0 (no earth discipline), drained by metal (0.35 * 6 = 2.1)
|
||||
expect(result.elementDrain['earth']).toBeCloseTo(2.1, 5);
|
||||
|
||||
// Air: produced = 0 (no air discipline), drained by lightning (0.35 * 30 = 10.5)
|
||||
expect(result.elementDrain['air']).toBeCloseTo(10.5, 5);
|
||||
// Air: produced = 0 (no air discipline), drained by lightning (0.35 * 6 = 2.1)
|
||||
expect(result.elementDrain['air']).toBeCloseTo(2.1, 5);
|
||||
|
||||
// Metal: produced = 0.35, not consumed by anything in this setup
|
||||
expect(result.elementDrain['metal'] || 0).toBe(0);
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
// Costs are deducted from regen (not from the mana pool).
|
||||
//
|
||||
// For a destination element at distance d:
|
||||
// rawCost = 10^(d+1)
|
||||
// componentCost = 10 * (d+1) per component
|
||||
// rawCost = 2 * d
|
||||
// componentCost = 3 * d per component
|
||||
|
||||
import type { ElementRecipe } from '../types';
|
||||
|
||||
@@ -20,11 +20,11 @@ export interface ConversionCost {
|
||||
}
|
||||
|
||||
function computeRawCost(distance: number): number {
|
||||
return Math.pow(10, distance + 1);
|
||||
return 2 * distance;
|
||||
}
|
||||
|
||||
function computeComponentCost(distance: number): number {
|
||||
return 10 * (distance + 1);
|
||||
return 3 * distance;
|
||||
}
|
||||
|
||||
/** Build a ConversionCost for a base element (distance 1, no components) */
|
||||
@@ -32,14 +32,14 @@ function baseElementCost(element: string): ConversionCost {
|
||||
return {
|
||||
element,
|
||||
distance: 1,
|
||||
rawCost: computeRawCost(1), // 100
|
||||
rawCost: computeRawCost(1), // 2
|
||||
componentCosts: {},
|
||||
};
|
||||
}
|
||||
|
||||
/** Build a ConversionCost for a composite element (distance 2) */
|
||||
function compositeElementCost(element: string, components: string[]): ConversionCost {
|
||||
const costPerComponent = computeComponentCost(2); // 30 each
|
||||
const costPerComponent = computeComponentCost(2); // 6 each
|
||||
const componentCosts: Record<string, number> = {};
|
||||
for (const c of components) {
|
||||
componentCosts[c] = (componentCosts[c] || 0) + costPerComponent;
|
||||
@@ -47,14 +47,14 @@ function compositeElementCost(element: string, components: string[]): Conversion
|
||||
return {
|
||||
element,
|
||||
distance: 2,
|
||||
rawCost: computeRawCost(2), // 1,000
|
||||
rawCost: computeRawCost(2), // 4
|
||||
componentCosts,
|
||||
};
|
||||
}
|
||||
|
||||
/** Build a ConversionCost for an exotic element (distance 3) */
|
||||
function exoticElementCost(element: string, components: string[]): ConversionCost {
|
||||
const costPerComponent = computeComponentCost(3); // 40 each
|
||||
const costPerComponent = computeComponentCost(3); // 9 each
|
||||
const componentCosts: Record<string, number> = {};
|
||||
for (const c of components) {
|
||||
componentCosts[c] = (componentCosts[c] || 0) + costPerComponent;
|
||||
@@ -62,14 +62,14 @@ function exoticElementCost(element: string, components: string[]): ConversionCos
|
||||
return {
|
||||
element,
|
||||
distance: 3,
|
||||
rawCost: computeRawCost(3), // 10,000
|
||||
rawCost: computeRawCost(3), // 6
|
||||
componentCosts,
|
||||
};
|
||||
}
|
||||
|
||||
/** Build a ConversionCost for time (distance 4) */
|
||||
function timeElementCost(element: string, components: string[]): ConversionCost {
|
||||
const costPerComponent = computeComponentCost(4); // 50 each
|
||||
const costPerComponent = computeComponentCost(4); // 12 each
|
||||
const componentCosts: Record<string, number> = {};
|
||||
for (const c of components) {
|
||||
componentCosts[c] = (componentCosts[c] || 0) + costPerComponent;
|
||||
@@ -77,7 +77,7 @@ function timeElementCost(element: string, components: string[]): ConversionCost
|
||||
return {
|
||||
element,
|
||||
distance: 4,
|
||||
rawCost: computeRawCost(4), // 100,000
|
||||
rawCost: computeRawCost(4), // 8
|
||||
componentCosts,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user