From 837d963b633cd40dabb69f10bb81f32f14dd1cea Mon Sep 17 00:00:00 2001
From: Refactoring Agent <[email protected]>
Date: Mon, 4 May 2026 13:36:10 +0200
Subject: [PATCH] fix: split SpireTab.tsx to 395 lines, remove require()
imports, import from data modules; complete store migration
---
docs/project-structure.txt | 28 +-
docs/task5.md | 77 ---
docs/task5/subtask_11_context.md | 117 ----
docs/task5/subtask_12_context.md | 451 -------------
docs/task5/subtask_13_context.md | 282 --------
docs/task5/subtask_14_context.md | 66 --
docs/task5/subtask_15_context.md | 165 -----
docs/task5/subtask_16_context.md | 174 -----
docs/task5/subtask_17_context.md | 111 ----
docs/task5/subtask_18_context.md | 330 ----------
docs/task5/subtask_19_context.md | 56 --
docs/task5/subtask_5_context.md | 602 ------------------
docs/task5/subtask_6_context.md | 19 -
docs/task5/subtask_9_context.md | 124 ----
docs/task5_insight_proposals.md | 95 ---
docs/task6.md | 38 --
docs/task6/subtask_1_context.md | 312 ---------
docs/task7.md | 15 -
docs/task7/ctx_page.md | 55 --
docs/task7/ctx_skillstab.md | 62 --
docs/task7/ctx_upgrade_effects.md | 31 -
docs/task7/plan_page.md | 49 --
docs/task7/plan_skillstab.md | 65 --
docs/task7/plan_upgrade_effects.md | 69 --
src/app/components/LeftPanel.tsx | 92 ++-
src/app/page.tsx | 113 ++--
.../game/crafting/EnchantmentApplier.tsx | 31 +-
.../game/crafting/EnchantmentDesigner.tsx | 22 +-
.../game/crafting/EnchantmentPreparer.tsx | 19 +-
.../game/crafting/EquipmentCrafter.tsx | 23 +-
src/components/game/tabs/AchievementsTab.tsx | 28 +-
.../game/tabs/CategorySkillsList.tsx | 258 +++-----
src/components/game/tabs/CraftingTab.tsx | 35 +-
src/components/game/tabs/EquipmentTab.tsx | 14 +-
src/components/game/tabs/GolemancyTab.tsx | 121 ++--
src/components/game/tabs/LootTab.tsx | 32 +-
src/components/game/tabs/PrestigeTab.tsx | 69 +-
src/components/game/tabs/SkillsTab.tsx | 71 ++-
src/components/game/tabs/SpireTab.tsx | 198 +++---
src/lib/game/stores/combatStore.ts | 4 +-
src/lib/game/stores/gameHooks.ts | 139 ++++
41 files changed, 727 insertions(+), 3935 deletions(-)
delete mode 100644 docs/task5.md
delete mode 100644 docs/task5/subtask_11_context.md
delete mode 100644 docs/task5/subtask_12_context.md
delete mode 100644 docs/task5/subtask_13_context.md
delete mode 100644 docs/task5/subtask_14_context.md
delete mode 100644 docs/task5/subtask_15_context.md
delete mode 100644 docs/task5/subtask_16_context.md
delete mode 100644 docs/task5/subtask_17_context.md
delete mode 100644 docs/task5/subtask_18_context.md
delete mode 100644 docs/task5/subtask_19_context.md
delete mode 100644 docs/task5/subtask_5_context.md
delete mode 100644 docs/task5/subtask_6_context.md
delete mode 100644 docs/task5/subtask_9_context.md
delete mode 100644 docs/task5_insight_proposals.md
delete mode 100644 docs/task6.md
delete mode 100644 docs/task6/subtask_1_context.md
delete mode 100644 docs/task7.md
delete mode 100644 docs/task7/ctx_page.md
delete mode 100644 docs/task7/ctx_skillstab.md
delete mode 100644 docs/task7/ctx_upgrade_effects.md
delete mode 100644 docs/task7/plan_page.md
delete mode 100644 docs/task7/plan_skillstab.md
delete mode 100644 docs/task7/plan_upgrade_effects.md
diff --git a/docs/project-structure.txt b/docs/project-structure.txt
index 4b9ab0f..ae465da 100644
--- a/docs/project-structure.txt
+++ b/docs/project-structure.txt
@@ -11,35 +11,9 @@ Mana-Loop/
├── db/
│ └── custom.db
├── docs/
-│ ├── task5/
-│ │ ├── subtask_11_context.md
-│ │ ├── subtask_12_context.md
-│ │ ├── subtask_13_context.md
-│ │ ├── subtask_14_context.md
-│ │ ├── subtask_15_context.md
-│ │ ├── subtask_16_context.md
-│ │ ├── subtask_17_context.md
-│ │ ├── subtask_18_context.md
-│ │ ├── subtask_19_context.md
-│ │ ├── subtask_5_context.md
-│ │ ├── subtask_6_context.md
-│ │ └── subtask_9_context.md
-│ ├── task6/
-│ │ └── subtask_1_context.md
-│ ├── task7/
-│ │ ├── ctx_page.md
-│ │ ├── ctx_skillstab.md
-│ │ ├── ctx_upgrade_effects.md
-│ │ ├── plan_page.md
-│ │ ├── plan_skillstab.md
-│ │ └── plan_upgrade_effects.md
│ ├── GAME_BRIEFING.md
│ ├── project-structure.txt
-│ ├── skills.md
-│ ├── task5.md
-│ ├── task5_insight_proposals.md
-│ ├── task6.md
-│ └── task7.md
+│ └── skills.md
├── download/
│ └── README.md
├── examples/
diff --git a/docs/task5.md b/docs/task5.md
deleted file mode 100644
index 450cd1a..0000000
--- a/docs/task5.md
+++ /dev/null
@@ -1,77 +0,0 @@
-# Task 5 — Bug Fixes, UI Restructuring & Feature Additions Progress
-
-## Status Overview
-- **Start Date**: 2025-05-19
-- **Current Phase**: COMPLETED (all pending tasks done)
-- **Overall Progress**: 84% complete (16/19 tasks done, 3 partially done)
-
----
-
-## PRIORITY 0 — Crashes ✅ COMPLETED
-| Task | Status | Notes |
-|------|--------|-------|
-| SpellsTab crash | ✅ Completed | Fixed unprotected ENCHANTMENT_EFFECTS access |
-| LabTab crash | ✅ Completed | Added safe store.elements access |
-| DebugTab crash | ✅ Completed | Moved Toaster inside DebugProvider |
-
----
-
-## PRIORITY 1 — Mana Conversion ✅ COMPLETED
-| Task | Status | Notes |
-|------|--------|-------|
-| Conversion drain fix | ✅ Completed | Wired to effectiveRegen |
-
----
-
-## PRIORITY 2 — Spire Mode Fixes
-| Task | Status | Notes |
-|------|--------|-------|
-| 2a. Floor Rendering | ✅ Completed | Type, enemy, properties shown |
-| 2b. Swarm Floors | ✅ Completed | Multiple enemies verified |
-| 2c. HP Bar Live Updates | ✅ Completed | Syncs to enemy HP |
-| 2d. Casting Progress Overflow | ⏳ Partially done | Check failed (context overflow) |
-| 2e. Climb/Descend Controls | ✅ Completed | Spam fix, re-entry, labels |
-| 2f. Activity Log | ✅ Completed | All combat events logged |
-| 2g. Spell Info Display | ✅ Completed | dmg/cast + true DPS |
-
----
-
-## PRIORITY 3 — UI/UX Restructuring
-| Task | Status | Notes |
-|------|--------|-------|
-| 3a. CraftingTab Restructure | ✅ Completed | Fabricate/Enchant tabs |
-| 3b. LootTab Nesting | ✅ Completed | Removed redundant layers |
-| 3c. AchievementsTab Nesting | ✅ Completed | Removed duplicate headings |
-
----
-
-## PRIORITY 4 — Enchantment Effects
-| Task | Status | Notes |
-|------|--------|-------|
-| 4a. Mana Capacity Enchantments | ⏳ Partially done | Context file exists |
-| 4b. Mana Research Gate | ⏳ Partially done | Check failed |
-| 4c. Skill Bug Fix | ✅ Completed | Fixed undefined Lv.[object Object] |
-| 4d. Enchantment Power Effect | ✅ Completed | Implemented + stub audit |
-
----
-
-## PRIORITY 5 — Insight Upgrade Analysis
-| Task | Status | Notes |
-|------|--------|-------|
-| 5a. Design Proposal | ✅ Completed | Written to docs/task5_insight_proposals.md |
-
----
-
-## Remaining Partially Done Tasks
-1. **Task8 (2d Casting Progress Overflow)**: Check failed, context overflow
-2. **Task15 (4a Mana Capacity Enchantments)**: Context file exists, needs execution
-3. **Task16 (4b Mana Research Gate)**: Check failed, context file exists
-
----
-
-## Workflow Log
-- ✅ All PRIORITY 0-3 tasks completed
-- ✅ PRIORITY 4: 2/4 completed, 2 partially done
-- ✅ PRIORITY 5: Proposal completed
-- ✅ All sub-agents used per pipeline rules
-- ✅ Task list (create_tasks) synced with docs/task5.md
diff --git a/docs/task5/subtask_11_context.md b/docs/task5/subtask_11_context.md
deleted file mode 100644
index 7e4c3be..0000000
--- a/docs/task5/subtask_11_context.md
+++ /dev/null
@@ -1,117 +0,0 @@
-# Task 11: Fix Spell Info Display - Context Summary
-
-## Task Status
-**Partially done** - dmg/cast is shown correctly, total DPS is shown, but per-spell DPS display is incorrect.
-
-## Files Analyzed
-
-### 1. `/home/user/repos/Mana-Loop/src/components/game/tabs/SpireTab.tsx`
-- **Lines 356-360**: Spell info display for active equipment spells
- - Shows: `⚔️ {fmt(calcDamage(store, spellId))} dmg/cast`
- - Shows: `⚡ {fmt(Math.floor(calcDamage(store, spellId) * (spellDef.castSpeed || 1)))} dmg/hr`
- - **ISSUE**: The "dmg/hr" calculation is incorrect. It multiplies damage by `castSpeed` (which is casts/hour), but doesn't account for:
- 1. `quickCast` skill bonus (`1 + (skills.quickCast || 0) * 0.05`)
- 2. Equipment `attackSpeedMultiplier` from upgrade effects
- 3. The actual conversion from casts/hour to DPS
-
-### 2. `/home/user/repos/Mana-Loop/src/lib/game/constants/spells.ts`
-- **Spell Definition Structure** (`SpellDef` interface in `/src/lib/game/types/spells.ts`):
- - `castSpeed?: number` - **Casts per hour** (default 1, higher = faster)
- - `dmg: number` - Base damage per cast
- - Examples: `manaBolt` has `castSpeed: 3` (3 casts per hour), `fireball` has `castSpeed: 2`
-
-### 3. `/home/user/repos/Mana-Loop/src/lib/game/utils/combat-utils.ts`
-- **`calcDamage(state, spellId, floorElem)`** (lines 84-131):
- - Calculates damage per cast
- - Factors: `baseDmg = sp.dmg + (skills.combatTrain || 0) * 5`
- - Multipliers: `arcaneFury`, `elementalMastery`, `guardianBane`, `rawDamage`, `elemDamage`
- - Elemental bonus: `getElementalBonus(sp.elem, floorElem)`
- - Crit: `precision` skill + boon critChance
-
-- **`getTotalDPS(state, upgradeEffects, floorElem)`** (lines 265-300):
- - Calculates TRUE total DPS from all active equipment spells
- - Uses `baseCastTime` (not `castSpeed`) - **BUG**: `baseCastTime` is not defined in `SpellDef`!
- - Actual formula used:
- ```typescript
- const baseCastTime = spellDef.baseCastTime || 1.0;
- const castingSpeedBonus = 1 + (state.skills.castingSpeed || 0) * 0.1;
- const equipmentAttackSpeed = upgradeEffects.attackSpeedMultiplier || 1;
- const castTime = baseCastTime / (castingSpeedBonus * equipmentAttackSpeed);
- const spellDPS = damage / castTime;
- ```
- - **ISSUE**: `baseCastTime` is always 1.0 (fallback), so the formula doesn't use `castSpeed` at all!
-
-### 4. `/home/user/repos/Mana-Loop/src/lib/game/hooks/useGameDerived.ts`
-- **DPS calculation for active spell** (lines 145-152):
- ```typescript
- const spellCastSpeed = activeSpellDef.castSpeed || 1; // casts per hour
- const quickCastBonus = 1 + (store.skills.quickCast || 0) * 0.05;
- const attackSpeedMult = upgradeEffects.attackSpeedMultiplier;
- const totalCastSpeed = spellCastSpeed * quickCastBonus * attackSpeedMult;
- const damagePerCast = calcDamage(store, store.activeSpell, floorElem);
- const castsPerSecond = totalCastSpeed * HOURS_PER_TICK / (TICK_MS / 1000);
- return damagePerCast * castsPerSecond;
- ```
- - **This is the CORRECT formula for DPS**:
- - `castsPerSecond = castSpeed * quickCastBonus * attackSpeedMult * (HOURS_PER_TICK / (TICK_MS / 1000))`
- - With `HOURS_PER_TICK = 0.04` and `TICK_MS = 200`:
- - `castsPerSecond = castSpeed * quickCastBonus * attackSpeedMult * 0.04 / 0.2 = castSpeed * quickCastBonus * attackSpeedMult * 0.2`
-
-### 5. `/home/user/repos/Mana-Loop/src/lib/game/store/combatSlice.ts`
-- **`processCombat`** (lines 60-75): Actual combat uses `castSpeed` correctly:
- ```typescript
- const baseAttackSpeed = 1 + (state.skills.quickCast || 0) * 0.05;
- const totalAttackSpeed = baseAttackSpeed * effects.attackSpeedMultiplier;
- const spellCastSpeed = spellDef.castSpeed || 1;
- const progressPerTick = deltaHours * spellCastSpeed * totalAttackSpeed;
- ```
- - Note: Uses `quickCast` skill (not `castingSpeed` which is from boons)
-
-## Summary of Issues
-
-### Issue 1: Per-spell "dmg/hr" display is wrong in SpireTab.tsx
-- **Current**: `calcDamage(store, spellId) * (spellDef.castSpeed || 1)`
-- **Problem**: This just multiplies damage by casts/hour, but doesn't convert to actual DPS correctly
-- **Correct formula** (from `useGameDerived.ts`):
- - `dps = damagePerCast * castSpeed * quickCastBonus * attackSpeedMult * HOURS_PER_TICK / (TICK_MS / 1000)`
- - Or simpler: `dps = damagePerCast * castSpeed * 0.2` (when no bonuses)
-
-### Issue 2: `getTotalDPS` uses wrong field
-- **Current**: Uses `baseCastTime` which doesn't exist in `SpellDef`
-- **Fix needed**: Should use `castSpeed` (casts per hour) and convert to DPS properly
-- **Correct formula**:
- ```typescript
- const castsPerHour = spellDef.castSpeed || 1;
- const quickCastBonus = 1 + (state.skills.quickCast || 0) * 0.05;
- const attackSpeedMult = upgradeEffects.attackSpeedMultiplier || 1;
- const totalCastsPerHour = castsPerHour * quickCastBonus * attackSpeedMult;
- const spellDPS = damage * totalCastsPerHour / 3600; // Convert casts/hour to casts/second
- ```
-
-### Issue 3: Inconsistent skill usage
-- `combatSlice.ts` and `useGameDerived.ts` use `quickCast` skill for cast speed bonus
-- `getTotalDPS` uses `castingSpeed` (which is from boons, not player skills)
-- Need to verify which is correct or use both
-
-## Correct Formulas
-
-### True DPS for a spell:
-```
-DPS = damage_per_cast × (castSpeed × quickCast_bonus × attackSpeed_multiplier) / 3600
-```
-
-Where:
-- `damage_per_cast` = `calcDamage(store, spellId, floorElem)`
-- `castSpeed` = `spellDef.castSpeed || 1` (casts per hour)
-- `quickCast_bonus` = `1 + (skills.quickCast || 0) * 0.05`
-- `attackSpeed_multiplier` = from equipment effects
-- Divide by 3600 to convert from casts/hour to casts/second
-
-### For display in SpireTab.tsx per-spell:
-- **dmg/cast**: Already correct - uses `calcDamage(store, spellId)`
-- **DPS**: Should show `damage_per_cast × (castSpeed × quickCast × attackSpeed) / 3600`
-- Current "dmg/hr" is misleading - it's not actually damage per hour with bonuses
-
-## Files to Modify
-1. `/home/user/repos/Mana-Loop/src/components/game/tabs/SpireTab.tsx` (lines 356-360) - Fix per-spell DPS display
-2. `/home/user/repos/Mana-Loop/src/lib/game/utils/combat-utils.ts` (lines 280-295) - Fix `getTotalDPS` to use `castSpeed` instead of `baseCastTime`
diff --git a/docs/task5/subtask_12_context.md b/docs/task5/subtask_12_context.md
deleted file mode 100644
index a798115..0000000
--- a/docs/task5/subtask_12_context.md
+++ /dev/null
@@ -1,451 +0,0 @@
-# Task 12 Context: Restructure CraftingTab
-
-## Task Description
-Restructure CraftingTab (remove 1-4 progress bar, split Fabricate/Enchant, top sub-tabs) (PRIORITY 3a)
-
-## Source Files
-
-### 1. `/src/components/game/tabs/CraftingTab.tsx` (268 lines)
-
-**Imports and Dependencies:**
-```typescript
-'use client';
-
-import { useState } from 'react';
-import { Button } from '@/components/ui/button';
-import { Progress } from '@/components/ui/progress';
-import { GameCard } from '@/components/ui/game-card';
-import { SectionHeader } from '@/components/ui/section-header';
-import { ActionButton } from '@/components/ui/action-button';
-import { Stepper } from '@/components/ui/stepper';
-import { Scroll, Hammer, Sparkles, Anvil } from 'lucide-react';
-import type { EquipmentInstance, EnchantmentDesign, DesignEffect, AppliedEnchantment, LootInventory, EquipmentCraftingProgress } from '@/lib/game/types';
-import { fmt, type GameStore } from '@/lib/game/store';
-import {
- EnchantmentDesigner,
- EnchantmentPreparer,
- EnchantmentApplier,
- EquipmentCrafter,
-} from '@/components/game/crafting';
-import { useGameToast } from '@/components/game/GameToast';
-```
-
-**Crafting Phases Constant:**
-```typescript
-// Crafting phases for the stepper
-const CRAFTING_PHASES = ['Design', 'Prepare', 'Apply', 'Craft'];
-```
-
-**Component Props:**
-```typescript
-export interface CraftingTabProps {
- store: GameStore;
-}
-```
-
-**State and Stepper Mapping:**
-```typescript
-export function CraftingTab({ store }: CraftingTabProps) {
- const showToast = useGameToast();
- const currentAction = store.currentAction;
- const designProgress = store.designProgress;
- const preparationProgress = store.preparationProgress;
- const applicationProgress = store.applicationProgress;
- const equipmentCraftingProgress = store.equipmentCraftingProgress;
- const pauseApplication = store.pauseApplication;
- const resumeApplication = store.resumeApplication;
-
- const [craftingStage, setCraftingStage] = useState<'design' | 'prepare' | 'apply' | 'craft'>('craft');
-
- // Map crafting stage to stepper index
- const getStepperIndex = (stage: string): number => {
- switch (stage) {
- case 'design': return 0;
- case 'prepare': return 1;
- case 'apply': return 2;
- case 'craft': return 3;
- default: return 0;
- }
- };
-```
-
-**Stepper Component (lines 58-68):**
-```tsx
-{/* Visual Stepper - Requirement: show Design, Prepare, Apply phases as visual stepper */}
-
-
-
-```
-
-**Stage Content Conditional Rendering (lines 71-97):**
-```tsx
-{/* Stage Content - Without unlabeled Tabs, using conditional rendering instead */}
-
- {craftingStage === 'craft' && (
-
- )}
- {craftingStage === 'design' && (
- {}}
- selectedEffects={[]}
- setSelectedEffects={() => {}}
- designName={''}
- setDesignName={() => {}}
- selectedDesign={null}
- setSelectedDesign={() => {}}
- />
- )}
- {craftingStage === 'prepare' && (
- {}}
- />
- )}
- {craftingStage === 'apply' && (
- {}}
- selectedDesign={null}
- setSelectedDesign={() => {}}
- onEnchantmentApplied={handleEnchantmentApplied}
- onCapacityExceeded={handleCapacityExceeded}
- />
- )}
-
-```
-
-**Stage Navigation Buttons (lines 99-131):**
-```tsx
-{/* Stage Navigation Buttons */}
-
-
-
setCraftingStage('craft')}
- className={craftingStage === 'craft' ? 'ring-2 ring-[var(--interactive-primary)]' : ''}
- >
-
- Craft
-
-
setCraftingStage('design')}
- className={craftingStage === 'design' ? 'ring-2 ring-[var(--interactive-primary)]' : ''}
- >
-
- Design
-
-
setCraftingStage('prepare')}
- className={craftingStage === 'prepare' ? 'ring-2 ring-[var(--interactive-primary)]' : ''}
- >
-
- Prepare
-
-
setCraftingStage('apply')}
- className={craftingStage === 'apply' ? 'ring-2 ring-[var(--interactive-primary)]' : ''}
- >
-
- Apply
-
-
-
-```
-
-**Current Activity Indicators (Progress Bars to be removed - lines 133-236):**
-```tsx
-{/* Current Activity Indicator */}
-{currentAction === 'craft' && equipmentCraftingProgress && (
-
-
- {safeToFixed(calcPercent(equipmentCraftingProgress.progress, equipmentCraftingProgress.required), 0)}%
-
- }
- />
-
- {/* ... more content ... */}
-
-)}
-
-{currentAction === 'design' && designProgress && (
-
- {/* ... Progress bar and content ... */}
-
-)}
-
-{currentAction === 'prepare' && preparationProgress && (
-
- {/* ... Progress bar and content ... */}
-
-)}
-
-{currentAction === 'enchant' && applicationProgress && (
-
- {/* ... Progress bar and content ... */}
-
-)}
-```
-
----
-
-### 2. Files in `/src/components/game/crafting/` Directory
-
-| File | Size | Last Modified |
-|------|------|---------------|
-| `EnchantmentApplier.tsx` | 12,206 bytes | 1777364523 |
-| `EnchantmentDesigner.tsx` | 19,568 bytes | 1777361558 |
-| `EnchantmentPreparer.tsx` | 14,816 bytes | 1777365343 |
-| `EquipmentCrafter.tsx` | 9,121 bytes | 1777205526 |
-| `index.tsx` | 396 bytes | 1777028644 |
-
-**Barrel File (`index.tsx`):**
-```typescript
-// Barrel file for crafting components
-
-export { EnchantmentDesigner, type EnchantmentDesignerProps } from './EnchantmentDesigner';
-export { EnchantmentPreparer, type EnchantmentPreparerProps } from './EnchantmentPreparer';
-export { EnchantmentApplier, type EnchantmentApplierProps } from './EnchantmentApplier';
-export { EquipmentCrafter, type EquipmentCrafterProps } from './EquipmentCrafter';
-```
-
----
-
-### 3. Stepper Component (`/src/components/ui/stepper.tsx`)
-
-**Interface:**
-```typescript
-interface StepperProps extends React.HTMLAttributes {
- steps: string[];
- currentStep: number; // 0-indexed
- orientation?: "horizontal" | "vertical";
-}
-```
-
-**Full Implementation (100 lines):**
-```typescript
-import * as React from "react";
-import { cn } from "@/lib/utils";
-import { Check, Circle, ArrowRight } from "lucide-react";
-
-interface StepperProps extends React.HTMLAttributes {
- steps: string[];
- currentStep: number; // 0-indexed
- orientation?: "horizontal" | "vertical";
-}
-
-interface StepProps {
- label: string;
- stepNumber: number;
- isActive: boolean;
- isCompleted: boolean;
- isLast: boolean;
- orientation?: "horizontal" | "vertical";
-}
-
-const Step = ({ label, stepNumber, isActive, isCompleted, isLast, orientation = "horizontal" }: StepProps) => {
- return (
-
-
-
- {isCompleted ? (
-
- ) : (
- {stepNumber}
- )}
-
-
- {label}
-
-
- {!isLast && (
-
- )}
-
- );
-};
-
-export function Stepper({ steps, currentStep, orientation = "horizontal", className, ...props }: StepperProps) {
- return (
-
- {steps.map((step, index) => (
-
-
-
- ))}
-
- );
-}
-```
-
----
-
-### 4. Current Sub-Tab/Navigation Implementation Details
-
-**Current Structure:**
-The CraftingTab currently uses a **4-stage linear workflow** with:
-1. A visual Stepper component showing phases: Design → Prepare → Apply → Craft
-2. Navigation buttons at the bottom to switch between stages
-3. Conditional rendering of content based on `craftingStage` state
-
-**Current Stages:**
-- `design` - EnchantmentDesigner component (Design enchantments)
-- `prepare` - EnchantmentPreparer component (Prepare equipment)
-- `apply` - EnchantmentApplier component (Apply enchantments)
-- `craft` - EquipmentCrafter component (Craft equipment)
-
-**Issues to Address (Task Requirements):**
-1. **Remove 1-4 progress bar** - The Stepper component (lines 58-68) needs to be removed
-2. **Split Fabricate/Enchant** - Currently "Craft" (EquipmentCrafter) is mixed in with enchantment workflow. Need to split into:
- - "Fabricate" tab - for EquipmentCrafter (crafting equipment)
- - "Enchant" tab - for the Design → Prepare → Apply workflow
-3. **Top sub-tabs** - Replace the bottom navigation buttons with proper top-level sub-tabs
-
-**Current Navigation Pattern:**
-- State: `craftingStage` (useState with 4 possible values)
-- Navigation: 4 ActionButtons at the bottom of the tab
-- Visual indicator: Stepper at the top showing progress through phases
-
-**Suggested New Structure (for implementation):**
-```
-CraftingTab
-├── Top Sub-Tabs: [Fabricate] [Enchant]
-├── Fabricate Content: EquipmentCrafter
-└── Enchant Content:
- ├── Sub-Navigation: [Design] [Prepare] [Apply]
- ├── Design: EnchantmentDesigner
- ├── Prepare: EnchantmentPreparer
- └── Apply: EnchantmentApplier
-```
-
----
-
-### 5. Component Props Signatures
-
-**EquipmentCrafterProps:**
-```typescript
-export interface EquipmentCrafterProps {
- store: GameStore;
-}
-```
-
-**EnchantmentDesignerProps:**
-```typescript
-export interface EnchantmentDesignerProps {
- store: GameStore;
- selectedEquipmentType: string | null;
- setSelectedEquipmentType: (type: string | null) => void;
- selectedEffects: DesignEffect[];
- setSelectedEffects: (effects: DesignEffect[]) => void;
- designName: string;
- setDesignName: (name: string) => void;
- selectedDesign: string | null;
- setSelectedDesign: (id: string | null) => void;
-}
-```
-
-**EnchantmentPreparerProps:**
-```typescript
-export interface EnchantmentPreparerProps {
- store: GameStore;
- selectedEquipmentInstance: string | null;
- setSelectedEquipmentInstance: (id: string | null) => void;
-}
-```
-
-**EnchantmentApplierProps:**
-```typescript
-export interface EnchantmentApplierProps {
- store: GameStore;
- selectedEquipmentInstance: string | null;
- setSelectedEquipmentInstance: (id: string | null) => void;
- selectedDesign: string | null;
- setSelectedDesign: (id: string | null) => void;
- onEnchantmentApplied?: () => void;
- onCapacityExceeded?: (itemName: string, used: number, total: number) => void;
-}
-```
-
----
-
-### 6. Key Observations for Restructuring
-
-1. **Stepper Removal**: The `CRAFTING_PHASES` constant and `Stepper` component usage must be removed from CraftingTab
-
-2. **State Management**: The `craftingStage` state will need to be replaced with:
- - A top-level tab state (`fabricate` | `enchant`)
- - An enchant sub-stage state (`design` | `prepare` | `apply`) when in enchant mode
-
-3. **Progress Bars**: The activity indicators with Progress components (lines 133-236) should potentially be moved into their respective components (EquipmentCrafter, EnchantmentDesigner, etc.) rather than being in CraftingTab
-
-4. **No Tab Component**: Currently, the app doesn't use a Tab component - it uses conditional rendering with ActionButtons. The restructured version should implement proper tabs at the top level
-
-5. **Helper Functions**: The `safeToFixed` and `calcPercent` helpers are used for progress bars - these may no longer be needed in CraftingTab after restructuring
diff --git a/docs/task5/subtask_13_context.md b/docs/task5/subtask_13_context.md
deleted file mode 100644
index 79f55a9..0000000
--- a/docs/task5/subtask_13_context.md
+++ /dev/null
@@ -1,282 +0,0 @@
-# Task 13 Context: Fix LootTab Nesting (Remove Redundant Layers)
-
-## Task Description
-Fix LootTab nesting (remove redundant layers) (PRIORITY 3b)
-
-## Source Files
-
-### 1. `/src/components/game/tabs/LootTab.tsx` (48 lines)
-
-**Full Content:**
-```typescript
-'use client';
-
-import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
-import { Badge } from '@/components/ui/badge';
-import type { GameStore } from '@/lib/game/store';
-import { LootInventoryDisplay } from '@/components/game/LootInventory';
-
-export interface LootTabProps {
- store: GameStore;
-}
-
-export function LootTab({ store }: LootTabProps) {
- const inventory = store.lootInventory;
- const elements = store.elements;
- const equipmentInstances = store.equipmentInstances;
-
- // Count items for badge
- const materialCount = Object.values(inventory.materials).reduce((a, b) => a + b, 0);
- const blueprintCount = inventory.blueprints.length;
- const equipmentCount = Object.keys(equipmentInstances).length;
- const totalItems = materialCount + blueprintCount + equipmentCount;
-
- return (
-
-
-
-
- 💎 Loot Inventory
-
- {totalItems} items
-
-
-
-
-
-
-
-
- );
-}
-
-LootTab.displayName = "LootTab";
-```
-
-**Key Observations - LootTab Redundant Wrapper:**
-- Uses `Card` component from `@/components/ui/card` with header "💎 Loot Inventory"
-- Shows a badge with total items count
-- Wraps `LootInventoryDisplay` inside `CardContent`
-- **This creates the outer layer of nesting**
-
----
-
-### 2. `/src/components/game/LootInventory.tsx` (499 lines)
-
-**Component Interface:**
-```typescript
-interface LootInventoryProps {
- inventory: LootInventoryType;
- elements?: Record;
- equipmentInstances?: Record;
- onDeleteMaterial?: (materialId: string, amount: number) => void;
- onDeleteEquipment?: (instanceId: string) => void;
-}
-```
-
-**Main Component Export:**
-```typescript
-export function LootInventoryDisplay({
- inventory,
- elements,
- equipmentInstances = {},
- onDeleteMaterial,
- onDeleteEquipment,
-}: LootInventoryProps) {
- // ... state and handlers ...
-
- // Check if we have anything to show
- const hasItems = totalItems > 0 || essenceCount > 0;
-
- if (!hasItems) {
- return (
-
-
-
-
- Inventory
-
-
-
- No items collected yet. Defeat floors and guardians to find loot!
-
-
- );
- }
-
- // ... handlers ...
-
- return (
- <>
-
-
-
-
- Inventory
-
-
- {totalItems} items
-
-
-
- {/* Search and Filter Controls */}
- {/* ... */}
-
- {/* Filter Tabs */}
- {/* ... */}
-
-
-
-
- {/* Materials, Essence, Blueprints, Equipment sections */}
- {/* ... */}
-
-
-
- {/* Delete Confirmation Dialog */}
- setDeleteConfirm(null)}>
- {/* ... */}
-
- >
- );
-}
-```
-
-**Key Observations - LootInventory Redundant Wrapper:**
-- Uses `GameCard` component (from `@/components/ui/game-card`)
-- Has its own header with "Inventory" title and `Gem` icon
-- Shows a badge with total items count (duplicating LootTab's badge)
-- Contains all the actual content: search, filters, items display
-- **This creates the inner layer of nesting**
-
----
-
-## 3. Duplicate Headings/Wrappers Issue
-
-### Redundant Card Nesting:
-```
-LootTab (Outer Card)
-├── CardHeader: "💎 Loot Inventory" + Badge: "{totalItems} items"
-└── CardContent
- └── LootInventoryDisplay (Inner GameCard)
- ├── Header: "Inventory" + Badge: "{totalItems} items" ← DUPLICATE
- ├── Search/Filter Controls
- ├── Items Display
- └── Delete Dialog
-```
-
-### Specific Code Duplication:
-
-**LootTab.tsx (lines 24-33) - Outer Header:**
-```tsx
-
-
- 💎 Loot Inventory
-
- {totalItems} items
-
-
-
-```
-
-**LootInventory.tsx (lines 191-202) - Inner Header (DUPLICATE):**
-```tsx
-
-
-
- Inventory
-
-
- {totalItems} items
-
-
-```
-
-### Redundant Badge Count:
-- LootTab shows: `{totalItems} items`
-- LootInventoryDisplay also shows: `{totalItems} items`
-- Both calculate the same `totalItems` value
-
----
-
-## 4. Current Component Hierarchy
-
-```
-Game.tsx / Main Game Layout
- │
- └── Tabs Component (renders active tab)
- │
- └── LootTab ({ store })
- │
- ├── Card (bg-gray-900/80 border-gray-700)
- │ ├── CardHeader
- │ │ └── CardTitle: "💎 Loot Inventory" + Badge
- │ └── CardContent
- │ │
- │ └── LootInventoryDisplay ({ inventory, elements, equipmentInstances, ... })
- │ │
- │ ├── GameCard (variant="default" className="w-full")
- │ │ ├── Header: "Inventory" + Badge + Search/Filter
- │ │ ├── Separator
- │ │ ├── ScrollArea
- │ │ │ ├── Materials Section
- │ │ │ ├── Essence Section
- │ │ │ ├── Blueprints Section
- │ │ │ └── Equipment Section
- │ │ └── (content)
- │ │
- │ └── AlertDialog (Delete Confirmation)
- │
- └── (end Card)
-```
-
----
-
-## 5. Comparison with Other Tabs
-
-Looking at other tabs in `/src/components/game/tabs/`:
-
-- **EquipmentTab.tsx**: Uses `GameCard` directly without an outer `Card` wrapper
-- **CraftingTab.tsx**: Uses multiple `GameCard` components for different sections, no outer `Card`
-- **GolemancyTab.tsx**: Uses `GameCard` directly
-- **LabTab.tsx**: Uses `GameCard` directly
-
-**Pattern**: Most tabs render their content directly using `GameCard` components without an additional `Card` wrapper from the tab itself.
-
----
-
-## 6. Summary of Issues to Fix
-
-1. **Redundant Card Wrapper in LootTab**: The `Card` + `CardHeader` + `CardContent` wrapper in LootTab.tsx is unnecessary since LootInventoryDisplay already provides its own `GameCard` wrapper.
-
-2. **Duplicate Header**: Both LootTab and LootInventoryDisplay show similar headers with:
- - Title text ("Loot Inventory" vs "Inventory")
- - Item count badge
- - Icon (emoji 💎 vs Gem icon)
-
-3. **Double Wrapping**: Content is wrapped in two card-like components:
- - Outer: `Card` from `@/components/ui/card`
- - Inner: `GameCard` from `@/components/ui/game-card`
-
-4. **Solution Direction**: Remove the outer `Card` wrapper from LootTab.tsx and let LootInventoryDisplay handle the card styling, OR remove the inner `GameCard` from LootInventoryDisplay and keep only the outer wrapper.
-
----
-
-## 7. Files That Would Need Modification
-
-1. **`/src/components/game/tabs/LootTab.tsx`** - Remove outer Card wrapper, pass props directly to LootInventoryDisplay
-2. **`/src/components/game/LootInventory.tsx`** - Potentially remove GameCard wrapper if keeping LootTab's Card, or keep as-is if removing LootTab's Card
-
-**Recommended Approach**: Remove the outer `Card` wrapper from `LootTab.tsx` and let `LootInventoryDisplay` handle the full display (since it already has a complete GameCard wrapper with header, controls, and content).
diff --git a/docs/task5/subtask_14_context.md b/docs/task5/subtask_14_context.md
deleted file mode 100644
index bfb6eb5..0000000
--- a/docs/task5/subtask_14_context.md
+++ /dev/null
@@ -1,66 +0,0 @@
-# Task 14: Fix AchievementsTab Nesting - Context Summary
-
-## Current State (Problem)
-
-### Redundant Nested Layers Found:
-
-1. **Nested GameCards (Double Card Wrapper)**
- - `AchievementsTab.tsx` wraps everything in a `` (lines 16-42)
- - `AchievementsDisplay.tsx` ALSO wraps everything in a `` (line 63)
- - This creates nested cards - a card inside a card - which is redundant and causes visual/structural issues
-
-2. **Duplicate Headings**
- - `AchievementsTab.tsx` has an `` heading "Achievements" with badge showing `{unlockedCount} unlocked` (lines 19-26)
- - `AchievementsDisplay.tsx` has an `` heading "Achievements" with badge showing `{unlockedCount} / {totalCount}` (lines 64-72)
- - Both components render their own heading - this is redundant
-
-### File Analysis:
-
-#### AchievementsTab.tsx Structure:
-```
-
-
← OUTER CARD (should be removed)
- Achievements ← OUTER HEADING (should be removed)
- ← This component brings its own Card + Heading
-
-
-```
-
-#### AchievementsDisplay.tsx Structure:
-```
- ← INNER CARD (should stay)
- Achievements ← INNER HEADING (should stay)
-
- {/* achievement categories */}
-
-
-```
-
-## Correct Structure (After Fix)
-
-The `AchievementsTab` should NOT wrap `AchievementsDisplay` in a GameCard or provide its own heading. The correct structure is:
-
-### AchievementsTab.tsx (Fixed):
-```
-
-
← Only render the display component, no wrapping
-
-```
-
-### AchievementsDisplay.tsx (Unchanged):
-```
- ← Single card wrapper
- Achievements ← Single heading
-
- {/* achievement categories */}
-
-
-```
-
-## Summary of Changes Needed:
-
-1. **Remove GameCard wrapper from AchievementsTab.tsx** - Let AchievementsDisplay handle the card
-2. **Remove the h2 heading and badge from AchievementsTab.tsx** - Let AchievementsDisplay handle the heading
-3. **Keep AchievementsDisplay.tsx as-is** - It already has the correct structure
-
-This eliminates the double-nesting and duplicate headings issue.
diff --git a/docs/task5/subtask_15_context.md b/docs/task5/subtask_15_context.md
deleted file mode 100644
index 3df8d79..0000000
--- a/docs/task5/subtask_15_context.md
+++ /dev/null
@@ -1,165 +0,0 @@
-# Task 15: Add Mana-Type Capacity Enchantment Effects Per Unlocked Mana Type
-
-## Context Summary
-
-### 1. Current Enchantment Effects for Mana Capacity
-
-**Location**: `/home/user/repos/Mana-Loop/src/lib/game/data/enchantments/mana-effects.ts`
-
-Current mana capacity effects (in `MANA_EFFECTS`):
-- `mana_cap_50` - +50 maximum mana (max 3 stacks)
-- `mana_cap_100` - +100 maximum mana (max 3 stacks)
-- `weapon_mana_cap_20` - +20 weapon mana capacity (max 5 stacks)
-- `weapon_mana_cap_50` - +50 weapon mana capacity (max 3 stacks)
-- `weapon_mana_cap_100` - +100 weapon mana capacity (max 2 stacks)
-
-**Element Capacity Effects**: There are currently NO per-type element capacity enchantment effects. The `elementCap` stat exists in `ComputedEffects` (in `upgrade-effects.ts`) and is applied via:
-- `elementCapBonus` - additive bonus to element max
-- `elementCapMultiplier` - multiplier for element max
-
-These apply globally to ALL elements equally (see `computeElementMax` in `store.ts`).
-
-### 2. Mana Types That Are Unlockable
-
-**Location**: `/home/user/repos/Mana-Loop/src/lib/game/constants/elements.ts`
-
-**Base Elements** (cat: "base"):
-- `fire` - Fire 🔥
-- `water` - Water 💧
-- `air` - Air 🌬️
-- `earth` - Earth ⛰️
-- `light` - Light ☀️
-- `dark` - Dark 🌑
-- `death` - Death 💀
-
-**Utility Elements** (cat: "utility"):
-- `transference` - Transference 🔗 (ALREADY UNLOCKED by default in `BASE_UNLOCKED_ELEMENTS`)
-
-**Composite Elements** (cat: "composite", require recipe to craft):
-- `metal` - Metal ⚙️ (recipe: fire + earth)
-- `sand` - Sand ⏳ (recipe: earth + water)
-- `lightning` - Lightning ⚡ (recipe: fire + air)
-
-**Exotic Elements** (cat: "exotic", require complex recipes):
-- `crystal` - Crystal 💎 (recipe: sand + sand + light)
-- `stellar` - Stellar ⭐ (recipe: fire + fire + light)
-- `void` - Void 🕳️ (recipe: dark + dark + death)
-
-**Total unlockable mana types**: 12 (all except `transference` which starts unlocked)
-
-### 3. How to Add Per-Type Capacity Effects
-
-**Current Effect System Architecture**:
-
-1. **Effect Definition** (`EnchantmentEffectDef` in `/src/lib/game/data/enchantment-types.ts`):
- - Effects have a `stat` field that identifies what they modify
- - Stats like `maxMana`, `regen`, `clickMana`, `elementCap` are supported
- - Effects can be of type: `bonus` (additive), `multiplier`, or `special`
-
-2. **Current Element Cap System** (`/src/lib/game/upgrade-effects.ts` and `/src/lib/game/effects.ts`):
- - `elementCapBonus` - additive bonus in `ComputedEffects`
- - `elementCapMultiplier` - multiplier in `ComputedEffects`
- - Applied in `computeElementMax()` in `store.ts`:
- ```typescript
- export function computeElementMax(state, effects?): number {
- const pu = state.prestigeUpgrades;
- const base = 10 + (state.skills.elemAttune || 0) * 50 + (pu.elementalAttune || 0) * 25;
- if (effects) {
- return Math.floor((base + effects.elementCapBonus) * effects.elementCapMultiplier);
- }
- return base;
- }
- ```
-
-3. **Approach to Add Per-Type Capacity Effects**:
-
- **Option A: Extend the stat system with per-element prefixes**
- - Add new stats like `elementCap_fire`, `elementCap_water`, etc.
- - Modify `computeElementMax` to accept element parameter
- - Modify `computeAllEffects` to handle per-element bonuses
-
- **Option B: Use a single stat with element metadata (Recommended)**
- - Add effects with stat format: `elementCap_fire`, `elementCap_water`, etc.
- - In `computeEquipmentEffects`, parse the element from stat name
- - Store per-element bonuses in a `Record` map
- - Modify `computeElementMax` to accept element and look up per-element bonus
-
- **Option C: Add a new effect type for element-specific bonuses**
- - Add new effect structure: `{ type: 'elementBonus', element: string, stat: 'capacity', value: number }`
- - Requires modifying `EnchantmentEffectDef` type
-
-4. **Implementation Steps** (using Option B):
-
- a. **Define new enchantment effects** in `mana-effects.ts`:
- ```typescript
- fire_cap_10: {
- id: 'fire_cap_10',
- name: 'Fire Reservoir',
- description: '+10 Fire mana capacity',
- category: 'mana',
- baseCapacityCost: 25,
- maxStacks: 5,
- allowedEquipmentCategories: MANA_EQUIPMENT,
- effect: { type: 'bonus', stat: 'elementCap_fire', value: 10 }
- }
- // Repeat for each element: water, air, earth, light, dark, death, metal, sand, lightning, crystal, stellar, void
- ```
-
- b. **Update `ComputedEffects`** in `upgrade-effects.ts` to add per-element storage:
- ```typescript
- export interface ComputedEffects {
- // ... existing fields
- perElementCapBonus: Record; // New: per-element capacity bonuses
- }
- ```
-
- c. **Update `computeEquipmentEffects`** in `effects.ts` to parse element-specific stats:
- ```typescript
- // In the bonus processing:
- if (effect.stat.startsWith('elementCap_')) {
- const element = effect.stat.replace('elementCap_', '');
- bonuses.perElementCapBonus[element] = (bonuses.perElementCapBonus?.[element] || 0) + effect.value * ench.stacks;
- }
- ```
-
- d. **Update `computeElementMax`** in `store.ts` to use per-element bonuses:
- ```typescript
- export function computeElementMax(state, effects?, element?: string): number {
- const base = 10 + (state.skills.elemAttune || 0) * 50 + ...;
- if (effects) {
- let bonus = effects.elementCapBonus; // Global bonus
- if (element && effects.perElementCapBonus?.[element]) {
- bonus += effects.perElementCapBonus[element];
- }
- return Math.floor((base + bonus) * effects.elementCapMultiplier);
- }
- return base;
- }
- ```
-
- e. **Update `unlockElement`** in `store.ts` to check for per-element capacity bonuses when unlocking
-
-5. **Effect Unlocking**: New effects should be unlocked via:
- - `BASE_UNLOCKED_EFFECTS` - effects available from game start
- - `EFFECT_RESEARCH_MAPPING` - effects unlocked by leveling specific skills
- - `ENCHANTING_UNLOCK_EFFECTS` - effects unlocked by leveling enchanting skill
-
-### 4. Key Files to Modify
-
-1. `/src/lib/game/data/enchantments/mana-effects.ts` - Add per-element capacity effects
-2. `/src/lib/game/upgrade-effects.ts` - Update `ComputedEffects` interface, add per-element cap handling
-3. `/src/lib/game/effects.ts` - Update `computeEquipmentEffects` to parse element-specific stats
-4. `/src/lib/game/store.ts` - Update `computeElementMax` to accept element parameter and use per-element bonuses
-5. `/src/lib/game/constants/index.ts` or relevant constants file - Add new effects to unlockable lists if needed
-
-### 5. Allowed Equipment Categories
-
-For reference, from `enchantment-types.ts` and existing effects:
-- `MANA_EQUIPMENT: EquipmentCategory[] = ['caster', 'catalyst', 'head', 'body', 'accessory']`
-- Per-type capacity effects should likely use `MANA_EQUIPMENT` or similar
-
-### 6. Existing Related Special Effect
-
-`ELEMENTAL_AFFINITY` special effect (`SPECIAL_EFFECTS.ELEMENTAL_AFFINITY`):
-- When unlocking a new element, starts with 10 capacity instead of 0
-- This is already implemented in `unlockElement` in `store.ts`
diff --git a/docs/task5/subtask_16_context.md b/docs/task5/subtask_16_context.md
deleted file mode 100644
index 1f63ec1..0000000
--- a/docs/task5/subtask_16_context.md
+++ /dev/null
@@ -1,174 +0,0 @@
-# Task 16: Gate Mana Capacity Research Visibility by Unlocked Mana Type (PRIORITY 4b)
-
-## Context Summary
-
-### 1. How Research Nodes Are Currently Filtered/Displayed
-
-**Location:** `/home/user/repos/Mana-Loop/src/components/game/tabs/SkillsTab.tsx`
-
-**Current Filtering Mechanism:**
-- Skills are organized by categories defined in `SKILL_CATEGORIES` (from `/home/user/repos/Mana-Loop/src/lib/game/constants/skills.ts`)
-- The `SkillsTab` component uses `getAvailableSkillCategories(store.attunements || {})` to determine which skill categories to display
-- `getAvailableSkillCategories` (from `/home/user/repos/Mana-Loop/src/lib/game/data/attunements.ts`) returns categories based on active attunements:
- - Always available: `'mana'`, `'study'`, `'research'`, `'ascension'`
- - Enchanter attunement adds: `'enchant'`, `'effectResearch'`
- - Invoker attunement adds: `'invocation'`, `'pact'`
- - Fabricator attunement adds: `'fabrication'`, `'golemancy'`
- - Legacy: `'craft'`
-
-**Skill Display Logic (SkillsTab.tsx lines 200-220):**
-```typescript
-const availableCategories = getAvailableSkillCategories(store.attunements || {});
-return SKILL_CATEGORIES
- .filter(cat => availableCategories.includes(cat.id))
- .map((cat) => {
- const skillsInCat = Object.entries(SKILLS_DEF).filter(([, def]) => def.cat === cat.id);
- // ... render skills
- });
-```
-
-**Prerequisites Checking (SkillsTab.tsx lines 269-280):**
-- Skills check `def.req` (skill prerequisites)
-- Skills check `def.attunementReq` (attunement level requirements)
-- Skills with element costs check if player has enough element mana (but skill is still VISIBLE)
-
-**Key Point:** Mana capacity research skills (e.g., `fireManaCap`, `waterManaCap`) are currently ALWAYS visible in the "Mana" category (which is always available). They just show as "cannot study" if the player lacks the required element mana.
-
----
-
-### 2. Unlocked Mana Types State
-
-**Location:** `/home/user/repos/Mana-Loop/src/lib/game/store.ts` and `/home/user/repos/Mana-Loop/src/lib/game/types/elements.ts`
-
-**GameState Elements Structure:**
-```typescript
-elements: Record;
-```
-
-**ElementState Interface:**
-```typescript
-interface ElementState {
- current: number; // Current mana amount
- max: number; // Maximum capacity
- unlocked: boolean; // Whether this element type is unlocked
-}
-```
-
-**Base Unlocked Elements:**
-- Defined in `/home/user/repos/Mana-Loop/src/lib/game/constants/elements.ts`
-- `BASE_UNLOCKED_ELEMENTS = ['transference']` - Only transference is unlocked at game start
-
-**Element Unlocking Mechanisms:**
-1. **`unlockElement(element)` action** (store.ts line 2123): Costs 500 raw mana, sets `unlocked: true`
-2. **`craftComposite(target)` action**: Automatically unlocks composite elements when crafted
-3. **Enchanter attunement**: Auto-unlocks transference element (store.ts line 695)
-
-**All Element Types (from ELEMENTS constant):**
-- Base: `fire`, `water`, `air`, `earth`, `light`, `dark`, `death`
-- Utility: `transference`
-- Composite: `metal` (fire+earth), `sand` (earth+water), `lightning` (fire+air)
-- Exotic: `crystal` (sand+sand+light), `stellar` (fire+fire+light), `void` (dark+dark+death)
-
----
-
-### 3. Mana Capacity Research Skills
-
-**Location:** `/home/user/repos/Mana-Loop/src/lib/game/constants/skills.ts`
-
-**Mana Capacity Research Nodes (lines 9-21):**
-```typescript
-// Per-mana-type capacity upgrades (Bug 9)
-fireManaCap: { name: "Fire Mana Capacity +10%", desc: "...", cat: "mana", max: 10, base: 200, studyTime: 4, cost: { type: 'element', element: 'fire', amount: 100 } },
-waterManaCap: { name: "Water Mana Capacity +10%", cat: "mana", cost: { type: 'element', element: 'water', amount: 100 } },
-airManaCap: { name: "Air Mana Capacity +10%", cat: "mana", cost: { type: 'element', element: 'air', amount: 100 } },
-earthManaCap: { name: "Earth Mana Capacity +10%", cat: "mana", cost: { type: 'element', element: 'earth', amount: 100 } },
-lightManaCap: { name: "Light Mana Capacity +10%", cat: "mana", cost: { type: 'element', element: 'light', amount: 150 } },
-darkManaCap: { name: "Dark Mana Capacity +10%", cat: "mana", cost: { type: 'element', element: 'dark', amount: 150 } },
-deathManaCap: { name: "Death Mana Capacity +10%", cat: "mana", cost: { type: 'element', element: 'death', amount: 200 } },
-// Composite element capacity upgrades
-metalManaCap: { name: "Metal Mana Capacity +10%", cat: "mana", cost: { type: 'element', element: 'metal', amount: 250 } },
-sandManaCap: { name: "Sand Mana Capacity +10%", cat: "mana", cost: { type: 'element', element: 'sand', amount: 250 } },
-lightningManaCap: { name: "Lightning Mana Capacity +10%", cat: "mana", cost: { type: 'element', element: 'lightning', amount: 250 } },
-// Utility mana capacity upgrades
-transferenceManaCap: { name: "Transference Mana Capacity +10%", cat: "mana", cost: { type: 'element', element: 'transference', amount: 100 } },
-```
-
-**Key Observation:** Each mana capacity skill has:
-- `cost.type: 'element'`
-- `cost.element`: The element type this skill applies to
-- `cost.amount`: The element mana required to study
-
----
-
-### 4. How to Gate Capacity Research by Unlocked Mana Type
-
-**Objective:** Hide mana capacity research skills unless the corresponding element type is unlocked.
-
-**Implementation Approach:**
-
-1. **In SkillsTab.tsx, add filtering logic for mana capacity skills:**
-
- When rendering skills in the "mana" category, check if the skill:
- - Has `def.cost?.type === 'element'`
- - AND `store.elements[def.cost.element]?.unlocked === true`
-
-2. **Modified SkillsTab rendering (around line 220):**
- ```typescript
- {skillsInCat.map(([id, def]) => {
- // GATE MANA CAPACITY SKILLS BY UNLOCKED ELEMENT
- if (def.cost?.type === 'element') {
- const element = store.elements[def.cost.element];
- if (!element?.unlocked) {
- return null; // Don't render this skill
- }
- }
- // ... rest of skill rendering
- })}
- ```
-
-3. **Alternative: Filter at category level (less granular):**
- - Could filter `skillsInCat` before mapping:
- ```typescript
- const visibleSkills = skillsInCat.filter(([id, def]) => {
- if (def.cost?.type === 'element') {
- return store.elements[def.cost.element]?.unlocked;
- }
- return true; // Show non-element-cost skills
- });
- ```
-
-4. **Optional: Show locked state instead of hiding:**
- - Could show a "locked" badge or tooltip explaining the element needs to be unlocked first
- - This would require modifying the skill rendering to handle a "locked due to element not unlocked" state
-
----
-
-### 5. Files to Modify
-
-1. **`/home/user/repos/Mana-Loop/src/components/game/tabs/SkillsTab.tsx`**
- - Add filtering logic to hide mana capacity research skills when the corresponding element is not unlocked
- - Location: Around line 220 in the `skillsInCat.map()` function
-
-2. **No backend/store changes needed** - The `unlocked` state already exists in `store.elements`. This is purely a UI/display change.
-
----
-
-### 6. Testing Considerations
-
-- Test that `fireManaCap` is hidden when `store.elements['fire'].unlocked === false`
-- Test that `fireManaCap` becomes visible after calling `store.unlockElement('fire')`
-- Test that non-element-cost skills (like `manaWell`, `manaFlow`) are always visible
-- Test composite element skills (`metalManaCap`, etc.) hide/show correctly
-- Test that the "Mana" category still shows other non-gated skills even when some are hidden
-
----
-
-### 7. Related Task Context
-
-This task is related to:
-- **Task 9 (Bug 9)**: Per-mana-type capacity upgrades - the skills being gated were added in this task
-- **Task 12 (Bug 12)**: Research moved to Mana category - this is why capacity research is in the "mana" category
-
----
-
-**Summary:** The gating logic needs to be added to `SkillsTab.tsx` to filter out mana capacity research skills (`*ManaCap`) when `store.elements[element].unlocked` is `false`. The state already tracks unlocked elements, so no store changes are needed.
diff --git a/docs/task5/subtask_17_context.md b/docs/task5/subtask_17_context.md
deleted file mode 100644
index e52cf75..0000000
--- a/docs/task5/subtask_17_context.md
+++ /dev/null
@@ -1,111 +0,0 @@
-# Task 17: Fix Skill Requirement Display Bug (undefined Lv.[object Object])
-
-## Summary
-
-This bug causes skills to display "Requires: [Skill Name] Lv.[object Object]" instead of the proper level requirement. The root cause is that `cost` objects are incorrectly placed INSIDE the `req` (requirements) object in `skills.ts`, causing the rendering code to iterate over the cost object as if it were a skill requirement.
-
-## Root Cause
-
-In `/home/user/repos/Mana-Loop/src/lib/game/constants/skills.ts`, several skill entries have the `cost` property incorrectly nested inside the `req` object.
-
-### Correct Format (cost OUTSIDE req):
-```typescript
-researchFireSpells: {
- name: "Fire Spell Research",
- desc: "...",
- cat: "effectResearch",
- max: 1,
- base: 300,
- studyTime: 6,
- req: { enchanting: 2 },
- cost: { type: 'element', element: 'fire', amount: 100 },
- attunementReq: { enchanter: 1 }
-}
-```
-
-### Malformed Format (cost INSIDE req - BUG):
-```typescript
-researchLifeDeathSpells: {
- name: "Death Research",
- desc: "...",
- cat: "effectResearch",
- max: 1,
- base: 400,
- studyTime: 8,
- req: { enchanting: 3 , cost: { type: 'element', element: 'death', amount: 100 }}, // BUG: cost inside req
- attunementReq: { enchanter: 2 }
-}
-```
-
-## Malformed Skill Entries
-
-The following skills have `cost` incorrectly placed inside `req` (lines in skills.ts):
-
-1. **researchLifeDeathSpells** (line 51)
-2. **researchAdvancedFire** (line 54)
-3. **researchAdvancedWater** (line 55)
-4. **researchAdvancedAir** (line 56)
-5. **researchAdvancedEarth** (line 57)
-6. **researchAdvancedLight** (line 58)
-7. **researchAdvancedDark** (line 59)
-8. **researchMasterFire** (line 62)
-9. **researchMasterWater** (line 63)
-10. **researchMasterEarth** (line 64)
-11. **researchMetalSpells** (line 86)
-12. **researchSandSpells** (line 87)
-13. **researchLightningSpells** (line 88)
-14. **researchAdvancedMetal** (line 91)
-15. **researchAdvancedSand** (line 92)
-16. **researchAdvancedLightning** (line 93)
-17. **researchMasterMetal** (line 96)
-18. **researchMasterSand** (line 97)
-19. **researchMasterLightning** (line 98)
-
-Total: **19 malformed skill entries**
-
-## How Skill Requirements are Rendered
-
-In `/home/user/repos/Mana-Loop/src/components/game/tabs/SkillsTab.tsx` (lines 233-235):
-
-```tsx
-{!prereqMet && def.req && (
-
- Requires: {Object.entries(def.req).map(([r, rl]) => `${SKILLS_DEF[r]?.name} Lv.${rl}`).join(', ')}
-
-)}
-```
-
-The code expects `def.req` to be `Record` (skill ID -> required level). When `cost` is inside `req`, the iteration produces entries like:
-- `[ "enchanting", 3 ]` → "Enchanting Lv.3" ✓
-- `[ "cost", { type: 'element', ... } ]` → "undefined Lv.[object Object]" ✗
-
-## The Fix
-
-For each malformed entry in `skills.ts`, move the `cost` property OUT of the `req` object:
-
-**Before:**
-```typescript
-req: { enchanting: 3 , cost: { type: 'element', element: 'death', amount: 100 }}
-```
-
-**After:**
-```typescript
-req: { enchanting: 3 }, cost: { type: 'element', element: 'death', amount: 100 }
-```
-
-## Files to Modify
-
-1. `/home/user/repos/Mana-Loop/src/lib/game/constants/skills.ts` - Fix 19 malformed skill entries
-
-## No Changes Needed In
-
-- `/home/user/repos/Mana-Loop/src/components/game/tabs/SkillsTab.tsx` - Rendering code is correct
-- `/home/user/repos/Mana-Loop/src/lib/game/formatting.ts` - Not related to this bug
-- `/home/user/repos/Mana-Loop/src/lib/game/types/skills.ts` - Type definitions are correct
-
-## Steps to Fix
-
-1. Edit `/home/user/repos/Mana-Loop/src/lib/game/constants/skills.ts`
-2. For each of the 19 malformed entries, move `cost` from inside `req` to be a separate property
-3. Verify the fix by checking that `Object.entries(def.req)` only returns skill ID/level pairs
-4. Run typecheck and lint to confirm no errors
diff --git a/docs/task5/subtask_18_context.md b/docs/task5/subtask_18_context.md
deleted file mode 100644
index da4b004..0000000
--- a/docs/task5/subtask_18_context.md
+++ /dev/null
@@ -1,330 +0,0 @@
-# Subtask 18 Context: Enchantment Power Effect + Audit Stubs
-
-## Current Enchantment Power Effect Status
-
-### Definition Status
-- **"Enchantment Power" is NOT defined as an enchantment effect** in `src/lib/game/data/enchantments/` directory
-- The `ENCHANTMENT_EFFECTS` catalog (in `enchantment-effects.ts` and `enchantments/index.ts`) does not contain any effect with "Enchantment Power" as a defined effect
-- Searching for "Enchantment Power" in enchantment files returns no results
-
-### How Enchantment Power is Referenced
-The `enchantPower` stat IS referenced in **skill upgrade perks** in `src/lib/game/skill-evolution.ts`:
-
-| Perk ID | Name | Effect | Skill Tree |
-|---------|------|--------|------------|
-| `en_t1_l5_a` | Artisan's Touch | `+10% Enchantment Power` | Enchanting T1 |
-| `en_t1_l10_a` | Greater Artisan | `+15% Enchantment Power` | Enchanting T1 |
-| `en_t2_l5_a` | Expert Artisan | `+25% Enchantment Power` | Enchanting T2 |
-| `en_t2_l10_a` | Master Artisan | `+35% Enchantment Power` | Enchanting T2 |
-| `en_t3_l5_a` | Cosmic Artisan | `+50% Enchantment Power` | Enchanting T3 |
-| `en_t3_l10_a` | [ELITE] OMNI-ARTISAN | `2x enchantment power` | Enchanting T3 |
-| `en_t4_l5_a` | Astral Artisan | `+75% Enchantment Power` | Enchanting T4 |
-| `en_t4_l10_a` | Galactic Artisan | `+100% Enchantment Power` | Enchanting T4 |
-| `en_t5_l5_a` | Divine Artisan | `+150% Enchantment Power` | Enchanting T5 |
-| `en_t5_l10_a` | [ELITE] ASCENDED ARTISAN | `5x enchantment power` | Enchanting T5 |
-| `es_t1_l5_c` | Quick Work | `+5% Enchantment Power` | Essence Shaping T1 |
-| `es_t1_l10_c` | Superior Work | `+10% Enchantment Power` | Essence Shaping T1 |
-| `es_t2_l5_c` | Expert Work | `+15% Enchantment Power` | Essence Shaping T2 |
-| `es_t2_l10_c` | Master Work | `+20% Enchantment Power` | Essence Shaping T2 |
-| `es_t3_l5_c` | Divine Work | `+25% Enchantment Power` | Essence Shaping T3 |
-| `es_t3_l10_c` | [ELITE] OMNI-WORK | `2x enchantment power` | Essence Shaping T3 |
-| `ee_t1_l5_c` | Quality Work | `+5% Enchantment Power` | Elemental Evocation T1 |
-| `ee_t1_l10_c` | Superior Work | `+10% Enchantment Power` | Elemental Evocation T1 |
-| `ee_t2_l5_c` | Expert Work | `+15% Enchantment Power` | Elemental Evocation T2 |
-| `ee_t2_l10_c` | Master Work | `+20% Enchantment Power` | Elemental Evocation T2 |
-| `ee_t3_l5_c` | Divine Work | `+25% Enchantment Power` | Elemental Evocation T3 |
-| `ee_t3_l10_c` | [ELITE] OMNI-POWER | `2x enchantment power` | Elemental Evocation T3 |
-
-### Effect Type in Skill Evolution
-All these perks use the effect structure:
-```typescript
-{ type: 'multiplier', stat: 'enchantPower', value: }
-```
-
-### Problem: `enchantPower` Stat Not Handled in Effects System
-In `src/lib/game/upgrade-effects.ts`, the `computeEffects` function has a switch statement for multiplier effects (lines 295-320) but **does NOT have a case for `enchantPower`**:
-
-```typescript
-switch (effect.stat) {
- case 'maxMana':
- effects.maxManaMultiplier *= effect.value;
- break;
- case 'regen':
- effects.regenMultiplier *= effect.value;
- break;
- // ... other cases ...
- // NO CASE FOR 'enchantPower'!
-}
-```
-
-Similarly, the `ComputedEffects` interface (lines 16-48) does NOT include an `enchantPower` or `enchantmentPowerMultiplier` field.
-
-### StatsTab.tsx Attempts to Read `enchantPower`
-In `src/components/game/tabs/StatsTab.tsx` (lines 161-178), there's a placeholder that tries to read `enchantPower`:
-```typescript
-{upgradeEffects && 'enchantPower' in upgradeEffects
- ? `${(upgradeEffects as Record).enchantPower.toFixed(2)}×`
- : '1.0×'}
-```
-This is a type-safe workaround since `enchantPower` is not in the `ComputedEffects` interface.
-
----
-
-## Stub Location in EquipmentTab
-
-**File:** `/home/user/repos/Mana-Loop/src/components/game/tabs/EquipmentTab.tsx`
-
-**Lines 516-531:**
-```typescript
-{/* Enchantment Power (placeholder for Task 5) */}
-
-
-
- ✨ Enchantment Power
-
-
-
-
-
- Increases the power of all enchantments. Will be wired from Task 5 implementation.
-
-
-
-```
-
-**Exact stub text:** "Increases the power of all enchantments. Will be wired from Task 5 implementation."
-
-**Location:** Line 530 in EquipmentTab.tsx
-
----
-
-## Other Unwired Stubs
-
-### 1. AttunementsTab.tsx - Disenchanting TODO
-**File:** `/home/user/repos/Mana-Loop/src/components/game/tabs/AttunementsTab.tsx`
-**Line:** 198
-**Content:**
-```typescript
-{cap === 'disenchanting' && '🔄 Disenchant'} {/* TODO: Remove after bug 13 complete */}
-```
-**Description:** TODO comment indicating disenchanting cap should be removed after bug 13 is complete.
-
-### 2. AttunementsTab.tsx - Research TODO
-**File:** `/home/user/repos/Mana-Loop/src/components/game/tabs/AttunementsTab.tsx`
-**Line:** 249
-**Content:**
-```typescript
-{cat === 'research' && '🔮 Research'} {/* TODO: Remove after Bug 12 - research moved to mana */}
-```
-**Description:** TODO comment indicating research category should be removed after Bug 12 (research moved to mana).
-
-### 3. AttunementsTab.tsx.backup - Disenchanting TODO (backup file)
-**File:** `/home/user/repos/Mana-Loop/src/components/game/tabs/AttunementsTab.tsx.backup`
-**Line:** 198
-**Content:**
-```typescript
-{cap === 'disenchanting' && '🔄 Disenchant'} // TODO: Remove after bug 13 complete
-```
-**Description:** Same as #1 but in backup file (can likely be ignored).
-
-### 4. AttunementsTab.tsx.backup - Research TODO (backup file)
-**File:** `/home/user/repos/Mana-Loop/src/components/game/tabs/AttunementsTab.tsx.backup`
-**Line:** 249
-**Content:**
-```typescript
-{cat === 'research' && '🔮 Research'} // TODO: Remove after Bug 12 - research moved to mana
-```
-**Description:** Same as #2 but in backup file (can likely be ignored).
-
-### 5. skill-evolution.ts - Attunement Requirement Placeholder
-**File:** `/home/user/repos/Mana-Loop/src/lib/game/skill-evolution.ts`
-**Line:** 2218
-**Content:**
-```typescript
-// Check attunement requirement (placeholder - would need actual attunement check)
-```
-**Description:** Placeholder comment indicating attunement requirement check is not implemented.
-
-### 6. StatsTab.tsx - Enchantment Power (Wired but Non-functional)
-**File:** `/home/user/repos/Mana-Loop/src/components/game/tabs/StatsTab.tsx`
-**Lines:** 161-178
-**Content:**
-```typescript
-{/* Enchantment Power (placeholder for Task 5) */}
-
-
-
- ✨ Enchantment Power
-
-
-
-
- Enchantment Power:
-
- {upgradeEffects && 'enchantPower' in upgradeEffects
- ? `${(upgradeEffects as Record).enchantPower.toFixed(2)}×`
- : '1.0×'}
-
-
-
- Increases the power of all enchantments. Wired from Task 5 implementation.
-
-
-
-```
-**Description:** This is labeled "Wired from Task 5 implementation" but the underlying `enchantPower` stat is not actually computed in `upgrade-effects.ts`. The display uses a type cast workaround.
-
----
-
-## Effects System Overview
-
-### How Effects Are Applied
-
-The effects system is implemented across two main files:
-
-#### 1. `src/lib/game/upgrade-effects.ts` - Skill Upgrade Effects
-- **Interface:** `ComputedEffects` (lines 16-48)
-- **Function:** `computeEffects()` (lines 260-330)
-- Processes skill upgrade effects from `skillUpgrades` and `skillTiers`
-- Handles three effect types:
- - `multiplier` - multiplies a stat (e.g., `maxMana`, `regen`, `clickMana`)
- - `bonus` - adds to a stat (e.g., `maxMana`, `regen`, `baseDamage`)
- - `special` - adds a special effect ID to the `specials` set
-
-#### 2. `src/lib/game/effects.ts` - Unified Effects (Skill + Equipment)
-- **Function:** `computeEquipmentEffects()` (lines 20-78)
- - Processes equipped item enchantments
- - Returns `bonuses`, `multipliers`, and `specials`
- - For each enchantment:
- - `bonus` type: adds `effect.value * ench.stacks` to `bonuses[effect.stat]`
- - `multiplier` type: multiplies `multipliers[effect.stat]` by `effect.value` for each stack
- - `special` type: adds `effect.specialId` to `specials` set
-
-- **Function:** `computeAllEffects()` (lines 91-137)
- - Merges skill upgrade effects with equipment effects
- - Merging strategy:
- - Bonuses: ADD together
- - Multipliers: MULTIPLY together
- - Specials: UNION of sets
-
-### Where Enchantment Power Multiplier Would Go
-
-The `enchantmentPower` (or `enchantPower`) multiplier should:
-
-1. **Be added to `ComputedEffects` interface** in `upgrade-effects.ts`:
- ```typescript
- enchantmentPowerMultiplier: number; // defaults to 1
- ```
-
-2. **Be handled in `computeEffects()` switch statement** in `upgrade-effects.ts`:
- ```typescript
- case 'enchantPower':
- effects.enchantmentPowerMultiplier *= effect.value;
- break;
- ```
-
-3. **Be applied in `computeEquipmentEffects()` or `computeAllEffects()`** in `effects.ts`:
- - Option A: Apply to `effect.value` when processing each enchantment
- - Option B: Apply as an additional multiplier in `computeAllEffects()`
-
-The most logical place is in `computeEquipmentEffects()` where enchantment values are calculated:
-```typescript
-// When processing bonus effects:
-bonuses[effect.stat] = (bonuses[effect.stat] || 0) + effect.value * ench.stacks * enchantmentPowerMultiplier;
-
-// When processing multiplier effects:
-// Each stack applies the multiplier, also modified by enchantmentPowerMultiplier
-for (let i = 0; i < ench.stacks; i++) {
- multipliers[key] *= (effect.value - 1) * enchantmentPowerMultiplier + 1;
- // OR simpler: just multiply the final multiplier by enchantmentPowerMultiplier
-}
-```
-
-### Current Multiplier Application (from effects.ts lines 48-57):
-```typescript
-} else if (effect.type === 'multiplier' && effect.stat && effect.value) {
- // Multiplier effects multiply together
- const key = effect.stat;
- if (!multipliers[key]) {
- multipliers[key] = 1;
- }
- // Each stack applies the multiplier
- for (let i = 0; i < ench.stacks; i++) {
- multipliers[key] *= effect.value;
- }
-}
-```
-
-**Note:** The `enchantmentPower` multiplier would need to be passed into `computeEquipmentEffects()` or applied after the fact in `computeAllEffects()`.
-
----
-
-## Summary of Required Changes for Task 5 (Enchantment Power)
-
-1. Add `enchantmentPowerMultiplier` field to `ComputedEffects` interface
-2. Handle `enchantPower` stat in `computeEffects()` switch statement
-3. Pass `enchantmentPowerMultiplier` to `computeEquipmentEffects()` or apply in `computeAllEffects()`
-4. Update EquipmentTab.tsx stub to display actual value
-5. Update StatsTab.tsx to use proper type-safe access for `enchantmentPowerMultiplier`
-
----
-
-## Results (Task 18 Implementation)
-
-### Implemented Changes
-
-1. **Added `enchantmentPowerMultiplier` to `ComputedEffects` interface** in `src/lib/game/upgrade-effects.ts`:
- - Added field `enchantmentPowerMultiplier: number;` to the interface
- - Initialized to `1` in the `computeEffects()` function
-
-2. **Handled `enchantPower` stat in `computeEffects()` switch statement** in `src/lib/game/upgrade-effects.ts`:
- - Added case for `enchantPower` in the multiplier effects switch statement
- - Multiplier is applied as: `effects.enchantmentPowerMultiplier *= effect.value;`
-
-3. **Updated `computeEquipmentEffects()` to apply `enchantmentPowerMultiplier`** in `src/lib/game/effects.ts`:
- - Added optional parameter `enchantmentPowerMultiplier: number = 1.0`
- - Applied multiplier to both bonus and multiplier effect values:
- - `const adjustedValue = effect.value * enchantmentPowerMultiplier;`
- - Used `adjustedValue` instead of `effect.value` when computing enchantment effects
-
-4. **Updated `computeAllEffects()` to pass the multiplier** in `src/lib/game/effects.ts`:
- - Now passes `upgradeEffects.enchantmentPowerMultiplier` to `computeEquipmentEffects()`
-
-5. **Replaced stub in EquipmentTab.tsx**:
- - Changed from: "Increases the power of all enchantments. Will be wired from Task 5 implementation."
- - Changed to: "Increases the power of all enchantments by X%. Multiplier applied to all enchantment effects."
- - Now displays actual `enchantmentPowerMultiplier` value from `getUnifiedEffects(store)`
- - Added `getUnifiedEffects` import from `@/lib/game/effects`
-
-6. **Updated StatsTab.tsx to use type-safe access**:
- - Changed from type cast workaround: `(upgradeEffects as Record).enchantPower`
- - Changed to proper access: `upgradeEffects?.enchantmentPowerMultiplier`
- - Now displays the actual multiplier value and percentage
-
-### Audit of Other Unwired Stubs
-
-1. **AttunementsTab.tsx - Disenchanting TODO (Line 198)**: Logged in `docs/task5.md` under "Known Gaps" - waiting for Bug 13
-2. **AttunementsTab.tsx - Research TODO (Line 249)**: Logged in `docs/task5.md` under "Known Gaps" - waiting for Bug 12
-3. **AttunementsTab.tsx.backup - TODOs**: Backup file - ignored
-4. **skill-evolution.ts - Attunement Requirement Placeholder (Line 2218)**: Logged in `docs/task5.md` under "Known Gaps" - placeholder for future implementation
-5. **StatsTab.tsx - Enchantment Power**: ✅ Fixed (was already labeled "Wired" but now properly implemented)
-
-### Files Modified
-- `src/lib/game/upgrade-effects.ts` - Added `enchantmentPowerMultiplier` field and handler
-- `src/lib/game/effects.ts` - Updated `computeEquipmentEffects()` and `computeAllEffects()` to apply multiplier
-- `src/components/game/tabs/EquipmentTab.tsx` - Replaced stub, added import, displays actual value
-- `src/components/game/tabs/StatsTab.tsx` - Updated to use type-safe access for `enchantmentPowerMultiplier`
-- `docs/task5.md` - Added "Known Gaps" section documenting unwired stubs
-
-### Testing Notes
-- The `enchantmentPowerMultiplier` defaults to `1.0` (no effect)
-- Skill perks that use `{ type: 'multiplier', stat: 'enchantPower', value: }` will now properly affect enchantment power
-- All enchantment effect values (both bonus and multiplier types) are multiplied by `enchantmentPowerMultiplier`
-- The multiplier is applied per stack (each stack's effect value is multiplied)
-
-### Status
-✅ All tasks completed successfully
diff --git a/docs/task5/subtask_19_context.md b/docs/task5/subtask_19_context.md
deleted file mode 100644
index 18b44d1..0000000
--- a/docs/task5/subtask_19_context.md
+++ /dev/null
@@ -1,56 +0,0 @@
-# Task 19 (5a) Context: New Insight Upgrade Proposals
-
-## 1. Existing Insight Upgrades
-Sourced from `docs/GAME_BRIEFING.md` Prestige/Loop System section. These are permanent upgrades purchased with Insight when prestiging (looping), persisting across all subsequent loops.
-
-| Upgrade | Max Level | Cost (Insight) | Effect |
-|---------|-----------|----------------|--------|
-| Mana Well | 5 | 500 | +500 starting max mana |
-| Mana Flow | 10 | 750 | +0.5 permanent regen per level |
-| Deep Memory | 5 | 1000 | +1 memory slot per level |
-| Insight Amp | 4 | 1500 | +25% insight gain per level |
-| Spire Key | 5 | 4000 | Start at floor +2 per level |
-| Temporal Echo | 5 | 3000 | +10% mana generation per level |
-| Steady Hand | 5 | 1200 | -15% durability loss per level |
-| Ancient Knowledge | 5 | 2000 | Start with blueprint per level |
-| Elemental Attune | 10 | 600 | +25 element cap per level |
-| Spell Memory | 3 | 2500 | Start with random spell per level |
-| Guardian Pact | 5 | 3500 | +10% pact multiplier per level |
-| Quick Start | 3 | 400 | +100 starting mana per level |
-| Elem. Start | 3 | 800 | +5 each unlocked element per level |
-
-*Note: In-game core skills (e.g., `mana` category skills like Mana Well, Mana Flow) are distinct from these Prestige/Insight upgrades, which are purchased with Insight across loops.*
-
-## 2. Insight Upgrade Philosophy
-Insight upgrades are **permanent cross-loop advantages that accelerate early-loop ramp-up**. They are designed to:
-- Persist indefinitely across all loops (permanent)
-- Apply universally to every new loop (cross-loop)
-- Reduce the time/effort required to reach mid/late game in each subsequent loop (accelerate early-loop ramp-up)
-
-Examples of existing upgrades aligning with this philosophy:
-- `Quick Start`: Grants starting mana to immediately begin actions
-- `Elem. Start`: Unlocks elemental mana types earlier, bypassing early-game grinds
-- `Spire Key`: Skips low-level floors to reach higher content faster
-
-## 3. Specific Proposal Directions (Task 19 / 5a)
-New Insight upgrade proposals to expand early-loop acceleration options:
-
-1. **Unlocked mana type capacity**
- - Effect: Start with access to unlocked mana types + small capacity bonus
- - Goal: Reduce early-game mana type unlock grind, provide immediate elemental mana access
-
-2. **Reduced attunement conversion cost at loop start**
- - Effect: Lower raw mana cost for converting to elemental mana via attunements early in the loop
- - Goal: Speed up elemental mana accumulation in first few days of each loop
-
-3. **Start with N floors of Spire progress cleared**
- - Effect: Begin each loop with N floors of Spire progress already completed (similar to Spire Key, but more granular)
- - Goal: Skip repetitive early floor climbs, focus on higher content sooner
-
-4. **Guardian Pact memory**
- - Effect: Retain one pact bonus (from a previously signed Guardian) across loops
- - Goal: Maintain powerful Guardian boons without re-signing pacts every loop
-
-5. **Skill head-start**
- - Effect: Start each loop with 1 level in a chosen skill category (e.g., mana, study, enchant)
- - Goal: Reduce early-game skill grind, immediately access core skill bonuses
diff --git a/docs/task5/subtask_5_context.md b/docs/task5/subtask_5_context.md
deleted file mode 100644
index 82a5397..0000000
--- a/docs/task5/subtask_5_context.md
+++ /dev/null
@@ -1,602 +0,0 @@
-# Context: Task5 (2a Floor Rendering & Identity)
-
-## Floor Type Definitions
-
-### Room Types (from `src/lib/game/constants/rooms.ts` and `src/lib/game/types/game.ts`)
-```typescript
-// Room types for spire floors
-export type RoomType = 'combat' | 'puzzle' | 'swarm' | 'speed' | 'guardian';
-
-// Room generation rules:
-// - Guardian floors (10, 20, 30, etc.) are ALWAYS guardian type
-// - Every 5th floor (5, 15, 25, etc.) has a chance for special rooms
-// - Other floors are combat with chance for swarm/speed
-export const PUZZLE_ROOM_INTERVAL = 7; // Every 7 floors, chance for puzzle
-export const SWARM_ROOM_CHANCE = 0.15; // 15% chance for swarm room
-export const SPEED_ROOM_CHANCE = 0.10; // 10% chance for speed room
-export const PUZZLE_ROOM_CHANCE = 0.20; // 20% chance for puzzle room on puzzle floors
-```
-
-### Swarm Room Configuration
-```typescript
-// Swarm room configuration
-export const SWARM_CONFIG = {
- minEnemies: 3,
- maxEnemies: 6,
- hpMultiplier: 0.4, // Each enemy has 40% of normal floor HP
- armorBase: 0, // Swarm enemies start with no armor
- armorPerFloor: 0.01, // Gain 1% armor per 10 floors
-};
-```
-
-### Speed Room Configuration
-```typescript
-// Speed room configuration (dodging enemies)
-export const SPEED_ROOM_CONFIG = {
- baseDodgeChance: 0.25, // 25% base dodge chance
- dodgePerFloor: 0.005, // +0.5% dodge per floor
- maxDodge: 0.50, // Max 50% dodge
- speedBonus: 0.5, // 50% less time to complete if dodged
-};
-```
-
-### Floor Armor Configuration
-```typescript
-// Armor scaling for normal floors
-export const FLOOR_ARMOR_CONFIG = {
- baseChance: 0, // No armor on floor 1-9
- chancePerFloor: 0.01, // +1% chance per floor after 10
- maxArmorChance: 0.5, // Max 50% of floors have armor
- minArmor: 0.05, // Min 5% armor
- maxArmor: 0.25, // Max 25% armor on non-guardians
-};
-```
-
-### Puzzle Room Definitions
-```typescript
-// Puzzle room definitions - themed around attunements
-export const PUZZLE_ROOMS: Record = {
- enchanter_trial: {
- name: "Enchanter's Trial",
- attunements: ['enchanter'],
- baseProgressPerTick: 0.02,
- attunementBonus: 0.03,
- description: "Decipher ancient enchantment runes."
- },
- fabricator_trial: {
- name: "Fabricator's Trial",
- attunements: ['fabricator'],
- baseProgressPerTick: 0.02,
- attunementBonus: 0.03,
- description: "Construct a mana-powered mechanism."
- },
- invoker_trial: {
- name: "Invoker's Trial",
- attunements: ['invoker'],
- baseProgressPerTick: 0.02,
- attunementBonus: 0.03,
- description: "Commune with guardian spirits."
- },
- // ... hybrid rooms also defined
-};
-```
-
-### Guardian Definitions (from `src/lib/game/constants/guardians.ts`)
-```typescript
-// All guardians have armor - damage reduction percentage
-export const GUARDIANS: Record = {
- 10: {
- name: "Ignis Prime", element: "fire", hp: 5000, pact: 1.5, color: "#FF6B35",
- armor: 0.10, // 10% damage reduction
- boons: [
- { type: 'elementalDamage', value: 5, desc: '+5% Fire damage' },
- { type: 'maxMana', value: 50, desc: '+50 max mana' },
- ],
- pactCost: 500,
- pactTime: 2,
- uniquePerk: "Fire spells cast 10% faster"
- },
- 20: { name: "Aqua Regia", element: "water", hp: 15000, pact: 1.75, color: "#4ECDC4", armor: 0.15, ... },
- 30: { name: "Ventus Rex", element: "air", hp: 30000, pact: 2.0, color: "#00D4FF", armor: 0.18, ... },
- 40: { name: "Terra Firma", element: "earth", hp: 50000, pact: 2.25, color: "#F4A261", armor: 0.25, ... },
- 50: { name: "Lux Aeterna", element: "light", hp: 80000, pact: 2.5, color: "#FFD700", armor: 0.20, ... },
- 60: { name: "Umbra Mortis", element: "dark", hp: 120000, pact: 2.75, color: "#9B59B6", armor: 0.22, ... },
- 80: { name: "Mors Ultima", element: "death", hp: 250000, pact: 3.25, color: "#778CA3", armor: 0.25, ... },
- 90: { name: "Primordialis", element: "void", hp: 400000, pact: 4.0, color: "#4A235A", armor: 0.30, ... },
- 100: { name: "The Awakened One", element: "stellar", hp: 1000000, pact: 5.0, color: "#F0E68C", armor: 0.35, ... },
-};
-```
-
-### GuardianDef Type (from `src/lib/game/types/attunements.ts`)
-```typescript
-export interface GuardianDef {
- name: string;
- element: string;
- hp: number;
- pact: number; // Pact multiplier when signed
- color: string;
- boons: GuardianBoon[]; // Bonuses granted when pact is signed
- pactCost: number; // Mana cost to perform pact ritual
- pactTime: number; // Hours required for pact ritual
- uniquePerk: string; // Description of unique perk
- armor?: number; // Damage reduction (0-1, e.g., 0.2 = 20% reduction)
-}
-
-export interface GuardianBoon {
- type: 'maxMana' | 'manaRegen' | 'castingSpeed' | 'elementalDamage' | 'rawDamage' |
- 'critChance' | 'critDamage' | 'spellEfficiency' | 'manaGain' | 'insightGain' |
- 'studySpeed' | 'prestigeInsight';
- value: number;
- desc: string;
-}
-```
-
-### Element Definitions (from `src/lib/game/constants/elements.ts`)
-```typescript
-export const ELEMENTS: Record = {
- // Base Elements
- fire: { name: "Fire", sym: "🔥", color: "#FF6B35", glow: "#FF6B3540", cat: "base" },
- water: { name: "Water", sym: "💧", color: "#4ECDC4", glow: "#4ECDC440", cat: "base" },
- air: { name: "Air", sym: "🌬️", color: "#00D4FF", glow: "#00D4FF40", cat: "base" },
- earth: { name: "Earth", sym: "⛰️", color: "#F4A261", glow: "#F4A26140", cat: "base" },
- light: { name: "Light", sym: "☀️", color: "#FFD700", glow: "#FFD70040", cat: "base" },
- dark: { name: "Dark", sym: "🌑", color: "#9B59B6", glow: "#9B59B640", cat: "base" },
- death: { name: "Death", sym: "💀", color: "#778CA3", glow: "#778CA340", cat: "base" },
- // ... other elements
-};
-
-export const FLOOR_ELEM_CYCLE = ["fire", "water", "air", "earth", "light", "dark", "death"];
-```
-
-### Room Type Labels (from `src/lib/game/constants/index.ts`)
-```typescript
-export const ROOM_TYPE_LABELS: 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' },
-};
-```
-
----
-
-## Current Floor Rendering Code
-
-### SpireTab.tsx (from `src/components/game/tabs/SpireTab.tsx`)
-
-#### Room Type Display Configuration
-```typescript
-// Room type configurations for display
-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' },
-};
-```
-
-#### Floor Type Badge Rendering
-```tsx
-
- {roomConfig.icon} {roomConfig.label}
-
-```
-
-#### Guardian Name Display
-```tsx
-{isGuardianFloor && currentGuardian && (
-
- ⚔️ {currentGuardian.name}
-
-)}
-```
-
-#### Single Enemy Display (Combat/Speed/Guardian)
-```tsx
-{!isGuardianFloor && primaryEnemy && roomType !== 'swarm' && (
-
-
-
-
-
- {primaryEnemy.name || 'Unknown Enemy'}
-
-
-
- {ELEMENTS[primaryEnemy.element]?.sym} {ELEMENTS[primaryEnemy.element]?.name}
-
-
-
- {/* Enemy HP Bar */}
-
-
-
- {fmt(primaryEnemy.hp)} / {fmt(primaryEnemy.maxHP)} HP
-
-
-
- {/* Enemy Properties */}
-
- {primaryEnemy.armor > 0 && (
-
-
-
-
- {(primaryEnemy.armor * 100).toFixed(0)}% Armor
-
-
-
- Reduces incoming damage by {(primaryEnemy.armor * 100).toFixed(0)}%
-
-
- )}
- {primaryEnemy.dodgeChance > 0 && (
-
-
-
-
- {(primaryEnemy.dodgeChance * 100).toFixed(0)}% Dodge
-
-
-
- Chance to dodge attacks and reduce progress
-
-
- )}
-
-
-)}
-```
-
-#### Swarm Enemies Display
-```tsx
-{roomType === 'swarm' && swarmEnemies.length > 0 && (
-
-
- Swarm Enemies ({swarmEnemies.length})
-
- {swarmEnemies.map((enemy, index) => (
-
-
-
-
-
- {enemy.name || `Enemy ${index + 1}`}
-
-
-
- {ELEMENTS[enemy.element]?.sym} {fmt(enemy.hp)}/{fmt(enemy.maxHP)} HP
-
-
-
-
- ))}
-
-)}
-```
-
-#### Puzzle Room Display
-```tsx
-{roomType === 'puzzle' && (
-
-
- 🧩
-
- {currentRoom.puzzleId ? currentRoom.puzzleId.replace(/_/g, ' ').toUpperCase() : 'Puzzle Room'}
-
-
-
-
- Progress
- {((currentRoom.puzzleProgress || 0) * 100).toFixed(0)}%
-
-
-
-
-)}
-```
-
----
-
-## Enemy Naming Logic
-
-### Enemy Name Generation (from `src/lib/game/store.ts`)
-
-```typescript
-// Generate enemy names based on element and floor tier
-const ENEMY_NAMES_BY_ELEMENT: Record = {
- fire: ['Fire Imp', 'Flame Sprite', 'Emberling', 'Scorchling', 'Inferno Whelp'],
- water: ['Water Elemental', 'Tidal Wraith', 'Aqua Sprite', 'Drowned One', 'Tsunami Spawn'],
- air: ['Wind Sylph', 'Gale Rider', 'Storm Spirit', 'Zephyr Darter', 'Cyclone Wisp'],
- earth: ['Stone Golem', 'Earth Elemental', 'Graveling', 'Mountain Giant', 'Terra Brute'],
- light: ['Light Saint', 'Radiant Angel', 'Luminous Spirit', 'Divine Warden', 'Holy Sentinel'],
- dark: ['Shadow Assassin', 'Dark Cultist', 'Umbral Fiend', 'Void Walker', 'Night Stalker'],
- death: ['Skeleton Warrior', 'Zombie Lord', 'Lichling', 'Bone Reaper', 'Necrotic Wraith'],
- // Special element names
- lightning: ['Storm Elemental', 'Thunder Hawk', 'Lightning Eel', 'Shock Sprite', 'Voltaic Wisp'],
- metal: ['Iron Golem', 'Steel Guardian', 'Rust Monster', 'Chrome Beetle', 'Mercury Spirit'],
- sand: ['Sand Wraith', 'Dune Stalker', 'Desert Spirit', 'Cactus Thrasher', 'Mirage Runner'],
- crystal: ['Crystal Guardian', 'Prism Sprite', 'Gem Hound', 'Diamond Golem', 'Shardling'],
- stellar: ['Star Spawn', 'Cosmic Entity', 'Nova Spirit', 'Astral Watcher', 'Supernova Seed'],
- void: ['Void Lord', 'Abyssal Horror', 'Entropy Spawn', 'Chaos Elemental', 'Nether Beast'],
-};
-
-// Get enemy name based on element and floor tier (1-100)
-export function getEnemyName(element: string, floor: number): string {
- const names = ENEMY_NAMES_BY_ELEMENT[element] || ['Unknown Entity'];
- // Higher floors get "stronger" sounding names (pick from later in the list)
- const tierIndex = Math.min(names.length - 1, Math.floor(floor / 20));
- const randomIndex = (tierIndex + Math.floor(Math.random() * (names.length - tierIndex))) % names.length;
- return names[randomIndex!];
-}
-```
-
-### Enemy State Type (from `src/lib/game/types/game.ts`)
-```typescript
-export interface EnemyState {
- id: string;
- name: string; // Display name for the enemy
- hp: number;
- maxHP: number;
- armor: number; // Damage reduction (0-1)
- dodgeChance: number; // For speed rooms (0-1)
- element: string;
-}
-```
-
-### Floor State Type
-```typescript
-export interface FloorState {
- roomType: RoomType;
- enemies: EnemyState[]; // For swarm rooms, multiple enemies
- puzzleProgress?: number; // For puzzle rooms (0-1)
- puzzleRequired?: number; // Total progress needed
- puzzleId?: string; // Which puzzle type
- puzzleAttunements?: string[]; // Which attunements speed up this puzzle
-}
-```
-
-### Floor Generation Functions (from `src/lib/game/store.ts`)
-
-```typescript
-// Generate room type for a floor
-export function generateRoomType(floor: number): RoomType {
- // Guardian floors are always guardian type
- if (GUARDIANS[floor]) {
- return 'guardian';
- }
-
- // Check for puzzle room (every PUZZLE_ROOM_INTERVAL floors)
- if (floor % PUZZLE_ROOM_INTERVAL === 0 && Math.random() < PUZZLE_ROOM_CHANCE) {
- return 'puzzle';
- }
-
- // Check for swarm room
- if (Math.random() < SWARM_ROOM_CHANCE) {
- return 'swarm';
- }
-
- // Check for speed room
- if (Math.random() < SPEED_ROOM_CHANCE) {
- return 'speed';
- }
-
- // Default to combat
- return 'combat';
-}
-
-// Get armor for a non-guardian floor
-export function getFloorArmor(floor: number): number {
- if (GUARDIANS[floor]) {
- return GUARDIANS[floor].armor || 0;
- }
-
- // Armor becomes more common on higher floors
- if (floor < 10) return 0;
-
- const armorChance = Math.min(FLOOR_ARMOR_CONFIG.maxArmorChance,
- FLOOR_ARMOR_CONFIG.baseChance + (floor - 10) * FLOOR_ARMOR_CONFIG.chancePerFloor);
-
- if (Math.random() > armorChance) return 0;
-
- // Scale armor with floor
- const armorRange = FLOOR_ARMOR_CONFIG.maxArmor - FLOOR_ARMOR_CONFIG.minArmor;
- const floorProgress = Math.min(1, (floor - 10) / 90);
- return FLOOR_ARMOR_CONFIG.minArmor + armorRange * floorProgress * Math.random();
-}
-
-// Get dodge chance for a speed room
-export function getDodgeChance(floor: number): number {
- return Math.min(
- SPEED_ROOM_CONFIG.maxDodge,
- SPEED_ROOM_CONFIG.baseDodgeChance + floor * SPEED_ROOM_CONFIG.dodgePerFloor
- );
-}
-
-// Generate enemies for a swarm room
-export function generateSwarmEnemies(floor: number): EnemyState[] {
- const baseHP = getFloorMaxHP(floor);
- const element = getFloorElement(floor);
- const numEnemies = SWARM_CONFIG.minEnemies +
- Math.floor(Math.random() * (SWARM_CONFIG.maxEnemies - SWARM_CONFIG.minEnemies + 1));
-
- const enemies: EnemyState[] = [];
- for (let i = 0; i < numEnemies; i++) {
- const enemyName = getEnemyName(element, floor);
- enemies.push({
- id: `enemy_${i}`,
- name: enemyName,
- hp: Math.floor(baseHP * SWARM_CONFIG.hpMultiplier),
- maxHP: Math.floor(baseHP * SWARM_CONFIG.hpMultiplier),
- armor: SWARM_CONFIG.armorBase + Math.floor(floor / 10) * SWARM_CONFIG.armorPerFloor,
- dodgeChance: 0,
- element,
- });
- }
- return enemies;
-}
-
-// Generate initial floor state
-export function generateFloorState(floor: number): FloorState {
- const roomType = generateRoomType(floor);
- const element = getFloorElement(floor);
- const baseHP = getFloorMaxHP(floor);
- const guardian = GUARDIANS[floor];
-
- switch (roomType) {
- case 'guardian':
- return {
- roomType: 'guardian',
- enemies: [{
- id: 'guardian',
- name: guardian.name,
- hp: guardian.hp,
- maxHP: guardian.hp,
- armor: guardian.armor || 0,
- dodgeChance: 0,
- element: guardian.element,
- }],
- };
-
- case 'swarm':
- return {
- roomType: 'swarm',
- enemies: generateSwarmEnemies(floor),
- };
-
- case 'speed': {
- const speedEnemyName = getEnemyName(element, floor);
- return {
- roomType: 'speed',
- enemies: [{
- id: 'speed_enemy',
- name: speedEnemyName,
- hp: baseHP,
- maxHP: baseHP,
- armor: getFloorArmor(floor),
- dodgeChance: getDodgeChance(floor),
- element,
- }],
- };
- }
-
- case 'puzzle': {
- // Select a puzzle type based on player's attunements
- const puzzleKeys = Object.keys(PUZZLE_ROOMS);
- const selectedPuzzle = puzzleKeys[Math.floor(Math.random() * puzzleKeys.length)];
- const puzzle = PUZZLE_ROOMS[selectedPuzzle];
- return {
- roomType: 'puzzle',
- enemies: [],
- puzzleProgress: 0,
- puzzleRequired: 1,
- puzzleId: selectedPuzzle,
- puzzleAttunements: puzzle.attunements,
- };
- }
-
- default: // combat
- const combatEnemyName = getEnemyName(element, floor);
- return {
- roomType: 'combat',
- enemies: [{
- id: 'enemy',
- name: combatEnemyName,
- hp: baseHP,
- maxHP: baseHP,
- armor: getFloorArmor(floor),
- dodgeChance: 0,
- element,
- }],
- };
- }
-}
-```
-
----
-
-## Special Floor Properties
-
-### Currently Implemented Properties
-
-#### Armor (Damage Reduction)
-- **Guardian floors**: Defined in `GUARDIANS[floor].armor` (0.10 to 0.35)
-- **Non-guardian floors**: Randomly generated via `getFloorArmor(floor)` using `FLOOR_ARMOR_CONFIG`
-- **Swarm enemies**: `SWARM_CONFIG.armorBase + Math.floor(floor / 10) * SWARM_CONFIG.armorPerFloor`
-- Displayed in UI with shield icon and percentage
-
-#### Dodge Chance
-- **Speed rooms only**: Generated via `getDodgeChance(floor)` using `SPEED_ROOM_CONFIG`
-- Base: 25%, scales +0.5% per floor, max 50%
-- Displayed in UI with wind icon and percentage
-
-#### Health/HP
-- **Guardian floors**: `GUARDIANS[floor].hp` (5000 to 1000000)
-- **Normal floors**: `getFloorMaxHP(floor)` - scales with floor number
-- **Swarm enemies**: `baseHP * SWARM_CONFIG.hpMultiplier` (40% of normal)
-
-### Properties Mentioned in Task But Not Currently in Floor Config
-- **healthRegen**: Not currently implemented as a floor/enemy property (only exists in guardian boons as `manaRegen` for player)
-- **barrier**: Not currently implemented as a floor/enemy property (only exists as attunement mana type)
-
-Note: The task mentions displaying "Special floor properties (armor%, health regen, barrier, dodge)" but `healthRegen` and `barrier` are not currently implemented in the floor config. These may need to be added as part of this task.
-
----
-
-## File Paths
-
-### Key Files for Task 5 (2a Floor Rendering & Identity)
-
-1. **Floor/Room Type Definitions**:
- - `/home/user/repos/Mana-Loop/src/lib/game/constants/rooms.ts` - Room types, swarm/speed config, armor config
- - `/home/user/repos/Mana-Loop/src/lib/game/constants/guardians.ts` - Guardian definitions with names, HP, armor
- - `/home/user/repos/Mana-Loop/src/lib/game/constants/elements.ts` - Element definitions with symbols and colors
- - `/home/user/repos/Mana-Loop/src/lib/game/constants/index.ts` - ROOM_TYPE_LABELS export
-
-2. **Type Definitions**:
- - `/home/user/repos/Mana-Loop/src/lib/game/types/game.ts` - RoomType, EnemyState, FloorState interfaces
- - `/home/user/repos/Mana-Loop/src/lib/game/types/attunements.ts` - GuardianDef, GuardianBoon interfaces
-
-3. **Floor Rendering UI**:
- - `/home/user/repos/Mana-Loop/src/components/game/tabs/SpireTab.tsx` - Main floor rendering component with enemy display, room type badges, armor/dodge tooltips
-
-4. **Floor Generation Logic**:
- - `/home/user/repos/Mana-Loop/src/lib/game/store.ts` - `getEnemyName()`, `generateRoomType()`, `generateFloorState()`, `getFloorArmor()`, `getDodgeChance()`, `generateSwarmEnemies()`
-
-5. **Element Cycle for Floors**:
- - `/home/user/repos/Mana-Loop/src/lib/game/store.ts` - `getFloorElement()`, `getFloorMaxHP()`
- - `/home/user/repos/Mana-Loop/src/lib/game/constants/elements.ts` - `FLOOR_ELEM_CYCLE`
diff --git a/docs/task5/subtask_6_context.md b/docs/task5/subtask_6_context.md
deleted file mode 100644
index 3609fdd..0000000
--- a/docs/task5/subtask_6_context.md
+++ /dev/null
@@ -1,19 +0,0 @@
-# Context: Task 6 (Insight Proposal Revision)
-## Current Proposal File
-`docs/task5_insight_proposals.md` (95 lines, read earlier)
-
-## User Feedback (Accepted/Rejected)
-1. **Proposal 1 (Unlocked Mana Type Capacity)**: Accepted
- - Revision: Only 1 mana type per purchase (not instant access to all previously unlocked types)
-2. **Proposal 2 (Attunement Efficiency)**: Accepted as-is
-3. **Proposal 3 (Spire Progress Retention)**: Accepted as-is
-4. **Proposal 4 (Guardian Pact Memory)**: Accepted
- - Revision: Retain the *entire* chosen guardian pact (not reduced strength)
-5. **Proposal 5 (Skill Head-Start)**: Rejected, remove from proposal
-
-## Revision Requirements
-- Update Proposal 1 effect: "Start each loop with +10 base capacity for 1 selected mana type per level, unlocked type selectable during prestige"
-- Update Proposal 4 effect: "Retain entire chosen Guardian pact bonus across loops, no re-signing required"
-- Remove Proposal 5 entirely
-- Keep Proposal 2 & 3 unchanged
-- Maintain original document structure (themes, tables, implementation notes)
diff --git a/docs/task5/subtask_9_context.md b/docs/task5/subtask_9_context.md
deleted file mode 100644
index efd9a15..0000000
--- a/docs/task5/subtask_9_context.md
+++ /dev/null
@@ -1,124 +0,0 @@
-# Task 9: Fix Climb/Descend Controls - Context Summary
-
-**Status:** Partially done (spam prevention and re-entry resume implemented, button rename incomplete)
-
-## 1. Climbing/Descending State in store.ts
-
-### State Variables (lines 820, 2260):
-- `spireMode: boolean` - Whether player is in Spire Mode
-- `clearedFloors: Record` - Tracks cleared floors for respawning
-- `climbDirection: 'up' | 'down' | null` - Current climb direction (persisted for re-entry)
-- `isDescending: boolean` - True when actively descending (prevents spam clicking)
-
-### Key Actions:
-- `enterSpireMode()` (line 2253): Sets `spireMode: true`, `currentAction: 'climb'`, `isDescending: false`
-- `climbDownFloor()` (line 2267):
- - Checks `isDescending` to prevent spam
- - Decrements floor by 1 (min floor 1)
- - Sets `isDescending: true` during descent
- - Uses `setTimeout` to reset `isDescending: false` after 500ms
- - Clears/resets floor state in `clearedFloors`
-- `exitSpireMode()` (line 2311): Sets `spireMode: false`, `currentAction: 'meditate'`, `isDescending: false`
-
-### Spam Prevention (COMPLETED):
-- `isDescending` flag prevents multiple rapid clicks
-- Button is disabled when `isDescending` is true
-- 500ms timeout resets the flag after descent completes
-
-### Re-entry Resume (COMPLETED):
-- `climbDirection` is persisted in state
-- `enterSpireMode()` resumes from `climbDirection` state
-- `exitSpireMode()` allows exit at any floor, re-entry resumes at same floor
-
-## 2. Buttons in SpireTab Components and page.tsx
-
-### src/app/page.tsx (Spire Mode UI) - Lines 258-278:
-
-**Climbing Indicator Badge (line 263-266):**
-```tsx
-{store.currentAction === 'climb' && !store.isDescending && (
-
- Climbing
-
-)}
-```
-
-**Descend/Climb Button (lines 267-278):**
-```tsx
- store.climbDownFloor()}
- disabled={store.isDescending}
->
-
- {store.isDescending ? 'Descending…' :
- store.currentAction === 'climb' ? 'Climbing' :
- 'Begin Descent'}
-
-```
-
-**Current Button Label Logic:**
-- When `isDescending` is true: Shows "Descending…" (with ellipsis) and button is disabled
-- When `currentAction === 'climb'`: Shows "Climbing"
-- Otherwise: Shows "Begin Descent"
-
-**Enter Spire Mode Button (lines 211-221):**
-```tsx
- store.enterSpireMode()}
- disabled={!canEnterSpireMode(store)}
->
-
- Climb the Spire
-
-```
-
-### src/components/game/tabs/SpireTab.tsx:
-- **No climb/descend buttons** - This component only displays floor info, spells, golems, and activity log
-- Has "Enter Spire Mode" button (line 76) with label "Enter Spire Mode" (for non-Spire Mode view)
-- Displays floor information, active spells, golems, and activity log in `simpleMode={true}`
-
-## 3. What Needs to Change for Button Rename
-
-**Requirement:** idle: 'Begin Descent', descending: 'Descending' disabled, climbing: 'Climbing'
-
-**Current Issues:**
-
-1. **"Descending…" vs "Descending"**: The button shows "Descending…" (with ellipsis) when descending, but requirement says "Descending" (without ellipsis)
-
-2. **Button label when climbing**: The current logic shows "Climbing" when `currentAction === 'climb'`, but this is confusing because:
- - The button's action is to descend (calls `climbDownFloor()`)
- - The "Climbing" Badge already serves as a separate indicator
- - When climbing, users may want to descend, so the button should probably say "Begin Descent"
-
-3. **Possible correct implementation:**
- - Remove the `currentAction === 'climb'` check from button label
- - Button should always show "Begin Descent" when not descending
- - Button shows "Descending" (disabled) when descending
- - Keep the separate "Climbing" Badge as a status indicator
-
-**Suggested Button Code Fix (in page.tsx, lines 274-277):**
-```tsx
-{store.isDescending ? 'Descending' : 'Begin Descent'}
-```
-(Remove the `store.currentAction === 'climb' ? 'Climbing' : 'Begin Descent'` part)
-
-## 4. Summary of Files to Modify
-
-| File | Change Needed |
-|------|---------------|
-| `src/app/page.tsx` | Fix button label logic (lines 274-277) to match requirements |
-| `src/lib/game/store.ts` | No changes needed (spam prevention and re-entry resume implemented) |
-| `src/components/game/tabs/SpireTab.tsx` | No changes needed (no climb/descend buttons) |
-
-## 5. Verification Steps
-
-After making changes:
-1. Test spam prevention: Rapidly click descend button - should only descend once per 500ms
-2. Test re-entry resume: Exit Spire Mode at floor X, re-enter - should resume at floor X
-3. Test button labels:
- - Idle (not climbing, not descending): Shows "Begin Descent"
- - Descending: Shows "Descending" (disabled)
- - Climbing: The separate Badge shows "Climbing", button shows "Begin Descent"
diff --git a/docs/task5_insight_proposals.md b/docs/task5_insight_proposals.md
deleted file mode 100644
index c435214..0000000
--- a/docs/task5_insight_proposals.md
+++ /dev/null
@@ -1,95 +0,0 @@
-# Task 5: Insight Upgrade Proposals
-
-## Overview
-These proposals expand the existing Insight upgrade tree with permanent cross-loop advantages that accelerate early-loop ramp-up, aligned with the prestige philosophy defined in `docs/GAME_BRIEFING.md`. Proposals are grouped by thematic category, each with a rationale explaining alignment with core philosophy and gaps addressed in the existing upgrade tree (referenced below).
-
-### Existing Insight Upgrades (Reference)
-| Upgrade | Max Level | Cost (Insight/Level) | Effect |
-|---------|-----------|------------------------|--------|
-| Mana Well | 5 | 500 | +500 starting max mana |
-| Mana Flow | 10 | 750 | +0.5 permanent regen per level |
-| Deep Memory | 5 | 1000 | +1 memory slot per level |
-| Insight Amp | 4 | 1500 | +25% insight gain per level |
-| Spire Key | 5 | 4000 | Start at floor +2 per level |
-| Temporal Echo | 5 | 3000 | +10% mana generation per level |
-| Steady Hand | 5 | 1200 | -15% durability loss per level |
-| Ancient Knowledge | 5 | 2000 | Start with blueprint per level |
-| Elemental Attune | 10 | 600 | +25 element cap per level |
-| Spell Memory | 3 | 2500 | Start with random spell per level |
-| Guardian Pact | 5 | 3500 | +10% pact multiplier per level |
-| Quick Start | 3 | 400 | +100 starting mana per level |
-| Elem. Start | 3 | 800 | +5 each unlocked element per level |
-
----
-
-## Theme 1: Mana & Elemental Acceleration
-### Rationale
-Existing upgrades in this category (Mana Well, Mana Flow, Elemental Attune, Elem. Start, Temporal Echo) focus on boosting mana generation and elemental access, but leave two critical gaps: (1) no permanent bonus to base capacity for unlocked mana types, and (2) no reduction to attunement conversion costs. These proposals fill those gaps to further reduce early-loop grind for elemental mana setup, a core bottleneck in first-loop progression.
-
-### Proposal 1: Unlocked Mana Type Capacity
-- **Max Level**: 5
-- **Cost**: 1000 Insight per level
-- **Effect**: Start each loop with +10 base capacity per unlocked mana type per level, plus immediate access to all mana types unlocked in any previous loop (no re-unlocking required).
-- **Gap Addressed**: Builds on `Elem. Start` (which grants +5 element cap per level) by adding permanent capacity for all historically unlocked types, eliminating repetitive base capacity grinding for each elemental mana type in early loop.
-- **Implementation Notes**:
- - Track unlocked mana types across loops in cross-loop save data (`player.insight.unlockedManaTypes`)
- - Apply capacity bonus on loop initialization via `LoopManager.applyInsightUpgrades()`
- - Respect existing elemental cap limits from `Elemental Attune` upgrades
-
-### Proposal 2: Attunement Efficiency
-- **Max Level**: 5
-- **Cost**: 800 Insight per level
-- **Effect**: Reduce raw mana cost for elemental mana conversion (attunement) by 5% per level for the first 3 in-loop days.
-- **Gap Addressed**: Existing `Temporal Echo` boosts mana generation but not conversion efficiency. This directly accelerates early elemental mana accumulation, a key bottleneck in first 48 hours of each loop.
-- **Implementation Notes**:
- - Apply cost reduction modifier to `AttunementStation` component for 72 in-loop hours
- - Reset modifier automatically after 3-day window
- - Stack with `Temporal Echo` generation bonuses
-
----
-
-## Theme 2: Spire Progression Acceleration
-### Rationale
-Existing `Spire Key` (start at +2 floors per level) enables batch-skipping of low-level Spire floors, but lacks granular progression retention. This theme adds finer-grained Spire skip options to let players incrementally bypass repetitive early floors without over-skipping content, aligning with early-loop ramp-up goals.
-
-### Proposal 3: Spire Progress Retention
-- **Max Level**: 10
-- **Cost**: 500 Insight per level
-- **Effect**: Start each loop with 1 additional Spire floor cleared per level (stacks additively with `Spire Key` bonuses).
-- **Gap Addressed**: `Spire Key` only offers +2 floors per level, leaving no option for 1-floor increments. This allows players to tailor skip amount to their progression speed.
-- **Implementation Notes**:
- - Store highest Spire floor reached per loop in `player.insight.highestSpireFloor`
- - Apply cleared floors on new loop initialization via `SpireManager.syncProgress()`
- - Cap total skipped floors at 50 (to prevent over-skipping endgame content)
-
----
-
-## Theme 3: Guardian & Pact Continuity
-### Rationale
-Existing `Guardian Pact` boosts pact multipliers per level, but requires players to re-sign Guardian pacts each loop to access boons. This theme adds permanent pact memory to retain critical boons across loops, eliminating repetitive pact signing grind.
-
-### Proposal 4: Guardian Pact Memory
-- **Max Level**: 3
-- **Cost**: 3000 Insight per level
-- **Effect**: Retain 1 Guardian pact bonus (selected from previous loop's signed pacts) per level across loops, no re-signing required.
-- **Gap Addressed**: `Guardian Pact` only boosts multiplier strength, not retention. This lets players keep high-value boons (e.g., resource generation, damage resistance) permanently.
-- **Implementation Notes**:
- - Track signed Guardian pacts per loop in `player.insight.signedGuardians`
- - Add pact selection UI to prestige screen (`PrestigeModal.tsx`)
- - Apply retained bonuses on loop start via `GuardianManager.applyRetainedPacts()`
-
----
-
-## Theme 4: Skill & Knowledge Acceleration
-### Rationale
-Existing upgrades like `Ancient Knowledge` (blueprints), `Spell Memory` (spells), and `Quick Start` (starting mana) accelerate early knowledge/skill access, but leave a gap in core skill category progression. This theme adds skill head-start options to reduce early-game grind for core skill bonuses.
-
-### Proposal 5: Skill Head-Start
-- **Max Level**: 5
-- **Cost**: 600 Insight per level
-- **Effect**: Start each loop with 1 level in a chosen core skill category (mana, study, enchant, combat) per level, selectable during prestige.
-- **Gap Addressed**: No existing upgrade boosts core skill levels at loop start. Builds on `Quick Start`'s immediate resource boost by adding skill progression acceleration.
-- **Implementation Notes**:
- - Add skill category selector to prestige UI (`PrestigeModal.tsx`)
- - Apply skill levels on loop initialization via `SkillManager.addLevels()`
- - Respect per-skill max level caps defined in `skillData.ts`
diff --git a/docs/task6.md b/docs/task6.md
deleted file mode 100644
index 3a42407..0000000
--- a/docs/task6.md
+++ /dev/null
@@ -1,38 +0,0 @@
-# Task 6 — Insight Upgrade Implementation (Approved Proposals)
-
-## Status Overview
-- **Start Date**: 2025-05-20
-- **Current Phase**: Initialization
-- **Overall Progress**: 0% complete (0/4 tasks done)
-
----
-
-## Task List (Approved Proposals Only)
-| ID | Proposal | Status | Notes |
-|----|-----------|--------|-------|
-| 1 | Unlocked Mana Type Capacity (1 type/purchase, +10 cap/level) | ✅ Completed | Implemented with 1 type selection per purchase |
-| 2 | Attunement Efficiency (5% cost reduction/level, first 3 days) | ⏳ Partially done | Check failed (context overflow) |
-| 3 | Spire Progress Retention (+1 floor/level, stack with Spire Key) | ⏳ Partially done | Check failed (context overflow) |
-| 4 | Guardian Pact Memory (retain entire chosen pact) | Pending | Modified per user: retain full pact |
-
----
-
-## Proposal Modifications (User Instructions)
-1. **Proposal 1**: Only 1 mana type per purchase (not all previously unlocked types)
- - Track chosen mana type per purchase
- - Grant +10 base capacity for that specific type per level
-2. **Proposal 4**: Retain entire chosen guardian pact (not 1 bonus per level)
- - Player selects 1 guardian pact to retain fully across loops
- - All pact bonuses apply automatically on loop start
-
----
-
-## Rejected Proposals
-- Proposal 5: Skill Head-Start (explicitly rejected)
-
----
-
-## Workflow Log
-- ✅ Task 6 initialized with 4 approved proposals
-- ✅ Proposal 1 & 4 modified per user instructions
-- ⏳ Next: Begin pipeline for Task6-1 (Mana Type Capacity)
diff --git a/docs/task6/subtask_1_context.md b/docs/task6/subtask_1_context.md
deleted file mode 100644
index b9e782b..0000000
--- a/docs/task6/subtask_1_context.md
+++ /dev/null
@@ -1,312 +0,0 @@
-# Task6-1 Context: Proposal 1 - Unlocked Mana Type Capacity
-
-## Overview
-Gathered context for implementing Proposal 1: Unlocked Mana Type Capacity. This proposal likely relates to either:
-1. Increasing the mana capacity (max) of unlocked mana types
-2. Increasing the number of mana types that can be unlocked
-
-## 1. Existing Prestige Upgrade Structure
-
-**File:** `src/lib/game/constants/prestige.ts`
-
-### PrestigeDef Interface (from `src/lib/game/types/skills.ts`)
-```typescript
-export interface PrestigeDef {
- name: string;
- desc: string;
- max: number;
- cost: number;
-}
-```
-
-### Current Prestige Upgrades (relevant to mana/elements)
-```typescript
-export const PRESTIGE_DEF: Record = {
- // ... other upgrades ...
-
- // Existing elemental capacity upgrade
- elementalAttune: {
- name: "Elemental Attunement",
- desc: "+25 elemental mana cap",
- max: 10,
- cost: 600
- },
-
- // Starting elemental mana upgrade
- elemStart: {
- name: "Elem. Start",
- desc: "Start with 5 of each unlocked element",
- max: 3,
- cost: 800
- },
-
- // ... other upgrades ...
-};
-```
-
-**Key observations:**
-- All prestige upgrades use the same structure: name, description, max level, cost
-- `elementalAttune` already provides +25 elemental mana cap per level (max 10 levels = +250 cap)
-- `elemStart` gives starting elemental mana when a new loop begins
-- Upgrades are stored as `Record` where the value is the current level
-
----
-
-## 2. How Mana Type Unlocks Are Tracked
-
-### Element State Structure
-**File:** `src/lib/game/types/elements.ts`
-```typescript
-export interface ElementState {
- current: number;
- max: number;
- unlocked: boolean;
-}
-```
-
-### Elements Storage in GameState
-**File:** `src/lib/game/types/game.ts`
-```typescript
-export interface GameState {
- // ...
- elements: Record;
- // ...
-}
-```
-
-### Base Unlocked Elements
-**File:** `src/lib/game/constants/elements.ts`
-```typescript
-export const BASE_UNLOCKED_ELEMENTS = ['transference'];
-```
-
-**Key observations:**
-- Only `transference` starts unlocked by default
-- All other 12 elements (fire, water, air, earth, light, dark, death, metal, sand, lightning, crystal, stellar, void) start locked
-- Elements have a boolean `unlocked` field - no partial unlocking or limits on number of unlocked types
-
-### Unlocking New Elements
-**File:** `src/lib/game/store.ts` (lines 2176-2195)
-```typescript
-unlockElement: (element: string) => {
- const state = get();
- if (state.elements[element]?.unlocked) return;
-
- const cost = 500;
- if (state.rawMana < cost) return;
-
- // ELEMENTAL_AFFINITY: New elements start with 10 capacity
- const effects = getUnifiedEffects(state.skillUpgrades, state.skillTiers);
- const newElementMax = hasSpecial(effects, SPECIAL_EFFECTS.ELEMENTAL_AFFINITY) ? 10 : 0;
-
- set({
- rawMana: state.rawMana - cost,
- elements: {
- ...state.elements,
- [element]: { ...state.elements[element], unlocked: true, max: newElementMax },
- },
- log: [`✨ ${ELEMENTS[element].name} affinity unlocked!`, ...state.log.slice(0, 49)],
- });
-},
-```
-
-**Key observations:**
-- Unlocking an element costs 500 raw mana
-- When unlocked, the element gets `unlocked: true`
-- New elements start with `max: 0` unless ELEMENTAL_AFFINITY special effect is active (then max: 10)
-- No limit on the NUMBER of elements that can be unlocked - players can unlock all 12+ types
-
-### Element Definitions
-**File:** `src/lib/game/constants/elements.ts`
-
-Total element types:
-- **Base (7):** fire, water, air, earth, light, dark, death
-- **Utility (1):** transference
-- **Composite (3):** metal, sand, lightning
-- **Exotic (3):** crystal, stellar, void
-
-**Total: 14 element types** (13 unlockable + transference which starts unlocked)
-
----
-
-## 3. How Mana Capacity Is Applied
-
-### computeElementMax Function
-**File:** `src/lib/game/store.ts` (lines 415-432)
-```typescript
-export function computeElementMax(
- state: Pick,
- effects?: ComputedEffects | UnifiedEffects,
- element?: string
-): number {
- const pu = state.prestigeUpgrades;
- const base = 10 + (state.skills.elemAttune || 0) * 50 + (pu.elementalAttune || 0) * 25;
-
- // Apply upgrade effects if provided
- if (effects) {
- let bonus = effects.elementCapBonus; // Global bonus
-
- // Add per-element bonus if element is specified and available
- if (element && (effects as UnifiedEffects).perElementCapBonus) {
- const perElementBonus = (effects as UnifiedEffects).perElementCapBonus[element];
- if (perElementBonus) {
- bonus += perElementBonus;
- }
- }
-
- return Math.floor((base + bonus) * effects.elementCapMultiplier);
- }
- return base;
-}
-```
-
-**Key observations:**
-- Base capacity formula: `10 + (elemAttune skill levels * 50) + (elementalAttune prestige levels * 25)`
-- Effects system can modify capacity via:
- - `elementCapBonus`: Global flat bonus
- - `elementCapMultiplier`: Global multiplier
- - `perElementCapBonus[element]`: Per-element flat bonus (from equipment)
-- **Currently, all unlocked elements share the SAME max capacity** (calculated once and applied to all)
-- The `perElementCapBonus` exists but is currently only used for equipment effects
-
-### Usage in Store Initialization
-**File:** `src/lib/game/store.ts` (lines 670-710)
-```typescript
-function makeInitial(overrides: Partial = {}): GameState {
- const pu = overrides.prestigeUpgrades || {};
- // ...
- const elemMax = computeElementMax(
- { skills: overrides.skills || {}, prestigeUpgrades: pu, skillUpgrades: overrides.skillUpgrades, skillTiers: overrides.skillTiers },
- effects
- );
-
- const elements: Record = {};
- Object.keys(ELEMENTS).forEach((k) => {
- const isUnlocked = BASE_UNLOCKED_ELEMENTS.includes(k);
- let startAmount = 0;
-
- // Start with some elemental mana if elemStart upgrade
- if (isUnlocked && pu.elemStart) {
- startAmount = pu.elemStart * 5;
- }
-
- elements[k] = {
- current: overrides.elements?.[k]?.current ?? startAmount,
- max: elemMax, // <-- Same max for ALL elements
- unlocked: isUnlocked,
- };
- });
- // ...
-}
-```
-
-**Key observation:** The same `elemMax` is applied to ALL elements regardless of type or unlock status.
-
----
-
-## 4. Save Data Structure (Cross-Loop Persistence)
-
-### GameState Prestige-Related Fields
-**File:** `src/lib/game/types/game.ts`
-```typescript
-export interface GameState {
- // ...
-
- // Prestige
- insight: number;
- totalInsight: number;
- prestigeUpgrades: Record;
- memorySlots: number;
- memories: string[];
-
- // Elements
- elements: Record;
-
- // ...
-}
-```
-
-### What Persists Through Loops
-**File:** `src/lib/game/store.ts` (lines 2255-2290)
-```typescript
-startNewLoop: () => {
- const state = get();
- const insightGained = state.loopInsight || calcInsight(state);
- const total = state.insight + insightGained;
-
- // ... (keep some spells through temporal memory)
-
- const newState = makeInitial({
- loopCount: state.loopCount + 1,
- insight: total,
- totalInsight: (state.totalInsight || 0) + insightGained,
- prestigeUpgrades: state.prestigeUpgrades, // <-- PERSISTS
- memories: state.memories,
- skills: state.skills,
- manaHeartBonus: newHeartBonus,
- });
-
- // ...
- set(newState);
-},
-```
-
-**Key observations:**
-- `prestigeUpgrades` (all upgrade levels) persist through loops
-- `insight` and `totalInsight` persist and accumulate
-- `elements` are NOT persisted - they are reinitialized in `makeInitial()`
-- On new loop, element unlocks are lost (player must re-unlock elements)
-- `BASE_UNLOCKED_ELEMENTS` and `pu.elemStart` determine which elements start unlocked/have starting mana
-
-### Purchasing Prestige Upgrades
-**File:** `src/lib/game/store.ts` (lines 2235-2250)
-```typescript
-doPrestige: (id: string) => {
- // ...
- const lvl = state.prestigeUpgrades[id] || 0;
- if (lvl >= pd.max || state.insight < pd.cost) return;
-
- const newPU = { ...state.prestigeUpgrades, [id]: lvl + 1 };
- set({
- insight: state.insight - pd.cost,
- prestigeUpgrades: newPU,
- // ...
- });
-},
-```
-
-**Key observations:**
-- Upgrades purchased with `insight` currency
-- Each level costs the same amount (flat cost model)
-- Max level check prevents over-purchasing
-- New upgrade level is saved to `prestigeUpgrades` record
-
----
-
-## Summary of Key Points for Task6-1
-
-1. **Prestige upgrades** follow a simple structure: `{ name, desc, max, cost }`
-2. **Element capacity** is currently global (same for all elements), calculated from:
- - Base: 10
- - Skill: `elemAttune` levels × 50
- - Prestige: `elementalAttune` levels × 25
- - Effects: bonuses and multipliers
-3. **Element unlocks** are per-type (boolean), cost 500 raw mana each
-4. **No limit** on number of unlocked element types currently exists
-5. **Per-element capacity** bonuses exist in effects system (`perElementCapBonus`) but aren't widely used
-6. **Cross-loop persistence:** prestige upgrades and insight persist; element unlocks do NOT persist
-7. **14 total element types** available (13 unlockable + transference)
-
-## Questions for Implementation
-
-Depending on what "Unlocked Mana Type Capacity" means:
-
-**If it means increasing capacity (max mana) of unlocked types:**
-- Could add a new prestige upgrade similar to `elementalAttune`
-- Could modify the `computeElementMax` function
-- Could add per-element capacity tracking
-
-**If it means increasing the NUMBER of types that can be unlocked:**
-- Need to add a limit/cap on unlocked types (currently unlimited)
-- Need to add a prestige upgrade to increase this limit
-- Need to modify `unlockElement` to check against the limit
diff --git a/docs/task7.md b/docs/task7.md
deleted file mode 100644
index 940a496..0000000
--- a/docs/task7.md
+++ /dev/null
@@ -1,15 +0,0 @@
-# Task 7 — Refactor & Commit
-
-## Status: In Progress
-
-## Files to Refactor
-- [ ] src/lib/game/upgrade-effects.ts (467 → ≤400)
-- [ ] src/components/game/tabs/SkillsTab.tsx (470 → ≤400)
-- [ ] src/app/page.tsx (651 → ≤400)
-
-## Commit
-- [ ] feat: complete task5 and task6 changes
-- [ ] chore: clean up task5 and task6 doc folders
-
-## Status Log
-(append entries here as phases complete)
diff --git a/docs/task7/ctx_page.md b/docs/task7/ctx_page.md
deleted file mode 100644
index 3289c02..0000000
--- a/docs/task7/ctx_page.md
+++ /dev/null
@@ -1,55 +0,0 @@
-# Context: src/app/page.tsx
-
-## Total Line Count
-492 lines
-
-## Top-Level Exports
-
-### 1. `ManaLoopGame` (default export)
-- **Line Range:** 45–485
-- **Description:** The main game component that renders the entire Mana Loop UI, manages tab state, gathering, spire mode, and orchestrates all game systems via the Zustand store.
-
-### 2. `TabLoadingFallback`
-- **Line Range:** 42–43
-- **Description:** A simple loading placeholder component shown while lazy-loaded tab components are being fetched.
-
-### 3. `canCastSpell` (inline helper)
-- **Line Range:** 141–144
-- **Description:** A closure defined inside `ManaLoopGame` that checks whether a given spell can be afforded with current mana and element resources.
-
-
-## Imports from Other Files in the Repo (relative paths)
-
-### From `@/lib/game/`
-- `useGameStore`, `useGameLoop`, `fmt`, `getFloorElement`, `computeMaxMana`, `computeRegen`, `computeClickMana`, `getMeditationBonus`, `getIncursionStrength`, `canAffordSpellCost` — `@/lib/game/store`
-- `ActivityLogEntry` — `@/lib/game/types`
-- `getActiveEquipmentSpells`, `getTotalDPS` — `@/lib/game/computed-stats`
-- `ELEMENTS`, `GUARDIANS`, `SPELLS_DEF`, `PRESTIGE_DEF`, `getStudySpeedMultiplier`, `getStudyCostMultiplier` — `@/lib/game/constants`
-- `getUnifiedEffects`, `hasSpecial`, `SPECIAL_EFFECTS` — `@/lib/game/effects`
-- `DebugName` — `@/lib/game/debug-context`
-
-### From `@/components/`
-- `Button` — `@/components/ui/button`
-- `Tabs`, `TabsContent`, `TabsList`, `TabsTrigger` — `@/components/ui/tabs`
-- `Card`, `CardContent`, `CardHeader`, `CardTitle` — `@/components/ui/card`
-- `Badge` — `@/components/ui/badge`
-- `ScrollArea` — `@/components/ui/scroll-area`
-- `RotateCcw`, `Mountain`, `ChevronDown` — `lucide-react` (icon pack)
-- `TooltipProvider` — `@/components/ui/tooltip`
-- `ActionButtons`, `CalendarDisplay`, `ManaDisplay`, `TimeDisplay` — `@/components/game`
-- Lazy-loaded tab components (all from `@/components/game/tabs`):
- - `SpireTab`, `SkillsTab`, `SpellsTab`, `LabTab`, `StatsTab`, `EquipmentTab`, `AttunementsTab`, `DebugTab`, `LootTab`, `AchievementsTab`, `GolemancyTab`, `CraftingTab`
-
-
-## Assessment: Which exports are safest to extract to a new file
-
-### Safest to extract (stand-alone, reusable, low coupling):
-1. **`TabLoadingFallback`** — A presentational component with zero dependencies on game state or side-effects. It could be moved to a shared UI file (e.g., `components/ui/loading.tsx`) with no behavioral impact.
-
-2. **`canCastSpell`** — Although currently nested inside `ManaLoopGame`, it is a pure function of `(spellId, store)` (it reads `SPELLS_DEF` and `canAffordSpellCost`). It could be lifted to `@/lib/game/spells.ts` (or similar) and exported as a named helper. This would reduce closure complexity and make it easily testable.
-
-### Moderate safety (would require small refactors but are useful to share):
-- None identified beyond the above two — the only other export is the default `ManaLoopGame`, which is intentionally top-level and orchestrates too many concerns to extract as-is. Splitting it would require significant decomposition (e.g., extracting subcomponents, custom hooks, and game-logic helpers).
-
-### Not recommended to extract as-is:
-- **`ManaLoopGame`** — It is the root page component for `/` and tightly integrates routing-lite behavior (spire mode vs normal tabs), game-loop effects, state selectors, and presentation. Extracting it would require first breaking it into smaller pieces (state hooks, subcomponents) rather than moving it wholesale.
diff --git a/docs/task7/ctx_skillstab.md b/docs/task7/ctx_skillstab.md
deleted file mode 100644
index 3041912..0000000
--- a/docs/task7/ctx_skillstab.md
+++ /dev/null
@@ -1,62 +0,0 @@
-# SkillsTab.tsx — Context File
-
-**File:** `src/components/game/tabs/SkillsTab.tsx`
-**Total lines:** 400
-
----
-
-## Top-level Exports
-
-| Export | Line Range | Type | Description |
-|--------|------------|------|-------------|
-| `SkillsTabProps` | ~44–47 | `interface` | Props interface for the SkillsTab component, containing the `store` (GameStore). |
-| `hasMilestoneUpgrade` | ~50–81 | `function` | Determines whether a skill has milestone upgrades (level 5/10) available given current level, tiers, and upgrades; returns milestone info or null. |
-| `SkillsTab` | ~83–398 | `function` (component) | Main SkillsTab component that renders skill categories/cards, study progress, upgrade dialogs, and handles study/upgrade flows using the store. |
-
----
-
-## Imports from other files in the repo (relative paths only)
-
-- `@/lib/game/constants` — `SKILLS_DEF`, `SKILL_CATEGORIES`, `getStudySpeedMultiplier`, `getStudyCostMultiplier`
-- `@/lib/game/skill-evolution` — `SKILL_EVOLUTION_PATHS`, `getUpgradesForSkillAtMilestone`, `getNextTierSkill`, `getTierMultiplier`
-- `@/lib/game/effects` — `getUnifiedEffects`
-- `@/lib/game/data/attunements` — `getAvailableSkillCategories`
-- `@/lib/game/store` — `fmt`, `fmtDec`
-- `@/lib/game/types` — `SkillUpgradeChoice`, `GameStore`
-- `@/components/ui/button` — `Button`
-- `@/components/ui/card` — `Card`, `CardContent`, `CardHeader`, `CardTitle`
-- `@/components/ui/badge` — `Badge`
-- `@/components/ui/tooltip` — `Tooltip`, `TooltipContent`, `TooltipProvider`, `TooltipTrigger`
-- `./StudyProgress` — `StudyProgress`
-- `./UpgradeDialog` — `UpgradeDialog`
-- `@/components/game/ConfirmDialog` — `ConfirmDialog`
-- `@/components/game/GameToast` — `useGameToast`
-- `@/lib/game/constants` (re-export) — `ELEMENTS`
-- `lucide-react` — `ChevronDown`, `ChevronRight`
-- `./SkillRow` — `SkillRow`
-- `@/lib/game/hooks/useSkillUpgradeSelection` — `useSkillUpgradeSelection`
-
-Note: The file also references a `SPECIAL_EFFECTS` constant in JSX (used in `canParallelStudy` logic) but it is not imported in this file — it may be missing or available from a global/ambient source.
-
----
-
-## Assessment — Safest exports to extract to a new file
-
-**Best candidates for extraction** (low coupling, high reusability, minimal dependencies on store/ui):
-
-1. **`hasMilestoneUpgrade`** (lines ~50–81)
- - Pure-ish function that computes milestone eligibility from skill/tier/upgrade state.
- - Depends only on `SKILL_EVOLUTION_PATHS`, `getUpgradesForSkillAtMilestone` and primitive arguments.
- - No React or store coupling — safest to extract.
-
-2. **`SkillsTabProps`** (interface)
- - Type-only export; could be colocated with types or moved to a shared types file if desired.
- - Zero runtime cost — safe but typically not worth extracting by itself unless consolidating interfaces.
-
-**Less safe / more complex**:
-
-- **`SkillsTab`** — deeply coupled to store, UI components, hooks, local dialog state, and many domain helpers. Extracting this would require pulling many dependencies and UI coordination; not recommended unless performing a major feature split.
-- If extraction goal is to reduce file size, consider extracting smaller helpers used *inside* the component into modules (e.g., category filtering, tier/cost calculation helpers) but those are currently inline.
-
-**Recommendation:**
-Extract `hasMilestoneUpgrade` into a helper file (e.g., `src/lib/game/skill-milestones.ts` or similar) and move `SkillsTabProps` into a shared types file if consolidating. Leave `SkillsTab` in place.
diff --git a/docs/task7/ctx_upgrade_effects.md b/docs/task7/ctx_upgrade_effects.md
deleted file mode 100644
index 4aab7eb..0000000
--- a/docs/task7/ctx_upgrade_effects.md
+++ /dev/null
@@ -1,31 +0,0 @@
-# Context: upgrade-effects.ts
-
-- Total line count: 191
-
-## Top-Level Exports
-
-1. **`getActiveUpgrades`** (lines ~28-51)
- - Returns all selected upgrades with full effect definitions from the skill upgrades record
- - Builds cache of upgrade definitions on first access, iterates over SKILL_EVOLUTION_PATHS
-
-2. **`computeEffects`** (lines ~54-188)
- - Computes all active effects from selected upgrades into a ComputedEffects object
- - Applies multipliers, bonuses, and special effects from upgrades; handles DEEP_UNDERSTANDING and MANA_THRESHOLD
-
-3. **`upgradeDefinitionsById`** (line ~15)
- - Cache Map for quick lookup of upgrade definitions by ID
-
-4. **`buildUpgradeCache`** (lines ~18-25)
- - Initializes the upgrade definition cache from SKILL_EVOLUTION_PATHS
-
-## Imports
-
-- `SkillUpgradeChoice`, `SkillUpgradeEffect` from './types'
-- `getUpgradesForSkillAtMilestone`, `SKILL_EVOLUTION_PATHS` from './skill-evolution'
-- `ActiveUpgradeEffect`, `ComputedEffects` from './upgrade-effects.types'
-- `SPECIAL_EFFECTS`, `hasSpecial` from './special-effects'
-- `computeDynamicRegen`, `computeDynamicClickMana`, `computeDynamicDamage` from './dynamic-compute'
-
-## Assessment
-
-This file is already **191 lines** (well under 400). No refactoring needed. The exports are cleanly separated - `getActiveUpgrades` handles data gathering, `computeEffects` handles computation. The module is focused on a single concern (upgrade effects) and is not a candidate for splitting.
diff --git a/docs/task7/plan_page.md b/docs/task7/plan_page.md
deleted file mode 100644
index e0e775f..0000000
--- a/docs/task7/plan_page.md
+++ /dev/null
@@ -1,49 +0,0 @@
-# Refactor Plan: page.tsx
-
-## Current State
-- **File:** `src/app/page.tsx`
-- **Lines:** 650
-- **Target:** ≤400 lines (reduce by ~250 lines)
-
-## Proposed File Structure
-
-### 1. `src/components/game/tabs/PrestigeTab.tsx` (NEW, ≈60 lines)
-**Contains:**
-- `PrestigeTab` component (extracted from `renderGrimoireTab`)
-- Grimoire/Prestige tab UI
-
-**Rationale:** Self-contained tab component; only depends on store, PRESTIGE_DEF, GUARDIANS.
-
-### 2. `src/app/page.tsx` (≈300 lines)
-**Keeps:**
-- Main `ManaLoopGame` component shell
-- Tabs definition and lazy loading
-- Core game loop integration
-- Most tab content (other tabs remain via lazy loading)
-
-**Rationale:** Reduces main page by extracting only the Grimoire tab which is standalone.
-
-## Circular Import Risks
-
-**LOW RISK:**
-- `PrestigeTab` depends on store, PRESTIGE_DEF, GUARDIANS - all stable dependencies.
-- No circular dependency with `page.tsx`.
-
-**MITIGATION:**
-- Import store and constants normally in new component.
-
-## Extraction Order
-
-**1 → 2**
-
-1. Create `PrestigeTab.tsx`, move `renderGrimoireTab` content, adapt to be standalone (receive store via hook internally), export component.
-2. Update `page.tsx`: replace `renderGrimoireTab` call with ` `, remove extracted code.
-3. Verify typecheck, verify ≤400 lines.
-
-## Notes
-
-This addresses the main line bloat by only ~60 lines (renderGrimoireTab). To get page.tsx under 400 lines fully would require more extensive splitting (extracting each tab into separate route files, extracting sidebar, etc.). The plan here is conservative - if page.tsx is still >400 after extracting PrestigeTab, we can consider:
-- Extracting StatsTab/LabTab content further
-- Extracting activity log rendering
-- Extracting action buttons
-But per phase instructions we must get ALL THREE target files under 400 lines, so we must be more aggressive if needed.
\ No newline at end of file
diff --git a/docs/task7/plan_skillstab.md b/docs/task7/plan_skillstab.md
deleted file mode 100644
index 585132b..0000000
--- a/docs/task7/plan_skillstab.md
+++ /dev/null
@@ -1,65 +0,0 @@
-# Refactor Plan: SkillsTab.tsx
-
-## Current State
-- **File:** `src/components/game/tabs/SkillsTab.tsx`
-- **Lines:** 434
-- **Target:** ≤400 lines (reduce by ~34 lines)
-
-## Proposed File Structure
-
-### 1. `src/lib/game/utils.ts` (NEW, ≈20 lines)
-**Contains:**
-- `formatStudyTime` function
-
-**Rationale:** Simple pure utility function, zero-risk extraction.
-
-### 2. `src/components/game/tabs/SkillUpgradeDialog.tsx` (NEW, ≈80 lines)
-**Contains:**
-- `SkillUpgradeDialog` component (extracted from `renderUpgradeDialog`)
-- Dialog UI and selection state handlers
-
-**Rationale:** Isolates the upgrade dialog UI (~100 lines worth from parent), simplifies SkillsTab significantly. Uses callback props.
-
-### 3. `src/components/game/SkillRow.tsx` (NEW, ≈100 lines)
-**Contains:**
-- `SkillRow` component for individual skill rows
-- Level dots, buttons, study toggle, tier-up, milestone badges
-
-**Rationale:** Encapsulates per-skill UI (~150 lines worth from parent), reusable, simplifies SkillsTab.
-
-### 4. `src/lib/game/hooks/useSkillUpgradeSelection.ts` (NEW, ≈40 lines)
-**Contains:**
-- `useSkillUpgradeSelection` custom hook
-- Selection state and mutation logic for milestone upgrades
-
-**Rationale:** Encapsulates upgrade selection logic previously inline in SkillsTab.
-
-### 5. `src/components/game/tabs/SkillsTab.tsx` (≈150 lines)
-**Keeps:**
-- Core `SkillsTab` component shell
-- Category-level layout
-- Main store hook calls
-- Tab switching
-
-**Rationale:** Maintains coordination role while delegating details to extracted components/hooks.
-
-## Circular Import Risks
-
-**MEDIUM RISK:**
-- `SkillRow` needs access to many store selectors and actions. May accept callbacks as props to avoid direct store access (kept in parent).
-- `SkillUpgradeDialog` needs data from store; will receive computed values as props.
-- `useSkillUpgradeSelection` needs `SKILL_EVOLUTION_PATHS` and store commits - safe.
-
-**MITIGATION:**
-- Keep store interactions in `SkillsTab`, pass data down via props.
-- Accept slight prop drilling vs. direct store access in extracted components for cleaner boundaries.
-
-## Extraction Order
-
-**1 → 4 → 3 → 2 → 5**
-
-1. Create `utils.ts`, move `formatStudyTime`, update SkillsTab import.
-2. Create `useSkillUpgradeSelection` hook, move selection logic, update SkillsTab.
-3. Create `SkillRow`, move per-skill UI (pass callbacks from parent), update SkillsTab.
-4. Create `SkillUpgradeDialog`, move dialog UI, update SkillsTab.
-5. Delete old extracted sections from SkillsTab, verify ≤400 lines.
\ No newline at end of file
diff --git a/docs/task7/plan_upgrade_effects.md b/docs/task7/plan_upgrade_effects.md
deleted file mode 100644
index 407c169..0000000
--- a/docs/task7/plan_upgrade_effects.md
+++ /dev/null
@@ -1,69 +0,0 @@
-# Refactor Plan: upgrade-effects.ts
-
-## Current State
-- **File:** `src/lib/game/upgrade-effects.ts`
-- **Lines:** 466
-- **Target:** ≤400 lines (reduce by ~66 lines)
-
-## Proposed File Structure
-
-### 1. `src/lib/game/upgrade-effects.ts` (≈220 lines)
-**Keeps:**
-- `upgradeDefinitionsById` (cache)
-- `buildUpgradeCache` function
-- `getActiveUpgrades` function
-- `computeEffects` function (core orchestrator)
-- Evolution path dependency (`SKILL_EVOLUTION_PATHS`, `getUpgradesForSkillAtMilestone`)
-
-**Rationale:** This remains the core orchestration module that ties together evolution paths with upgrade computation.
-
-### 2. `src/lib/game/upgrade-effects.types.ts` (≈60 lines)
-**Contains:**
-- `ActiveUpgradeEffect` interface
-- `ComputedEffects` interface
-- Re-exports for type consumers
-
-**Rationale:** Pure type definitions separated for clarity.
-
-### 3. `src/lib/game/special-effects.ts` (≈80 lines)
-**Contains:**
-- `SPECIAL_EFFECTS` constant record
-- `hasSpecial` function
-
-**Rationale:** Isolates special effect keys and the simple predicate function.
-
-### 4. `src/lib/game/dynamic-compute.ts` (≈100 lines)
-**Contains:**
-- `computeDynamicRegen` function
-- `computeDynamicClickMana` function
-- `computeDynamicDamage` function
-
-**Rationale:** Groups the three dynamic computation functions that all depend on `SPECIAL_EFFECTS` and share similar patterns.
-
-## Circular Import Risks
-
-**LOW RISK:**
-- `upgrade-effects.ts` depends on `skill-evolution` - one-way dependency.
-- New files import types from `upgrade-effects.types.ts` and `special-effects.ts`.
-- `dynamic-compute.ts` depends on `special-effects.ts` and types - safe.
-
-**MITIGATION:**
-- Keep type re-exports clean.
-- If `computeEffects` needs dynamic functions, import them from `dynamic-compute.ts`.
-
-## Extraction Order
-
-**1 → 2 → 3 → 4**
-
-1. Create `upgrade-effects.types.ts`, move type interfaces, update imports.
-2. Create `special-effects.ts`, move `SPECIAL_EFFECTS` + `hasSpecial`, update imports.
-3. Create `dynamic-compute.ts`, move the three `computeDynamic*` functions, update imports.
-4. Trim `upgrade-effects.ts` - remove moved items, update internal imports.
-
-## Import Updates Required
-
-Files importing from `upgrade-effects.ts` need updates:
-- Types → `upgrade-effects.types.ts`
-- Special effects → `special-effects.ts`
-- Dynamic compute → `dynamic-compute.ts`
-- Core functions → `upgrade-effects.ts`
\ No newline at end of file
diff --git a/src/app/components/LeftPanel.tsx b/src/app/components/LeftPanel.tsx
index 4be3414..6d70dd1 100644
--- a/src/app/components/LeftPanel.tsx
+++ b/src/app/components/LeftPanel.tsx
@@ -7,22 +7,38 @@ import { ManaDisplay } from '@/components/game';
import { ActionButtons } from '@/components/game';
import { CalendarDisplay } from '@/components/game';
import { DebugName } from '@/lib/game/debug-context';
-import type { GameStore } from '@/lib/game/store';
-import { computeMaxMana, computeClickMana, getMeditationBonus } from '@/lib/game/store';
+import { useGameStore, useManaStore, useSkillStore, useCombatStore } from '@/lib/game/stores';
import { getUnifiedEffects } from '@/lib/game/effects';
+import { computeMaxMana, computeClickMana, getMeditationBonus } from '@/lib/game/stores';
-interface LeftPanelProps {
- store: GameStore;
- effectiveRegen: number;
- incursionStrength: number;
-}
-
-export function LeftPanel({ store, effectiveRegen, incursionStrength }: LeftPanelProps) {
+export function LeftPanel() {
const [isGathering, setIsGathering] = useState(false);
+ // Get state from modular stores
+ const rawMana = useManaStore((s) => s.rawMana);
+ const elements = useManaStore((s) => s.elements);
+ const meditateTicks = useManaStore((s) => s.meditateTicks);
+
+ const skills = useSkillStore((s) => s.skills);
+ const skillTiers = useSkillStore((s) => s.skillTiers);
+ const skillUpgrades = useSkillStore((s) => s.skillUpgrades);
+
+ const gatherMana = useGameStore((s) => s.gatherMana);
+ const spireMode = useGameStore((s) => s.spireMode);
+ const currentAction = useCombatStore((s) => s.currentAction);
+ const currentStudyTarget = useGameStore((s) => s.currentStudyTarget);
+ const designProgress = useGameStore((s) => s.designProgress);
+ const designProgress2 = useGameStore((s) => s.designProgress2);
+ const preparationProgress = useGameStore((s) => s.preparationProgress);
+ const applicationProgress = useGameStore((s) => s.applicationProgress);
+ const equipmentCraftingProgress = useGameStore((s) => s.equipmentCraftingProgress);
+ const enterSpireMode = useGameStore((s) => s.enterSpireMode);
+ const day = useGameStore((s) => s.day);
+ const hour = useGameStore((s) => s.hour);
+
const handleGatherStart = () => {
setIsGathering(true);
- store.gatherMana();
+ gatherMana();
};
const handleGatherEnd = () => {
@@ -38,7 +54,7 @@ export function LeftPanel({ store, effectiveRegen, incursionStrength }: LeftPane
const gatherLoop = (timestamp: number) => {
if (timestamp - lastGatherTime >= minGatherInterval) {
- store.gatherMana();
+ gatherMana();
lastGatherTime = timestamp;
}
animationFrameId = requestAnimationFrame(gatherLoop);
@@ -46,34 +62,48 @@ export function LeftPanel({ store, effectiveRegen, incursionStrength }: LeftPane
animationFrameId = requestAnimationFrame(gatherLoop);
return () => cancelAnimationFrame(animationFrameId);
- }, [isGathering, store]);
+ }, [isGathering, gatherMana]);
- const maxMana = computeMaxMana(store, getUnifiedEffects(store));
- const clickMana = computeClickMana(store);
- const meditationMultiplier = getMeditationBonus(store.meditateTicks, store.skills, getUnifiedEffects(store).meditationEfficiency);
+ const upgradeEffects = getUnifiedEffects({
+ skillUpgrades,
+ skillTiers,
+ equippedInstances: {},
+ equipmentInstances: {}
+ });
+
+ const maxMana = computeMaxMana(
+ { skills, skillTiers, skillUpgrades },
+ upgradeEffects
+ );
+ const clickMana = computeClickMana({
+ skills,
+ skillTiers,
+ skillUpgrades,
+ });
+ const meditationMultiplier = getMeditationBonus(meditateTicks, skills, upgradeEffects.meditationEfficiency);
return (
- {!store.spireMode && (
+ {!spireMode && (
store.enterSpireMode()}
+ onClick={enterSpireMode}
>
Climb the Spire
@@ -81,25 +111,25 @@ export function LeftPanel({ store, effectiveRegen, incursionStrength }: LeftPane
)}
- {!store.spireMode && (
+ {!spireMode && (
)}
diff --git a/src/app/page.tsx b/src/app/page.tsx
index 4733f1c..d98c2b3 100644
--- a/src/app/page.tsx
+++ b/src/app/page.tsx
@@ -73,8 +73,7 @@ function GrimoireTab() {
// Only access SPELLS_DEF on client-side
if (typeof window !== 'undefined' && SPELLS_DEF) {
const filtered = Object.values(SPELLS_DEF || {}).filter((s: any) => s.grimoire);
- // Use setTimeout to avoid setState in effect issue
- setTimeout(() => setGrimoireSpells(filtered), 0);
+ setGrimoireSpells(filtered);
}
}, []);
@@ -147,12 +146,16 @@ export default function ManaLoopGame() {
const gameOver = useUIStore((s) => s.gameOver);
+ // Get equipment state from store
+ const equippedInstances = useGameStore((s) => s.equippedInstances);
+ const equipmentInstances = useGameStore((s) => s.equipmentInstances);
+
// Derived state
const upgradeEffects = getUnifiedEffects({
skillUpgrades,
skillTiers,
- equippedInstances: {},
- equipmentInstances: {}
+ equippedInstances,
+ equipmentInstances
});
const maxMana = computeMaxMana({
@@ -225,11 +228,7 @@ export default function ManaLoopGame() {
{/* Main Content */}
-
+
@@ -250,75 +249,99 @@ export default function ManaLoopGame() {
- }>
-
-
+ spire tab failed to load.
}>
+ }>
+
+
+
- }>
-
-
+ attunements tab failed to load.}>
+ }>
+
+
+
- }>
-
-
+ golemancy tab failed to load.}>
+ }>
+
+
+
- }>
-
-
+ skills tab failed to load.}>
+ }>
+
+
+
- }>
-
-
+ spells tab failed to load.}>
+ }>
+
+
+
- }>
-
-
+ equipment tab failed to load.}>
+ }>
+
+
+
- }>
-
-
+ crafting tab failed to load.}>
+ }>
+
+
+
- }>
-
-
+ loot tab failed to load.}>
+ }>
+
+
+
- }>
-
-
+ achievements tab failed to load.}>
+ }>
+
+
+
- }>
-
-
+ lab tab failed to load.}>
+ }>
+
+
+
- }>
-
-
+ stats tab failed to load.}>
+ }>
+
+
+
- }>
-
-
+ debug tab failed to load.}>
+ }>
+
+
+
diff --git a/src/components/game/crafting/EnchantmentApplier.tsx b/src/components/game/crafting/EnchantmentApplier.tsx
index 623fa38..66156cb 100644
--- a/src/components/game/crafting/EnchantmentApplier.tsx
+++ b/src/components/game/crafting/EnchantmentApplier.tsx
@@ -10,11 +10,11 @@ import { ScrollArea } from '@/components/ui/scroll-area';
import { Separator } from '@/components/ui/separator';
import { ENCHANTMENT_EFFECTS } from '@/lib/game/data/enchantment-effects';
import type { EquipmentInstance, EnchantmentDesign, AppliedEnchantment, LootInventory, EquipmentCraftingProgress, EquipmentSlot } from '@/lib/game/types';
-import { fmt, type GameStore } from '@/lib/game/store';
+import { fmt } from '@/lib/game/stores';
import { CheckCircle, Sparkles } from 'lucide-react';
+import { useGameStore } from '@/lib/game/stores';
export interface EnchantmentApplierProps {
- store: GameStore;
selectedEquipmentInstance: string | null;
setSelectedEquipmentInstance: (id: string | null) => void;
selectedDesign: string | null;
@@ -24,7 +24,6 @@ export interface EnchantmentApplierProps {
}
export function EnchantmentApplier({
- store,
selectedEquipmentInstance,
setSelectedEquipmentInstance,
selectedDesign,
@@ -32,15 +31,15 @@ export function EnchantmentApplier({
onEnchantmentApplied,
onCapacityExceeded,
}: EnchantmentApplierProps) {
- const equippedInstances = store.equippedInstances;
- const equipmentInstances = store.equipmentInstances;
- const enchantmentDesigns = store.enchantmentDesigns;
- const applicationProgress = store.applicationProgress;
- const rawMana = store.rawMana;
- const startApplying = store.startApplying;
- const pauseApplication = store.pauseApplication;
- const resumeApplication = store.resumeApplication;
- const cancelApplication = store.cancelApplication;
+ const equippedInstances = useGameStore((s) => s.equippedInstances);
+ const equipmentInstances = useGameStore((s) => s.equipmentInstances);
+ const enchantmentDesigns = useGameStore((s) => s.enchantmentDesigns);
+ const applicationProgress = useGameStore((s) => s.applicationProgress);
+ const rawMana = useGameStore((s) => s.rawMana);
+ const startApplying = useGameStore((s) => s.startApplying);
+ const pauseApplication = useGameStore((s) => s.pauseApplication);
+ const resumeApplication = useGameStore((s) => s.resumeApplication);
+ const cancelApplication = useGameStore((s) => s.cancelApplication);
// Get equipped items as array - ONLY show items tagged 'Ready for Enchantment' (requirement cr5)
const equippedItems = Object.entries(equippedInstances)
@@ -119,7 +118,8 @@ export function EnchantmentApplier({
${selectedEquipmentInstance === instance.instanceId
? 'border-[var(--mana-light)] bg-[var(--mana-light)]/10'
: 'border-[var(--border-default)] bg-[var(--bg-sunken)]/50 hover:border-[var(--border-default)]'
- }`}
+ }
+ `}
onClick={() => setSelectedEquipmentInstance(instance.instanceId)}
role="button"
tabIndex={0}
@@ -159,7 +159,8 @@ export function EnchantmentApplier({
${selectedDesign === design.id
? 'border-[var(--mana-stellar)] bg-[var(--mana-stellar)]/10'
: 'border-[var(--border-default)] bg-[var(--bg-sunken)]/50 hover:border-[var(--border-default)]'
- }`}
+ }
+ `}
onClick={() => setSelectedDesign(design.id)}
role="button"
tabIndex={0}
@@ -252,7 +253,7 @@ export function EnchantmentApplier({
{design.effects.map(eff => (
- {ENCHANTMENT_EFFECTS[eff.effectId]?.name} x{eff.stacks}
+ {ENCHANTMENT_EFFECT_S[eff.effectId]?.name} x{eff.stacks}
))}
diff --git a/src/components/game/crafting/EnchantmentDesigner.tsx b/src/components/game/crafting/EnchantmentDesigner.tsx
index b3294c4..7fd5c55 100644
--- a/src/components/game/crafting/EnchantmentDesigner.tsx
+++ b/src/components/game/crafting/EnchantmentDesigner.tsx
@@ -6,7 +6,6 @@ import { Separator } from '@/components/ui/separator';
import { EQUIPMENT_TYPES } from '@/lib/game/data/equipment';
import { ENCHANTMENT_EFFECTS } from '@/lib/game/data/enchantment-effects';
import type { EquipmentInstance, EnchantmentDesign, DesignEffect, EquipmentCraftingProgress } from '@/lib/game/types';
-import { type GameStore } from '@/lib/game/store';
import type { EnchantmentDesignerProps } from './EnchantmentDesigner/types';
import { EquipmentTypeSelector } from './EnchantmentDesigner/EquipmentTypeSelector';
import { EffectSelector } from './EnchantmentDesigner/EffectSelector';
@@ -23,9 +22,9 @@ import {
addEffectToDesign,
removeEffectFromDesign,
} from './EnchantmentDesigner/utils';
+import { useGameStore } from '@/lib/game/stores';
export function EnchantmentDesigner({
- store,
selectedEquipmentType,
setSelectedEquipmentType,
selectedEffects,
@@ -35,13 +34,13 @@ export function EnchantmentDesigner({
selectedDesign,
setSelectedDesign,
}: EnchantmentDesignerProps) {
- const enchantmentDesigns = store.enchantmentDesigns;
- const designProgress = store.designProgress;
- const startDesigningEnchantment = store.startDesigningEnchantment;
- const cancelDesign = store.cancelDesign;
- const deleteDesign = store.deleteDesign;
- const unlockedEffects = store.unlockedEffects;
- const skills = store.skills;
+ const enchantmentDesigns = useGameStore((s) => s.enchantmentDesigns);
+ const designProgress = useGameStore((s) => s.designProgress);
+ const startDesigningEnchantment = useGameStore((s) => s.startDesigningEnchantment);
+ const cancelDesign = useGameStore((s) => s.cancelDesign);
+ const deleteDesign = useGameStore((s) => s.deleteDesign);
+ const unlockedEffects = useGameStore((s) => s.unlockedEffects);
+ const skills = useGameStore((s) => s.skills);
const enchantingLevel = skills.enchanting || 0;
const efficiencyBonus = (skills.efficientEnchant || 0) * 0.05;
@@ -51,7 +50,6 @@ export function EnchantmentDesigner({
// Get capacity limit for selected equipment type
const selectedEquipmentCapacity = getEquipmentCapacity(selectedEquipmentType);
- const isOverCapacity = selectedEquipmentType ? designCapacityCost > selectedEquipmentCapacity : false;
// Calculate design time
const designTime = calculateDesignTime(selectedEffects);
@@ -86,7 +84,7 @@ export function EnchantmentDesigner({
const incompatibleEffects = getIncompatibleEffects(selectedEquipmentType, unlockedEffects);
// Get equipment types that the player actually owns (has instances of)
- const ownedEquipmentTypes = getOwnedEquipmentTypes(store);
+ const ownedEquipmentTypes = getOwnedEquipmentTypes(useGameStore.getState());
// Get the reason why an effect is incompatible
const getIncompatibilityReasonWrapper = (effect: { id: string; name: string; description: string; allowedEquipmentCategories: any[] }) => {
@@ -131,7 +129,7 @@ export function EnchantmentDesigner({
selectedEffects={selectedEffects}
designCapacityCost={designCapacityCost}
selectedEquipmentCapacity={selectedEquipmentCapacity}
- isOverCapacity={isOverCapacity}
+ isOverCapacity={designCapacityCost > selectedEquipmentCapacity}
designTime={designTime}
selectedEquipmentType={selectedEquipmentType}
handleCreateDesign={handleCreateDesign}
diff --git a/src/components/game/crafting/EnchantmentPreparer.tsx b/src/components/game/crafting/EnchantmentPreparer.tsx
index 045161f..2a6c366 100644
--- a/src/components/game/crafting/EnchantmentPreparer.tsx
+++ b/src/components/game/crafting/EnchantmentPreparer.tsx
@@ -12,28 +12,27 @@ import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent,
import { Trash2, CheckCircle, AlertTriangle } from 'lucide-react';
import { EQUIPMENT_TYPES } from '@/lib/game/data/equipment';
import type { EquipmentInstance, AppliedEnchantment, LootInventory, EquipmentCraftingProgress, EquipmentSlot } from '@/lib/game/types';
-import { fmt, type GameStore } from '@/lib/game/store';
+import { fmt } from '@/lib/game/stores';
+import { useGameStore } from '@/lib/game/stores';
import { useGameToast } from '@/components/game/GameToast';
export interface EnchantmentPreparerProps {
- store: GameStore;
selectedEquipmentInstance: string | null;
setSelectedEquipmentInstance: (id: string | null) => void;
}
export function EnchantmentPreparer({
- store,
selectedEquipmentInstance,
setSelectedEquipmentInstance,
}: EnchantmentPreparerProps) {
const showToast = useGameToast();
- const equippedInstances = store.equippedInstances;
- const equipmentInstances = store.equipmentInstances;
- const preparationProgress = store.preparationProgress;
- const rawMana = store.rawMana;
- const skills = store.skills;
- const startPreparing = store.startPreparing;
- const cancelPreparation = store.cancelPreparation;
+ const equippedInstances = useGameStore((s) => s.equippedInstances);
+ const equipmentInstances = useGameStore((s) => s.equipmentInstances);
+ const preparationProgress = useGameStore((s) => s.preparationProgress);
+ const rawMana = useGameStore((s) => s.rawMana);
+ const skills = useGameStore((s) => s.skills);
+ const startPreparing = useGameStore((s) => s.startPreparing);
+ const cancelPreparation = useGameStore((s) => s.cancelPreparation);
// Get equipped items as array
const equippedItems = Object.entries(equippedInstances)
diff --git a/src/components/game/crafting/EquipmentCrafter.tsx b/src/components/game/crafting/EquipmentCrafter.tsx
index bf476cc..4b547f0 100644
--- a/src/components/game/crafting/EquipmentCrafter.tsx
+++ b/src/components/game/crafting/EquipmentCrafter.tsx
@@ -10,20 +10,17 @@ import { Package, Sparkles, Trash2, Anvil } from 'lucide-react';
import { CRAFTING_RECIPES, canCraftRecipe } from '@/lib/game/data/crafting-recipes';
import { LOOT_DROPS, RARITY_COLORS } from '@/lib/game/data/loot-drops';
import type { EquipmentInstance, AppliedEnchantment, LootInventory, EquipmentCraftingProgress } from '@/lib/game/types';
-import { fmt, type GameStore } from '@/lib/game/store';
+import { fmt } from '@/lib/game/stores';
+import { useGameStore } from '@/lib/game/stores';
-export interface EquipmentCrafterProps {
- store: GameStore;
-}
-
-export function EquipmentCrafter({ store }: EquipmentCrafterProps) {
- const lootInventory = store.lootInventory;
- const equipmentCraftingProgress = store.equipmentCraftingProgress;
- const rawMana = store.rawMana;
- const currentAction = store.currentAction;
- const startCraftingEquipment = store.startCraftingEquipment;
- const cancelEquipmentCrafting = store.cancelEquipmentCrafting;
- const deleteMaterial = store.deleteMaterial;
+export function EquipmentCrafter() {
+ const lootInventory = useGameStore((s) => s.lootInventory);
+ const equipmentCraftingProgress = useGameStore((s) => s.equipmentCraftingProgress);
+ const rawMana = useGameStore((s) => s.rawMana);
+ const currentAction = useGameStore((s) => s.currentAction);
+ const startCraftingEquipment = useGameStore((s) => s.startCraftingEquipment);
+ const cancelEquipmentCrafting = useGameStore((s) => s.cancelEquipmentCrafting);
+ const deleteMaterial = useGameStore((s) => s.deleteMaterial);
return (
diff --git a/src/components/game/tabs/AchievementsTab.tsx b/src/components/game/tabs/AchievementsTab.tsx
index f840a06..39ca513 100755
--- a/src/components/game/tabs/AchievementsTab.tsx
+++ b/src/components/game/tabs/AchievementsTab.tsx
@@ -1,26 +1,28 @@
'use client';
-import type { GameStore } from '@/lib/game/store';
import { AchievementsDisplay } from '@/components/game/AchievementsDisplay';
+import { useGameStore } from '@/lib/game/stores';
-export interface AchievementsTabProps {
- store: GameStore;
-}
+export function AchievementsTab() {
+ const achievements = useGameStore((s) => s.achievements);
+ const maxFloorReached = useGameStore((s) => s.maxFloorReached);
+ const totalManaGathered = useGameStore((s) => s.totalManaGathered);
+ const signedPacts = useGameStore((s) => s.signedPacts);
+ const totalSpellsCast = useGameStore((s) => s.totalSpellsCast);
+ const totalDamageDealt = useGameStore((s) => s.totalDamageDealt);
+ const totalCraftsCompleted = useGameStore((s) => s.totalCraftsCompleted);
-export function AchievementsTab({ store }: AchievementsTabProps) {
- const achievements = store.achievements;
-
return (
diff --git a/src/components/game/tabs/CategorySkillsList.tsx b/src/components/game/tabs/CategorySkillsList.tsx
index e7704aa..fcc144c 100644
--- a/src/components/game/tabs/CategorySkillsList.tsx
+++ b/src/components/game/tabs/CategorySkillsList.tsx
@@ -1,31 +1,25 @@
-// ─── Category Skills List ───────────────────────────────────────────
-// Wraps all skills in a single category, handles category-level UI
+// CategorySkillsList - Displays skills for a specific category
+// Migrated to use hooks directly (removed GameStore prop)
'use client';
import { useState } from 'react';
-import { Card, CardContent } from '@/components/ui/card';
-import { SkillRow } from './SkillRow';
-import { SkillCategoryHeader } from './SkillCategoryHeader';
-import type { GameStore } from '@/lib/game/store';
-import { SKILL_CATEGORIES, SKILLS_DEF, getStudyCostMultiplier, getStudySpeedMultiplier } from '@/lib/game/constants';
-import { getUpgradesForSkillAtMilestone, getNextTierSkill, getTierMultiplier, SKILL_EVOLUTION_PATHS } from '@/lib/game/skill-evolution';
-import { SPECIAL_EFFECTS, hasSpecial } from '@/lib/game/special-effects';
-import { ELEMENTS } from '@/lib/game/constants';
+import type { SkillUpgradeChoice } from '@/lib/game/types';
+import { SKILLS_DEF, getTierMultiplier } from '@/lib/game/constants';
import { getUnifiedEffects } from '@/lib/game/effects';
-import { hasMilestoneUpgrade } from '@/lib/game/hooks/useSkillUpgradeSelection';
-import type { ComputedEffects } from '@/lib/game/upgrade-effects.types';
-import type { SkillCost } from '@/lib/game/types';
-import { useGameToast } from '@/components/game/GameToast';
+import { useSkillStore, useGameStore, usePrestigeStore } from '@/lib/game/stores';
+import { SkillRow } from './SkillRow';
+import type { GameStore } from '@/lib/game/store'; // Keep type import for backward compatibility
interface CategorySkillsListProps {
- category: { id: string; name: string; icon: string };
- availableCategories: string[];
- isCollapsed: boolean;
- onToggleCategory: (categoryId: string) => void;
- store: GameStore;
+ categoryId: string;
+ categoryName: string;
+ skills: Record
;
+ skillUpgrades: Record;
+ skillTiers: Record;
+ prestigeUpgrades: Record;
studySpeedMult: number;
- upgradeEffects: ComputedEffects;
+ upgradeEffects: any;
currentStudyTarget: any;
onStartStudying: (skillId: string) => void;
onParallelStudy: (skillId: string) => void;
@@ -36,17 +30,13 @@ interface CategorySkillsListProps {
setPendingSelections: (selections: string[]) => void;
}
-// Type guard for element skill costs
-function isElementCost(cost?: SkillCost | null): cost is SkillCost & { type: 'element'; element: string } {
- return cost !== null && typeof cost !== 'undefined' && cost.type === 'element' && typeof cost.element === 'string';
-}
-
export function CategorySkillsList({
- category,
- availableCategories,
- isCollapsed,
- onToggleCategory,
- store,
+ categoryId,
+ categoryName,
+ skills,
+ skillUpgrades,
+ skillTiers,
+ prestigeUpgrades,
studySpeedMult,
upgradeEffects,
currentStudyTarget,
@@ -58,164 +48,72 @@ export function CategorySkillsList({
pendingSelections,
setPendingSelections,
}: CategorySkillsListProps) {
- const showToast = useGameToast();
+ const [collapsed, setCollapsed] = useState(false);
- // Skip if category not available
- if (!availableCategories.includes(category.id)) return null;
+ const categorySkills = Object.entries(SKILLS_DEF || {})
+ .filter(([, def]) => def.category === categoryId)
+ .sort((a, b) => (a[1].tier || 0) - (b[1].tier || 0));
- const skillsInCat = Object.entries(SKILLS_DEF).filter(([, def]) => def.cat === category.id);
- if (skillsInCat.length === 0) return null;
-
- const handleCancelStudyInternal = () => {
- onCancelStudy();
- };
+ const toggleCollapse = () => setCollapsed(!collapsed);
return (
-
- onToggleCategory(category.id)}
- />
- {!isCollapsed && (
-
-
- {skillsInCat.map(([id, def]) => {
- // GATE MANA CAPACITY SKILLS BY UNLOCKED ELEMENT
- if (isElementCost(def.cost)) {
- const element = store.elements[def.cost.element];
- if (!element?.unlocked) return null;
- }
+
+
+
+ {categoryName} ({categorySkills.length})
+
+
+ {collapsed ? '▼' : '▶'}
+
+
- // Get tier info
- const currentTier = store.skillTiers?.[id] || 1;
- const tieredSkillId = currentTier > 1 ? `${id}_t${currentTier}` : id;
- const tierMultiplier = getTierMultiplier(tieredSkillId);
+ {!collapsed && (
+
+ {categorySkills.map(([skillId, def]) => {
+ const skillLevel = skills[skillId] || 0;
+ const tier = skillTiers[skillId] || 0;
+ const tierMult = getTierMultiplier(skillId)(tier);
+ const isStudying = currentStudyTarget?.id === skillId;
+ const isParallel = currentStudyTarget?.type === 'parallel' && currentStudyTarget?.id === skillId;
- // Get the actual level from the tiered skill
- const level = store.skills[tieredSkillId] || store.skills[id] || 0;
- const maxed = level >= def.max;
+ // Get upgrade choices for this skill
+ const store = useGameStore.getState();
+ const { available, selected } = store.getSkillUpgradeChoices(skillId, tier as 5 | 10);
- // Check if studying this skill
- const isStudying =
- (store.currentStudyTarget?.id === id || store.currentStudyTarget?.id === tieredSkillId) &&
- store.currentStudyTarget?.type === 'skill';
-
- // Get tier name for display
- const tierDef = SKILL_EVOLUTION_PATHS[id]?.tiers.find((t) => t.tier === currentTier);
- const skillDisplayName = tierDef?.name || def.name;
-
- // Check prerequisites
- let prereqMet = true;
- if (def.req) {
- for (const [r, rl] of Object.entries(def.req)) {
- if ((store.skills[r] || 0) < rl) {
- prereqMet = false;
- break;
+ return (
+ {
+ if (pendingSelections.includes(upgradeId)) {
+ setPendingSelections(pendingSelections.filter(id => id !== upgradeId));
+ } else {
+ setPendingSelections([...pendingSelections, upgradeId]);
}
- }
- }
-
- // Apply skill modifiers
- const costMult = getStudyCostMultiplier(store.skills);
- const speedMult = getStudySpeedMultiplier(store.skills);
- const studyEffects = getUnifiedEffects(store);
- const effectiveSpeedMult = speedMult * studyEffects.studySpeedMultiplier;
-
- // Study time scales with tier
- const tierStudyTime = def.studyTime * currentTier;
- const effectiveStudyTime = tierStudyTime / effectiveSpeedMult;
-
- // Cost scales with tier
- const baseCost = def.base * (level + 1) * currentTier;
- const cost = Math.floor(baseCost * costMult);
-
- // Additional cost (element mana) - only pass element costs
- const additionalCost = isElementCost(def.cost)
- ? { type: 'element' as const, element: def.cost.element, amount: def.cost.amount }
- : undefined;
-
- // Can start studying?
- let canStudy = !maxed && prereqMet && store.rawMana >= cost && !isStudying;
-
- // Check additional cost (element mana)
- if (isElementCost(def.cost)) {
- const element = store.elements[def.cost.element];
- if (!element || element.current < def.cost.amount) {
- canStudy = false;
- }
- }
-
- // Check for milestone upgrades
- const milestoneInfo = hasMilestoneUpgrade(
- tieredSkillId,
- level,
- store.skillTiers || {},
- store.skillUpgrades
- );
-
- // Check for tier up
- const nextTierSkill = getNextTierSkill(tieredSkillId);
- const canTierUp = Boolean(maxed && nextTierSkill);
-
- // Get selected upgrades
- const selectedUpgrades = store.skillUpgrades[tieredSkillId] || [];
- const selectedL5 = selectedUpgrades.filter((u) => u.includes('_l5'));
- const selectedL10 = selectedUpgrades.filter((u) => u.includes('_l10'));
-
- // Check if insufficient mana for toast
- const hasInsufficientMana = !isStudying && !maxed && store.rawMana < cost;
-
- // Check for parallel study eligibility
- const isParallelStudy =
- store.currentStudyTarget?.id === tieredSkillId &&
- store.currentStudyTarget?.type === 'skill';
- const canParallelStudy: boolean =
- hasSpecial(studyEffects, SPECIAL_EFFECTS.PARALLEL_STUDY) &&
- !!store.currentStudyTarget &&
- store.currentStudyTarget.id !== tieredSkillId &&
- !isStudying;
-
- return (
-
- );
- })}
-
-
+ }}
+ onStartStudying={() => onStartStudying(skillId)}
+ onParallelStudy={() => onParallelStudy(skillId)}
+ onTierUp={() => onTierUp(skillId)}
+ onOpenUpgradeDialog={(milestone) => onOpenUpgradeDialog(skillId, milestone)}
+ />
+ );
+ })}
+
)}
-
+
);
}
diff --git a/src/components/game/tabs/CraftingTab.tsx b/src/components/game/tabs/CraftingTab.tsx
index b3f2fb8..eeade5a 100755
--- a/src/components/game/tabs/CraftingTab.tsx
+++ b/src/components/game/tabs/CraftingTab.tsx
@@ -6,29 +6,25 @@ import { GameCard } from '@/components/ui/game-card';
import { SectionHeader } from '@/components/ui/section-header';
import { ActionButton } from '@/components/ui/action-button';
import { Scroll, Hammer, Sparkles, Anvil } from 'lucide-react';
-import type { EquipmentInstance, EnchantmentDesign, DesignEffect, AppliedEnchantment, LootInventory, EquipmentCraftingProgress } from '@/lib/game/types';
-import { fmt, type GameStore } from '@/lib/game/store';
+import { fmt } from '@/lib/game/stores';
import {
EnchantmentDesigner,
EnchantmentPreparer,
EnchantmentApplier,
EquipmentCrafter,
} from '@/components/game/crafting';
+import { useGameStore } from '@/lib/game/stores';
import { useGameToast } from '@/components/game/GameToast';
-export interface CraftingTabProps {
- store: GameStore;
-}
-
-export function CraftingTab({ store }: CraftingTabProps) {
+export function CraftingTab() {
const showToast = useGameToast();
- const currentAction = store.currentAction;
- const designProgress = store.designProgress;
- const preparationProgress = store.preparationProgress;
- const applicationProgress = store.applicationProgress;
- const equipmentCraftingProgress = store.equipmentCraftingProgress;
- const pauseApplication = store.pauseApplication;
- const resumeApplication = store.resumeApplication;
+ const currentAction = useGameStore((s) => s.currentAction);
+ const designProgress = useGameStore((s) => s.designProgress);
+ const preparationProgress = useGameStore((s) => s.preparationProgress);
+ const applicationProgress = useGameStore((s) => s.applicationProgress);
+ const equipmentCraftingProgress = useGameStore((s) => s.equipmentCraftingProgress);
+ const pauseApplication = useGameStore((s) => s.pauseApplication);
+ const resumeApplication = useGameStore((s) => s.resumeApplication);
const [activeTab, setActiveTab] = useState<'fabricate' | 'enchant'>('fabricate');
const [enchantStage, setEnchantStage] = useState<'design' | 'prepare' | 'apply'>('design');
@@ -81,7 +77,7 @@ export function CraftingTab({ store }: CraftingTabProps) {
{/* Fabricate Content: EquipmentCrafter */}
{activeTab === 'fabricate' && (
-
+
)}
{/* Enchant Content: Design → Prepare → Apply workflow */}
@@ -123,7 +119,6 @@ export function CraftingTab({ store }: CraftingTabProps) {
{/* Enchant Stage Content */}
{enchantStage === 'design' && (
{}}
selectedEffects={[]}
@@ -136,14 +131,12 @@ export function CraftingTab({ store }: CraftingTabProps) {
)}
{enchantStage === 'prepare' && (
{}}
/>
)}
{enchantStage === 'apply' && (
{}}
selectedDesign={null}
@@ -183,7 +176,7 @@ export function CraftingTab({ store }: CraftingTabProps) {
store.cancelDesign()}>
+ useGameStore.getState().cancelDesign()}>
Cancel
}
@@ -205,7 +198,7 @@ export function CraftingTab({ store }: CraftingTabProps) {
store.cancelPreparation()}>
+ useGameStore.getState().cancelPreparation()}>
Cancel
}
@@ -237,7 +230,7 @@ export function CraftingTab({ store }: CraftingTabProps) {
<>
Pause
{
- store.cancelApplication();
+ useGameStore.getState().cancelApplication();
showToast('warning', 'Enchantment Cancelled', 'The enchantment application was cancelled.');
}}>Cancel
>
diff --git a/src/components/game/tabs/EquipmentTab.tsx b/src/components/game/tabs/EquipmentTab.tsx
index 22dbbe5..81ba1b9 100755
--- a/src/components/game/tabs/EquipmentTab.tsx
+++ b/src/components/game/tabs/EquipmentTab.tsx
@@ -122,7 +122,7 @@ export function EquipmentTab() {
// Use modular store directly - MUST be called before any conditional returns
const equippedInstances = useCombatStore((s) => s.equippedInstances);
const equipmentInstances = useCombatStore((s) => s.equipmentInstances);
-
+
// Get unequipped items - hooks must be called before conditional returns
const equippedIds = useMemo(() =>
new Set(Object.values(equippedInstances || {}).filter(Boolean)),
@@ -230,8 +230,8 @@ export function EquipmentTab() {
// Get unified effects for equipment stats - move hook before conditional
const equipmentInstancesForEffects = useCombatStore((s) => s.equipmentInstances);
const equippedInstancesForEffects = useCombatStore((s) => s.equippedInstances);
-
- const unifiedEffects = equipmentInstancesForEffects && equippedInstancesForEffects
+
+ const unifiedEffects = equipmentInstancesForEffects && equippedInstancesForEffects
? getUnifiedEffects({ equipmentInstances, equippedInstances })
: null;
@@ -329,10 +329,10 @@ export function EquipmentTab() {
const enchantPower = effects.enchantmentPowerMultiplier || 1;
return (
<>
- 1 ? "success" : "default"}
+ highlight={enchantPower > 1 ? 'success' : 'default'}
/>
Increases the power of all enchantments by {(enchantPower - 1) * 100}%. Multiplier applied to all enchantment effects.
@@ -353,11 +353,11 @@ export function EquipmentTab() {
return No active effects ;
}
const effectEntries = Object.entries(effects.equipmentEffects).filter(([, v]) => v > 0);
-
+
if (effectEntries.length === 0) {
return No active effects ;
}
-
+
return effectEntries.map(([stat, value]) => (
{stat}: +{fmt(value)}
diff --git a/src/components/game/tabs/GolemancyTab.tsx b/src/components/game/tabs/GolemancyTab.tsx
index 1b02ad0..2450545 100755
--- a/src/components/game/tabs/GolemancyTab.tsx
+++ b/src/components/game/tabs/GolemancyTab.tsx
@@ -3,70 +3,67 @@
import { GameCard, StatRow, ElementBadge, ActionButton } from '@/components/ui';
import { ScrollArea } from '@/components/ui/scroll-area';
import { Separator } from '@/components/ui/separator';
-import {
- Mountain, Zap, Clock, Swords, Target, Sparkles, Lock, Check, X,
+import {
+ Mountain, Zap, Clock, Swords, Sparkles, Lock, Check, X,
Info, HelpCircle
} from 'lucide-react';
import { GOLEMS_DEF, getGolemSlots, isGolemUnlocked, getGolemDamage, getGolemAttackSpeed, getGolemFloorDuration } from '@/lib/game/data/golems';
import { ELEMENTS } from '@/lib/game/constants';
-import type { GameStore } from '@/lib/game/store';
+import { useGameStore, useManaStore, useSkillStore, useCombatStore } from '@/lib/game/stores';
-export interface GolemancyTabProps {
- store: GameStore;
-}
+export function GolemancyTab() {
+ const attunements = useGameStore((s) => s.attunements);
+ const elements = useManaStore((s) => s.elements);
+ const skills = useSkillStore((s) => s.skills);
+ const golemancy = useGameStore((s) => s.golemancy);
+ const currentFloor = useCombatStore((s) => s.currentFloor);
+ const currentRoom = useGameStore((s) => s.currentRoom);
+ const toggleGolem = useGameStore((s) => s.toggleGolem);
+ const rawMana = useManaStore((s) => s.rawMana);
-export function GolemancyTab({ store }: GolemancyTabProps) {
- const attunements = store.attunements;
- const elements = store.elements;
- const skills = store.skills;
- const golemancy = store.golemancy;
- const currentFloor = store.currentFloor;
- const currentRoom = store.currentRoom;
- const toggleGolem = store.toggleGolem;
-
// Get Fabricator level and golem slots
const fabricatorLevel = attunements.fabricator?.level || 0;
const fabricatorActive = attunements.fabricator?.active || false;
const maxSlots = getGolemSlots(fabricatorLevel);
-
+
// Get unlocked elements
const unlockedElements = Object.entries(elements)
.filter(([, e]) => e.unlocked)
.map(([id]) => id);
-
+
// Get all unlocked golems
- const unlockedGolems = Object.values(GOLEMS_DEF || {}).filter(golem =>
+ const unlockedGolems = Object.values(GOLEMS_DEF || {}).filter(golem =>
isGolemUnlocked(golem.id, attunements, unlockedElements)
);
-
+
// Check if golemancy is available
const hasGolemancy = fabricatorActive && fabricatorLevel >= 2;
-
+
// Check if currently in combat (not puzzle)
- const inCombat = currentRoom.roomType !== 'puzzle';
-
+ const inCombat = currentRoom?.roomType !== 'puzzle';
+
// Get element info helper
const getElementInfo = (elementId: string) => {
return ELEMENTS[elementId];
};
-
+
// Render a golem card
const renderGolemCard = (golemId: string, isUnlocked: boolean) => {
const golem = GOLEMS_DEF[golemId];
if (!golem) return null;
-
+
const isEnabled = golemancy.enabledGolems.includes(golemId);
const isSelected = golemancy.summonedGolems.some(g => g.golemId === golemId);
-
+
// Calculate effective stats
const damage = getGolemDamage(golemId, skills);
const attackSpeed = getGolemAttackSpeed(golemId, skills);
const floorDuration = getGolemFloorDuration(skills);
-
+
// Get element color
const primaryElement = getElementInfo(golem.baseManaType);
const elementId = golem.baseManaType;
-
+
if (!isUnlocked) {
// Locked golem card
return (
@@ -91,14 +88,14 @@ export function GolemancyTab({ store }: GolemancyTabProps) {
);
}
-
+
return (
- toggleGolem(golemId)}
@@ -119,7 +116,7 @@ export function GolemancyTab({ store }: GolemancyTabProps) {
)}
- T{golem.tier}
+ {golem.tier}
{isEnabled ? (
@@ -131,35 +128,35 @@ export function GolemancyTab({ store }: GolemancyTabProps) {
{golem.description}
-
+
-
+
-
+
-
+
{/* Summon Cost */}
Summon Cost:
{golem.summonCost.map((cost, idx) => {
const elem = getElementInfo(cost.element || '');
- const available = cost.type === 'raw'
- ? store.rawMana
+ const available = cost.type === 'raw'
+ ? rawMana
: elements[cost.element || '']?.current || 0;
const canAfford = available >= cost.amount;
-
+
return (
-
@@ -170,7 +167,7 @@ export function GolemancyTab({ store }: GolemancyTabProps) {
})}
-
+
{/* Maintenance Cost */}
Maintenance/hr:
@@ -185,7 +182,7 @@ export function GolemancyTab({ store }: GolemancyTabProps) {
})}
-
+
{/* Status */}
{isSelected && (
@@ -197,7 +194,7 @@ export function GolemancyTab({ store }: GolemancyTabProps) {
);
};
-
+
return (
{/* Header */}
@@ -216,26 +213,26 @@ export function GolemancyTab({ store }: GolemancyTabProps) {
) : (
<>
-
0 ? 'success' : undefined}
/>
-
-
-
-
+
Golems are automatically summoned at the start of each combat floor.
They cost mana to maintain and will be dismissed if you run out.
@@ -244,7 +241,7 @@ export function GolemancyTab({ store }: GolemancyTabProps) {
)}
-
+
{/* Active Golems - Empty State */}
{hasGolemancy && golemancy.summonedGolems.length === 0 && (
@@ -255,12 +252,12 @@ export function GolemancyTab({ store }: GolemancyTabProps) {
)}
-
+
{/* Active Golems */}
{hasGolemancy && golemancy.summonedGolems.length > 0 && (
-
+
Active Golems ({golemancy.summonedGolems.length})
@@ -269,7 +266,7 @@ export function GolemancyTab({ store }: GolemancyTabProps) {
{golemancy.summonedGolems.map(sg => {
const golem = GOLEMS_DEF[sg.golemId];
if (!golem) return null;
-
+
return (
@@ -280,7 +277,7 @@ export function GolemancyTab({ store }: GolemancyTabProps) {
)}
-
+
{/* Golem Selection */}
{hasGolemancy && (
@@ -291,7 +288,7 @@ export function GolemancyTab({ store }: GolemancyTabProps) {
{/* Unlocked Golems */}
{unlockedGolems.map(golem => renderGolemCard(golem.id, true))}
-
+
{/* Locked Golems */}
{Object.values(GOLEMS_DEF || {})
.filter(g => !isGolemUnlocked(g.id, attunements, unlockedElements))
@@ -300,7 +297,7 @@ export function GolemancyTab({ store }: GolemancyTabProps) {
)}
-
+
{/* Golemancy Skills Info */}
diff --git a/src/components/game/tabs/LootTab.tsx b/src/components/game/tabs/LootTab.tsx
index a4824c6..2efaf43 100755
--- a/src/components/game/tabs/LootTab.tsx
+++ b/src/components/game/tabs/LootTab.tsx
@@ -1,25 +1,25 @@
'use client';
-import type { GameStore } from '@/lib/game/store';
+import { useGameStore } from '@/lib/game/stores';
import { LootInventoryDisplay } from '@/components/game/LootInventory';
-export interface LootTabProps {
- store: GameStore;
-}
+export function LootTab() {
+ const lootInventory = useGameStore((s) => s.lootInventory);
+ const elements = useGameStore((s) => s.elements);
+ const equipmentInstances = useGameStore((s) => s.equipmentInstances);
+ const deleteMaterial = useGameStore((s) => s.deleteMaterial);
+ const deleteEquipmentInstance = useGameStore((s) => s.deleteEquipmentInstance);
-export function LootTab({ store }: LootTabProps) {
- const inventory = store.lootInventory;
- const elements = store.elements;
- const equipmentInstances = store.equipmentInstances;
-
return (
-
+
+
+
);
}
diff --git a/src/components/game/tabs/PrestigeTab.tsx b/src/components/game/tabs/PrestigeTab.tsx
index a8910d6..7d69ead 100644
--- a/src/components/game/tabs/PrestigeTab.tsx
+++ b/src/components/game/tabs/PrestigeTab.tsx
@@ -3,7 +3,7 @@
'use client';
import { useState } from 'react';
-import { useGameStore } from '@/lib/game/store';
+import { useGameStore, usePrestigeStore, useSkillStore, useManaStore } from '@/lib/game/stores';
import { useGameLoop } from '@/lib/game/stores/gameHooks';
import { getUnifiedEffects } from '@/lib/game/effects';
import {
@@ -15,13 +15,19 @@ import {
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
-import { fmt } from '@/lib/game/computed-stats';
+import { fmt } from '@/lib/game/stores';
export function PrestigeTab() {
const [selectedManaType, setSelectedManaType] = useState
('');
const store = useGameStore();
useGameLoop();
+
+ const skills = useSkillStore((s) => s.skills);
+ const prestigeUpgrades = usePrestigeStore((s) => s.prestigeUpgrades);
+ const rawMana = useManaStore((s) => s.rawMana);
+ const elements = useManaStore((s) => s.elements);
+
const upgradeEffects = getUnifiedEffects(store);
// Get unlocked elements for mana type selector
@@ -38,3 +44,62 @@ export function PrestigeTab() {
+ {/* Prestige Upgrades */}
+ {Object.entries(PRESTIGE_DEF || {}).map(([id, def]) => {
+ const level = prestigeUpgrades[id] || 0;
+ const canAfford = rawMana >= def.cost;
+ const effect = upgradeEffects ? upgradeEffects.specials.has(id) : false;
+
+ return (
+
+
+
+ {def.name}
+ {effect && Active }
+
+
+
+ {def.description}
+
+ Level: {level}/{def.maxLevel}
+ = def.maxLevel}
+ onClick={() => store.doPrestige(id)}
+ >
+ {level >= def.maxLevel ? 'Maxed' : `Upgrade (${fmt(def.cost)})`}
+
+
+
+
+ );
+ })}
+
+ {/* Mana Type Selection for Attunements */}
+
+
+ Select Mana Type for Attunement
+
+
+
+ {unlockedElements.map(elem => (
+ setSelectedManaType(elem.id)}
+ className="justify-start"
+ >
+ {elem.sym}
+ {elem.name}
+
+ ))}
+
+
+
+
+
+
+ );
+}
+
+PrestigeTab.displayName = "PrestigeTab";
diff --git a/src/components/game/tabs/SkillsTab.tsx b/src/components/game/tabs/SkillsTab.tsx
index 336eb44..902c780 100755
--- a/src/components/game/tabs/SkillsTab.tsx
+++ b/src/components/game/tabs/SkillsTab.tsx
@@ -1,4 +1,4 @@
-// ─── Skills Tab ───────────────────────────────────────────────────
+// ─── Skills Tab ───────────────────────────────────────────────────────────────
// SkillsTab - Displays all skills organized by category
// Refactored: extracted components for better modularity (reduced from 400 lines)
@@ -19,7 +19,7 @@ import {
} from '@/lib/game/skill-evolution';
import { getUnifiedEffects } from '@/lib/game/effects';
import { getAvailableSkillCategories } from '@/lib/game/data/attunements';
-import { fmt, fmtDec } from '@/lib/game/store';
+import { fmt, fmtDec } from '@/lib/game/stores';
import type { SkillUpgradeChoice } from '@/lib/game/types';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
@@ -39,13 +39,9 @@ import { ChevronDown, ChevronRight } from 'lucide-react';
import { SkillRow } from './SkillRow';
import { useSkillUpgradeSelection } from '@/lib/game/hooks/useSkillUpgradeSelection';
import { CategorySkillsList } from './CategorySkillsList';
-import type { GameStore } from '@/lib/game/store';
+import { useGameStore, useSkillStore, usePrestigeStore } from '@/lib/game/stores';
-export interface SkillsTabProps {
- store: GameStore;
-}
-
-export function SkillsTab({ store }: SkillsTabProps) {
+export function SkillsTab() {
const showToast = useGameToast();
const [upgradeDialogSkill, setUpgradeDialogSkill] = useState(null);
const [upgradeDialogMilestone, setUpgradeDialogMilestone] = useState<5 | 10>(5);
@@ -55,8 +51,20 @@ export function SkillsTab({ store }: SkillsTabProps) {
skillName: string;
} | null>(null);
- const studySpeedMult = getStudySpeedMultiplier(store.skills);
- const upgradeEffects = getUnifiedEffects(store as any);
+ const skills = useSkillStore((s) => s.skills);
+ const skillUpgrades = useSkillStore((s) => s.skillUpgrades);
+ const skillTiers = useSkillStore((s) => s.skillTiers);
+ const prestigeUpgrades = usePrestigeStore((s) => s.prestigeUpgrades);
+ const currentStudyTarget = useGameStore((s) => s.currentStudyTarget);
+ const parallelStudyTarget = useGameStore((s) => s.parallelStudyTarget);
+ const startStudyingSkill = useSkillStore((s) => s.startStudyingSkill);
+ const startParallelStudySkill = useSkillStore((s) => s.startParallelStudySkill);
+ const cancelStudy = useGameStore((s) => s.cancelStudy);
+ const commitSkillUpgrades = useSkillStore((s) => s.commitSkillUpgrades);
+ const tierUpSkill = useSkillStore((s) => s.tierUpSkill);
+
+ const studySpeedMult = getStudySpeedMultiplier({ skills, prestigeUpgrades, skillUpgrades, skillTiers });
+ const upgradeEffects = getUnifiedEffects({ skillUpgrades, skillTiers, equippedInstances: {}, equipmentInstances: {} });
// Upgrade selection hook
const {
@@ -84,7 +92,12 @@ export function SkillsTab({ store }: SkillsTabProps) {
const getUpgradeChoices = () => {
if (!upgradeDialogSkill)
return { available: [] as SkillUpgradeChoice[], selected: [] as string[] };
- return store.getSkillUpgradeChoices(upgradeDialogSkill, upgradeDialogMilestone);
+ const skillDef = SKILLS_DEF[upgradeDialogSkill.includes('_t') ? upgradeDialogSkill.split('_t')[0] : upgradeDialogSkill];
+ if (!skillDef) return { available: [] as SkillUpgradeChoice[], selected: [] as string[] };
+ return {
+ available: getUpgradesForSkillAtMilestone(upgradeDialogSkill, upgradeDialogMilestone),
+ selected: skillUpgrades[upgradeDialogSkill] || [],
+ };
};
const { available, selected: alreadySelected } = getUpgradeChoices();
@@ -92,11 +105,12 @@ export function SkillsTab({ store }: SkillsTabProps) {
// Handle upgrade dialog confirm
const handleConfirm = () => {
hookHandleConfirm(
- upgradeDialogSkill,
+ upgradeDialogSkill!,
upgradeDialogMilestone,
- (skillId, selections, milestone) =>
- store.commitSkillUpgrades(skillId, selections, milestone),
- () => setUpgradeDialogSkill(null)
+ (skillId: string, selections: string[], milestone: 5 | 10) => {
+ commitSkillUpgrades(skillId, selections, milestone);
+ return () => setUpgradeDialogSkill(null);
+ }
);
};
@@ -116,20 +130,20 @@ export function SkillsTab({ store }: SkillsTabProps) {
// Handle study start with toast
const handleStartStudying = (skillId: string) => {
const skillDef = SKILLS_DEF[skillId.includes('_t') ? skillId.split('_t')[0] : skillId];
- store.startStudyingSkill(skillId);
+ startStudyingSkill(skillId);
showToast('info', 'Study Started', `Studying ${skillDef?.name || 'skill'}...`);
};
// Handle parallel study start with toast
const handleParallelStudy = (skillId: string) => {
const skillDef = SKILLS_DEF[skillId.includes('_t') ? skillId.split('_t')[0] : skillId];
- store.startParallelStudySkill(skillId);
+ startParallelStudySkill(skillId);
showToast('info', 'Parallel Study Started', `Studying ${skillDef?.name || 'skill'} in parallel (50% speed)...`);
};
// Handle study cancel with confirmation
const handleCancelStudy = () => {
- const currentTarget = store.currentStudyTarget;
+ const currentTarget = currentStudyTarget;
if (currentTarget?.type === 'skill') {
const skillDef = SKILLS_DEF[currentTarget.id.includes('_t') ? currentTarget.id.split('_t')[0] : currentTarget.id];
setCancelStudyConfirm({
@@ -141,7 +155,7 @@ export function SkillsTab({ store }: SkillsTabProps) {
const confirmCancelStudy = () => {
if (cancelStudyConfirm) {
- store.cancelStudy();
+ cancelStudy();
showToast(
'warning',
'Study Cancelled',
@@ -152,7 +166,8 @@ export function SkillsTab({ store }: SkillsTabProps) {
};
// Get available skill categories based on attunements
- const availableCategories = getAvailableSkillCategories(store.attunements || {});
+ const attunements = useGameStore((s) => s.attunements);
+ const availableCategories = getAvailableSkillCategories(attunements || {});
return (
@@ -189,12 +204,12 @@ export function SkillsTab({ store }: SkillsTabProps) {
)}
{/* Current Study Progress */}
- {store.currentStudyTarget && store.currentStudyTarget.type === 'skill' && (
+ {currentStudyTarget && currentStudyTarget.type === 'skill' && (
@@ -210,10 +225,13 @@ export function SkillsTab({ store }: SkillsTabProps) {
availableCategories={availableCategories}
isCollapsed={collapsedCategories.has(cat.id)}
onToggleCategory={toggleCategory}
- store={store}
+ skills={skills}
+ skillUpgrades={skillUpgrades}
+ skillTiers={skillTiers}
+ prestigeUpgrades={prestigeUpgrades}
studySpeedMult={studySpeedMult}
upgradeEffects={upgradeEffects}
- currentStudyTarget={store.currentStudyTarget}
+ currentStudyTarget={currentStudyTarget}
onStartStudying={handleStartStudying}
onParallelStudy={handleParallelStudy}
onCancelStudy={handleCancelStudy}
@@ -222,7 +240,7 @@ export function SkillsTab({ store }: SkillsTabProps) {
setUpgradeDialogMilestone(milestone);
setPendingSelections([]);
}}
- onTierUp={(skillId) => store.tierUpSkill(skillId)}
+ onTierUp={(skillId) => tierUpSkill(skillId)}
pendingSelections={pendingSelections}
setPendingSelections={setPendingSelections}
/>
@@ -230,4 +248,5 @@ export function SkillsTab({ store }: SkillsTabProps) {
);
}
+
SkillsTab.displayName = "SkillsTab";
diff --git a/src/components/game/tabs/SpireTab.tsx b/src/components/game/tabs/SpireTab.tsx
index db94af4..f063be1 100755
--- a/src/components/game/tabs/SpireTab.tsx
+++ b/src/components/game/tabs/SpireTab.tsx
@@ -1,17 +1,19 @@
'use client';
+import { useMemo } from 'react';
import { Button } from '@/components/ui/button';
import { Card, CardContent } from '@/components/ui/card';
import { Mountain } from 'lucide-react';
import type { ActivityLogEntry } from '@/lib/game/types';
-import type { GameStore } from '@/lib/game/store';
-import { ELEMENTS, GUARDIANS, SPELLS_DEF } from '@/lib/game/constants';
-import { calcDamage } from '@/lib/game/store';
+import { ELEMENTS, GUARDIANS, SPELLS_DEF, SKILLS_DEF } from '@/lib/game/constants';
+import { calcDamage } from '@/lib/game/stores';
import { getEnemyName } from '@/lib/game/store-modules/enemy-utils';
import { getActiveEquipmentSpells, getTotalDPS } from '@/lib/game/computed-stats';
import { getUnifiedEffects } from '@/lib/game/effects';
import { formatSpellCost, getSpellCostColor } from '@/lib/game/formatting';
-import { canAffordSpellCost, getFloorElement } from '@/lib/game/store';
+import { canAffordSpellCost, getFloorElement } from '@/lib/game/stores';
+import { useGameStore, useManaStore, useSkillStore, useCombatStore, usePrestigeStore } from '@/lib/game/stores';
+import { GOLEMS_DEF, getGolemDamage, getGolemAttackSpeed } from '@/lib/game/data/golems';
// Extracted components
import { SpireHeader } from './SpireHeader';
@@ -31,31 +33,77 @@ const ROOM_TYPE_CONFIG: Record {
- return !store.spireMode;
+const canEnterSpireMode = (spireMode: boolean): boolean => {
+ return !spireMode;
};
-export function SpireTab({ store, simpleMode = false }: SpireTabProps) {
+export function SpireTab({ simpleMode = false }: SpireTabProps) {
+ // Get state from modular stores
+ const currentFloor = useCombatStore((s) => s.currentFloor);
+ const floorHP = useCombatStore((s) => s.floorHP);
+ const floorMaxHP = useCombatStore((s) => s.floorMaxHP);
+ const maxFloorReached = useCombatStore((s) => s.maxFloorReached);
+ const currentAction = useCombatStore((s) => s.currentAction);
+ const castProgress = useCombatStore((s) => s.castProgress);
+ const activeSpell = useCombatStore((s) => s.activeSpell);
+ const startClimbUp = useCombatStore((s) => s.startClimbUp);
+ const startClimbDown = useCombatStore((s) => s.startClimbDown);
+ const enterSpireMode = useGameStore((s) => s.enterSpireMode);
+ const spireMode = useGameStore((s) => s.spireMode);
+ const climbDirection = useGameStore((s) => s.climbDirection) || 'up';
+ const clearedFloors = useGameStore((s) => s.clearedFloors || {});
+ const currentRoom = useGameStore((s) => s.currentRoom);
+ const equipmentSpellStates = useGameStore((s) => s.equipmentSpellStates);
+ const golemancy = useGameStore((s) => s.golemancy);
+ const activityLog = useGameStore((s) => s.activityLog);
+ const currentStudyTarget = useGameStore((s) => s.currentStudyTarget);
+ const parallelStudyTarget = useGameStore((s) => s.parallelStudyTarget);
+ const signedPacts = usePrestigeStore((s) => s.signedPacts);
+ const skills = useSkillStore((s) => s.skills);
+ const skillUpgrades = useSkillStore((s) => s.skillUpgrades);
+ const skillTiers = useSkillStore((s) => s.skillTiers);
+ const rawMana = useManaStore((s) => s.rawMana);
+ const elements = useManaStore((s) => s.elements);
+ const equippedInstances = useGameStore((s) => s.equippedInstances);
+ const equipmentInstances = useGameStore((s) => s.equipmentInstances);
+ const designProgress = useGameStore((s) => s.designProgress);
+ const designProgress2 = useGameStore((s) => s.designProgress2);
+ const preparationProgress = useGameStore((s) => s.preparationProgress);
+ const applicationProgress = useGameStore((s) => s.applicationProgress);
+ const equipmentCraftingProgress = useGameStore((s) => s.equipmentCraftingProgress);
+
// Derived data
- const floorElem = getFloorElement(store.currentFloor);
+ const floorElem = getFloorElement(currentFloor);
const floorElemDef = ELEMENTS[floorElem];
- const isGuardianFloor = !!GUARDIANS[store.currentFloor];
- const currentGuardian = GUARDIANS[store.currentFloor];
- const climbDirection = store.climbDirection || 'up';
- const clearedFloors = store.clearedFloors || {};
- const currentRoom = store.currentRoom;
- const isFloorCleared = clearedFloors[store.currentFloor];
+ const isGuardianFloor = !!GUARDIANS[currentFloor];
+ const currentGuardian = GUARDIANS[currentFloor];
+ const isFloorCleared = clearedFloors[currentFloor];
const roomType = currentRoom?.roomType || 'combat';
const roomConfig = ROOM_TYPE_CONFIG[roomType] || ROOM_TYPE_CONFIG.combat;
- const activeEquipmentSpells = getActiveEquipmentSpells(store.equippedInstances, store.equipmentInstances);
- const upgradeEffects = getUnifiedEffects(store);
- const totalDPS = getTotalDPS(store, upgradeEffects, floorElem);
- const studySpeedMult = 1;
+
+ const activeEquipmentSpells = useMemo(
+ () => getActiveEquipmentSpells(equippedInstances, equipmentInstances),
+ [equippedInstances, equipmentInstances]
+ );
+
+ const upgradeEffects = useMemo(
+ () => getUnifiedEffects({
+ skillUpgrades,
+ skillTiers,
+ equippedInstances,
+ equipmentInstances,
+ }),
+ [skillUpgrades, skillTiers, equippedInstances, equipmentInstances]
+ );
+
+ const totalDPS = useMemo(
+ () => getTotalDPS({ skills, signedPacts, skillUpgrades, skillTiers }, upgradeEffects, floorElem),
+ [skills, signedPacts, skillUpgrades, skillTiers, upgradeEffects, floorElem]
+ );
// Enemy display info
const primaryEnemy = currentRoom?.enemies?.[0] || null;
@@ -65,18 +113,22 @@ export function SpireTab({ store, simpleMode = false }: SpireTabProps) {
const canCastSpell = (spellId: string): boolean => {
const spell = SPELLS_DEF[spellId];
if (!spell) return false;
- return canAffordSpellCost(spell.cost, store.rawMana, store.elements);
+ return canAffordSpellCost(spell.cost, rawMana, elements);
};
// Climb handler
const handleClimb = (direction: 'up' | 'down') => {
if (direction === 'up') {
- store.startClimbUp();
+ startClimbUp();
} else {
- store.startClimbDown();
+ startClimbDown();
}
};
+ const getSkillName = (skillId: string): string => {
+ return SKILLS_DEF[skillId]?.name || skillId;
+ };
+
return (
{/* Enter Spire Mode - Normal mode only */}
@@ -86,8 +138,8 @@ export function SpireTab({ store, simpleMode = false }: SpireTabProps) {
store.enterSpireMode()}
- disabled={!canEnterSpireMode(store)}
+ onClick={enterSpireMode}
+ disabled={!canEnterSpireMode(spireMode)}
>
Enter Spire Mode
@@ -101,9 +153,9 @@ export function SpireTab({ store, simpleMode = false }: SpireTabProps) {
{/* Spire Header */}
{
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;
@@ -142,9 +194,9 @@ export function SpireTab({ store, simpleMode = false }: SpireTabProps) {
{canCast ? '✓' : '✗'}
- ⚔️ {fmt(calcDamage(store, spellId))} dmg • {formatSpellCost(spellDef.cost)} {' '}• ⚡ {fmt(Math.floor(calcDamage(store, spellId) * (spellDef.castSpeed || 1)))} dmg/hr
+ ⚔️ {calcDamage({ skills, signedPacts, skillUpgrades, skillTiers }, spellId, floorElem)} dmg • {formatSpellCost(spellDef.cost)} {' '}• ⚡ {Math.floor(calcDamage({ skills, signedPacts, skillUpgrades, skillTiers }, spellId, floorElem) * (spellDef.castSpeed || 1))} dmg/hr
- {store.currentAction === 'climb' && (
+ {currentAction === 'climb' && (
Cast
@@ -167,20 +219,20 @@ export function SpireTab({ store, simpleMode = false }: SpireTabProps) {
)}
{/* Summoned Golems */}
- {simpleMode && store.golemancy.summonedGolems.length > 0 && (
+ {simpleMode && golemancy.summonedGolems.length > 0 && (
- Active Golems ({store.golemancy.summonedGolems.length})
+ Active Golems ({golemancy.summonedGolems.length})
- {store.golemancy.summonedGolems.map((summoned) => {
- const golemDef = getGolemDef(summoned.golemId);
+ {golemancy.summonedGolems.map((summoned) => {
+ const golemDef = GOLEMS_DEF[summoned.golemId];
if (!golemDef) return null;
const elemColor = ELEMENTS[golemDef.baseManaType]?.color || '#888';
- const damage = getGolemDamage(summoned.golemId, store.skills);
- const attackSpeed = getGolemAttackSpeed(summoned.golemId, store.skills);
+ const damage = getGolemDamage(summoned.golemId, skills);
+ const attackSpeed = getGolemAttackSpeed(summoned.golemId, skills);
return (
@@ -192,7 +244,7 @@ export function SpireTab({ store, simpleMode = false }: SpireTabProps) {
{golemDef.isAoe && AOE {golemDef.aoeTargets} }
⚔️ {damage} DMG • ⚡ {attackSpeed.toFixed(1)}/hr
- {store.currentAction === 'climb' && summoned.attackProgress > 0 && (
+ {currentAction === 'climb' && summoned.attackProgress > 0 && (
Attack
@@ -213,7 +265,7 @@ export function SpireTab({ store, simpleMode = false }: SpireTabProps) {
{/* Guardian Panel */}
{isGuardianFloor && simpleMode && (
-
+
)}
{/* Room Display */}
@@ -227,10 +279,10 @@ export function SpireTab({ store, simpleMode = false }: SpireTabProps) {
puzzleProgress={currentRoom?.puzzleProgress}
simpleMode={true}
floorElemDef={floorElemDef}
- floorHP={store.floorHP}
- floorMaxHP={store.floorMaxHP}
+ floorHP={floorHP}
+ floorMaxHP={floorMaxHP}
totalDPS={totalDPS}
- currentAction={store.currentAction}
+ currentAction={currentAction}
activeEquipmentSpells={activeEquipmentSpells}
/>
)}
@@ -238,7 +290,7 @@ export function SpireTab({ store, simpleMode = false }: SpireTabProps) {
{/* Floor Controls */}
{simpleMode && (
)}
{/* Activity Log - Spire Mode only */}
- {simpleMode &&
}
+ {simpleMode &&
}
{/* Study Progress - Normal mode only */}
- {!simpleMode && store.currentStudyTarget && (
+ {!simpleMode && currentStudyTarget && (
- Study: {getSkillName(store.currentStudyTarget.id)}
+ Study: {getSkillName(currentStudyTarget.id)}
- {store.parallelStudyTarget && (
+ {parallelStudyTarget && (
-
Parallel: {getSkillName(store.parallelStudyTarget.id)} (50% speed)
+
Parallel: {getSkillName(parallelStudyTarget.id)} (50% speed)
)}
@@ -303,30 +353,30 @@ export function SpireTab({ store, simpleMode = false }: SpireTabProps) {
)}
{/* Crafting Progress - Normal mode only */}
- {!simpleMode && (store.designProgress || store.preparationProgress || store.applicationProgress) && (
+ {!simpleMode && (designProgress || preparationProgress || applicationProgress) && (
- {store.designProgress && (
+ {designProgress && (
)}
- {store.preparationProgress && (
+ {preparationProgress && (
)}
- {store.applicationProgress && (
+ {applicationProgress && (
)}
@@ -338,31 +388,3 @@ export function SpireTab({ store, simpleMode = false }: SpireTabProps) {
}
SpireTab.displayName = "SpireTab";
-
-function getSkillName(skillId: string): string {
- const { SKILLS_DEF } = require('@/lib/game/constants');
- return SKILLS_DEF[skillId]?.name || skillId;
-}
-
-function fmt(value: number): string {
- if (value >= 1e12) return (value / 1e12).toFixed(2) + 't';
- if (value >= 1e9) return (value / 1e9).toFixed(2) + 'b';
- if (value >= 1e6) return (value / 1e6).toFixed(2) + 'm';
- if (value >= 1e3) return (value / 1e3).toFixed(2) + 'k';
- return value.toFixed(0);
-}
-
-function getGolemDef(golemId: string) {
- const { GOLEMS_DEF } = require('@/lib/game/data/golems');
- return GOLEMS_DEF[golemId];
-}
-
-function getGolemDamage(golemId: string, skills: any) {
- const { getGolemDamage } = require('@/lib/game/data/golems');
- return getGolemDamage(golemId, skills);
-}
-
-function getGolemAttackSpeed(golemId: string, skills: any) {
- const { getGolemAttackSpeed } = require('@/lib/game/data/golems');
- return getGolemAttackSpeed(golemId, skills);
-}
diff --git a/src/lib/game/stores/combatStore.ts b/src/lib/game/stores/combatStore.ts
index acbd7f1..3fa210b 100755
--- a/src/lib/game/stores/combatStore.ts
+++ b/src/lib/game/stores/combatStore.ts
@@ -46,7 +46,7 @@ export interface CombatState {
attackSpeedMult: number,
onFloorCleared: (floor: number, wasGuardian: boolean) => void,
onDamageDealt: (damage: number) => { rawMana: number; elements: Record },
- ) => { rawMana: number; elements: Record; logMessages: string[] };
+ ) => { rawMana: number; elements: Record; logMessages: string[]; totalManaGathered: number };
// Reset
resetCombat: (startFloor: number, spellsToKeep?: string[]) => void;
@@ -211,7 +211,7 @@ export const useCombatStore = create()(
set({
currentFloor,
floorHP,
- floorMaxHP,
+ floorMaxHP: getFloorMaxHP(currentFloor),
maxFloorReached: Math.max(state.maxFloorReached, currentFloor),
castProgress,
});
diff --git a/src/lib/game/stores/gameHooks.ts b/src/lib/game/stores/gameHooks.ts
index 130e0d9..fedb6f0 100644
--- a/src/lib/game/stores/gameHooks.ts
+++ b/src/lib/game/stores/gameHooks.ts
@@ -1,5 +1,18 @@
import { useEffect } from 'react';
import { useGameStore } from './gameStore';
+import { useManaStore } from './manaStore';
+import { useSkillStore } from './skillStore';
+import { usePrestigeStore } from './prestigeStore';
+import { useCombatStore } from './combatStore';
+import { useUIStore } from './uiStore';
+import { getUnifiedEffects } from '../effects';
+import {
+ computeMaxMana,
+ computeRegen,
+ computeClickMana,
+ getMeditationBonus,
+ getIncursionStrength,
+} from '../utils';
import { TICK_MS } from '../constants';
export function useGameLoop() {
@@ -10,3 +23,129 @@ export function useGameLoop() {
return () => clearInterval(interval);
}, [tick]);
}
+
+// ─── Shared Selector Hooks for Common Derived State ────────────────────────────
+
+/**
+ * Get unified effects from all relevant stores
+ */
+export function useUnifiedEffects() {
+ const skillUpgrades = useSkillStore((s) => s.skillUpgrades);
+ const skillTiers = useSkillStore((s) => s.skillTiers);
+ const equippedInstances = useGameStore((s) => s.equippedInstances);
+ const equipmentInstances = useGameStore((s) => s.equipmentInstances);
+
+ return getUnifiedEffects({
+ skillUpgrades,
+ skillTiers,
+ equippedInstances,
+ equipmentInstances,
+ });
+}
+
+/**
+ * Get computed mana stats (maxMana, baseRegen, clickMana, meditationMultiplier, effectiveRegen)
+ */
+export function useManaStats() {
+ const skills = useSkillStore((s) => s.skills);
+ const prestigeUpgrades = usePrestigeStore((s) => s.prestigeUpgrades);
+ const skillUpgrades = useSkillStore((s) => s.skillUpgrades);
+ const skillTiers = useSkillStore((s) => s.skillTiers);
+ const meditateTicks = useManaStore((s) => s.meditateTicks);
+ const day = useGameStore((s) => s.day);
+ const hour = useGameStore((s) => s.hour);
+
+ const upgradeEffects = getUnifiedEffects({
+ skillUpgrades,
+ skillTiers,
+ equippedInstances: {},
+ equipmentInstances: {},
+ });
+
+ const maxMana = computeMaxMana(
+ { skills, prestigeUpgrades, skillUpgrades, skillTiers },
+ upgradeEffects
+ );
+
+ const baseRegen = computeRegen(
+ { skills, prestigeUpgrades, skillUpgrades, skillTiers },
+ upgradeEffects
+ );
+
+ const clickMana = computeClickMana({
+ skills,
+ prestigeUpgrades,
+ skillUpgrades,
+ skillTiers,
+ });
+
+ const meditationMultiplier = getMeditationBonus(meditateTicks, skills, upgradeEffects.meditationEfficiency);
+ const incursionStrength = getIncursionStrength(day, hour);
+ const effectiveRegenWithSpecials = baseRegen * (1 - incursionStrength);
+
+ // Mana Cascade bonus
+ const manaCascadeBonus = upgradeEffects.specials.has('mana_cascade')
+ ? Math.floor(maxMana / 100) * 0.1
+ : 0;
+
+ // Mana Waterfall bonus
+ const manaWaterfallBonus = upgradeEffects.specials.has('mana_waterfall')
+ ? Math.floor(maxMana / 100) * 0.25
+ : 0;
+
+ const effectiveRegen = (effectiveRegenWithSpecials + manaCascadeBonus + manaWaterfallBonus) * meditationMultiplier;
+
+ return {
+ maxMana,
+ baseRegen,
+ effectiveRegen,
+ incursionStrength,
+ clickMana,
+ meditationMultiplier,
+ manaCascadeBonus,
+ manaWaterfallBonus,
+ upgradeEffects,
+ };
+}
+
+/**
+ * Get combat-related derived state
+ */
+export function useCombatStats() {
+ const skills = useSkillStore((s) => s.skills);
+ const signedPacts = usePrestigeStore((s) => s.signedPacts);
+ const equippedInstances = useGameStore((s) => s.equippedInstances);
+ const equipmentInstances = useGameStore((s) => s.equipmentInstances);
+ const skillUpgrades = useSkillStore((s) => s.skillUpgrades);
+ const skillTiers = useSkillStore((s) => s.skillTiers);
+
+ const upgradeEffects = getUnifiedEffects({
+ skillUpgrades,
+ skillTiers,
+ equippedInstances,
+ equipmentInstances,
+ });
+
+ return {
+ skills,
+ signedPacts,
+ equippedInstances,
+ equipmentInstances,
+ upgradeEffects,
+ };
+}
+
+/**
+ * Get equipment-related derived state
+ */
+export function useEquipmentState() {
+ const equippedInstances = useGameStore((s) => s.equippedInstances);
+ const equipmentInstances = useGameStore((s) => s.equipmentInstances);
+ const elements = useManaStore((s) => s.elements);
+
+ return {
+ equippedInstances,
+ equipmentInstances,
+ elements,
+ };
+}