diff --git a/docs/circular-deps.txt b/docs/circular-deps.txt index 00e6a33..24765e9 100644 --- a/docs/circular-deps.txt +++ b/docs/circular-deps.txt @@ -1,4 +1,4 @@ # Circular Dependencies -Generated: 2026-05-28T13:28:24.658Z +Generated: 2026-05-28T16:14:24.376Z No circular dependencies found. ✅ diff --git a/docs/dependency-graph.json b/docs/dependency-graph.json index e6c7ec2..2ad1a6a 100644 --- a/docs/dependency-graph.json +++ b/docs/dependency-graph.json @@ -1,6 +1,6 @@ { "_meta": { - "generated": "2026-05-28T13:28:22.544Z", + "generated": "2026-05-28T16:14:22.630Z", "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." }, @@ -556,6 +556,7 @@ "constants.ts", "effects/discipline-effects.ts", "stores/combatStore.ts", + "stores/discipline-slice.ts", "stores/gameStore.types.ts", "stores/manaStore.ts", "stores/prestigeStore.ts", diff --git a/src/components/game/tabs/DisciplinesTab.tsx b/src/components/game/tabs/DisciplinesTab.tsx index f1d24f5..4b74519 100644 --- a/src/components/game/tabs/DisciplinesTab.tsx +++ b/src/components/game/tabs/DisciplinesTab.tsx @@ -16,7 +16,7 @@ import { useManaStore } from '@/lib/game/stores/manaStore'; import { usePrestigeStore } from '@/lib/game/stores/prestigeStore'; import clsx from 'clsx'; import { DebugName } from '@/components/game/debug/debug-context'; -import { TICKS_PER_SECOND, computePerkCurrentEffect, computeTotalPerkBonusForStat } from './disciplines-utils'; +import { TICKS_PER_SECOND, computePerkCurrentEffect, computeTotalPerkBonusForStat, isRateStat } from './disciplines-utils'; import type { ComputedPerkEffect } from './disciplines-utils'; // ─── Attunement Tabs ───────────────────────────────────────────────────────── @@ -56,13 +56,14 @@ const DisciplineCard: React.FC = ({ }) => { const { id, name, description, manaType, perks, - statBonus, baseValue, drainBase, difficultyFactor, scalingFactor, + statBonus, drainBase, difficultyFactor, scalingFactor, conversionRate, sourceManaTypes, } = definition; const displayXp = xp; const progressPercent = Math.min(displayXp / Math.max(1, concurrentLimit * 100), 100); - const activeStatBonus = calculateStatBonus(baseValue, displayXp, scalingFactor); + // statBonus.baseValue is the correct field — not a top-level baseValue + const activeStatBonus = calculateStatBonus(statBonus.baseValue, displayXp, scalingFactor); const drainPerSecond = calculateManaDrain(drainBase, displayXp, difficultyFactor) * TICKS_PER_SECOND; const elementDef = ELEMENTS[manaType]; @@ -134,14 +135,23 @@ const DisciplineCard: React.FC = ({ )} {/* Stat Bonus with Perk Total */} -
- Stat Bonus: {activeStatBonus.toFixed(2)}/sec on {statBonusLabel} - {perkBonusTotal > 0 && ( - - ({statBonusTotal.toFixed(2)}/sec with perks) - - )} -
+ {(() => { + // NaN guard — if the calculation produced NaN, show a safe fallback + const safeActive = Number.isFinite(activeStatBonus) ? activeStatBonus : 0; + const safePerk = Number.isFinite(perkBonusTotal) ? perkBonusTotal : 0; + const safeTotal = Number.isFinite(statBonusTotal) ? statBonusTotal : 0; + const rateSuffix = isRateStat(statBonus.stat) ? '/sec' : ''; + return ( +
+ Stat Bonus: {safeActive.toFixed(2)}{rateSuffix} on {statBonusLabel} + {safePerk > 0 && ( + + ({safeTotal.toFixed(2)}{rateSuffix} with perks) + + )} +
+ ); + })()} {/* Perks */}
@@ -207,7 +217,7 @@ export const DisciplinesTab: React.FC = () => { const handleToggle = useCallback((id: string, paused: boolean) => { if (paused) { - activate(id, { rawMana, elements, signedPacts }); + activate(id, { elements, signedPacts }); } else { deactivate(id); } @@ -261,7 +271,7 @@ export const DisciplinesTab: React.FC = () => { {/* Summary */}
-
Active Disciple{activeIds.length}{activeIds.length === 1 ? '' : 's'} / {concurrentLimit}
+
Active Disciplines: {activeIds.length} / {concurrentLimit}
Concurrent Limit: {concurrentLimit}
diff --git a/src/components/game/tabs/disciplines-utils.ts b/src/components/game/tabs/disciplines-utils.ts index e0fea7d..14c8f2a 100644 --- a/src/components/game/tabs/disciplines-utils.ts +++ b/src/components/game/tabs/disciplines-utils.ts @@ -8,6 +8,23 @@ import { TICK_MS } from '@/lib/game/constants/core'; // TICK_MS = 200, so 5 ticks per second export const TICKS_PER_SECOND = 1000 / TICK_MS; +// Stat keys that represent per-second rates. All other stat keys are +// flat bonuses (capacities, powers, multipliers, percentages, etc.). +const RATE_STAT_KEYS = new Set([ + 'regenBonus', + 'disciplineXpBonus', +]); + +// Dynamic stat keys that are rates (checked by prefix) +const RATE_STAT_PREFIXES = [ + 'conversion_', +]; + +export function isRateStat(statKey: string): boolean { + if (RATE_STAT_KEYS.has(statKey)) return true; + return RATE_STAT_PREFIXES.some((prefix) => statKey.startsWith(prefix)); +} + export interface ComputedPerkEffect { description: string; currentEffect: string; @@ -29,15 +46,17 @@ export function computePerkCurrentEffect( return 'unlocked'; } - const { amount } = perk.bonus; + const { amount, stat } = perk.bonus; + const rateSuffix = isRateStat(stat) ? '/sec' : ''; switch (perk.type) { case 'once': - return `+${amount} (unlocked)`; + return `+${amount}${rateSuffix}`; case 'infinite': { const tier = calculatePerkTier(xp, perk.threshold, perk.value); const total = tier * amount; - return `+${total.toFixed(2)}/sec`; + if (total === 0) return `+0${rateSuffix}`; + return `+${total.toFixed(2)}${rateSuffix}`; } case 'capped': { let tier = calculatePerkTier(xp, perk.threshold, perk.value); @@ -45,7 +64,8 @@ export function computePerkCurrentEffect( tier = Math.min(tier, perk.maxTier); } const total = tier * amount; - return `+${total.toFixed(2)}/sec`; + if (total === 0) return `+0${rateSuffix}`; + return `+${total.toFixed(2)}${rateSuffix}`; } default: return 'active';