fix: resolve priority 4 issues — discipline mutation, skill→discipline migration, uiStore persistence, game loop interval, toast listener leak, page re-renders
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m21s

- Issue 70: Fix discipline-slice.ts nested element mutation (use immutable spread)
- Issue 71: Add persist middleware to uiStore for paused/gameOver/victory
- Issue 72: Wire discipline effects into calcDamage (spell-casting, void-manipulation)
- Issue 73: Fix useGameLoop interval recreation (use getState() + empty deps)
- Issue 74: Fix use-toast.ts listener leak (change [state] dep to [])
- Issue 75: Reduce page.tsx re-renders with useShallow for multi-field subscriptions
- Issue 76: Fix createGatherMana hardcoded click mana (use computeClickMana with discipline effects)
- Issue 77: Pass discipline effects to computeMaxMana/computeRegen/calcInsight in tick()
- Export DisciplineBonuses type and useDisciplineStore from barrel exports
- Update tests to match new function signatures
This commit is contained in:
2026-05-19 13:53:33 +02:00
parent ebcaab62bf
commit 50a9a62060
17 changed files with 215 additions and 154 deletions
+3 -2
View File
@@ -9,7 +9,8 @@ import { ActionButtons } from '@/components/game';
import { AttunementStatus } from '@/components/game/AttunementStatus';
import { ActivityLogPanel } from '@/components/game/ActivityLogPanel';
import { DebugName } from '@/components/game/debug/debug-context';
import { useGameStore, useManaStore, useCombatStore, useCraftingStore, usePrestigeStore } from '@/lib/game/stores';
import { useGameStore, useManaStore, useCombatStore, useCraftingStore, usePrestigeStore, useDisciplineStore } from '@/lib/game/stores';
import { computeDisciplineEffects } from '@/lib/game/effects/discipline-effects';
import { getUnifiedEffects } from '@/lib/game/effects';
import { getMeditationBonus, getIncursionStrength } from '@/lib/game/stores';
import { computeTotalMaxMana, computeTotalRegen, computeTotalClickMana } from '@/lib/game/effects';
@@ -56,7 +57,7 @@ export function LeftPanel() {
const maxMana = computeTotalMaxMana({ skills: {}, prestigeUpgrades, skillUpgrades: {}, skillTiers: {}, equippedInstances, equipmentInstances }, upgradeEffects);
const baseRegen = computeTotalRegen({ skills: {}, prestigeUpgrades, skillUpgrades: {}, skillTiers: {}, equippedInstances, equipmentInstances }, upgradeEffects);
const clickMana = computeTotalClickMana({ skills: {}, skillUpgrades: {}, skillTiers: {}, equippedInstances, equipmentInstances }, upgradeEffects);
const meditationMultiplier = getMeditationBonus(meditateTicks, {}, upgradeEffects.meditationEfficiency);
const meditationMultiplier = getMeditationBonus(meditateTicks, {}, (upgradeEffects as any).meditationEfficiency);
const incursionStrength = getIncursionStrength(useGameStore((s) => s.day), useGameStore((s) => s.hour));
const effectiveRegen = baseRegen * (1 - incursionStrength) * meditationMultiplier;
+19 -22
View File
@@ -1,6 +1,7 @@
'use client';
import { useEffect, useState, lazy, Suspense } from 'react';
import { useShallow } from 'zustand/react/shallow';
// Import from new modular stores
import {
@@ -10,6 +11,7 @@ import {
useCombatStore,
usePrestigeStore,
useCraftingStore,
useDisciplineStore,
fmt,
computeMaxMana,
computeRegen,
@@ -17,6 +19,7 @@ import {
getMeditationBonus,
getIncursionStrength,
} from '@/lib/game/stores';
import { computeDisciplineEffects } from '@/lib/game/effects/discipline-effects';
import { useGameLoop } from '@/lib/game/stores/gameHooks';
import { getUnifiedEffects } from '@/lib/game/effects';
import { SPELLS_DEF } from '@/lib/game/constants';
@@ -120,21 +123,13 @@ export default function ManaLoopGame() {
const [activeTab, setActiveTab] = useState('spells');
// ALL hooks must be called before any conditional returns
const day = useGameStore((s) => s.day);
const hour = useGameStore((s) => s.hour);
const initGame = useGameStore((s) => s.initGame);
useGameLoop();
const prestigeUpgrades = usePrestigeStore((s) => s.prestigeUpgrades);
const insight = usePrestigeStore((s) => s.insight);
const loopInsight = usePrestigeStore((s) => s.loopInsight);
const rawMana = useManaStore((s) => s.rawMana);
const meditateTicks = useManaStore((s) => s.meditateTicks);
// Use useShallow to combine multi-field subscriptions and reduce re-renders
const { day, hour, initGame } = useGameStore(useShallow(s => ({ day: s.day, hour: s.hour, initGame: s.initGame })));
const { prestigeUpgrades, insight, loopInsight } = usePrestigeStore(useShallow(s => ({ prestigeUpgrades: s.prestigeUpgrades, insight: s.insight, loopInsight: s.loopInsight })));
const { rawMana, meditateTicks } = useManaStore(useShallow(s => ({ rawMana: s.rawMana, meditateTicks: s.meditateTicks })));
const spireMode = useCombatStore((s) => s.spireMode);
const gameOver = useUIStore((s) => s.gameOver);
// Get equipment state from crafting store
@@ -149,40 +144,42 @@ export default function ManaLoopGame() {
equipmentInstances
});
// Compute discipline bonuses from active disciplines
const disciplineStoreState = useDisciplineStore();
const disciplineEffects = computeDisciplineEffects(disciplineStoreState as any);
const maxMana = computeMaxMana({
skills: {},
prestigeUpgrades,
skillUpgrades: {},
skillTiers: {}
}, upgradeEffects);
}, upgradeEffects as any, disciplineEffects);
const baseRegen = computeRegen({
skills: {},
prestigeUpgrades,
skillUpgrades: {},
skillTiers: {}
}, upgradeEffects);
skillTiers: {},
attunements: {},
}, upgradeEffects as any, disciplineEffects);
const clickMana = computeClickMana({
skills: {},
prestigeUpgrades,
skillUpgrades: {},
skillTiers: {}
});
}, disciplineEffects);
const meditationMultiplier = getMeditationBonus(meditateTicks, {}, upgradeEffects.meditationEfficiency);
const meditationMultiplier = getMeditationBonus(meditateTicks, {}, (upgradeEffects as any).meditationEfficiency);
const incursionStrength = getIncursionStrength(day, hour);
// Effective regen with incursion penalty
const effectiveRegenWithSpecials = baseRegen * (1 - incursionStrength);
// Mana Cascade bonus
const manaCascadeBonus = hasSpecial(upgradeEffects, SPECIAL_EFFECTS.MANA_CASCADE)
const manaCascadeBonus = hasSpecial(upgradeEffects as any, SPECIAL_EFFECTS.MANA_CASCADE)
? Math.floor(maxMana / 100) * 0.1
: 0;
// Mana Waterfall bonus
const manaWaterfallBonus = hasSpecial(upgradeEffects, SPECIAL_EFFECTS.MANA_WATERFALL)
const manaWaterfallBonus = hasSpecial(upgradeEffects as any, SPECIAL_EFFECTS.MANA_WATERFALL)
? Math.floor(maxMana / 100) * 0.25
: 0;