feat: add mana conversion system spec and ticket
Build and Publish Mana Loop Docker Image / build-and-publish (push) Failing after 1m2s

This commit is contained in:
2026-06-04 13:24:15 +02:00
parent 23e629f37e
commit c22f9c3bd5
4 changed files with 430 additions and 2 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
# Circular Dependencies
Generated: 2026-06-04T09:16:19.999Z
Generated: 2026-06-04T09:37:34.683Z
No circular dependencies found. ✅
+1 -1
View File
@@ -1,6 +1,6 @@
{
"_meta": {
"generated": "2026-06-04T09:16:18.073Z",
"generated": "2026-06-04T09:37:32.833Z",
"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."
},
+1
View File
@@ -27,6 +27,7 @@ Mana-Loop/
│ │ │ │ │ └── pact-system-spec.md
│ │ │ │ └── invoker-spec.md
│ │ │ └── attunement-system-spec.md
│ │ ├── mana-conversion-spec.md
│ │ ├── spire-climbing-spec.md
│ │ └── spire-combat-spec.md
│ ├── GAME_BRIEFING.md
+427
View File
@@ -0,0 +1,427 @@
# Mana Conversion System — Specification
## Overview
This spec defines a unified mana conversion system that replaces the current fragmented approach (attunement conversions, discipline conversions, manual conversion, and guardian pact conversions). All conversion types use the same core mechanics: consuming source mana types to produce a destination mana type, with costs deducted from **regen** (not from the mana pool directly).
---
## 1. Element Distance from Raw Mana
Every mana type has a **distance** from raw mana. This value is used in two places:
1. Calculating conversion cost ratios
2. Calculating meditation multiplier strength for that element's conversion
### Distance Table
| Element | Category | Distance |
|---------|----------|----------|
| Raw | — | 0 |
| Fire, Water, Air, Earth, Light, Dark, Death | Base | 1 |
| Transference | Utility | 1 |
| Metal, Sand, Lightning, Frost, BlackFlame, RadiantFlames, Miasma, ShadowGlass | Composite | 2 |
| Crystal, Stellar, Void, Soul, Plasma | Exotic (tier 1) | 3 |
| Time | Exotic (tier 2) | 4 |
### Reusable Function
```typescript
// src/lib/game/utils/element-distance.ts
export function getElementDistance(elementId: string): number
```
Returns the distance for any element. If a composite element's recipe contains components at different distances, the element's distance = max(component distances) + 1.
---
## 2. Conversion Cost Ratios
All conversions produce **1 unit** of destination mana. The cost depends on the destination's distance from raw.
### Cost Formula
For a destination element at distance `d`:
- **Raw mana cost** = `10^(d+1)`
- Distance 1 (base): `10^2 = 100` raw per 1 element
- Distance 2 (composite): `10^3 = 1,000` raw per 1 element
- Distance 3 (exotic): `10^4 = 10,000` raw per 1 element
- Distance 4 (time): `10^5 = 100,000` raw per 1 element
- **Each component mana cost** = `10 * (d + 1)` per 1 destination element
- Distance 1: `10 * 2 = 20` of that element per 1 destination
- Distance 2: `10 * 3 = 30` of that element per 1 destination
- Distance 3: `10 * 4 = 40` of that element per 1 destination
- Distance 4: `10 * 5 = 50` of that element per 1 destination
### Cost Table (per 1 unit of destination mana)
| Destination | Distance | Raw Cost | Each Component Cost | Components |
|-------------|----------|----------|---------------------|------------|
| Fire (base) | 1 | 100 | — | — |
| Transference | 1 | 100 | — | — |
| Metal | 2 | 1,000 | 30 fire + 30 earth | fire, earth |
| Sand | 2 | 1,000 | 30 earth + 30 water | earth, water |
| Lightning | 2 | 1,000 | 30 fire + 30 air | fire, air |
| Frost | 2 | 1,000 | 30 air + 30 water | air, water |
| BlackFlame | 2 | 1,000 | 30 dark + 30 fire | dark, fire |
| Radiant Flames | 2 | 1,000 | 30 light + 30 fire | light, fire |
| Miasma | 2 | 1,000 | 30 air + 30 death | air, death |
| Shadow Glass | 2 | 1,000 | 30 earth + 30 dark | earth, dark |
| Crystal | 3 | 10,000 | 40 sand + 40 light | sand, light |
| Stellar | 3 | 10,000 | 40 plasma + 40 light | plasma, light |
| Void | 3 | 10,000 | 40 dark + 40 death | dark, death |
| Soul | 3 | 10,000 | 40 light + 40 dark + 40 transference | light, dark, transference |
| Plasma | 3 | 10,000 | 40 lightning + 40 fire + 40 transference | lightning, fire, transference |
| Time | 4 | 100,000 | 50 soul + 50 sand + 50 transference | soul, sand, transference |
### Key Constraint
Raw mana cost is always **greater** than any individual component cost. This is inherent in the formula: `10^(d+1)` for raw vs `10*(d+1)` for each component.
---
## 3. Conversion Rate — Unified Formula
All three sources (disciplines, attunements, guardian pacts) contribute to a single **base conversion rate** for each element. This rate is then exponentially boosted by attunement levels and pact bonuses.
### Formula
```
finalRate = (disciplineRate + attunementBaseRate + pactBaseRate) ^ (1 + attunementLevelBonus + pactLevelBonus)
```
Where:
- `disciplineRate` = sum of conversion rates from active disciplines for this element (see §4)
- `attunementBaseRate` = sum of base conversion rates from attunements for this element (see §5)
- `pactBaseRate` = sum of base conversion rates from guardian pacts for this element (see §6)
- `attunementLevelBonus` = sum of relevant attunement levels (e.g., Enchanter level for transference, Fabricator level for earth)
- `pactLevelBonus` = count of pacts with guardians that have this element as primary × Invoker attunement level
### Example
A player with:
- Fire Conversion discipline active (rate = 0.5)
- Enchanter attunement level 3 (no fire base rate, but level contributes to exponent if fire is the attunement's primary)
- Fabricator attunement level 2 (earth primary, so contributes to earth conversions)
- 2 fire-type guardian pacts, Invoker level 3
For **fire mana** conversion:
```
baseRate = 0.5 (discipline) + 0 (no attunement base for fire) + 0 (no pact base for fire)
exponent = 1 + 0 (no attunement has fire as primary) + 0 (no fire-type pact bonus)
finalRate = 0.5^1 = 0.5/hr
```
For **metal mana** conversion (fire + earth):
```
baseRate = 0.35 (metal discipline) + 0 (no attunement base) + 0 (no pact base)
exponent = 1 + 2 (Fabricator level 2, earth is a component of metal) + 0
finalRate = 0.35^3 = 0.0429/hr
```
Wait — this produces *lower* rates at higher levels, which is wrong. The exponent should be a **multiplier**, not an exponent on the rate. Let me restate:
### Corrected Formula
```
finalRate = (disciplineRate + attunementBaseRate + pactBaseRate) × (1 + attunementLevelBonus + pactLevelBonus)
```
Where the multiplier is additive:
- `attunementLevelBonus` = sum of relevant attunement levels × 0.5 (each level adds +50% to rate)
- `pactLevelBonus` = count of pacts with this element × Invoker level × 0.25
So:
```
finalRate = baseRate × (1 + Σ(attunementLevel_i × 0.5) + Σ(pactCount_element × invokerLevel × 0.25))
```
### Revised Example
For **metal mana** with Metal Conversion discipline (0.35/hr), Fabricator level 2:
```
baseRate = 0.35
multiplier = 1 + (2 × 0.5) = 2.0
finalRate = 0.35 × 2.0 = 0.70/hr
```
For **transference mana** with Transference Conversion discipline (0.4/hr), Enchanter level 3:
```
baseRate = 0.4
multiplier = 1 + (3 × 0.5) = 2.5
finalRate = 0.4 × 2.5 = 1.0/hr
```
---
## 4. Discipline Contributions
Each conversion discipline provides a **base rate** that scales with XP.
### Base Rates (per hour)
| Element | Base Rate | Difficulty Factor | Scaling Factor |
|---------|-----------|-------------------|----------------|
| Fire, Water, Air, Earth, Light, Dark, Death | 0.5 | 120 | 60 |
| Transference | 0.4 | 100 | 50 |
| Metal, Sand, Lightning, Frost | 0.35 | 160 | 80 |
| BlackFlame, RadiantFlames, Miasma, ShadowGlass | 0.30 | 170 | 85 |
| Crystal, Void | 0.25 | 220 | 110 |
| Stellar, Soul, Plasma | 0.20 | 240 | 120 |
| Time | 0.15 | 260 | 130 |
### XP Scaling
The discipline's effective rate bonus follows the standard stat bonus formula:
```
statBonus = baseValue × (XP / scalingFactor)^0.65
```
The discipline's total contribution to the base rate is:
```
disciplineRate = baseRate + statBonus
```
### Perks
Each discipline has perks that add flat bonuses to the rate:
- **`once` perk**: grants `+baseRate` to the conversion rate at threshold XP
- **`infinite` perk**: every N XP grants `+baseRate × 0.5` to the conversion rate
---
## 5. Attunement Contributions
Attunements provide a **base conversion rate** for their primary mana type, plus a **level-based multiplier** to all conversions involving their element.
### Attunement Base Rates
| Attunement | Primary Mana | Base Rate (per hour) |
|------------|--------------|---------------------|
| Enchanter | Transference | 0.2 |
| Fabricator | Earth | 0.25 |
| Invoker | None | 0 |
### Attunement Level Multiplier
Each attunement level adds +0.5 to the multiplier for conversions where the attunement's primary element is either:
- The destination element, OR
- A component element of the destination
Example: Fabricator (earth) level 3 boosts:
- Earth conversions (earth is destination)
- Metal conversions (earth is component)
- Sand conversions (earth is component)
- Shadow Glass conversions (earth is component)
But NOT fire conversions (earth is not involved).
---
## 6. Guardian Pact Contributions
Guardian pacts provide:
1. A **base conversion rate** for the guardian's element
2. A **level bonus** to the multiplier, scaled by Invoker attunement level
### Pact Base Rate
Each signed pact grants `+0.15/hr` base rate for the guardian's primary element.
### Pact Level Bonus
For each signed pact whose guardian has element E as primary:
```
pactLevelBonus_E += invokerLevel × 0.25
```
So an Invoker at level 4 with 2 fire-type pacts grants:
```
pactLevelBonus_fire = 2 × 4 × 0.25 = 2.0
```
This adds to the multiplier for fire conversions and any composite that uses fire.
---
## 7. Meditation Multiplier
Meditation boosts conversion rates, but the boost is reduced for elements further from raw.
### Formula
```
meditationBoost = 1 + (meditationMultiplier - 1) / distance
```
Where `distance` is the destination element's distance from raw mana.
| Element Distance | Meditation Strength |
|-----------------|-------------------|
| 1 (base) | Full: `meditationMultiplier` |
| 2 (composite) | Half: `1 + (med - 1) / 2` |
| 3 (exotic) | Third: `1 + (med - 1) / 3` |
| 4 (time) | Quarter: `1 + (med - 1) / 4` |
For elements with components at different distances, use the **highest** distance value (i.e., the weakest meditation boost).
---
## 8. Regen Deduction Model
All conversion costs are deducted from **mana regen**, not from the mana pool directly. This means:
1. Each element has a **gross regen** (from attunements, upgrades, etc.)
2. Conversions that consume this element as a source **reduce** the effective regen
3. The remaining regen is the **net regen** that actually adds to the pool
### Raw Mana
```
rawNetRegen = rawGrossRegen
- Σ (conversionRate_destination × rawCost_destination) for all active conversions
```
### Element Mana (e.g., fire)
```
fireNetRegen = fireGrossRegen
+ fireProducedRate (from raw→fire conversion)
- Σ (conversionRate_destination × fireCost_destination) for all conversions using fire as component
```
### Display Format
Each element's regen display shows:
```
Fire Mana Regen:
+0.50/hr converted from raw mana (Fire Conversion discipline, rate × attunement multiplier × meditation)
-0.15/hr being converted into Metal mana (30 per unit × 0.005 units/hr)
-0.10/hr being converted into Lightning mana (30 per unit × 0.0033 units/hr)
─────────────────
+0.25/hr net fire mana regen
```
---
## 9. Insufficient Regen — Auto-Pause
If a conversion's source cost exceeds the **gross regen** of that source type, the conversion is **completely disabled** (not partially throttled).
### Conditions
A conversion for element E is paused if:
```
conversionRate_E × sourceCost_source > sourceGrossRegen
```
for **any** source type (raw or component element) in the conversion.
### UI Warning
When a conversion is paused due to insufficient regen:
- The conversion's entry in the stats tab shows a **red warning**: "⚠️ PAUSED: Insufficient [source] regen (need X/hr, have Y/hr)"
- The mana display for the source element shows a warning icon next to the draining conversion
### Auto-Resume
When regen increases (e.g., attunement levels up, new discipline XP gained, meditation active), paused conversions automatically resume if the regen now covers the cost.
---
## 10. No Manual Conversion
The existing `convertMana` action and `processConvertAction` are **removed**. All mana conversion happens passively through the unified system. The "convert" player action is removed from the action buttons.
---
## 11. Stats Tab Display
The Stats tab includes a new **Conversion Stats** section showing:
### Per-Element Conversion Table
For each element with active conversions:
```
┌─────────────────────────────────────────────────────────────┐
│ 🔥 FIRE MANA CONVERSION │
│ │
│ Base Rate: 0.50/hr (Fire Conversion discipline) │
│ Attunement Bonus: ×1.00 (no attunement for fire) │
│ Pact Bonus: ×1.00 (0 fire-type pacts) │
│ Meditation: ×1.00 (not meditating) │
│ ───────────────────────────────────────── │
│ Effective Rate: 0.50/hr → produces 0.50 fire/hr │
│ │
│ Costs (deducted from raw regen): │
│ Raw: 100 × 0.50 = 50.0 raw/hr │
│ │
│ Drained by downstream conversions: │
│ → Metal: 30 × 0.005 = 0.15 fire/hr │
│ → Lightning: 30 × 0.003 = 0.10 fire/hr │
│ │
│ Net Fire Regen: +0.50 - 0.15 - 0.10 = +0.25 fire/hr │
└─────────────────────────────────────────────────────────────┘
```
### Formula Summary
A collapsible formula reference is shown at the top:
```
Conversion Rate Formula:
finalRate = (disciplineRate + attunementBase + pactBase) × attunementMult × pactMult × meditationMult
Where:
attunementMult = 1 + Σ(relevantAttunementLevel × 0.5)
pactMult = 1 + Σ(pactCount_element × invokerLevel × 0.25)
meditationMult = 1 + (meditationMultiplier - 1) / elementDistance
Cost per 1 unit of destination:
rawCost = 10^(distance+1)
componentCost = 10 × (distance+1) per component
All costs deducted from source regen (not from mana pool).
Conversions pause if source regen < conversion cost.
```
---
## 12. Implementation Notes
### New Files
- `src/lib/game/utils/element-distance.ts``getElementDistance()` function
- `src/lib/game/utils/conversion-rates.ts` — Unified conversion rate calculator
- `src/lib/game/data/conversion-costs.ts` — Cost ratio table per element
### Modified Files
- `src/lib/game/data/disciplines/elemental-regen.ts` — Update base rates, remove drain model
- `src/lib/game/data/disciplines/elemental-regen-advanced.ts` — Update base rates, remove drain model
- `src/lib/game/data/attunements.ts` — Update conversion rates to match new system
- `src/lib/game/effects/discipline-effects.ts` — Update conversion computation
- `src/lib/game/stores/gameStore.ts` — Replace tick conversion logic with unified system
- `src/lib/game/stores/manaStore.ts` — Remove `convertMana`, `processConvertAction`, `craftComposite`
- `src/lib/game/stores/prestigeStore.ts` — Add pact conversion rate data
- `src/components/game/tabs/StatsTab/ElementStatsSection.tsx` — Add conversion display
- `src/components/game/ManaDisplay.tsx` — Add per-element regen breakdown
### Removed
- Manual conversion (`convertMana`, `processConvertAction`)
- Composite crafting via `craftComposite` (replaced by passive conversion)
- The "convert" action from player actions
- Per-tick mana pool deduction for conversions (replaced by regen deduction)
---
## 13. Migration Notes
Existing save data will need migration:
- Active discipline conversion rates are preserved (the XP and discipline IDs stay the same)
- Attunement conversion rates are recalculated from the new base rates
- Any manually-converted element mana in pools is preserved
- The `convertMana` and `craftComposite` store actions are kept as no-ops for save compatibility but have no UI