diff --git a/docs/SPEC-mana-conversion-fix.md b/docs/SPEC-mana-conversion-fix.md new file mode 100644 index 0000000..ad11162 --- /dev/null +++ b/docs/SPEC-mana-conversion-fix.md @@ -0,0 +1,93 @@ +# SPEC: Mana Conversion Attunement Fix + +## 1. Objective +Fix the mana conversion logic for attunements (e.g., Enchanter Transference) that incorrectly deducts mana from the player's mana pool every tick instead of reducing raw mana regen. This bug causes players to get stuck at 1 mana below their mana cap. + +**Why**: +- Current behavior breaks core mana progression loop +- Players cannot reach full mana capacity when attunements are active +- Violates intended design where attunement costs should reduce regen rate, not drain pool + +## 2. Controls/API +No new player-facing controls or APIs are added. This is an internal logic fix. + +### Modified Game Internals: +- `computeRegen()` / `computeEffectiveRegen()` in `computed-stats.ts`: Adjust to account for attunement regen reductions +- `manaStore.ts` tick logic: Remove direct mana pool deductions for attunement costs +- Attunement effect application: Add regen reduction to unified effect system instead of pool deductions +- Game tick loop: Ensure regen reduction is applied before mana regen calculation + +### Public API Changes: +None (internal bug fix only) + +## 3. Project Layout +Follow existing modular architecture rules from AGENTS.md: + +### Files to Modify: +| File | Purpose | Line Count Check | +|------|---------|------------------| +| `src/lib/game/stores/manaStore.ts` | Remove pool deduction logic for attunements, ensure regen reduction applied | Must stay <400 lines | +| `src/lib/game/computed-stats.ts` | Update `computeRegen()` to apply attunement regen reductions from unified effects | Must stay <400 lines | +| `src/lib/game/effects.ts` | Add attunement regen reduction to unified effect system if not already present | Must stay <400 lines | +| `src/lib/game/stores/gameLoopActions.ts` | Ensure correct tick order: apply regen reductions → calculate regen → update mana pool | Must stay <400 lines | +| `src/lib/game/constants/attunements.ts` (or similar) | Verify attunement effect definitions use regen reduction instead of pool drain | Must stay <400 lines | + +### Files to Create: +| File | Purpose | Line Count Check | +|------|---------|------------------| +| `src/lib/game/stores/__tests__/mana-conversion-fix.test.ts` | Regression test for fix | Must stay <400 lines | + +### Module Ownership: +- Mana logic: `manaStore.ts` (store module) +- Computed stats: `computed-stats.ts` (shared utility) +- Effects: `effects.ts` (unified effect system) +- Tests: `stores/__tests__/` (test directory) + +## 4. Code Style +Follow existing project conventions: +- TypeScript strict mode, explicit type annotations for game state +- Zustand store patterns: `set()`, `get()` for state updates, avoid direct mutations +- Unified effect system: All stat modifications flow through `getUnifiedEffects()` +- Naming: camelCase for variables/functions, PascalCase for interfaces/types +- No `any` types, use defined interfaces from `src/lib/game/types.ts` +- Follow ESLint rules (run `npm run lint` before committing) +- Use existing patterns for regen/reduction calculations (e.g., `regenBonus`, `regenMultiplier` in unified effects) + +## 5. Testing +### What to Test: +1. **Mana regen with active attunements**: Verify attunement costs reduce regen rate, not mana pool +2. **Mana cap behavior**: Player can reach full mana cap when attunements are active +3. **No pool drain**: Mana pool is never deducted directly by attunement costs +4. **Unified effect integration**: Attunement regen reductions are properly included in `getUnifiedEffects()` +5. **Tick order**: Regen reductions are applied before mana regen calculation in game tick + +### How to Test: +- Unit tests using Vitest (existing test framework) +- Test files located in `src/lib/game/stores/__tests__/` +- Mock game state to simulate active attunements +- Assert regen values and mana pool behavior +- Run `npm run test` to execute all tests + +### Tooling: +- Vitest (test runner) +- Zustand store testing patterns (use `getState()` for assertions) +- Mock `getUnifiedEffects()` to return attunement regen reductions + +## 6. Boundaries (Out-of-Scope Items) +- No changes to attunement definitions (only their effect application) +- No new attunements or mana types added +- No changes to combat, crafting, or prestige systems (unless directly related to mana regen) +- No UI changes (this is internal logic only) +- No modifications to legacy `store.ts` (use modular stores only) +- No changes to banned content rules or mana type hierarchy + +## Acceptance Criteria (Per Requirement) +| Requirement | Acceptance Criterion | +|-------------|----------------------| +| Attunement costs reduce raw mana regen instead of deducting from pool | Unit test passes: `computeRegen()` returns reduced value when attunement regen reduction is applied | +| Deduction applied before mana regen calculations | Unit test passes: Game tick applies regen reduction before calculating mana addition to pool | +| Mana pool no longer stuck below cap | Integration test passes: Mana pool reaches full cap when attunements are active after sufficient time | +| No direct mana pool deductions from attunements | Code review: No calls to `spendRawMana()` or direct `rawMana` deductions in attunement logic | +| Follow unified effect system | Code review: Attunement regen reductions are added to `UnifiedEffects` interface and applied via `getUnifiedEffects()` | +| All files stay under 400 lines | Pre-commit hook passes: No modified files exceed 400 lines | +| Regression test added | Test file exists and runs successfully in `npm run test` | diff --git a/docs/context-grimoire-tab-fix.md b/docs/context-grimoire-tab-fix.md new file mode 100644 index 0000000..99534dc --- /dev/null +++ b/docs/context-grimoire-tab-fix.md @@ -0,0 +1,37 @@ +# Context: Grimoire/Spells Tab "cost.map" Error Fix + +## Problem Statement +The Grimoire/Spells tab fails to load with error: `TypeError: e.cost.map is not a function` + +## Error Analysis +This error indicates that `e.cost` is not an array (or is undefined) when `.map()` is called. Likely causes: +1. Spell cost is defined as a single value instead of an array +2. Missing or malformed spell definitions +3. Incorrect data structure in spells constants +4. Legacy store returning incorrect spell state + +## Key Files to Investigate/Modify +- `src/components/game/tabs/SpellsTab.tsx` - Spells tab component (likely "Grimoire" tab) +- `src/lib/game/constants/spells.ts` - Spell definitions +- `src/lib/game/constants/spells-modules/` - Modular spell definitions +- `src/lib/game/stores/skillStore.ts` - Skill state (affects spell unlocking) +- `src/lib/game/stores/combatStore.ts` - Spell state + +## Architecture Rules (from AGENTS.md) +- Use modular stores: import from `src/lib/game/stores/` +- Spell definitions belong in `src/lib/game/constants/spells.ts` or `spells-modules/` +- All files must stay under 400 lines +- No legacy store references + +## Debugging Steps +1. Find where `.map()` is called on spell cost in SpellsTab.tsx +2. Check spell definition structure for `cost` field +3. Verify spell cost is always an array (even for single cost) +4. Check if cost is properly initialized for all spells +5. Ensure spells are correctly loaded from constants + +## Expected Outcome +- Spells tab loads without errors +- All spell costs are properly formatted as arrays +- Spell definitions are consistent and valid +- Regression test added to `src/lib/game/stores/__tests__/index-tests/spell-cost.test.ts` diff --git a/docs/context-legacy-store-cleanup.md b/docs/context-legacy-store-cleanup.md new file mode 100644 index 0000000..51356c4 --- /dev/null +++ b/docs/context-legacy-store-cleanup.md @@ -0,0 +1,38 @@ +# Context: Legacy Store Reference Cleanup + +## Problem Statement +Remaining references to the old legacy store (`store.ts` pattern) need to be identified and replaced with modular store imports from `src/lib/game/stores/`. + +## Legacy Store Locations to Check +- `src/lib/game/store.ts` - Main legacy store (to be deprecated) +- `src/lib/game/store/` - Legacy store slices +- Any file importing from these legacy paths + +## Required Actions +1. Search entire codebase for imports from: + - `src/lib/game/store` + - `src/lib/game/store.ts` + - Any reference to legacy store patterns + +2. Replace with modular store imports: + - `useGameStore` from `src/lib/game/stores/gameStore` + - `useManaStore` from `src/lib/game/stores/manaStore` + - `useCombatStore` from `src/lib/game/stores/combatStore` + - `useSkillStore` from `src/lib/game/stores/skillStore` + - `usePrestigeStore` from `src/lib/game/stores/prestigeStore` + - `useUiStore` from `src/lib/game/stores/uiStore` + +## Architecture Rules (from AGENTS.md) +- NEVER use legacy store pattern +- All state management must use modular Zustand stores in `src/lib/game/stores/` +- Use barrel exports from `src/lib/game/stores/index.ts` + +## Files to Search +- All `.ts` and `.tsx` files in `src/` +- Focus on `src/components/game/` and `src/lib/game/` + +## Expected Outcome +- Zero references to legacy store in codebase +- All imports use modular store pattern +- No functional changes (only import path updates) +- List of fixed files added to commit message diff --git a/docs/context-mana-conversion-fix.md b/docs/context-mana-conversion-fix.md new file mode 100644 index 0000000..4df3fb8 --- /dev/null +++ b/docs/context-mana-conversion-fix.md @@ -0,0 +1,34 @@ +# Context: Mana Conversion Attunement Fix + +## Problem Statement +Mana conversion from attunements (e.g., Enchanter Transference mana) incorrectly deducts mana from the player's mana pool every tick instead of reducing raw mana regen. This causes players to get stuck at 1 mana below their mana cap. + +## Required Fix +Modify attunement mana conversion logic to: +1. Calculate conversion costs as a reduction to raw mana regen (not a direct deduction from mana pool) +2. Ensure the deduction is applied before mana regen calculations +3. Prevent mana pool from being stuck below cap due to continuous deductions + +## Key Files to Investigate/Modify +- `src/lib/game/attunements/` - Attunement definitions and logic +- `src/lib/game/stores/manaStore.ts` - Mana state and regen calculations +- `src/lib/game/stores/gameLoopActions.ts` - Game tick logic +- `src/lib/game/stores/gameStore.ts` - Core tick processing +- `src/lib/game/upgrade-effects.ts` - Effect calculations + +## Architecture Rules (from AGENTS.md) +- Use modular stores in `src/lib/game/stores/` - NEVER use legacy `store.ts` pattern +- All files must stay under 400 lines (pre-commit hook enforced) +- No banned content (lifesteal, healing, banned mana types: life, blood, wood, mental, force) +- Use unified effect system (`effects.ts`) for stat modifications + +## Relevant Code References +- Attunement transference cost calculation +- `computeRegen()` function in computed-stats.ts +- Mana pool cap logic in manaStore.ts +- Game tick loop that processes attunement effects + +## Expected Outcome +- Mana conversion costs reduce raw mana regen rate instead of deducting from mana pool +- Players no longer get stuck below mana cap +- Regression test added to `src/lib/game/stores/__tests__/mana-store-tests/` diff --git a/docs/context-spire-tab-fix.md b/docs/context-spire-tab-fix.md new file mode 100644 index 0000000..862227b --- /dev/null +++ b/docs/context-spire-tab-fix.md @@ -0,0 +1,36 @@ +# Context: Spire Tab "maxFloorReached" Error Fix + +## Problem Statement +The Spire tab fails to load with error: `TypeError: Cannot read properties of undefined (reading 'maxFloorReached')` + +## Error Analysis +This error indicates that the code is trying to access `maxFloorReached` on an undefined object. Likely causes: +1. Missing or undefined combat store state +2. Incorrect access of combat state in SpireTab.tsx +3. Race condition in store initialization +4. Legacy store references in Spire tab components + +## Key Files to Investigate/Modify +- `src/components/game/tabs/SpireTab.tsx` - Spire tab component +- `src/lib/game/stores/combatStore.ts` - Combat state (contains maxFloorReached) +- `src/lib/game/stores/index.ts` - Store exports +- `src/components/game/tabs/SpireHeader.tsx` - Spire header component +- `src/components/game/tabs/FloorControls.tsx` - Floor control components + +## Architecture Rules (from AGENTS.md) +- Use modular stores: import `useCombatStore` from `src/lib/game/stores/` +- NEVER import from legacy `src/lib/game/store.ts` or `src/lib/game/store/` +- All files must stay under 400 lines +- Use Zustand store hooks properly (avoid direct getState() in render) + +## Debugging Steps +1. Check SpireTab.tsx for undefined state access +2. Verify combatStore.ts has `maxFloorReached` properly initialized +3. Ensure store subscriptions are correctly set up +4. Check for race conditions in component mounting + +## Expected Outcome +- Spire tab loads without errors +- `maxFloorReached` is properly accessed from combatStore +- All legacy store references in Spire-related files are removed +- Regression test added to `src/lib/game/stores/__tests__/combat-store-tests/` diff --git a/docs/project-structure.txt b/docs/project-structure.txt index 9c67c05..b868b3b 100644 --- a/docs/project-structure.txt +++ b/docs/project-structure.txt @@ -12,6 +12,11 @@ Mana-Loop/ │ └── custom.db ├── docs/ │ ├── GAME_BRIEFING.md +│ ├── SPEC-mana-conversion-fix.md +│ ├── context-grimoire-tab-fix.md +│ ├── context-legacy-store-cleanup.md +│ ├── context-mana-conversion-fix.md +│ ├── context-spire-tab-fix.md │ ├── project-structure.txt │ └── skills.md ├── download/ diff --git a/src/components/game/tabs/FloorControls.tsx b/src/components/game/tabs/FloorControls.tsx index 3b4e69e..f77cf85 100644 --- a/src/components/game/tabs/FloorControls.tsx +++ b/src/components/game/tabs/FloorControls.tsx @@ -3,21 +3,47 @@ import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; -import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'; -import { ChevronUp, ChevronDown, Mountain, Shield, Skull, Heart, Wind, ShieldCheck } from 'lucide-react'; +import { ChevronUp, ChevronDown, Mountain, Skull } from 'lucide-react'; import { ELEMENTS } from '@/lib/game/constants'; -import type { FloorControlsProps } from '@/lib/game/types'; -const ROOM_TYPE_CONFIG: Record = { - combat: { label: 'Combat', icon: '⚔️', color: '#EF4444' }, - swarm: { label: 'Swarm', icon: '🐝', color: '#F59E0B' }, - speed: { label: 'Speed', icon: '💨', color: '#3B82F6' }, - guardian: { label: 'Guardian', icon: '🛡️', color: '#EF4444' }, - puzzle: { label: 'Puzzle', icon: '🧩', color: '#8B5CF6' }, -}; +interface FloorControlsProps { + // Store values passed as individual props + currentFloor: number; + floorHP: number; + floorMaxHP: number; + maxFloorReached: number; + equipmentSpellStates: any[]; + + // Other props + climbDirection: 'up' | 'down' | null; + isGuardianFloor: boolean; + currentRoom: any; + currentGuardian: any; + isFloorCleared: boolean; + floorElemDef: any; + roomType: string; + roomConfig: { label: string; icon: string; color: string }; + activeEquipmentSpells: any[]; + floorElem: string; + totalDPS: number; + calcDamage: (state: { skills: Record; signedPacts: number[] }, spellId: string, floorElem?: string) => number; + SPELLS_DEF: Record; + canCastSpell: (spellId: string) => boolean; + storeCurrentAction: string; + handleClimb: (direction: 'up' | 'down') => void; + formatSpellCost: (cost: any) => string; + getSpellCostColor: (cost: any) => string; + // Skills and pacts needed for calcDamage + skills: Record; + signedPacts: number[]; +} export function FloorControls({ - store, + currentFloor, + floorHP, + floorMaxHP, + maxFloorReached, + equipmentSpellStates, climbDirection, isGuardianFloor, currentRoom, @@ -27,10 +53,8 @@ export function FloorControls({ roomType, roomConfig, activeEquipmentSpells, - upgradeEffects, floorElem, totalDPS, - getEnemyName, calcDamage, SPELLS_DEF, canCastSpell, @@ -38,6 +62,8 @@ export function FloorControls({ handleClimb, formatSpellCost, getSpellCostColor, + skills, + signedPacts, }: FloorControlsProps) { return ( @@ -50,7 +76,7 @@ export function FloorControls({ variant="outline" size="sm" onClick={() => handleClimb('up')} - disabled={storeCurrentAction === 'climb' || isFloorCleared || store.maxFloorReached >= 100} + disabled={storeCurrentAction === 'climb' || isFloorCleared || maxFloorReached >= 100} className="border-gray-600 hover:bg-gray-800" > @@ -60,7 +86,7 @@ export function FloorControls({ variant="outline" size="sm" onClick={() => handleClimb('down')} - disabled={storeCurrentAction === 'climb' || store.currentFloor <= 1} + disabled={storeCurrentAction === 'climb' || currentFloor <= 1} className="border-gray-600 hover:bg-gray-800" > @@ -97,14 +123,14 @@ export function FloorControls({
- {fmt(store.floorHP)} / {fmt(store.floorMaxHP)} HP + {fmt(floorHP)} / {fmt(floorMaxHP)} HP DPS: {activeEquipmentSpells.length > 0 ? fmtDec(totalDPS) : '—'} @@ -117,7 +143,7 @@ export function FloorControls({ {activeEquipmentSpells.map(({ spellId, equipmentId }) => { const spellDef = SPELLS_DEF[spellId]; if (!spellDef) return null; - const spellState = store.equipmentSpellStates?.find( + const spellState = equipmentSpellStates?.find( s => s.spellId === spellId && s.sourceEquipment === equipmentId ); const progress = spellState?.castProgress || 0; @@ -134,7 +160,7 @@ export function FloorControls({
- ⚔️ {fmt(calcDamage(store, spellId))} dmg • {' '} + ⚔️ {fmt(calcDamage({ skills, signedPacts }, spellId, floorElem))} dmg • {' '} {formatSpellCost(spellDef.cost)} diff --git a/src/components/game/tabs/SpireTab.tsx b/src/components/game/tabs/SpireTab.tsx index 1c234c8..80caa3a 100755 --- a/src/components/game/tabs/SpireTab.tsx +++ b/src/components/game/tabs/SpireTab.tsx @@ -292,6 +292,13 @@ export function SpireTab({ simpleMode = false }: SpireTabProps) { {/* Floor Controls */} {simpleMode && (