fix: pass rawMana to discipline activate to allow re-practicing after stop
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m20s

This commit is contained in:
2026-05-27 15:57:17 +02:00
parent 5e76fe7145
commit 5f8a860a3c
5 changed files with 105 additions and 4 deletions
+3 -2
View File
@@ -218,16 +218,17 @@ export const DisciplinesTab: React.FC = () => {
const [activeAttunement, setActiveAttunement] = useState<string>('base');
const rawMana = useManaStore((s) => s.rawMana);
const elements = useManaStore((s) => s.elements);
const signedPacts = usePrestigeStore((s) => s.signedPacts);
const handleToggle = useCallback((id: string, paused: boolean) => {
if (paused) {
activate(id, { elements, signedPacts });
activate(id, { rawMana, elements, signedPacts });
} else {
deactivate(id);
}
}, [activate, deactivate, elements]);
}, [activate, deactivate, rawMana, elements, signedPacts]);
const activeTab = ATTUNEMENT_TABS.find((t) => t.key === activeAttunement);
@@ -0,0 +1,87 @@
import { describe, it, expect, beforeEach } from 'vitest';
import { useDisciplineStore } from '../stores/discipline-slice';
function resetDisciplineStore() {
useDisciplineStore.setState({
disciplines: {},
activeIds: [],
concurrentLimit: 1,
totalXP: 0,
processedPerks: [],
practicingCallbacks: null,
});
}
describe('DisciplineStore — reactivate after deactivate (bug #163)', () => {
beforeEach(resetDisciplineStore);
it('BUG: should reactivate a raw discipline after deactivate (requires rawMana in gameState)', () => {
// First activation succeeds because disciplineState is undefined (optimistic)
useDisciplineStore.getState().activate('raw-mastery');
expect(useDisciplineStore.getState().activeIds).toContain('raw-mastery');
// Deactivate
useDisciplineStore.getState().deactivate('raw-mastery');
expect(useDisciplineStore.getState().activeIds).not.toContain('raw-mastery');
expect(useDisciplineStore.getState().disciplines['raw-mastery'].paused).toBe(true);
// Reactivate WITHOUT rawMana — reproduces the bug (silently fails)
useDisciplineStore.getState().activate('raw-mastery', { elements: {}, signedPacts: [] });
expect(useDisciplineStore.getState().activeIds).not.toContain('raw-mastery'); // BUG: still inactive
// Reactivate WITH rawMana — the fix
useDisciplineStore.getState().activate('raw-mastery', { rawMana: 1000, elements: {}, signedPacts: [] });
expect(useDisciplineStore.getState().activeIds).toContain('raw-mastery');
expect(useDisciplineStore.getState().disciplines['raw-mastery'].paused).toBe(false);
});
it('should reactivate a discipline with elements after deactivating it', () => {
useDisciplineStore.getState().activate('attune-fire', {
elements: { fire: { unlocked: true, current: 100, max: 100 } },
});
expect(useDisciplineStore.getState().activeIds).toContain('attune-fire');
useDisciplineStore.getState().deactivate('attune-fire');
expect(useDisciplineStore.getState().activeIds).not.toContain('attune-fire');
useDisciplineStore.getState().activate('attune-fire', {
elements: { fire: { unlocked: true, current: 100, max: 100 } },
});
expect(useDisciplineStore.getState().activeIds).toContain('attune-fire');
expect(useDisciplineStore.getState().disciplines['attune-fire'].paused).toBe(false);
});
it('should reactivate after processTick auto-pauses due to no mana', () => {
useDisciplineStore.getState().activate('raw-mastery', { rawMana: 100, elements: {} });
expect(useDisciplineStore.getState().activeIds).toContain('raw-mastery');
// Tick with no mana — discipline auto-pauses
useDisciplineStore.getState().processTick({ rawMana: 0, elements: {} });
expect(useDisciplineStore.getState().activeIds).not.toContain('raw-mastery');
expect(useDisciplineStore.getState().disciplines['raw-mastery'].paused).toBe(true);
// Reactivate with sufficient mana
useDisciplineStore.getState().activate('raw-mastery', { rawMana: 1000, elements: {} });
expect(useDisciplineStore.getState().activeIds).toContain('raw-mastery');
expect(useDisciplineStore.getState().disciplines['raw-mastery'].paused).toBe(false);
});
it('should preserve XP when deactivating and reactivating', () => {
useDisciplineStore.getState().activate('raw-mastery', { rawMana: 1000, elements: {} });
useDisciplineStore.getState().processTick({ rawMana: 1000, elements: {} });
useDisciplineStore.getState().processTick({ rawMana: 1000, elements: {} });
useDisciplineStore.getState().processTick({ rawMana: 1000, elements: {} });
expect(useDisciplineStore.getState().disciplines['raw-mastery'].xp).toBe(3);
useDisciplineStore.getState().deactivate('raw-mastery');
expect(useDisciplineStore.getState().disciplines['raw-mastery'].xp).toBe(3);
expect(useDisciplineStore.getState().disciplines['raw-mastery'].paused).toBe(true);
useDisciplineStore.getState().activate('raw-mastery', { rawMana: 1000, elements: {} });
expect(useDisciplineStore.getState().activeIds).toContain('raw-mastery');
expect(useDisciplineStore.getState().disciplines['raw-mastery'].xp).toBe(3);
useDisciplineStore.getState().processTick({ rawMana: 1000, elements: {} });
expect(useDisciplineStore.getState().disciplines['raw-mastery'].xp).toBe(4);
});
});