diff --git a/docs/circular-deps.txt b/docs/circular-deps.txt index b1c9b6c..b41ef31 100644 --- a/docs/circular-deps.txt +++ b/docs/circular-deps.txt @@ -1,8 +1,8 @@ # Circular Dependencies -Generated: 2026-05-26T09:20:42.073Z +Generated: 2026-05-26T15:02:41.168Z Found: 6 circular chain(s) — these MUST be fixed before modifying involved files. -1. Processed 135 files (1.7s) (2 warnings) +1. Processed 135 files (1.5s) (2 warnings) 2. 1) utils/floor-utils.ts > utils/room-utils.ts > utils/enemy-utils.ts 3. 2) utils/floor-utils.ts > utils/room-utils.ts 4. 3) stores/gameStore.ts > stores/gameActions.ts diff --git a/docs/dependency-graph.json b/docs/dependency-graph.json index f9cf806..2b5a979 100644 --- a/docs/dependency-graph.json +++ b/docs/dependency-graph.json @@ -1,6 +1,6 @@ { "_meta": { - "generated": "2026-05-26T09:20:40.084Z", + "generated": "2026-05-26T15:02:39.505Z", "description": "Import dependency graph for src/lib/game. Keys are files, values are arrays of files they import.", "usage": "To find what a file affects, search for its path in the VALUES. To find what a file depends on, look at its KEY entry." }, @@ -567,6 +567,7 @@ "stores/prestigeStore.ts": [ "constants.ts", "data/guardian-encounters.ts", + "stores/manaStore.ts", "utils/result.ts", "utils/safe-persist.ts" ], diff --git a/src/lib/game/data/disciplines/base.ts b/src/lib/game/data/disciplines/base.ts index 58330e6..5d72006 100644 --- a/src/lib/game/data/disciplines/base.ts +++ b/src/lib/game/data/disciplines/base.ts @@ -23,6 +23,7 @@ export const baseDisciplines: DisciplineDefinition[] = [ threshold: 100, value: 0, description: '+50 Max Mana', + bonus: { stat: 'maxManaBonus', amount: 50 }, }, { id: 'raw-mastery-2', @@ -30,6 +31,7 @@ export const baseDisciplines: DisciplineDefinition[] = [ threshold: 500, value: 100, description: 'Every 100 XP: +25 Max Mana', + bonus: { stat: 'maxManaBonus', amount: 25 }, }, ], }, diff --git a/src/lib/game/data/disciplines/elemental-regen-advanced.ts b/src/lib/game/data/disciplines/elemental-regen-advanced.ts index ea5a000..24518e4 100644 --- a/src/lib/game/data/disciplines/elemental-regen-advanced.ts +++ b/src/lib/game/data/disciplines/elemental-regen-advanced.ts @@ -26,6 +26,7 @@ export const elementalRegenAdvancedDisciplines: DisciplineDefinition[] = [ threshold: 150, value: 0, description: '+0.35 Metal Regen/tick', + bonus: { stat: 'regen_metal', amount: 0.35 }, }, { id: 'regen-metal-inf', @@ -33,6 +34,7 @@ export const elementalRegenAdvancedDisciplines: DisciplineDefinition[] = [ threshold: 400, value: 100, description: 'Every 100 XP: +0.15 Metal Regen/tick', + bonus: { stat: 'regen_metal', amount: 0.15 }, }, ], }, @@ -55,6 +57,7 @@ export const elementalRegenAdvancedDisciplines: DisciplineDefinition[] = [ threshold: 150, value: 0, description: '+0.35 Sand Regen/tick', + bonus: { stat: 'regen_sand', amount: 0.35 }, }, { id: 'regen-sand-inf', @@ -62,6 +65,7 @@ export const elementalRegenAdvancedDisciplines: DisciplineDefinition[] = [ threshold: 400, value: 100, description: 'Every 100 XP: +0.15 Sand Regen/tick', + bonus: { stat: 'regen_sand', amount: 0.15 }, }, ], }, @@ -84,6 +88,7 @@ export const elementalRegenAdvancedDisciplines: DisciplineDefinition[] = [ threshold: 150, value: 0, description: '+0.35 Lightning Regen/tick', + bonus: { stat: 'regen_lightning', amount: 0.35 }, }, { id: 'regen-lightning-inf', @@ -91,6 +96,7 @@ export const elementalRegenAdvancedDisciplines: DisciplineDefinition[] = [ threshold: 400, value: 100, description: 'Every 100 XP: +0.15 Lightning Regen/tick', + bonus: { stat: 'regen_lightning', amount: 0.15 }, }, ], }, @@ -114,6 +120,7 @@ export const elementalRegenAdvancedDisciplines: DisciplineDefinition[] = [ threshold: 200, value: 0, description: '+0.25 Crystal Regen/tick', + bonus: { stat: 'regen_crystal', amount: 0.25 }, }, { id: 'regen-crystal-inf', @@ -121,6 +128,7 @@ export const elementalRegenAdvancedDisciplines: DisciplineDefinition[] = [ threshold: 500, value: 100, description: 'Every 100 XP: +0.1 Crystal Regen/tick', + bonus: { stat: 'regen_crystal', amount: 0.1 }, }, ], }, @@ -143,6 +151,7 @@ export const elementalRegenAdvancedDisciplines: DisciplineDefinition[] = [ threshold: 200, value: 0, description: '+0.25 Stellar Regen/tick', + bonus: { stat: 'regen_stellar', amount: 0.25 }, }, { id: 'regen-stellar-inf', @@ -150,6 +159,7 @@ export const elementalRegenAdvancedDisciplines: DisciplineDefinition[] = [ threshold: 500, value: 100, description: 'Every 100 XP: +0.1 Stellar Regen/tick', + bonus: { stat: 'regen_stellar', amount: 0.1 }, }, ], }, @@ -172,6 +182,7 @@ export const elementalRegenAdvancedDisciplines: DisciplineDefinition[] = [ threshold: 200, value: 0, description: '+0.25 Void Regen/tick', + bonus: { stat: 'regen_void', amount: 0.25 }, }, { id: 'regen-void-inf', @@ -179,6 +190,7 @@ export const elementalRegenAdvancedDisciplines: DisciplineDefinition[] = [ threshold: 500, value: 100, description: 'Every 100 XP: +0.1 Void Regen/tick', + bonus: { stat: 'regen_void', amount: 0.1 }, }, ], }, diff --git a/src/lib/game/data/disciplines/elemental-regen.ts b/src/lib/game/data/disciplines/elemental-regen.ts index b3baf94..a5ed408 100644 --- a/src/lib/game/data/disciplines/elemental-regen.ts +++ b/src/lib/game/data/disciplines/elemental-regen.ts @@ -31,6 +31,7 @@ function makeBaseRegen(id: string, name: string, manaType: string, cost: number) threshold: 100, value: 0, description: `+${BASE_REGEN} ${name} Regen/tick`, + bonus: { stat: `regen_${shortId}`, amount: BASE_REGEN }, }, { id: `${id}-inf`, @@ -38,6 +39,7 @@ function makeBaseRegen(id: string, name: string, manaType: string, cost: number) threshold: 300, value: 100, description: `Every 100 XP: +0.25 ${name} Regen/tick`, + bonus: { stat: `regen_${shortId}`, amount: 0.25 }, }, ], }; @@ -73,6 +75,7 @@ export const elementalRegenDisciplines: DisciplineDefinition[] = [ threshold: 100, value: 0, description: '+0.4 Transference Regen/tick', + bonus: { stat: 'regen_transference', amount: 0.4 }, }, { id: 'regen-transference-inf', @@ -80,6 +83,7 @@ export const elementalRegenDisciplines: DisciplineDefinition[] = [ threshold: 300, value: 100, description: 'Every 100 XP: +0.2 Transference Regen/tick', + bonus: { stat: 'regen_transference', amount: 0.2 }, }, ], }, diff --git a/src/lib/game/data/disciplines/elemental.ts b/src/lib/game/data/disciplines/elemental.ts index 34124b5..b262482 100644 --- a/src/lib/game/data/disciplines/elemental.ts +++ b/src/lib/game/data/disciplines/elemental.ts @@ -45,6 +45,7 @@ function makeElementalAttunement(cfg: ElementalAttunementConfig): DisciplineDefi threshold: 200, value: 0, description: `+10 ${cfg.name} Capacity`, + bonus: { stat: `elementCap_${cfg.manaType}`, amount: 10 }, }, ], }; diff --git a/src/lib/game/data/disciplines/invoker.ts b/src/lib/game/data/disciplines/invoker.ts index d52bf90..7df9954 100644 --- a/src/lib/game/data/disciplines/invoker.ts +++ b/src/lib/game/data/disciplines/invoker.ts @@ -23,6 +23,7 @@ export const invokerDisciplines: DisciplineDefinition[] = [ threshold: 200, value: 0, description: '+10 Base Damage', + bonus: { stat: 'baseDamageBonus', amount: 10 }, }, { id: 'spell-2', @@ -30,6 +31,7 @@ export const invokerDisciplines: DisciplineDefinition[] = [ threshold: 400, value: 30, description: 'Every 300 XP: +5 Base Damage', + bonus: { stat: 'baseDamageBonus', amount: 5 }, }, ], }, diff --git a/src/lib/game/effects.ts b/src/lib/game/effects.ts index 785f13f..fa0ff00 100644 --- a/src/lib/game/effects.ts +++ b/src/lib/game/effects.ts @@ -102,6 +102,14 @@ export function computeAllEffects( } } + // Merge per-element cap bonuses from discipline effects (elementCap_{element}) + for (const [key, value] of Object.entries(disciplineEffects.bonuses)) { + if (key.startsWith('elementCap_')) { + const element = key.replace('elementCap_', ''); + perElementCapBonus[element] = (perElementCapBonus[element] || 0) + value; + } + } + const merged: UnifiedEffects = { ...upgradeEffects, maxManaBonus: upgradeEffects.maxManaBonus + (equipmentEffects.bonuses.maxMana || 0) + (disciplineEffects.bonuses.maxManaBonus || 0), diff --git a/src/lib/game/effects/discipline-effects.ts b/src/lib/game/effects/discipline-effects.ts index fc19dd7..1f215cd 100644 --- a/src/lib/game/effects/discipline-effects.ts +++ b/src/lib/game/effects/discipline-effects.ts @@ -5,7 +5,23 @@ import type { DisciplineStoreState } from '../stores/discipline-slice'; import type { DisciplineState } from '../types/disciplines'; import { useDisciplineStore } from '../stores/discipline-slice'; import { ALL_DISCIPLINES } from '../data/disciplines'; -import { calculateStatBonus, getUnlockedPerks } from '../utils/discipline-math'; +import { + calculateStatBonus, + calculatePerkTier, + getUnlockedPerks, +} from '../utils/discipline-math'; + +/** + * Known stat keys consumed by computeAllEffects() in effects.ts. + * Perk bonuses are routed to these keys so they flow into the unified system. + */ +const KNOWN_BONUS_STATS = new Set([ + 'maxManaBonus', + 'regenBonus', + 'clickManaBonus', + 'baseDamageBonus', + 'elementCapBonus', +]); export function computeDisciplineEffects(_state?: DisciplineStoreState): { bonuses: Record; @@ -22,29 +38,50 @@ export function computeDisciplineEffects(_state?: DisciplineStoreState): { const multipliers: Record = {}; const specials = new Set(); + function addBonus(stat: string, amount: number) { + bonuses[stat] = (bonuses[stat] || 0) + amount; + } + for (const { disc, def } of activeDiscs) { // Continuous stat bonus const statBonus = calculateStatBonus(def.statBonus.baseValue, disc.xp, def.scalingFactor); if (def.statBonus.stat) { - bonuses[def.statBonus.stat] = (bonuses[def.statBonus.stat] || 0) + statBonus; + addBonus(def.statBonus.stat, statBonus); } // Perk unlocks const perks = getUnlockedPerks(def, disc.xp); for (const perk of perks) { - if (perk.type === 'once' || perk.type === 'infinite') { - // Once/infinite perks can be treated as additive bonuses or special flags - // For simplicity, we add them as a special flag; actual effect depends on perk.id - specials.add(perk.id); + if (perk.type === 'once') { + if (perk.bonus) { + addBonus(perk.bonus.stat, perk.bonus.amount); + } else if (!perk.unlocksEffects) { + // Fallback: qualitative perk with no structured bonus — add as special flag + specials.add(perk.id); + } + // Perks with unlocksEffects are handled by discipline-slice.ts processTick() + } else if (perk.type === 'infinite') { + if (perk.bonus) { + const interval = perk.value; + const tier = calculatePerkTier(disc.xp, perk.threshold, interval); + if (tier > 0) { + addBonus(perk.bonus.stat, tier * perk.bonus.amount); + } + } else if (!perk.unlocksEffects) { + specials.add(perk.id); + } } else if (perk.type === 'capped') { - // Capped perks act as multipliers after certain thresholds - // For now, we treat them as additive to a multiplier stat (example) - // In a real implementation, each perk would have a specific effect. - // Here we just add a generic perk multiplier placeholder. - multipliers[`perk_${perk.id}`] = (multipliers[`perk_${perk.id}`] || 1) + perk.value / 100; + if (perk.bonus) { + const tier = calculatePerkTier(disc.xp, perk.threshold, perk.value); + if (tier > 0) { + addBonus(perk.bonus.stat, tier * perk.bonus.amount); + } + } else if (!perk.unlocksEffects) { + specials.add(perk.id); + } } } } return { bonuses, multipliers, specials }; -} \ No newline at end of file +} diff --git a/src/lib/game/types/disciplines.ts b/src/lib/game/types/disciplines.ts index fc5f95f..34cfd0e 100644 --- a/src/lib/game/types/disciplines.ts +++ b/src/lib/game/types/disciplines.ts @@ -13,6 +13,11 @@ export enum DisciplinesAttunementType { // ─── Perk Types ─────────────────────────────────────────────────────────────── export type PerkType = 'capped' | 'once' | 'infinite'; +export interface PerkBonus { + stat: string; + amount: number; +} + export interface DisciplinePerk { id: string; type: PerkType; @@ -20,6 +25,7 @@ export interface DisciplinePerk { value: number; description: string; unlocksEffects?: string[]; + bonus?: PerkBonus; } // ─── Discipline Definition ────────────────────────────────────────────────────