fix: Elemental Mana Capacity disciplines now increase element capacity
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m23s

- Add optional baseMax field to ElementState to track prestige-derived max separately from bonuses
- Add computeElementMaxWithBonuses action to manaStore that computes max = baseMax + per-element bonus
- Apply per-element cap bonuses from disciplines and equipment in game tick (elementCap_* keys)
- Fix resetMana to use correct prestige key (elementalAttune instead of nonexistent elemMax)
- Add store migration (v1->v2) to populate baseMax for existing saved games
- Extract pact ritual processing to pipelines/pact-ritual.ts
- Extract element cap bonus utilities to utils/element-cap-bonus.ts
- Fix inline element types in crafting-fabricator.ts
- Update test fixtures to include baseMax in element literals

Fixes #185
This commit is contained in:
2026-05-28 19:49:47 +02:00
parent 8fef73d233
commit 6355cf308b
11 changed files with 183 additions and 51 deletions
+28 -33
View File
@@ -12,6 +12,8 @@ import type { ComputedEffects } from '../effects/upgrade-effects.types';
import { computeDisciplineEffects } from '../effects/discipline-effects';
import { computeMaxMana, computeRegen, getMeditationBonus, getIncursionStrength, calcInsight } from '../utils';
import { mergePerElementCapBonuses } from '../utils/element-cap-bonus';
import { processPactRitual } from './pipelines/pact-ritual';
import { useUIStore } from './uiStore';
import { usePrestigeStore } from './prestigeStore';
import { useManaStore } from './manaStore';
@@ -204,40 +206,18 @@ export const useGameStore = create<GameCoordinatorStore>()(
}
// Pact ritual
if (ctx.prestige.pactRitualFloor !== null) {
const guardian = getGuardianForFloor(ctx.prestige.pactRitualFloor);
if (guardian) {
const pactAffinity = Math.min(0.9,
(ctx.prestige.prestigeUpgrades.pactAffinity || 0) * 0.1 + (disciplineEffects.bonuses.pactAffinityBonus || 0));
const requiredTime = guardian.pactTime * (1 - pactAffinity);
if (ctx.prestige.pactRitualProgress + HOURS_PER_TICK >= requiredTime) {
addLog(`📜 Pact signed with ${guardian.name}! You have gained their boons.`);
// Unlock mana types granted by this guardian
const manaStore = useManaStore.getState();
for (const manaType of guardian.unlocksMana || []) {
const result = manaStore.unlockElement(manaType, 0);
if (result.success) {
addLog(`${manaType.charAt(0).toUpperCase() + manaType.slice(1)} mana unlocked!`);
}
}
writes.prestige = {
...(writes.prestige || {}),
signedPacts: [...ctx.prestige.signedPacts, ctx.prestige.pactRitualFloor],
defeatedGuardians: ctx.prestige.defeatedGuardians.filter(f => f !== ctx.prestige.pactRitualFloor),
pactRitualFloor: null,
pactRitualProgress: 0,
};
} else {
writes.prestige = {
...(writes.prestige || {}),
pactRitualProgress: ctx.prestige.pactRitualProgress + HOURS_PER_TICK,
};
}
}
const pactResult = processPactRitual(
ctx.prestige.pactRitualFloor,
ctx.prestige.pactRitualProgress,
ctx.prestige.signedPacts,
ctx.prestige.defeatedGuardians,
ctx.prestige.prestigeUpgrades.pactAffinity || 0,
disciplineEffects.bonuses.pactAffinityBonus || 0,
);
if (pactResult.writes) {
writes.prestige = { ...(writes.prestige || {}), ...pactResult.writes };
}
pactResult.logs.forEach(l => addLog(l));
// Discipline tick
const disciplineResult = useDisciplineStore.getState().processTick({
@@ -294,6 +274,21 @@ export const useGameStore = create<GameCoordinatorStore>()(
}
}
// Apply per-element capacity bonuses from disciplines and equipment
const perElementCapBonuses = mergePerElementCapBonuses(
disciplineEffects.bonuses,
equipmentEffects.bonuses,
);
useManaStore.getState().computeElementMaxWithBonuses(perElementCapBonuses);
// Sync updated max/baseMax from mana store into tick elements snapshot
const manaStateAfter = useManaStore.getState();
for (const [ek, es] of Object.entries(manaStateAfter.elements)) {
if (elements[ek]) {
elements[ek] = { ...elements[ek], max: es.max, baseMax: es.baseMax };
}
}
// Combat — delegate to combatStore
if (ctx.combat.currentAction === 'climb') {
const combatResult = useCombatStore.getState().processCombatTick(