feat: overhaul mana conversion system to unified regen-deduction model
Build and Publish Mana Loop Docker Image / build-and-publish (push) Failing after 1m2s

- New files: element-distance.ts, conversion-costs.ts, conversion-rates.ts
- All conversion types (discipline, attunement, pact) use unified formula
- Conversion costs scale exponentially by element tier (10^(d+1) raw, 10*(d+1) per component)
- Costs deducted from regen, not from mana pool
- Auto-pause on insufficient regen with UI warning
- Meditation boosts conversion rates (reduced by distance)
- Attunement levels provide +50% multiplicative bonus per level
- Guardian pacts provide +0.15/hr base rate + invoker level bonus
- Removed convertMana, processConvertAction, craftComposite from manaStore
- Stats tab shows per-element conversion breakdown with formulas
- ManaDisplay shows per-element net regen rates
- All 916 tests pass, all files under 400 lines
This commit is contained in:
2026-06-04 18:12:41 +02:00
parent 94a2b671b9
commit ab3afae2a6
19 changed files with 742 additions and 572 deletions
+30 -8
View File
@@ -1,5 +1,9 @@
// ─── Discipline Effects ───────────────────────────────────────────────────────
// Computes bonuses from active disciplines and integrates with the unified effect system
//
// NEW MODEL: Conversion disciplines contribute to conversion_{element} stat bonuses.
// The unified conversion-rates.ts calculator handles rate computation and regen deduction.
// This file no longer builds a direct conversions map for the tick pipeline.
import type { DisciplineStoreState } from '../stores/discipline-slice';
import type { DisciplineState } from '../types/disciplines';
@@ -32,21 +36,40 @@ const KNOWN_BONUS_STATS = new Set([
'disciplineXpBonus',
'clickManaMultiplier',
'studySpeed',
// Conversion stat bonuses (one per element)
'conversion_fire',
'conversion_water',
'conversion_air',
'conversion_earth',
'conversion_light',
'conversion_dark',
'conversion_death',
'conversion_transference',
'conversion_metal',
'conversion_sand',
'conversion_lightning',
'conversion_frost',
'conversion_blackflame',
'conversion_radiantflames',
'conversion_miasma',
'conversion_shadowglass',
'conversion_crystal',
'conversion_stellar',
'conversion_void',
'conversion_soul',
'conversion_plasma',
'conversion_time',
]);
export interface DisciplineEffectsResult {
bonuses: Record<string, number>;
multipliers: Record<string, number>;
specials: Set<string>;
/**
* Bonus to the meditation multiplier cap from disciplines.
* Each point of meditationCapBonus adds +0.5 to the max meditation multiplier.
*/
meditationCapBonus: number;
/**
* Conversion entries: for each active discipline with a conversionRate,
* maps target mana type → { rate, sourceManaTypes }.
* The tick pipeline drains source mana types and adds to the target.
* Used by the unified conversion calculator for rate computation.
*/
conversions: Record<string, { rate: number; sourceManaTypes: string[] }>;
}
@@ -77,15 +100,15 @@ export function computeDisciplineEffects(_state?: DisciplineStoreState): Discipl
}
for (const { disc, def } of activeDiscs) {
// Continuous stat bonus
// Continuous stat bonus (includes conversion_{element} for regen disciplines)
const statBonus = calculateStatBonus(def.statBonus.baseValue, disc.xp, def.scalingFactor);
if (def.statBonus.stat) {
addBonus(def.statBonus.stat, statBonus);
}
// Conversion entry — if this discipline defines conversionRate
// This is used by the unified conversion calculator
if (def.conversionRate && def.sourceManaTypes && def.sourceManaTypes.length > 0) {
// Scale the conversion rate by the stat bonus multiplier
const scaledRate = def.conversionRate + statBonus;
conversions[def.manaType] = {
rate: scaledRate,
@@ -102,7 +125,6 @@ export function computeDisciplineEffects(_state?: DisciplineStoreState): Discipl
} else if (!perk.unlocksEffects) {
specials.add(perk.id);
}
// Perks with unlocksEffects are handled by discipline-slice.ts processTick()
} else if (perk.type === 'infinite') {
if (perk.bonus) {
const interval = perk.value;