[priority: high] Discipline tab shows NaN for stat bonuses and perk effects #184

Closed
opened 2026-05-28 15:57:22 +02:00 by Anexim · 1 comment
Owner

Bug: NaN displayed in Discipline tab for stat bonuses and perks

Description

The Discipline tab shows "NaN" in multiple places:

  • Stat Bonus: NaN/sec on Max Mana(NaN/sec with perks) — seen on "Raw Mana Mastery"
  • Perk descriptions like Every 100 XP: +25 Max Mana — +14825.00/sec showing wildly wrong values

Root Cause Analysis

The issue is in /src/lib/game/effects/discipline-effects.ts and /src/lib/game/utils/discipline-math.ts:

  1. calculateStatBonus(baseValue, xp, scalingFactor) — When xp is 0 (discipline exists but hasn't started accumulating XP), the function correctly returns 0 due to the if (xp <= 0) return 0; guard. However, the perk bonus computation in computeTotalPerkBonusForStat() in disciplines-utils.ts may be computing tiers incorrectly.

  2. computePerkCurrentEffect() in disciplines-utils.ts line ~47: For infinite perks, it calls calculatePerkTier(xp, perk.threshold, perk.value). When XP is, say, 500 and threshold is 500 with interval 100, tier = floor((500-500)/100) + 1 = 1. That seems right.

  3. The real NaN source: The activeStatBonus in DisciplineCard is computed as:

    const activeStatBonus = calculateStatBonus(baseValue, displayXp, scalingFactor);
    

    This looks correct. BUT the perkBonusTotal from computeTotalPerkBonusForStat(perks, displayXp, statBonus.stat) computes the perk bonus for the same stat key as the discipline's statBonus. For disciplines like "Raw Mana Mastery" with stat: 'maxManaBonus', the perks ALSO have stat: 'maxManaBonus'. So the total is activeStatBonus + perkBonusTotal. This double-counts if activeStatBonus already includes the base stat bonus scaling.

  4. The /sec label is wrong for flat bonuses: "Raw Mana Mastery" increases Max Mana by a flat amount, not per second. The UI displays {activeStatBonus.toFixed(2)}/sec on {statBonusLabel} — the /sec suffix is hardcoded but many stats (Max Mana, element capacity) are flat bonuses, not rates.

Affected Files

  • src/components/game/tabs/DisciplinesTab.tsx — the /sec label and stat bonus display
  • src/components/game/tabs/disciplines-utils.tscomputePerkCurrentEffect and computeTotalPerkBonusForStat
  • src/lib/game/effects/discipline-effects.tscomputeDisciplineEffects

Steps to Reproduce

  1. Start practicing "Raw Mana Mastery" discipline
  2. Accumulate some XP (e.g., 100+ XP)
  3. Observe the stat bonus line in the Discipline tab
  4. NaN appears when statBonus.stat is undefined or when the calculation produces NaN

Expected Behavior

  • Flat bonuses (Max Mana, element capacity) should display as flat values (e.g., "+150 Max Mana")
  • Rate bonuses (regen, discipline XP) should display as "/sec"
  • Perk effects should show correct values with proper formatting
  • NaN should never appear

Suggested Fix Direction

  1. Add a isRate flag to statBonus or derive it from the stat key name
  2. Only append /sec for rate-based stats
  3. Verify the perk tier calculation handles edge cases (XP exactly at threshold, XP=0)
  4. Add NaN guards in the display layer
## Bug: NaN displayed in Discipline tab for stat bonuses and perks ### Description The Discipline tab shows "NaN" in multiple places: - `Stat Bonus: NaN/sec on Max Mana(NaN/sec with perks)` — seen on "Raw Mana Mastery" - Perk descriptions like `Every 100 XP: +25 Max Mana — +14825.00/sec` showing wildly wrong values ### Root Cause Analysis The issue is in `/src/lib/game/effects/discipline-effects.ts` and `/src/lib/game/utils/discipline-math.ts`: 1. **`calculateStatBonus(baseValue, xp, scalingFactor)`** — When `xp` is 0 (discipline exists but hasn't started accumulating XP), the function correctly returns 0 due to the `if (xp <= 0) return 0;` guard. However, the **perk bonus computation** in `computeTotalPerkBonusForStat()` in `disciplines-utils.ts` may be computing tiers incorrectly. 2. **`computePerkCurrentEffect()`** in `disciplines-utils.ts` line ~47: For `infinite` perks, it calls `calculatePerkTier(xp, perk.threshold, perk.value)`. When XP is, say, 500 and threshold is 500 with interval 100, tier = floor((500-500)/100) + 1 = 1. That seems right. 3. **The real NaN source**: The `activeStatBonus` in `DisciplineCard` is computed as: ``` const activeStatBonus = calculateStatBonus(baseValue, displayXp, scalingFactor); ``` This looks correct. BUT the `perkBonusTotal` from `computeTotalPerkBonusForStat(perks, displayXp, statBonus.stat)` computes the perk bonus for the **same stat key** as the discipline's statBonus. For disciplines like "Raw Mana Mastery" with `stat: 'maxManaBonus'`, the perks ALSO have `stat: 'maxManaBonus'`. So the total is `activeStatBonus + perkBonusTotal`. This double-counts if `activeStatBonus` already includes the base stat bonus scaling. 4. **The `/sec` label is wrong for flat bonuses**: "Raw Mana Mastery" increases Max Mana by a flat amount, not per second. The UI displays `{activeStatBonus.toFixed(2)}/sec on {statBonusLabel}` — the `/sec` suffix is hardcoded but many stats (Max Mana, element capacity) are flat bonuses, not rates. ### Affected Files - `src/components/game/tabs/DisciplinesTab.tsx` — the `/sec` label and stat bonus display - `src/components/game/tabs/disciplines-utils.ts` — `computePerkCurrentEffect` and `computeTotalPerkBonusForStat` - `src/lib/game/effects/discipline-effects.ts` — `computeDisciplineEffects` ### Steps to Reproduce 1. Start practicing "Raw Mana Mastery" discipline 2. Accumulate some XP (e.g., 100+ XP) 3. Observe the stat bonus line in the Discipline tab 4. NaN appears when `statBonus.stat` is undefined or when the calculation produces NaN ### Expected Behavior - Flat bonuses (Max Mana, element capacity) should display as flat values (e.g., "+150 Max Mana") - Rate bonuses (regen, discipline XP) should display as "/sec" - Perk effects should show correct values with proper formatting - NaN should never appear ### Suggested Fix Direction 1. Add a `isRate` flag to `statBonus` or derive it from the stat key name 2. Only append `/sec` for rate-based stats 3. Verify the perk tier calculation handles edge cases (XP exactly at threshold, XP=0) 4. Add NaN guards in the display layer
Anexim added the ai:todo label 2026-05-28 15:57:22 +02:00
n8n-gitea was assigned by Anexim 2026-05-28 15:57:22 +02:00
Author
Owner

Fix applied for issue #184: Discipline tab NaN display

Root Causes Found:

  1. baseValue was undefinedDisciplineCard destructured baseValue directly from definition, but DisciplineDefinition has it at statBonus.baseValue. This made calculateStatBonus(undefined, xp, ...) produce NaN, which rendered as "NaN" in the UI.
  2. Hardcoded /sec label — The stat bonus and perk displays always appended /sec even for flat bonuses (Max Mana, element capacity, enchant power, etc.).
  3. computePerkCurrentEffect always showed /sec — Even for flat perks like "+25 Max Mana".

Changes:

src/components/game/tabs/DisciplinesTab.tsx

  • Fixed destructuring: statBonus.baseValue instead of undefined baseValue
  • Added IIFE-based stat bonus display with proper isRateStat() detection
  • NaN safety guards on activeStatBonus, perkBonusTotal, statBonusTotal
  • Cleaned up summary label text

src/components/game/tabs/disciplines-utils.ts

  • Added isRateStat(statKey) helper with explicit rate/non-rate stat key sets
  • Rate stats: regenBonus, disciplineXpBonus, conversion_* prefix
  • Flat stats: everything else (maxManaBonus, elementCap_*, enchantPower, clickManaBonus, meditationCapBonus, studySpeed, golemCapacity, craftingCostReduction, pactAffinityBonus, guardianBoonMultiplier, clickManaMultiplier, regenMultiplier)
  • computePerkCurrentEffect: /sec only appended for rate stats; once perks no longer show "(unlocked)" suffix
  • Added zero-tier early returns for clean display

Verification:

  • TypeScript: 0 errors in changed files
  • Tests: 917/917 passed
  • Pre-commit: all checks passed
## Fix applied for issue #184: Discipline tab NaN display ### Root Causes Found: 1. **`baseValue` was `undefined`** — `DisciplineCard` destructured `baseValue` directly from `definition`, but `DisciplineDefinition` has it at `statBonus.baseValue`. This made `calculateStatBonus(undefined, xp, ...)` produce NaN, which rendered as "NaN" in the UI. 2. **Hardcoded `/sec` label** — The stat bonus and perk displays always appended `/sec` even for flat bonuses (Max Mana, element capacity, enchant power, etc.). 3. **`computePerkCurrentEffect` always showed `/sec`** — Even for flat perks like "+25 Max Mana". ### Changes: **`src/components/game/tabs/DisciplinesTab.tsx`** - Fixed destructuring: `statBonus.baseValue` instead of undefined `baseValue` - Added IIFE-based stat bonus display with proper `isRateStat()` detection - NaN safety guards on `activeStatBonus`, `perkBonusTotal`, `statBonusTotal` - Cleaned up summary label text **`src/components/game/tabs/disciplines-utils.ts`** - Added `isRateStat(statKey)` helper with explicit rate/non-rate stat key sets - Rate stats: `regenBonus`, `disciplineXpBonus`, `conversion_*` prefix - Flat stats: everything else (`maxManaBonus`, `elementCap_*`, `enchantPower`, `clickManaBonus`, `meditationCapBonus`, `studySpeed`, `golemCapacity`, `craftingCostReduction`, `pactAffinityBonus`, `guardianBoonMultiplier`, `clickManaMultiplier`, `regenMultiplier`) - `computePerkCurrentEffect`: `/sec` only appended for rate stats; `once` perks no longer show "(unlocked)" suffix - Added zero-tier early returns for clean display ### Verification: - TypeScript: 0 errors in changed files - Tests: 917/917 passed - Pre-commit: all checks passed
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Anexim/Mana-Loop#184