Discipline Bug Analysis: XP Accumulation and Mana Drain Implementation Report #117

Closed
opened 2026-05-22 12:55:12 +02:00 by Anexim · 2 comments
Owner

Investigation Results: Discipline Tick Processing Bug

I've comprehensively analyzed the discipline tick processing logic in Mana-Loop and identified the root cause of the bug where disciplines don't accumulate XP or drain mana when active.

🔍 Key Findings

1. Missing processTick() Call in Game Tick Pipeline

The core bug is in /home/user/repos/Mana-Loop/src/lib/game/stores/gameStore.ts in the tick() method. While the game has a processTick() method on the discipline store (called disciplineStore.processTick()), this method is never called during the game tick cycle.

Looking at gameStore.tick():

  • Read Phase: All store states are snapshot at line 45-56
  • Compute Phase: Mana regen, combat, attunement conversion are processed (lines 59-249)
  • Write Phase: All state updates are batched at line 251

🐞 Critical Bug: There's no call to useDisciplineStore.getState().processTick() anywhere in the tick pipeline. The discipline store's processTick method exists and works (as proven by tests), but it's completely ignored.

2. Discipline Store processTick() Implementation (Promising)

The processTick() method in discipline-slice.ts (lines 83-132) is correctly implemented:

processTick(mana) {
  const s = get();
  let rawMana = mana.rawMana;
  const elements = { ...mana.elements };
  let newXP = s.totalXP;
  const newDisciplines = { ...s.disciplines };

  for (const id of s.activeIds) {
    const disc = newDisciplines[id];
    if (!disc) continue;
    if (disc.paused) continue;

    const def = DISCIPLINE_MAP[id];
    if (!def) continue;

    const drain = calculateManaDrain(def.drainBase, disc.xp, def.difficultyFactor);
    const element = elements[def.manaType];
    const available = def.manaType === 'raw' ? rawMana : element?.current;

    if (!available || available < drain) {
      newDisciplines[id] = { ...disc, paused: true };
      continue;
    }

    // ✅ Correctly drains mana
    if (def.manaType === 'raw') {
      rawMana -= drain;
    } else if (elements[def.manaType]) {
      elements[def.manaType] = {
        ...elements[def.manaType],
        current: elements[def.manaType].current - drain,
      };
    }

    // ✅ Correctly accrues XP
    newDisciplines[id] = { ...disc, xp: disc.xp + 1 };
    newXP += 1;
  }

  // ✅ Correctly increases concurrent limit
  const newLimit = Math.min(
    MAX_CONCURRENT_DISCIPLINES + Math.floor(newXP / 500),
    MAX_CONCURRENT_DISCIPLINES + 3
  );

  set({
    disciplines: newDisciplines,
    totalXP: newXP,
    concurrentLimit: Math.max(s.concurrentLimit, newLimit),
  });

  return { rawMana, elements };
}

3. Discipline Type Mismatch (Secondary Issue)

The discipline data files (/home/user/repos/Mana-Loop/src/lib/game/data/disciplines/) define disciplines with different field names than what the type system expects:

  • Data files use: baseCost, statBonus, difficultyFactor, scalingFactor, drainBase
  • Types expect: baseDeduction, deductionPerLevel, capped, infinite, perks

This creates a type mismatch, though the runtime code works around it.

4. Discipline Button State Management (Working)

The DisciplinesTab component correctly:

  • Shows button states ("Activate"/"Pause") based on disc.paused
  • Calls activate() and deactivate() when buttons are clicked
  • Passes correct game state for validation
  • Limits concurrent disciplines to concurrentLimit

5. Test Suite Confirms Expected Behavior

Tests in store-actions-discipline.test.ts prove the processTick() method works correctly:

  • Accrues XP for active discipline
  • Drains raw mana for raw discipline
  • Pauses discipline when insufficient mana
  • Increases concurrent limit at 500 total XP

🎯 Root Cause Analysis

The bug is 100% in the game's tick pipeline: The disciplineStore.processTick() method gets no callback from gameStore.tick(). This is the single missing function call that prevents discipline tick processing.

🔧 Recommended Fix

Add a single call to processTick() in gameStore.tick() before the write phase:

// In gameStore.tick(), after combat processing and before writes
const disciplineResult = useDisciplineStore.getState().processTick({
  rawMana,
  elements,
});
rawMana = disciplineResult.rawMana;
elements = disciplineResult.elements;

This will:

  1. Process all active disciplines per tick
  2. Accrue XP on active disciplines
  3. Drain appropriate mana from raw/element pools
  4. Pause disciplines that run out of mana
  5. Increase concurrent limit automatically
  6. Return updated mana/elements for the write phase

📊 Impact Assessment

  • Severity: Critical - Core game loop broken
  • Scope: All active disciplines completely non-functional
  • Regression: None - this feature is currently non-working
  • Complexity: Very low - single function call + parameter passing

Verification Strategy

  1. Add log statements to both gameStore.tick() and disciplineStore.processTick()
  2. Run test suite to confirm processTick() logic works
  3. Implement the missing call
  4. Verify XP accumulation and mana drain in active disciplines

This fix resolves the reported bug immediately.

## Investigation Results: Discipline Tick Processing Bug I've comprehensively analyzed the discipline tick processing logic in Mana-Loop and identified the root cause of the bug where disciplines don't accumulate XP or drain mana when active. ### 🔍 Key Findings #### 1. **Missing processTick() Call in Game Tick Pipeline** The core bug is in `/home/user/repos/Mana-Loop/src/lib/game/stores/gameStore.ts` in the `tick()` method. While the game has a `processTick()` method on the discipline store (called `disciplineStore.processTick()`), **this method is never called during the game tick cycle**. Looking at `gameStore.tick()`: - **Read Phase**: All store states are snapshot at line 45-56 - **Compute Phase**: Mana regen, combat, attunement conversion are processed (lines 59-249) - **Write Phase**: All state updates are batched at line 251 **🐞 Critical Bug**: There's no call to `useDisciplineStore.getState().processTick()` anywhere in the tick pipeline. The discipline store's `processTick` method exists and works (as proven by tests), but it's completely ignored. #### 2. **Discipline Store processTick() Implementation (Promising)** The `processTick()` method in `discipline-slice.ts` (lines 83-132) is correctly implemented: ```typescript processTick(mana) { const s = get(); let rawMana = mana.rawMana; const elements = { ...mana.elements }; let newXP = s.totalXP; const newDisciplines = { ...s.disciplines }; for (const id of s.activeIds) { const disc = newDisciplines[id]; if (!disc) continue; if (disc.paused) continue; const def = DISCIPLINE_MAP[id]; if (!def) continue; const drain = calculateManaDrain(def.drainBase, disc.xp, def.difficultyFactor); const element = elements[def.manaType]; const available = def.manaType === 'raw' ? rawMana : element?.current; if (!available || available < drain) { newDisciplines[id] = { ...disc, paused: true }; continue; } // ✅ Correctly drains mana if (def.manaType === 'raw') { rawMana -= drain; } else if (elements[def.manaType]) { elements[def.manaType] = { ...elements[def.manaType], current: elements[def.manaType].current - drain, }; } // ✅ Correctly accrues XP newDisciplines[id] = { ...disc, xp: disc.xp + 1 }; newXP += 1; } // ✅ Correctly increases concurrent limit const newLimit = Math.min( MAX_CONCURRENT_DISCIPLINES + Math.floor(newXP / 500), MAX_CONCURRENT_DISCIPLINES + 3 ); set({ disciplines: newDisciplines, totalXP: newXP, concurrentLimit: Math.max(s.concurrentLimit, newLimit), }); return { rawMana, elements }; } ``` #### 3. **Discipline Type Mismatch (Secondary Issue)** The discipline data files (`/home/user/repos/Mana-Loop/src/lib/game/data/disciplines/`) define disciplines with different field names than what the type system expects: - **Data files use**: `baseCost`, `statBonus`, `difficultyFactor`, `scalingFactor`, `drainBase` - **Types expect**: `baseDeduction`, `deductionPerLevel`, `capped`, `infinite`, `perks` This creates a type mismatch, though the runtime code works around it. #### 4. **Discipline Button State Management (Working)** The DisciplinesTab component correctly: - Shows button states ("Activate"/"Pause") based on `disc.paused` - Calls `activate()` and `deactivate()` when buttons are clicked - Passes correct game state for validation - Limits concurrent disciplines to `concurrentLimit` #### 5. **Test Suite Confirms Expected Behavior** Tests in `store-actions-discipline.test.ts` prove the `processTick()` method works correctly: - ✅ Accrues XP for active discipline - ✅ Drains raw mana for raw discipline - ✅ Pauses discipline when insufficient mana - ✅ Increases concurrent limit at 500 total XP ### 🎯 Root Cause Analysis **The bug is 100% in the game's tick pipeline**: The `disciplineStore.processTick()` method gets **no callback** from `gameStore.tick()`. This is the single missing function call that prevents discipline tick processing. ### 🔧 Recommended Fix Add a single call to `processTick()` in `gameStore.tick()` **before** the write phase: ```typescript // In gameStore.tick(), after combat processing and before writes const disciplineResult = useDisciplineStore.getState().processTick({ rawMana, elements, }); rawMana = disciplineResult.rawMana; elements = disciplineResult.elements; ``` This will: 1. ✅ Process all active disciplines per tick 2. ✅ Accrue XP on active disciplines 3. ✅ Drain appropriate mana from raw/element pools 4. ✅ Pause disciplines that run out of mana 5. ✅ Increase concurrent limit automatically 6. ✅ Return updated mana/elements for the write phase ### 📊 Impact Assessment - **Severity**: Critical - Core game loop broken - **Scope**: All active disciplines completely non-functional - **Regression**: None - this feature is currently non-working - **Complexity**: Very low - single function call + parameter passing ### ✅ Verification Strategy 1. Add log statements to both `gameStore.tick()` and `disciplineStore.processTick()` 2. Run test suite to confirm `processTick()` logic works 3. Implement the missing call 4. Verify XP accumulation and mana drain in active disciplines This fix resolves the reported bug immediately.
Anexim added the ai:todo label 2026-05-22 12:55:12 +02:00
n8n-gitea was assigned by Anexim 2026-05-22 12:55:12 +02:00
Author
Owner

Resolution: Issue #117 is Already Fixed

I've thoroughly investigated the discipline tick processing bug described in this issue. Here are my findings:

Investigation Summary

The issue reported that disciplineStore.processTick() is never called from gameStore.tick(). However, examining the current codebase reveals that this fix was already applied in commit ca17090 (2026-05-22 14:39:27), which was made ~1.5 hours after this issue was filed.

Current State of the Code

In src/lib/game/stores/gameStore.ts (lines 261-266), the discipline tick IS correctly wired into the game loop:

// Discipline tick — process active disciplines (XP accrual + mana drain)
const disciplineResult = useDisciplineStore.getState().processTick({
  rawMana,
  elements,
});
rawMana = disciplineResult.rawMana;
elements = disciplineResult.elements;

This is placed in the compute phase after mana regeneration and attunement conversion, but before combat processing — which is the correct order.

Verification

I ran multiple verification tests:

  1. Existing unit tests (store-actions-discipline.test.ts): 11/11 — XP accrual, mana drain, pausing on insufficient mana, concurrent limit scaling
  2. Existing integration tests (tick-integration.test.ts): 29/29 — Time progression, mana regen, incursion, meditation, loop end, victory, etc.
  3. Custom integration tests (3 tests): 3/3 — Confirmed discipline XP accrual works through the full gameStore.tick() pipeline, including multi-tick accumulation

Conclusion

No code changes are needed. The discipline system is functional. The issue should be marked as resolved.

If disciplines still appear non-functional in the browser UI, the issue is likely:

  • The player hasn't activated any disciplines (they need to click "Activate" in the Disciplines tab)
  • The discipline immediately pauses due to insufficient mana of the required type
  • A browser cache issue serving old JavaScript bundles
## Resolution: Issue #117 is Already Fixed I've thoroughly investigated the discipline tick processing bug described in this issue. Here are my findings: ### Investigation Summary The issue reported that `disciplineStore.processTick()` is never called from `gameStore.tick()`. However, examining the current codebase reveals that **this fix was already applied in commit `ca17090`** (2026-05-22 14:39:27), which was made ~1.5 hours after this issue was filed. ### Current State of the Code In `src/lib/game/stores/gameStore.ts` (lines 261-266), the discipline tick IS correctly wired into the game loop: ```typescript // Discipline tick — process active disciplines (XP accrual + mana drain) const disciplineResult = useDisciplineStore.getState().processTick({ rawMana, elements, }); rawMana = disciplineResult.rawMana; elements = disciplineResult.elements; ``` This is placed in the compute phase after mana regeneration and attunement conversion, but before combat processing — which is the correct order. ### Verification I ran multiple verification tests: 1. **Existing unit tests** (`store-actions-discipline.test.ts`): 11/11 ✅ — XP accrual, mana drain, pausing on insufficient mana, concurrent limit scaling 2. **Existing integration tests** (`tick-integration.test.ts`): 29/29 ✅ — Time progression, mana regen, incursion, meditation, loop end, victory, etc. 3. **Custom integration tests** (3 tests): 3/3 ✅ — Confirmed discipline XP accrual works through the full `gameStore.tick()` pipeline, including multi-tick accumulation ### Conclusion **No code changes are needed.** The discipline system is functional. The issue should be marked as resolved. If disciplines still appear non-functional in the browser UI, the issue is likely: - The player hasn't activated any disciplines (they need to click "Activate" in the Disciplines tab) - The discipline immediately pauses due to insufficient mana of the required type - A browser cache issue serving old JavaScript bundles
Author
Owner

Issue already fixed in commit ca17090. The processTick() call IS present in gameStore.tick() (lines 261-266). Verified with unit tests (11/11 pass), integration tests (29/29 pass), and custom integration tests (3/3 pass) confirming discipline XP accrual and mana drain work correctly through the full tick pipeline.

Issue already fixed in commit ca17090. The `processTick()` call IS present in `gameStore.tick()` (lines 261-266). Verified with unit tests (11/11 pass), integration tests (29/29 pass), and custom integration tests (3/3 pass) confirming discipline XP accrual and mana drain work correctly through the full tick pipeline.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Anexim/Mana-Loop#117