fix: Use BASE_ELEMENTS constant for debug unlock to prevent unlocking all 22 elements
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m18s
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m18s
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
# Circular Dependencies
|
# Circular Dependencies
|
||||||
Generated: 2026-06-12T05:02:18.108Z
|
Generated: 2026-06-12T07:05:52.901Z
|
||||||
Found: 4 circular chain(s) — these MUST be fixed before modifying involved files.
|
Found: 4 circular chain(s) — these MUST be fixed before modifying involved files.
|
||||||
|
|
||||||
1. 1) data/guardian-encounters.ts > data/guardian-procedural.ts
|
1. 1) data/guardian-encounters.ts > data/guardian-procedural.ts
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"_meta": {
|
"_meta": {
|
||||||
"generated": "2026-06-12T05:02:13.512Z",
|
"generated": "2026-06-12T07:05:50.633Z",
|
||||||
"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."
|
||||||
},
|
},
|
||||||
@@ -581,6 +581,13 @@
|
|||||||
"stores/prestigeStore.ts",
|
"stores/prestigeStore.ts",
|
||||||
"utils/spire-utils.ts"
|
"utils/spire-utils.ts"
|
||||||
],
|
],
|
||||||
|
"stores/combat-reset.ts": [
|
||||||
|
"stores/combat-actions.ts",
|
||||||
|
"stores/combat-state.types.ts",
|
||||||
|
"types.ts",
|
||||||
|
"utils/index.ts",
|
||||||
|
"utils/spire-utils.ts"
|
||||||
|
],
|
||||||
"stores/combat-state.types.ts": [
|
"stores/combat-state.types.ts": [
|
||||||
"types.ts"
|
"types.ts"
|
||||||
],
|
],
|
||||||
@@ -588,6 +595,7 @@
|
|||||||
"data/guardian-encounters.ts",
|
"data/guardian-encounters.ts",
|
||||||
"stores/combat-actions.ts",
|
"stores/combat-actions.ts",
|
||||||
"stores/combat-descent-actions.ts",
|
"stores/combat-descent-actions.ts",
|
||||||
|
"stores/combat-reset.ts",
|
||||||
"stores/combat-state.types.ts",
|
"stores/combat-state.types.ts",
|
||||||
"stores/discipline-slice.ts",
|
"stores/discipline-slice.ts",
|
||||||
"stores/golemancy-actions.ts",
|
"stores/golemancy-actions.ts",
|
||||||
|
|||||||
@@ -250,7 +250,8 @@ Mana-Loop/
|
|||||||
│ │ │ │ ├── store-actions-discipline.test.ts
|
│ │ │ │ ├── store-actions-discipline.test.ts
|
||||||
│ │ │ │ ├── store-actions-mana.test.ts
|
│ │ │ │ ├── store-actions-mana.test.ts
|
||||||
│ │ │ │ ├── store-actions.test.ts
|
│ │ │ │ ├── store-actions.test.ts
|
||||||
│ │ │ │ └── tick-integration.test.ts
|
│ │ │ │ ├── tick-integration.test.ts
|
||||||
|
│ │ │ │ └── unlock-base-elements.test.ts
|
||||||
│ │ │ ├── constants/
|
│ │ │ ├── constants/
|
||||||
│ │ │ │ ├── spells-modules/
|
│ │ │ │ ├── spells-modules/
|
||||||
│ │ │ │ │ ├── advanced-spells.ts
|
│ │ │ │ │ ├── advanced-spells.ts
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { DebugName, useDebug } from '@/components/game/debug/debug-context';
|
import { DebugName, useDebug } from '@/components/game/debug/debug-context';
|
||||||
import { useGameStore, useManaStore, useUIStore, usePrestigeStore, useCraftingStore } from '@/lib/game/stores';
|
import { useGameStore, useManaStore, useUIStore, usePrestigeStore, useCraftingStore } from '@/lib/game/stores';
|
||||||
|
import { BASE_ELEMENTS } from '@/lib/game/constants/elements';
|
||||||
import { computeTotalMaxMana } from '@/lib/game/effects';
|
import { computeTotalMaxMana } from '@/lib/game/effects';
|
||||||
import { getUnifiedEffects } from '@/lib/game/effects';
|
import { getUnifiedEffects } from '@/lib/game/effects';
|
||||||
|
|
||||||
@@ -263,8 +264,9 @@ export function GameStateDebug() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleUnlockBase = () => {
|
const handleUnlockBase = () => {
|
||||||
['fire', 'water', 'air', 'earth', 'light', 'dark', 'death'].forEach(e => {
|
const currentElements = useManaStore.getState().elements;
|
||||||
if (!elements[e]?.unlocked) {
|
BASE_ELEMENTS.forEach(e => {
|
||||||
|
if (!currentElements[e]?.unlocked) {
|
||||||
unlockElement(e, 500);
|
unlockElement(e, 500);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { DebugName, useDebug } from '@/components/game/debug/debug-context';
|
import { DebugName, useDebug } from '@/components/game/debug/debug-context';
|
||||||
import { useGameStore, useManaStore, useUIStore, usePrestigeStore, useCraftingStore } from '@/lib/game/stores';
|
import { useGameStore, useManaStore, useUIStore, usePrestigeStore, useCraftingStore } from '@/lib/game/stores';
|
||||||
|
import { BASE_ELEMENTS } from '@/lib/game/constants/elements';
|
||||||
import { computeTotalMaxMana } from '@/lib/game/effects';
|
import { computeTotalMaxMana } from '@/lib/game/effects';
|
||||||
import { getUnifiedEffects } from '@/lib/game/effects';
|
import { getUnifiedEffects } from '@/lib/game/effects';
|
||||||
|
|
||||||
@@ -245,8 +246,9 @@ export function GameStateDebugSection() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleUnlockBase = () => {
|
const handleUnlockBase = () => {
|
||||||
['fire', 'water', 'air', 'earth', 'light', 'dark', 'death'].forEach(e => {
|
const currentElements = useManaStore.getState().elements;
|
||||||
if (!elements[e]?.unlocked) {
|
BASE_ELEMENTS.forEach(e => {
|
||||||
|
if (!currentElements[e]?.unlocked) {
|
||||||
unlockElement(e, 0);
|
unlockElement(e, 0);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,125 @@
|
|||||||
|
import { describe, it, expect, beforeEach } from 'vitest';
|
||||||
|
import { useManaStore } from '../stores/manaStore';
|
||||||
|
import { ELEMENTS, BASE_UNLOCKED_ELEMENTS, BASE_ELEMENTS } from '../constants/elements';
|
||||||
|
|
||||||
|
// ─── Regression test: "Unlock All Base Elements" only unlocks 7 base ───────
|
||||||
|
// Issue #374: Button was unlocking all 22 elements instead of just 7 base.
|
||||||
|
|
||||||
|
function resetManaStore() {
|
||||||
|
useManaStore.setState({
|
||||||
|
rawMana: 10000,
|
||||||
|
meditateTicks: 0,
|
||||||
|
totalManaGathered: 0,
|
||||||
|
elements: Object.fromEntries(
|
||||||
|
Object.keys(ELEMENTS).map(k => [
|
||||||
|
k,
|
||||||
|
{
|
||||||
|
current: 0,
|
||||||
|
max: 10,
|
||||||
|
baseMax: 10,
|
||||||
|
unlocked: BASE_UNLOCKED_ELEMENTS.includes(k),
|
||||||
|
},
|
||||||
|
])
|
||||||
|
) as Record<string, { current: number; max: number; baseMax: number; unlocked: boolean }>,
|
||||||
|
elementRegen: {},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('Unlock All Base Elements debug button', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
resetManaStore();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('BASE_ELEMENTS contains exactly 7 base elements', () => {
|
||||||
|
expect(BASE_ELEMENTS).toHaveLength(7);
|
||||||
|
expect(BASE_ELEMENTS).toContain('fire');
|
||||||
|
expect(BASE_ELEMENTS).toContain('water');
|
||||||
|
expect(BASE_ELEMENTS).toContain('air');
|
||||||
|
expect(BASE_ELEMENTS).toContain('earth');
|
||||||
|
expect(BASE_ELEMENTS).toContain('light');
|
||||||
|
expect(BASE_ELEMENTS).toContain('dark');
|
||||||
|
expect(BASE_ELEMENTS).toContain('death');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('BASE_ELEMENTS does NOT contain utility, composite, or exotic elements', () => {
|
||||||
|
expect(BASE_ELEMENTS).not.toContain('transference');
|
||||||
|
expect(BASE_ELEMENTS).not.toContain('metal');
|
||||||
|
expect(BASE_ELEMENTS).not.toContain('sand');
|
||||||
|
expect(BASE_ELEMENTS).not.toContain('lightning');
|
||||||
|
expect(BASE_ELEMENTS).not.toContain('frost');
|
||||||
|
expect(BASE_ELEMENTS).not.toContain('blackflame');
|
||||||
|
expect(BASE_ELEMENTS).not.toContain('radiantflames');
|
||||||
|
expect(BASE_ELEMENTS).not.toContain('miasma');
|
||||||
|
expect(BASE_ELEMENTS).not.toContain('shadowglass');
|
||||||
|
expect(BASE_ELEMENTS).not.toContain('crystal');
|
||||||
|
expect(BASE_ELEMENTS).not.toContain('stellar');
|
||||||
|
expect(BASE_ELEMENTS).not.toContain('void');
|
||||||
|
expect(BASE_ELEMENTS).not.toContain('soul');
|
||||||
|
expect(BASE_ELEMENTS).not.toContain('time');
|
||||||
|
expect(BASE_ELEMENTS).not.toContain('plasma');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('unlockElement for all BASE_ELEMENTS only unlocks those 7', () => {
|
||||||
|
const store = useManaStore.getState();
|
||||||
|
|
||||||
|
// All base elements should start locked (only transference is base-unlocked)
|
||||||
|
BASE_ELEMENTS.forEach(e => {
|
||||||
|
expect(store.elements[e]?.unlocked).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Unlock all base elements (cost 0, simulating debug button)
|
||||||
|
BASE_ELEMENTS.forEach(e => {
|
||||||
|
store.unlockElement(e, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
const afterState = useManaStore.getState();
|
||||||
|
|
||||||
|
// All 7 base elements should now be unlocked
|
||||||
|
BASE_ELEMENTS.forEach(e => {
|
||||||
|
expect(afterState.elements[e]?.unlocked).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Non-base elements should NOT be unlocked
|
||||||
|
const nonBaseElements = Object.keys(ELEMENTS).filter(
|
||||||
|
e => !BASE_ELEMENTS.includes(e as never) && !BASE_UNLOCKED_ELEMENTS.includes(e)
|
||||||
|
);
|
||||||
|
nonBaseElements.forEach(e => {
|
||||||
|
expect(afterState.elements[e]?.unlocked).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('debug handleUnlockBase does not unlock transference', () => {
|
||||||
|
const store = useManaStore.getState();
|
||||||
|
|
||||||
|
// Verify transference starts unlocked
|
||||||
|
expect(store.elements['transference']?.unlocked).toBe(true);
|
||||||
|
|
||||||
|
// Run the unlock logic
|
||||||
|
BASE_ELEMENTS.forEach(e => {
|
||||||
|
if (!store.elements[e]?.unlocked) {
|
||||||
|
store.unlockElement(e, 0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const afterState = useManaStore.getState();
|
||||||
|
// transference was already unlocked, should still be unlocked
|
||||||
|
expect(afterState.elements['transference']?.unlocked).toBe(true);
|
||||||
|
// But no new utility elements were unlocked
|
||||||
|
expect(afterState.elements['metal']?.unlocked).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('total unlocked count after unlock base is exactly 8 (7 base + transference)', () => {
|
||||||
|
const store = useManaStore.getState();
|
||||||
|
const unlockedBefore = Object.values(store.elements).filter(e => e.unlocked).length;
|
||||||
|
// Only transference should be unlocked initially
|
||||||
|
expect(unlockedBefore).toBe(1);
|
||||||
|
|
||||||
|
BASE_ELEMENTS.forEach(e => {
|
||||||
|
store.unlockElement(e, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
const afterState = useManaStore.getState();
|
||||||
|
const unlockedAfter = Object.values(afterState.elements).filter(e => e.unlocked).length;
|
||||||
|
expect(unlockedAfter).toBe(8); // 7 base + transference
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -110,3 +110,6 @@ export const ELEMENT_ICON_NAMES: Record<string, string> = {
|
|||||||
|
|
||||||
// ─── Base Unlocked Elements ───────────────────────────────────────────────────
|
// ─── Base Unlocked Elements ───────────────────────────────────────────────────
|
||||||
export const BASE_UNLOCKED_ELEMENTS = ['transference'];
|
export const BASE_UNLOCKED_ELEMENTS = ['transference'];
|
||||||
|
|
||||||
|
// ─── Base Element IDs (7 base elements only — NOT composite/exotic/utility) ──
|
||||||
|
export const BASE_ELEMENTS = ['fire', 'water', 'air', 'earth', 'light', 'dark', 'death'] as const;
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ export { getStudySpeedMultiplier, getStudyCostMultiplier } from './core';
|
|||||||
|
|
||||||
// Element-related constants
|
// Element-related constants
|
||||||
export { MANA_PER_ELEMENT, rawCost, elemCost, ELEMENTS, FLOOR_ELEM_CYCLE } from './elements';
|
export { MANA_PER_ELEMENT, rawCost, elemCost, ELEMENTS, FLOOR_ELEM_CYCLE } from './elements';
|
||||||
export { ELEMENT_OPPOSITES, ELEMENT_ICON_NAMES, BASE_UNLOCKED_ELEMENTS } from './elements';
|
export { ELEMENT_OPPOSITES, ELEMENT_ICON_NAMES, BASE_UNLOCKED_ELEMENTS, BASE_ELEMENTS } from './elements';
|
||||||
|
|
||||||
// Spell constants
|
// Spell constants
|
||||||
export { SPELLS_DEF } from './spells';
|
export { SPELLS_DEF } from './spells';
|
||||||
|
|||||||
Reference in New Issue
Block a user