Documentation: - Add comprehensive README.md with project overview - Update AGENTS.md with new file structure and slice pattern - Add AUDIT_REPORT.md documenting unimplemented effects Refactoring (page.tsx: 1695 → 434 lines, 74% reduction): - Extract SkillsTab.tsx component - Extract StatsTab.tsx component - Extract UpgradeDialog.tsx component - Move getDamageBreakdown and getTotalDPS to computed-stats.ts - Move ELEMENT_ICON_NAMES to constants.ts All lint checks pass, functionality preserved.
12 KiB
Mana Loop - Project Architecture Guide
This document provides a comprehensive overview of the project architecture for AI agents working on this codebase.
⚠️ MANDATORY GIT WORKFLOW - MUST BE FOLLOWED
Before starting ANY work, you MUST:
-
Pull the latest changes:
cd /home/z/my-project && git pull origin master -
Do your task - Make all necessary code changes
-
Before finishing, commit and push:
cd /home/z/my-project git add -A git commit -m "descriptive message about changes" git push origin master
This workflow is ENFORCED and NON-NEGOTIABLE. Every agent session must:
- Start with
git pull - End with
git add,git commit,git push
Git Remote: git@gitea.tailf367e3.ts.net:Anexim/Mana-Loop.git
Project Overview
Mana Loop is an incremental/idle game built with:
- Framework: Next.js 16 with App Router
- Language: TypeScript 5
- Styling: Tailwind CSS 4 with shadcn/ui components
- State Management: Zustand with persist middleware
- Database: Prisma ORM with SQLite (for persistence features)
Core Game Loop
- Mana Gathering: Click or auto-generate mana over time
- Studying: Spend mana to learn skills and spells
- Combat: Climb the Spire, defeat guardians, sign pacts
- Crafting: Enchant equipment with spell effects
- Prestige: Reset progress for permanent bonuses (Insight)
Directory Structure
src/
├── app/
│ ├── page.tsx # Main game UI (~1700 lines, single page application)
│ ├── layout.tsx # Root layout with providers
│ └── api/ # API routes (minimal use)
├── components/
│ ├── ui/ # shadcn/ui components (auto-generated)
│ └── game/
│ ├── index.ts # Barrel exports
│ ├── ActionButtons.tsx # Main action buttons (Meditate, Climb, Study, etc.)
│ ├── CalendarDisplay.tsx # Day calendar with incursion indicators
│ ├── CraftingProgress.tsx # Design/preparation/application progress bars
│ ├── StudyProgress.tsx # Current study progress with cancel button
│ ├── ManaDisplay.tsx # Mana/gathering section with progress bar
│ ├── TimeDisplay.tsx # Day/hour display with pause toggle
│ └── tabs/ # Tab-specific components
│ ├── index.ts # Tab component exports
│ ├── CraftingTab.tsx # Enchantment crafting UI
│ ├── LabTab.tsx # Skill upgrade and lab features
│ ├── SpellsTab.tsx # Spell management and equipment spells
│ └── SpireTab.tsx # Combat and spire climbing
└── lib/
├── game/
│ ├── store.ts # Zustand store (~1650 lines, main state + tick logic)
│ ├── computed-stats.ts # Computed stats functions (extracted utilities)
│ ├── navigation-slice.ts # Floor navigation actions (setClimbDirection, changeFloor)
│ ├── study-slice.ts # Study system actions (startStudying*, cancelStudy)
│ ├── crafting-slice.ts # Equipment/enchantment logic
│ ├── familiar-slice.ts # Familiar system actions
│ ├── effects.ts # Unified effect computation
│ ├── upgrade-effects.ts # Skill upgrade effect definitions
│ ├── constants.ts # Game definitions (spells, skills, etc.)
│ ├── skill-evolution.ts # Skill tier progression paths
│ ├── types.ts # TypeScript interfaces
│ ├── formatting.ts # Display formatters
│ ├── utils.ts # Utility functions
│ └── data/
│ ├── equipment.ts # Equipment type definitions
│ └── enchantment-effects.ts # Enchantment effect catalog
└── utils.ts # General utilities (cn function)
Key Systems
1. State Management (store.ts)
The game uses a Zustand store organized with slice pattern for better maintainability:
Store Slices
- Main Store (
store.ts): Core state, tick logic, and main actions - Navigation Slice (
navigation-slice.ts): Floor navigation (setClimbDirection, changeFloor) - Study Slice (
study-slice.ts): Study system (startStudyingSkill, startStudyingSpell, cancelStudy) - Crafting Slice (
crafting-slice.ts): Equipment/enchantment (createEquipmentInstance, startDesigningEnchantment) - Familiar Slice (
familiar-slice.ts): Familiar system (addFamiliar, removeFamiliar)
Computed Stats (computed-stats.ts)
Extracted utility functions for stat calculations:
computeMaxMana(),computeRegen(),computeEffectiveRegen()calcDamage(),calcInsight(),getElementalBonus()getFloorMaxHP(),getFloorElement(),getMeditationBonus()canAffordSpellCost(),deductSpellCost()
interface GameState {
// Time
day: number;
hour: number;
paused: boolean;
// Mana
rawMana: number;
elements: Record<string, ElementState>;
// Combat
currentFloor: number;
floorHP: number;
activeSpell: string;
castProgress: number;
// Progression
skills: Record<string, number>;
spells: Record<string, SpellState>;
skillUpgrades: Record<string, string[]>;
skillTiers: Record<string, number>;
// Equipment
equipmentInstances: Record<string, EquipmentInstance>;
equippedInstances: Record<string, string | null>;
enchantmentDesigns: EnchantmentDesign[];
// Prestige
insight: number;
prestigeUpgrades: Record<string, number>;
signedPacts: number[];
}
2. Effect System (effects.ts)
CRITICAL: All stat modifications flow through the unified effect system.
// Effects come from two sources:
// 1. Skill Upgrades (milestone bonuses)
// 2. Equipment Enchantments (crafted bonuses)
getUnifiedEffects(state) => UnifiedEffects {
maxManaBonus, maxManaMultiplier,
regenBonus, regenMultiplier,
clickManaBonus, clickManaMultiplier,
baseDamageBonus, baseDamageMultiplier,
attackSpeedMultiplier,
critChanceBonus, critDamageMultiplier,
studySpeedMultiplier,
specials: Set<string>, // Special effect IDs
}
When adding new stats:
- Add to
ComputedEffectsinterface inupgrade-effects.ts - Add mapping in
computeEquipmentEffects()ineffects.ts - Apply in the relevant game logic (tick, damage calc, etc.)
3. Combat System
Combat uses a cast speed system:
- Each spell has
castSpeed(casts per hour) - Cast progress accumulates:
progress += castSpeed * attackSpeedMultiplier * HOURS_PER_TICK - When
progress >= 1, spell is cast (cost deducted, damage dealt) - DPS =
damagePerCast * castsPerSecond
Damage calculation order:
- Base spell damage
- Skill bonuses (combatTrain, arcaneFury, etc.)
- Upgrade effects (multipliers, bonuses)
- Special effects (Overpower, Berserker, etc.)
- Elemental modifiers (same element +25%, super effective +50%)
4. Crafting/Enchantment System
Three-stage process:
- Design: Select effects, takes time based on complexity
- Prepare: Pay mana to prepare equipment, takes time
- Apply: Apply design to equipment, costs mana per hour
Equipment has capacity that limits total enchantment power.
5. Skill Evolution System
Skills have 5 tiers of evolution:
- At level 5: Choose 2 of 4 milestone upgrades
- At level 10: Choose 2 more upgrades, then tier up
- Each tier multiplies the skill's base effect by 10x
Important Patterns
Adding a New Effect
- Define in
enchantment-effects.ts:
my_new_effect: {
id: 'my_new_effect',
name: 'Effect Name',
description: '+10% something',
category: 'combat',
baseCapacityCost: 30,
maxStacks: 3,
allowedEquipmentCategories: ['caster', 'hands'],
effect: { type: 'multiplier', stat: 'attackSpeed', value: 1.10 }
}
- Add stat mapping in
effects.ts(if new stat):
// In computeEquipmentEffects()
if (effect.stat === 'myNewStat') {
bonuses.myNewStat = (bonuses.myNewStat || 0) + effect.value;
}
- Apply in game logic:
const effects = getUnifiedEffects(state);
damage *= effects.myNewStatMultiplier;
Adding a New Skill
- Define in
constants.tsSKILLS_DEF - Add evolution path in
skill-evolution.ts - Add prerequisite checks in
store.ts - Update UI in
page.tsx
Adding a New Spell
- Define in
constants.tsSPELLS_DEF - Add spell enchantment in
enchantment-effects.ts - Add research skill in
constants.ts - Map research to effect in
EFFECT_RESEARCH_MAPPING
Common Pitfalls
- Forgetting to call
getUnifiedEffects(): Always use unified effects for stat calculations - Direct stat modification: Never modify stats directly; use effect system
- Missing tier multiplier: Use
getTierMultiplier(skillId)for tiered skills - Ignoring special effects: Check
hasSpecial(effects, SPECIAL_EFFECTS.X)for special abilities
Testing Guidelines
- Run
bun run lintafter changes - Check dev server logs at
/home/z/my-project/dev.log - Test with fresh game state (clear localStorage)
Slice Pattern for Store Organization
The store uses a slice pattern to organize related actions into separate files. This improves maintainability and makes the codebase more modular.
Creating a New Slice
- Create the slice file (e.g.,
my-feature-slice.ts):
// Define the actions interface
export interface MyFeatureActions {
doSomething: (param: string) => void;
undoSomething: () => void;
}
// Create the slice factory
export function createMyFeatureSlice(
set: StoreApi<GameStore>['setState'],
get: StoreApi<GameStore>['getState']
): MyFeatureActions {
return {
doSomething: (param: string) => {
set((state) => {
// Update state
});
},
undoSomething: () => {
set((state) => {
// Update state
});
},
};
}
- Add to main store (
store.ts):
import { createMyFeatureSlice, MyFeatureActions } from './my-feature-slice';
// Extend GameStore interface
interface GameStore extends GameState, MyFeatureActions, /* other slices */ {}
// Spread into store creation
const useGameStore = create<GameStore>()(
persist(
(set, get) => ({
...createMyFeatureSlice(set, get),
// other slices and state
}),
// persist config
)
);
Existing Slices
| Slice | File | Purpose |
|---|---|---|
| Navigation | navigation-slice.ts |
Floor navigation (setClimbDirection, changeFloor) |
| Study | study-slice.ts |
Study system (startStudyingSkill, startStudyingSpell, cancelStudy) |
| Crafting | crafting-slice.ts |
Equipment/enchantment (createEquipmentInstance, startDesigningEnchantment) |
| Familiar | familiar-slice.ts |
Familiar system (addFamiliar, removeFamiliar) |
File Size Guidelines
Current File Sizes (After Refactoring)
| File | Lines | Notes |
|---|---|---|
store.ts |
~1650 | Core state + tick logic (reduced from 2138, 23% reduction) |
page.tsx |
~1695 | Main UI (reduced from 2554, 34% reduction) |
computed-stats.ts |
~200 | Extracted utility functions |
navigation-slice.ts |
~50 | Navigation actions |
study-slice.ts |
~100 | Study system actions |
Guidelines
- Keep
page.tsxunder 2000 lines by extracting to components (ActionButtons, ManaDisplay, etc.) - Keep
store.tsunder 1800 lines by extracting to slices (navigation, study, crafting, familiar) - Extract computed stats and utility functions to
computed-stats.tswhen >50 lines - Use barrel exports (
index.ts) for clean imports - Follow the slice pattern for store organization (see below)