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
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m20s
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
# Circular Dependencies
|
# Circular Dependencies
|
||||||
Generated: 2026-05-27T12:39:49.991Z
|
Generated: 2026-05-27T13:22:21.442Z
|
||||||
|
|
||||||
No circular dependencies found. ✅
|
No circular dependencies found. ✅
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"_meta": {
|
"_meta": {
|
||||||
"generated": "2026-05-27T12:39:48.093Z",
|
"generated": "2026-05-27T13:22:19.613Z",
|
||||||
"description": "Import dependency graph for src/lib/game. Keys are files, values are arrays of files they import.",
|
"description": "Import dependency graph for src/lib/game. Keys are files, values are arrays of files they import.",
|
||||||
"usage": "To find what a file affects, search for its path in the VALUES. To find what a file depends on, look at its KEY entry."
|
"usage": "To find what a file affects, search for its path in the VALUES. To find what a file depends on, look at its KEY entry."
|
||||||
},
|
},
|
||||||
@@ -365,10 +365,22 @@
|
|||||||
"data/equipment/equipment-types-data.ts",
|
"data/equipment/equipment-types-data.ts",
|
||||||
"data/equipment/types.ts"
|
"data/equipment/types.ts"
|
||||||
],
|
],
|
||||||
|
"data/fabricator-material-recipes.ts": [
|
||||||
|
"data/fabricator-recipe-types.ts"
|
||||||
|
],
|
||||||
|
"data/fabricator-physical-recipes.ts": [
|
||||||
|
"data/fabricator-recipe-types.ts"
|
||||||
|
],
|
||||||
"data/fabricator-recipe-types.ts": [
|
"data/fabricator-recipe-types.ts": [
|
||||||
"data/equipment/types.ts"
|
"data/equipment/types.ts"
|
||||||
],
|
],
|
||||||
"data/fabricator-recipes.ts": [
|
"data/fabricator-recipes.ts": [
|
||||||
|
"data/fabricator-material-recipes.ts",
|
||||||
|
"data/fabricator-physical-recipes.ts",
|
||||||
|
"data/fabricator-recipe-types.ts",
|
||||||
|
"data/fabricator-wizard-recipes.ts"
|
||||||
|
],
|
||||||
|
"data/fabricator-wizard-recipes.ts": [
|
||||||
"data/fabricator-recipe-types.ts"
|
"data/fabricator-recipe-types.ts"
|
||||||
],
|
],
|
||||||
"data/golems/base-golems.ts": [
|
"data/golems/base-golems.ts": [
|
||||||
|
|||||||
@@ -209,6 +209,7 @@ Mana-Loop/
|
|||||||
│ │ │ ├── cross-module-prestige-discipline.test.ts
|
│ │ │ ├── cross-module-prestige-discipline.test.ts
|
||||||
│ │ │ ├── discipline-math.test.ts
|
│ │ │ ├── discipline-math.test.ts
|
||||||
│ │ │ ├── discipline-prerequisites.test.ts
|
│ │ │ ├── discipline-prerequisites.test.ts
|
||||||
|
│ │ │ ├── discipline-reactivate-bug.test.ts
|
||||||
│ │ │ ├── enemy-barrier-utils.test.ts
|
│ │ │ ├── enemy-barrier-utils.test.ts
|
||||||
│ │ │ ├── enemy-generator.test.ts
|
│ │ │ ├── enemy-generator.test.ts
|
||||||
│ │ │ ├── enemy-utils.test.ts
|
│ │ │ ├── enemy-utils.test.ts
|
||||||
|
|||||||
@@ -218,16 +218,17 @@ export const DisciplinesTab: React.FC = () => {
|
|||||||
|
|
||||||
const [activeAttunement, setActiveAttunement] = useState<string>('base');
|
const [activeAttunement, setActiveAttunement] = useState<string>('base');
|
||||||
|
|
||||||
|
const rawMana = useManaStore((s) => s.rawMana);
|
||||||
const elements = useManaStore((s) => s.elements);
|
const elements = useManaStore((s) => s.elements);
|
||||||
const signedPacts = usePrestigeStore((s) => s.signedPacts);
|
const signedPacts = usePrestigeStore((s) => s.signedPacts);
|
||||||
|
|
||||||
const handleToggle = useCallback((id: string, paused: boolean) => {
|
const handleToggle = useCallback((id: string, paused: boolean) => {
|
||||||
if (paused) {
|
if (paused) {
|
||||||
activate(id, { elements, signedPacts });
|
activate(id, { rawMana, elements, signedPacts });
|
||||||
} else {
|
} else {
|
||||||
deactivate(id);
|
deactivate(id);
|
||||||
}
|
}
|
||||||
}, [activate, deactivate, elements]);
|
}, [activate, deactivate, rawMana, elements, signedPacts]);
|
||||||
|
|
||||||
const activeTab = ATTUNEMENT_TABS.find((t) => t.key === activeAttunement);
|
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);
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user