fix: remove discipline pool-drain model, add conversion stats UI per mana-conversion-spec
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m17s

DISC-2: Removed old pool-drain model from discipline-slice.ts processTick()
- Disciplines no longer drain rawMana or element pools
- canProceedDiscipline() no longer checks mana sufficiency
- Removed auto-pause on insufficient mana from processTick()
- Removed drain display and auto-paused message from DisciplineCard.tsx
- Removed auto-paused log from gameStore.ts tick()

DISC-4: Audited crafting pipeline — no composite crafting logic remains
- craftComposite already removed from manaStore.ts (comment only)
- No other composite crafting references found

DISC-5: Added collapsible formula reference to Conversion Stats section
- Shows unified formula, multipliers, cost formulas, and constraints

DISC-6: Added per-element net regen summary line
- Shows 'Net Fire Regen: +0.50/hr − 0.15/hr = +0.35/hr' per element

DISC-7: Created dedicated 'Conversion Stats' section in Stats tab
- Renamed from 'Conversion Breakdown' to dedicated section header

DISC-8: Added detailed per-element regen breakdown to ManaDisplay
- Each element card now expandable to show produced rate and downstream drains
- New ElementRegenBreakdown type and elementRegenBreakdown prop

Tests: Updated 4 test files to reflect new no-drain behavior
- All 1090 tests pass
This commit is contained in:
2026-06-09 11:18:41 +02:00
parent c89d8fd2d8
commit 3ad919a047
13 changed files with 230 additions and 93 deletions
+6 -29
View File
@@ -5,7 +5,6 @@ import { createSafeStorage } from '../utils/safe-persist';
import type { DisciplineState } from '../types/disciplines';
import type { ElementState } from '../types';
import {
calculateManaDrain,
calculateStatBonus,
canProceedDiscipline,
checkDisciplinePrerequisites,
@@ -171,7 +170,7 @@ export const useDisciplineStore = create<DisciplineStore>()(
processTick(mana) {
const s = get();
let rawMana = mana.rawMana;
const rawMana = mana.rawMana;
const elements = { ...mana.elements };
let newXP = s.totalXP;
const newDisciplines = { ...s.disciplines };
@@ -179,8 +178,6 @@ export const useDisciplineStore = create<DisciplineStore>()(
const newUnlockedRecipes: string[] = [];
const newProcessedPerks = [...(s.processedPerks ?? [])];
const drainedIds: string[] = [];
const drainedNames: string[] = [];
for (const id of s.activeIds ?? []) {
const disc = newDisciplines[id];
if (!disc) continue;
@@ -190,26 +187,6 @@ export const useDisciplineStore = create<DisciplineStore>()(
const def = DISCIPLINE_MAP[id];
if (!def) continue;
const drain = calculateManaDrain(def.drainBase, disc.xp, def.difficultyFactor);
const element = elements[def.manaType];
const available = def.manaType === 'raw' ? rawMana : element?.current;
if (!available || available < drain) {
newDisciplines[id] = { ...disc, paused: true, autoPaused: true };
drainedIds.push(id);
drainedNames.push(def.name);
continue;
}
if (def.manaType === 'raw') {
rawMana -= drain;
} else if (elements[def.manaType]) {
elements[def.manaType] = {
...elements[def.manaType],
current: elements[def.manaType].current - drain,
};
}
const oldXP = disc.xp;
// Compute discipline XP bonus directly to avoid circular import
let xpBonus = 0;
@@ -264,21 +241,21 @@ export const useDisciplineStore = create<DisciplineStore>()(
MAX_CONCURRENT_DISCIPLINES + 3
);
// Remove mana-drained disciplines from activeIds so onStopPracticing fires
const newActiveIds = (s.activeIds ?? []).filter((aid) => !drainedIds.includes(aid));
if (newActiveIds.length === 0 && s.activeIds.length > 0) {
// Check if no active disciplines remain to fire onStopPracticing
const keepActiveIds = s.activeIds ?? [];
if (keepActiveIds.length === 0 && s.activeIds.length > 0) {
get().practicingCallbacks?.onStopPracticing?.();
}
set({
disciplines: newDisciplines,
activeIds: newActiveIds,
activeIds: keepActiveIds,
totalXP: newXP,
concurrentLimit: Math.max(s.concurrentLimit, newLimit),
processedPerks: newProcessedPerks,
});
return { rawMana, elements, unlockedEffects: newUnlockedEffects, unlockedRecipes: newUnlockedRecipes, autoPausedNames: drainedNames };
return { rawMana, elements, unlockedEffects: newUnlockedEffects, unlockedRecipes: newUnlockedRecipes, autoPausedNames: [] };
},
}),
{ storage: createSafeStorage(), name: 'mana-loop-discipline-store', version: 1, partialize: (state) => ({ disciplines: state.disciplines, activeIds: state.activeIds, concurrentLimit: state.concurrentLimit, totalXP: state.totalXP, processedPerks: state.processedPerks }) }