fix: apply mana drain to conversion disciplines (bug #379)
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m24s

- Remove isConversionDiscipline guard in discipline-slice.ts:processTick
  that was skipping mana drain for all 24 conversion disciplines
- Update test to verify conversion disciplines DO drain mana
- Add regression tests for auto-pause and XP accrual on conversion disciplines
- Fix misleading comments in elemental-regen.ts and elemental-regen-advanced.ts

All 1196 tests pass.
This commit is contained in:
2026-06-12 18:56:51 +02:00
parent fde5911780
commit 4863dbc324
8 changed files with 657 additions and 15 deletions
@@ -131,7 +131,7 @@ describe('DisciplineStore', () => {
expect(result.elements.fire.current).toBe(98);
});
it('should not drain mana for conversion disciplines (sourceManaTypes)', () => {
it('should drain mana for conversion disciplines (regression: was zero cost)', () => {
useDisciplineStore.setState({
disciplines: {},
activeIds: [],
@@ -139,13 +139,45 @@ describe('DisciplineStore', () => {
totalXP: 0,
});
// regen-fire is a conversion discipline with sourceManaTypes: ['raw']
// It has manaType='fire' and drainBase=1.5 — it MUST drain fire mana
useDisciplineStore.getState().activate('regen-fire', {
elements: { fire: { unlocked: true, current: 100, max: 100, baseMax: 100 } },
});
const result = useDisciplineStore.getState().processTick({ rawMana: 1000, elements: { fire: { unlocked: true, current: 100, max: 100, baseMax: 100 } } });
// Conversion disciplines should NOT drain from pools
expect(result.rawMana).toBe(1000);
expect(result.elements.fire.current).toBe(100);
// Conversion disciplines MUST drain from their mana pool (bug fix #379)
// regen-fire: drainBase=1.5, xp=0, difficultyFactor=120
// drain = 1.5 * (1 + (0/120)^0.4) = 1.5
expect(result.elements.fire.current).toBeCloseTo(98.5, 1);
});
it('should auto-pause conversion discipline when element mana is insufficient', () => {
useDisciplineStore.setState({
disciplines: {},
activeIds: [],
concurrentLimit: 1,
totalXP: 0,
});
// regen-fire has drainBase=1.5, needs at least 1.5 fire mana
useDisciplineStore.getState().activate('regen-fire', {
elements: { fire: { unlocked: true, current: 1, max: 100, baseMax: 100 } },
});
const result = useDisciplineStore.getState().processTick({ rawMana: 1000, elements: { fire: { unlocked: true, current: 1, max: 100, baseMax: 100 } } });
expect(useDisciplineStore.getState().disciplines['regen-fire'].autoPaused).toBe(true);
expect(result.autoPausedNames).toContain('Fire Mana Conversion Speed');
});
it('should still accrue XP for conversion disciplines when mana is sufficient', () => {
useDisciplineStore.setState({
disciplines: {},
activeIds: [],
concurrentLimit: 1,
totalXP: 0,
});
useDisciplineStore.getState().activate('regen-fire', {
elements: { fire: { unlocked: true, current: 100, max: 100, baseMax: 100 } },
});
useDisciplineStore.getState().processTick({ rawMana: 1000, elements: { fire: { unlocked: true, current: 100, max: 100, baseMax: 100 } } });
expect(useDisciplineStore.getState().disciplines['regen-fire'].xp).toBe(1);
});
it('should re-activate auto-paused discipline when mana is restored', () => {
@@ -4,7 +4,7 @@
//
// NEW MODEL: Disciplines contribute to conversion_{element} stat bonus.
// The unified conversion-rates.ts calculator handles rate computation.
// No direct mana drain — costs are deducted from regen.
// Mana drain is applied from the target mana type pool each tick.
import { DisciplinesAttunementType } from '../../types/disciplines';
import type { DisciplineDefinition } from '../../types/disciplines';
@@ -4,7 +4,7 @@
//
// NEW MODEL: Disciplines contribute to conversion_{element} stat bonus.
// The unified conversion-rates.ts calculator handles rate computation.
// No direct mana drain — costs are deducted from regen.
// Mana drain is applied from the target mana type pool each tick.
import { DisciplinesAttunementType } from '../../types/disciplines';
import type { DisciplineDefinition } from '../../types/disciplines';
+5 -7
View File
@@ -212,13 +212,11 @@ export const useDisciplineStore = create<DisciplineStore>()(
const def = DISCIPLINE_MAP[id];
if (!def) continue;
// ── Mana drain (skip for conversion disciplines) ──────────────
// Conversion disciplines (with sourceManaTypes) are handled by
// the unified conversion system — they contribute to conversion
// rates, not direct pool drain.
const isConversionDiscipline = !!(def.sourceManaTypes && def.sourceManaTypes.length > 0);
if (!isConversionDiscipline) {
// ── Mana drain ──────────────────────────────────────────────────
// All disciplines (including conversion) drain from their mana pool.
// Conversion disciplines both drain their target mana type AND
// contribute to conversion rates.
{
const drain = calculateManaDrain(def.drainBase, disc.xp, def.difficultyFactor);
// Determine which mana pool to drain from