[High] [Bug] Elemental mana conversions incorrectly paused — rawGrossRegen compared against per-element drain instead of total #348

Closed
opened 2026-06-10 10:13:31 +02:00 by Anexim · 3 comments
Owner

Bug: Elemental mana conversions incorrectly paused due to wrong rawGrossRegen value

Steps to reproduce

  1. Progress through the game until you have Earth and Transference mana unlocked with active disciplines
  2. Observe the Conversion Stats section in the Stats tab — conversions show as PAUSED with "Insufficient raw regen"
  3. Observe the ManaDisplay shows earth mana regen as +33.43/hr (correct)
  4. The conversion listing shows Final: 0.00/hr and ⚠️ Insufficient raw regen (need 3342.95/hr, have 2.00/hr)

Expected

With 3342.95 raw regen/hr available, Earth conversion (which needs ~3342.95 raw/hr at d=1 with rawCost=100) should be active and producing mana. The have value should reflect the player's actual raw regen.

Actual

The conversion system reports have 2.00/hr — the base raw regen without attunement bonuses — and pauses ALL conversions even though the player has 3342.95/hr raw regen.

Root Cause

Two distinct bugs:

Bug 1: rawGrossRegen passed to computeConversionRates is wrong (gameStore.ts line ~176)

In gameStore.ts tick pipeline, baseRegen is computed as:

const baseRegen = computeRegen({ prestigeUpgrades, attunements: {} }, ...) 

Note: attunements: {}empty object! This means computeRegengetTotalAttunementRegen({}) returns 0, so baseRegen is just the base 2/hr (+ prestige/equipment bonuses).

This baseRegen (≈2.00/hr) is then passed as rawGrossRegen to computeConversionRates().

However, the actual raw regen used elsewhere in the same tick (for the net raw regen calculation on line ~236) is:

baseRegen * (1 - incursionStrength) * meditationMultiplier - conversionResult.totalRawDrain

This means the raw regen used for the conversion pause check (2.00/hr) is much smaller than the actual raw regen available (3342.95/hr).

Fix: computeRegen should be called with the actual attunements, not an empty object. The attunement raw mana regen (with level scaling) must be included in rawGrossRegen.

Bug 2: grossRegen (per-element) in buildConversionParams uses conversionRate instead of rawManaRegen (gameStore.ts line ~388-396)

The buildConversionParams function builds grossRegen per element from attunements:

grossRegen[def.primaryManaType] += (def.conversionRate || 0);

But conversionRate (0.2 for enchanter, 0.25 for fabricator) is the conversion base rate, NOT the raw mana regen. The raw mana regen is rawManaRegen (0.5 for enchanter, 0.4 for fabricator).

The grossRegen per-element map is used in conversion-rates.ts line ~172 to check if component regen is sufficient:

const compGross = grossRegen[comp] || 0;
if (compDrain > compGross) { paused = true; ... }

Since grossRegen uses the wrong field (conversionRate=0.2 instead of rawManaRegen=0.5), the per-element gross regen values are wrong. For composite elements (e.g., Metal = Fire + Earth), the component check compares against tiny values.

Fix: buildConversionParams should use def.rawManaRegen instead of def.conversionRate for the per-element gross regen. Also, level scaling should be applied (matching getTotalAttunementRegen).

Affected files

  • src/lib/game/stores/gameStore.tsbuildConversionParams() function (line ~375-397) and the tick pipeline where baseRegen is computed (line ~119-122, ~171-176)
  • src/lib/game/utils/conversion-rates.tscomputeConversionRates() pause logic (line ~155-175)

Impact

  • Severity: High — All elemental mana conversions are permanently paused for all players who have unlocked elements, because the pause check compares against base regen (2/hr) instead of actual regen (potentially thousands)
  • Players cannot generate any elemental mana through conversion, crippling progression
  • The ManaDisplay shows correct regen numbers (it reads from elementRegen store which is written from the same conversion result, but the finalRate is set to 0 when paused, so the display is also wrong — the +33.43/hr in the bug report may be from a different code path or stale state)

Evidence from bug report

⛰️ Earth (d=1) ⏸️ PAUSED
  Base: 22.29/hr
  Final: 0.00/hr
  ⚠️ Insufficient raw regen (need 3342.95/hr, have 2.00/hr)
  • Base: 22.29/hr = disciplineRate(22.04) + attunementBase(0.25) — correct
  • need 3342.95/hr = 22.29 × 1.50(attMult) × 100(rawCost) ≈ 3343.5 — correct
  • have 2.00/hrWRONG, should be ~3342.95 or higher

Regression test needed

A test that:

  1. Sets up attunements with enough raw regen (>1000/hr)
  2. Calls computeConversionRates with the correct rawGrossRegen
  3. Verifies that base element conversions (d=1) are NOT paused
  4. Verifies that finalRate > 0 for elements with active conversion rates
## Bug: Elemental mana conversions incorrectly paused due to wrong rawGrossRegen value ### Steps to reproduce 1. Progress through the game until you have Earth and Transference mana unlocked with active disciplines 2. Observe the Conversion Stats section in the Stats tab — conversions show as PAUSED with "Insufficient raw regen" 3. Observe the ManaDisplay shows earth mana regen as `+33.43/hr` (correct) 4. The conversion listing shows `Final: 0.00/hr` and `⚠️ Insufficient raw regen (need 3342.95/hr, have 2.00/hr)` ### Expected With 3342.95 raw regen/hr available, Earth conversion (which needs ~3342.95 raw/hr at d=1 with rawCost=100) should be active and producing mana. The `have` value should reflect the player's actual raw regen. ### Actual The conversion system reports `have 2.00/hr` — the base raw regen without attunement bonuses — and pauses ALL conversions even though the player has 3342.95/hr raw regen. ### Root Cause **Two distinct bugs:** #### Bug 1: `rawGrossRegen` passed to `computeConversionRates` is wrong (gameStore.ts line ~176) In `gameStore.ts` tick pipeline, `baseRegen` is computed as: ``` const baseRegen = computeRegen({ prestigeUpgrades, attunements: {} }, ...) ``` Note: `attunements: {}` — **empty object!** This means `computeRegen` → `getTotalAttunementRegen({})` returns 0, so `baseRegen` is just the base 2/hr (+ prestige/equipment bonuses). This `baseRegen` (≈2.00/hr) is then passed as `rawGrossRegen` to `computeConversionRates()`. However, the **actual** raw regen used elsewhere in the same tick (for the net raw regen calculation on line ~236) is: ``` baseRegen * (1 - incursionStrength) * meditationMultiplier - conversionResult.totalRawDrain ``` This means the raw regen used for the conversion pause check (2.00/hr) is much smaller than the actual raw regen available (3342.95/hr). **Fix:** `computeRegen` should be called with the actual attunements, not an empty object. The attunement raw mana regen (with level scaling) must be included in `rawGrossRegen`. #### Bug 2: `grossRegen` (per-element) in `buildConversionParams` uses `conversionRate` instead of `rawManaRegen` (gameStore.ts line ~388-396) The `buildConversionParams` function builds `grossRegen` per element from attunements: ``` grossRegen[def.primaryManaType] += (def.conversionRate || 0); ``` But `conversionRate` (0.2 for enchanter, 0.25 for fabricator) is the **conversion base rate**, NOT the raw mana regen. The raw mana regen is `rawManaRegen` (0.5 for enchanter, 0.4 for fabricator). The `grossRegen` per-element map is used in `conversion-rates.ts` line ~172 to check if component regen is sufficient: ``` const compGross = grossRegen[comp] || 0; if (compDrain > compGross) { paused = true; ... } ``` Since `grossRegen` uses the wrong field (conversionRate=0.2 instead of rawManaRegen=0.5), the per-element gross regen values are wrong. For composite elements (e.g., Metal = Fire + Earth), the component check compares against tiny values. **Fix:** `buildConversionParams` should use `def.rawManaRegen` instead of `def.conversionRate` for the per-element gross regen. Also, level scaling should be applied (matching `getTotalAttunementRegen`). ### Affected files - `src/lib/game/stores/gameStore.ts` — `buildConversionParams()` function (line ~375-397) and the tick pipeline where `baseRegen` is computed (line ~119-122, ~171-176) - `src/lib/game/utils/conversion-rates.ts` — `computeConversionRates()` pause logic (line ~155-175) ### Impact - **Severity: High** — All elemental mana conversions are permanently paused for all players who have unlocked elements, because the pause check compares against base regen (2/hr) instead of actual regen (potentially thousands) - Players cannot generate any elemental mana through conversion, crippling progression - The ManaDisplay shows correct regen numbers (it reads from `elementRegen` store which is written from the same conversion result, but the `finalRate` is set to 0 when paused, so the display is also wrong — the `+33.43/hr` in the bug report may be from a different code path or stale state) ### Evidence from bug report ``` ⛰️ Earth (d=1) ⏸️ PAUSED Base: 22.29/hr Final: 0.00/hr ⚠️ Insufficient raw regen (need 3342.95/hr, have 2.00/hr) ``` - `Base: 22.29/hr` = disciplineRate(22.04) + attunementBase(0.25) — correct - `need 3342.95/hr` = 22.29 × 1.50(attMult) × 100(rawCost) ≈ 3343.5 — correct - `have 2.00/hr` — **WRONG**, should be ~3342.95 or higher ### Regression test needed A test that: 1. Sets up attunements with enough raw regen (>1000/hr) 2. Calls `computeConversionRates` with the correct `rawGrossRegen` 3. Verifies that base element conversions (d=1) are NOT paused 4. Verifies that `finalRate > 0` for elements with active conversion rates
Anexim added the ai:todo label 2026-06-10 10:13:31 +02:00
n8n-gitea was assigned by Anexim 2026-06-10 10:13:31 +02:00
Author
Owner

Investigation Summary

Confirmed: Two root causes identified

Bug 1 — rawGrossRegen is ~2.00/hr instead of actual regen (gameStore.ts)

In the tick pipeline (gameStore.ts ~line 119-122):

const baseRegen = computeRegen(
  { prestigeUpgrades: ctx.prestige.prestigeUpgrades, attunements: {} },  // ← EMPTY!
  undefined,
  disciplineEffects
) * (1 + (disciplineEffects.multipliers.regenMultiplier || 0));

attunements: {} means getTotalAttunementRegen({}) returns 0. The rawGrossRegen passed to computeConversionRates is therefore just the base 2/hr (+ small prestige bonuses), NOT the actual regen including attunement contributions (which can be 3000+/hr).

This is why the pause check says have 2.00/hr — it's comparing conversion drain against base regen only.

Bug 2 — grossRegen per-element uses wrong field (gameStore.ts buildConversionParams)

grossRegen[def.primaryManaType] += (def.conversionRate || 0);

conversionRate (0.2 enchanter, 0.25 fabricator) is the conversion base rate, NOT raw mana regen. Should be rawManaRegen (0.5 enchanter, 0.4 fabricator). Also missing level scaling (1.5^(level-1)).

Traced code path

  1. gameStore.ts:tick() → computes baseRegen with empty attunements → passes as rawGrossRegen
  2. gameStore.ts:tick() → calls buildConversionParams() → builds grossRegen with wrong field
  3. conversion-rates.ts:computeConversionRates() → uses rawGrossRegen (2/hr) for pause check → pauses everything
  4. conversion-rates.ts:computeConversionRates() → uses grossRegen[comp] (0.2) for component check → also pauses composites
  5. gameStore.ts:tick() → sets finalRate: paused ? 0 : finalRate → all rates become 0
  6. gameStore.ts:tick() → writes elementRegen to store → all zeros
  7. ManaDisplay.tsx → reads elementRegen from store → shows 0 or stale values

Why the two different numbers in the bug report

  • ManaDisplay +33.43/hr — this is the elementRegen value from the store, which may be from a previous tick before the pause logic kicked in, or from the netRate calculation that doesn't account for the pause correctly
  • Conversion listing have 2.00/hr — this is the rawGrossRegen value (base regen without attunements)

Files involved

  • src/lib/game/stores/gameStore.ts — primary bug location (lines ~119-122, ~171-176, ~375-397)
  • src/lib/game/utils/conversion-rates.ts — pause logic (lines ~155-175)
  • src/lib/game/data/attunements.tsrawManaRegen vs conversionRate fields
  • src/lib/game/utils/mana-utils.tscomputeRegen and getTotalAttunementRegen
  • src/app/components/LeftPanel.tsx — display side (lines ~79-96, same bug in buildConversionParams equivalent)
  • src/components/game/tabs/StatsTab/ElementStatsSection.tsx — display side (lines ~38-55, same bug)
## Investigation Summary ### Confirmed: Two root causes identified **Bug 1 — `rawGrossRegen` is ~2.00/hr instead of actual regen (gameStore.ts)** In the tick pipeline (`gameStore.ts` ~line 119-122): ```ts const baseRegen = computeRegen( { prestigeUpgrades: ctx.prestige.prestigeUpgrades, attunements: {} }, // ← EMPTY! undefined, disciplineEffects ) * (1 + (disciplineEffects.multipliers.regenMultiplier || 0)); ``` `attunements: {}` means `getTotalAttunementRegen({})` returns 0. The `rawGrossRegen` passed to `computeConversionRates` is therefore just the base 2/hr (+ small prestige bonuses), NOT the actual regen including attunement contributions (which can be 3000+/hr). This is why the pause check says `have 2.00/hr` — it's comparing conversion drain against base regen only. **Bug 2 — `grossRegen` per-element uses wrong field (gameStore.ts `buildConversionParams`)** ```ts grossRegen[def.primaryManaType] += (def.conversionRate || 0); ``` `conversionRate` (0.2 enchanter, 0.25 fabricator) is the conversion base rate, NOT raw mana regen. Should be `rawManaRegen` (0.5 enchanter, 0.4 fabricator). Also missing level scaling (`1.5^(level-1)`). ### Traced code path 1. `gameStore.ts:tick()` → computes `baseRegen` with empty attunements → passes as `rawGrossRegen` 2. `gameStore.ts:tick()` → calls `buildConversionParams()` → builds `grossRegen` with wrong field 3. `conversion-rates.ts:computeConversionRates()` → uses `rawGrossRegen` (2/hr) for pause check → pauses everything 4. `conversion-rates.ts:computeConversionRates()` → uses `grossRegen[comp]` (0.2) for component check → also pauses composites 5. `gameStore.ts:tick()` → sets `finalRate: paused ? 0 : finalRate` → all rates become 0 6. `gameStore.ts:tick()` → writes `elementRegen` to store → all zeros 7. `ManaDisplay.tsx` → reads `elementRegen` from store → shows 0 or stale values ### Why the two different numbers in the bug report - ManaDisplay `+33.43/hr` — this is the `elementRegen` value from the store, which may be from a previous tick before the pause logic kicked in, or from the `netRate` calculation that doesn't account for the pause correctly - Conversion listing `have 2.00/hr` — this is the `rawGrossRegen` value (base regen without attunements) ### Files involved - `src/lib/game/stores/gameStore.ts` — primary bug location (lines ~119-122, ~171-176, ~375-397) - `src/lib/game/utils/conversion-rates.ts` — pause logic (lines ~155-175) - `src/lib/game/data/attunements.ts` — `rawManaRegen` vs `conversionRate` fields - `src/lib/game/utils/mana-utils.ts` — `computeRegen` and `getTotalAttunementRegen` - `src/app/components/LeftPanel.tsx` — display side (lines ~79-96, same bug in `buildConversionParams` equivalent) - `src/components/game/tabs/StatsTab/ElementStatsSection.tsx` — display side (lines ~38-55, same bug)
Anexim added ai:in-progress and removed ai:todo labels 2026-06-10 10:56:58 +02:00
Author
Owner

Investigating elemental mana conversion pause bug — rawGrossRegen compared against per-element drain instead of total drain.

Investigating elemental mana conversion pause bug — rawGrossRegen compared against per-element drain instead of total drain.
Anexim added ai:done and removed ai:in-progress labels 2026-06-10 11:21:03 +02:00
Author
Owner

Fix Complete — Bug #348

Root Causes Fixed

Bug 1: rawGrossRegen was ~2/hr instead of actual regen

  • gameStore.ts: computeRegen now receives ctx.attunement.attunements instead of {}, so rawGrossRegen includes attunement contributions (0.5/hr base × 1.5^(level-1) scaling)
  • useGameDerived.ts: Same fix — passes actual attunements to computeRegen for the baseRegen calculation used by StatsTab

Bug 2: grossRegen per-element used wrong field

  • gameStore.ts buildConversionParams: Changed from def.conversionRate (0.2 enchanter) to def.rawManaRegen (0.5 enchanter) with level scaling × 1.5^(level-1)
  • LeftPanel.tsx: Same fix + includes attunement regen in rawGrossRegen display value
  • ElementStatsSection.tsx: Same fix

Files Changed (8 files, +290/-13)

  • src/lib/game/stores/gameStore.ts — primary tick pipeline fix
  • src/lib/game/hooks/useGameDerived.ts — display-side baseRegen fix
  • src/app/components/LeftPanel.tsx — display-side grossRegen + rawGrossRegen fix
  • src/components/game/tabs/StatsTab/ElementStatsSection.tsx — display-side grossRegen fix
  • src/lib/game/__tests__/conversion-pause-bug-regression.test.ts — new regression test (7 tests)

Test Results

  • All 63 test files passed (1131 tests total, 0 failures)
  • Pre-commit hooks passed (file size ≤400 lines, tests green, dependency graph updated)

Commit

076282c — pushed to master

## Fix Complete — Bug #348 ### Root Causes Fixed **Bug 1: `rawGrossRegen` was ~2/hr instead of actual regen** - `gameStore.ts`: `computeRegen` now receives `ctx.attunement.attunements` instead of `{}`, so `rawGrossRegen` includes attunement contributions (0.5/hr base × 1.5^(level-1) scaling) - `useGameDerived.ts`: Same fix — passes actual attunements to `computeRegen` for the `baseRegen` calculation used by StatsTab **Bug 2: `grossRegen` per-element used wrong field** - `gameStore.ts` `buildConversionParams`: Changed from `def.conversionRate` (0.2 enchanter) to `def.rawManaRegen` (0.5 enchanter) with level scaling `× 1.5^(level-1)` - `LeftPanel.tsx`: Same fix + includes attunement regen in `rawGrossRegen` display value - `ElementStatsSection.tsx`: Same fix ### Files Changed (8 files, +290/-13) - `src/lib/game/stores/gameStore.ts` — primary tick pipeline fix - `src/lib/game/hooks/useGameDerived.ts` — display-side baseRegen fix - `src/app/components/LeftPanel.tsx` — display-side grossRegen + rawGrossRegen fix - `src/components/game/tabs/StatsTab/ElementStatsSection.tsx` — display-side grossRegen fix - `src/lib/game/__tests__/conversion-pause-bug-regression.test.ts` — new regression test (7 tests) ### Test Results - All 63 test files passed (1131 tests total, 0 failures) - Pre-commit hooks passed (file size ≤400 lines, tests green, dependency graph updated) ### Commit `076282c` — pushed to master
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Anexim/Mana-Loop#348