Deduct mana conversion rates from raw regen (Bug 10)
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 4m35s

- Added getTotalAttunementConversionDrain function to calculate total conversion drain
- Updated computeRegen to include attunement regen bonus
- Added computeEffectiveRegenForDisplay function for UI display
- Added conversionDrains to GameState to track per-attunement drain
- Updated tick function to track and persist conversion drains
- Build passes successfully
This commit is contained in:
Refactoring Agent
2026-04-27 12:25:17 +02:00
parent 8261baab54
commit ebb9d15e9e
7 changed files with 121 additions and 12 deletions
+13
View File
@@ -101,6 +101,19 @@ export function getTotalAttunementRegen(attunements: Record<string, { active: bo
}, 0);
}
// Helper function to calculate total conversion drain from all active attunements (per hour)
export function getTotalAttunementConversionDrain(attunements: Record<string, { active: boolean; level: number; experience: number }>): number {
return Object.entries(attunements)
.filter(([, state]) => state.active)
.reduce((total, [id, state]) => {
const def = ATTUNEMENTS_DEF[id];
if (!def || def.conversionRate <= 0) return total;
// Use the same level scaling as getAttunementConversionRate
const scaledRate = getAttunementConversionRate(id, state.level || 1);
return total + scaledRate;
}, 0);
}
// Get conversion rate with level scaling
export function getAttunementConversionRate(attunementId: string, level: number): number {
const def = ATTUNEMENTS_DEF[attunementId];
+32 -2
View File
@@ -48,7 +48,7 @@ import {
import { getActiveEquipmentSpells, type ActiveEquipmentSpell } from './utils/combat-utils';
import { EQUIPMENT_TYPES, getValidSlotsForEquipmentType } from './data/equipment';
import { ENCHANTMENT_EFFECTS, calculateEffectCapacityCost } from './data/enchantment-effects';
import { ATTUNEMENTS_DEF, getTotalAttunementRegen, getAttunementConversionRate, getAttunementXPForLevel, MAX_ATTUNEMENT_LEVEL } from './data/attunements';
import { ATTUNEMENTS_DEF, getTotalAttunementRegen, getAttunementConversionRate, getAttunementXPForLevel, MAX_ATTUNEMENT_LEVEL, getTotalAttunementConversionDrain } from './data/attunements';
import { GOLEMS_DEF, getGolemSlots, isGolemUnlocked, getGolemDamage, getGolemAttackSpeed, getGolemFloorDuration, canAffordGolemSummon, deductGolemSummonCost, canAffordGolemMaintenance, deductGolemMaintenance } from './data/golems';
// Default empty effects for when effects aren't provided
@@ -378,6 +378,23 @@ export function computeRegen(
return regen;
}
// Compute the effective regen (raw regen minus conversion drains) for display purposes
export function computeEffectiveRegenForDisplay(
state: Pick<GameState, 'skills' | 'prestigeUpgrades' | 'skillUpgrades' | 'skillTiers' | 'equipmentInstances' | 'equippedInstances' | 'attunements'>,
effects?: ComputedEffects | UnifiedEffects
): { rawRegen: number; conversionDrain: number; effectiveRegen: number } {
// Get the full raw regen (without conversion drain)
const rawRegen = computeRegen(state, effects);
// Calculate conversion drain
const conversionDrain = getTotalAttunementConversionDrain(state.attunements || {});
// Effective regen is what actually increases raw mana
const effectiveRegen = Math.max(0, rawRegen - conversionDrain);
return { rawRegen, conversionDrain, effectiveRegen };
}
/**
* Compute regen with dynamic special effects (needs current mana, max mana, incursion)
*/
@@ -749,6 +766,9 @@ function makeInitial(overrides: Partial<GameState> = {}): GameState {
incursionStrength: 0,
containmentWards: 0,
// Conversion drains tracking (for UI display)
conversionDrains: {},
log: ['✨ The loop begins. You start with a Basic Staff (Mana Bolt) and civilian clothes. Gather your strength, mage.'],
loopInsight: 0,
flowSurgeEndTime: 0, // Hour timestamp for FLOW_SURGE effect (0 = inactive)
@@ -763,7 +783,7 @@ function makeInitial(overrides: Partial<GameState> = {}): GameState {
// ─── Game Store ───────────────────────────────────────────────────────────────
interface GameStore extends GameState, CraftingActions {
export interface GameStore extends GameState, CraftingActions {
// Actions
tick: () => void;
gatherMana: () => void;
@@ -950,7 +970,11 @@ export const useGameStore = create<GameStore>()(
let totalManaGathered = state.totalManaGathered;
// Attunement mana conversion - convert raw mana to attunement's primary mana type
// The effectiveRegen already accounts for the conversion drain
// This conversion moves the 'drained' mana from raw to elemental
let elements = state.elements;
let totalConversionDrain = 0; // Track total conversion for UI display (Sub-Task 9)
let conversionDrains: Record<string, number> = {}; // Per-attunement drain for UI display
if (state.attunements) {
Object.entries(state.attunements).forEach(([attId, attState]) => {
if (!attState.active) return;
@@ -977,6 +1001,9 @@ export const useGameStore = create<GameStore>()(
current: elem.current + actualConversion,
},
};
totalConversionDrain += actualConversion;
// Track per-attunement drain for UI display
conversionDrains[attId] = (conversionDrains[attId] || 0) + actualConversion / HOURS_PER_TICK; // Store as per-hour rate
}
});
}
@@ -1694,6 +1721,7 @@ export const useGameStore = create<GameStore>()(
comboHitCount,
floorHitCount,
consecutiveStudyHours,
conversionDrains, // Track conversion drains for UI display (Sub-Task 9)
...craftingUpdates,
});
},
@@ -2842,6 +2870,8 @@ export const useGameStore = create<GameStore>()(
lootInventory: state.lootInventory,
// Golemancy
golemancy: state.golemancy,
// Conversion drains tracking
conversionDrains: state.conversionDrains,
}),
}
)
+3
View File
@@ -207,6 +207,9 @@ export interface GameState {
incursionStrength: number;
containmentWards: number;
// Conversion drains tracking (for UI display - Sub-Task 9)
conversionDrains: Record<string, number>; // attunement id -> drain per hour
// Log
log: string[];
+1
View File
@@ -8,6 +8,7 @@ export {
computeElementMax,
computeRegen,
computeEffectiveRegen,
computeEffectiveRegenForDisplay,
computeClickMana,
getMeditationBonus
} from './mana-utils';
+23 -1
View File
@@ -3,6 +3,7 @@
import type { GameState } from '../types';
import type { ComputedEffects } from '../upgrade-effects';
import { HOURS_PER_TICK } from '../constants';
import { getTotalAttunementRegen, getTotalAttunementConversionDrain } from '../data/attunements';
export function computeMaxMana(
state: Pick<GameState, 'skills' | 'prestigeUpgrades' | 'skillUpgrades' | 'skillTiers'>,
@@ -36,7 +37,7 @@ export function computeElementMax(
}
export function computeRegen(
state: Pick<GameState, 'skills' | 'prestigeUpgrades' | 'skillUpgrades' | 'skillTiers'>,
state: Pick<GameState, 'skills' | 'prestigeUpgrades' | 'skillUpgrades' | 'skillTiers' | 'attunements'>,
effects?: ComputedEffects
): number {
const pu = state.prestigeUpgrades;
@@ -49,6 +50,10 @@ export function computeRegen(
let regen = base * temporalBonus;
// Add attunement raw mana regen
const attunementRegen = getTotalAttunementRegen(state.attunements || {});
regen += attunementRegen;
// Apply upgrade effects if provided
if (effects) {
regen = (regen + effects.regenBonus + effects.permanentRegenBonus) * effects.regenMultiplier;
@@ -57,6 +62,23 @@ export function computeRegen(
return regen;
}
// Compute the effective regen (raw regen minus conversion drains) for display purposes
export function computeEffectiveRegenForDisplay(
state: Pick<GameState, 'skills' | 'prestigeUpgrades' | 'skillUpgrades' | 'skillTiers' | 'attunements'>,
effects?: ComputedEffects
): { rawRegen: number; conversionDrain: number; effectiveRegen: number } {
// Get the full raw regen (without conversion drain)
const rawRegen = computeRegen(state, effects);
// Calculate conversion drain
const conversionDrain = getTotalAttunementConversionDrain(state.attunements || {});
// Effective regen is what actually increases raw mana
const effectiveRegen = Math.max(0, rawRegen - conversionDrain);
return { rawRegen, conversionDrain, effectiveRegen };
}
/**
* Compute regen with dynamic special effects (needs current mana, max mana, incursion)
*/