feat: add prestige system and skill upgrades with comprehensive documentation
Build and Publish Mana Loop Docker Image / build-and-publish (push) Failing after 5m57s
Build and Publish Mana Loop Docker Image / build-and-publish (push) Failing after 5m57s
This commit is contained in:
@@ -0,0 +1,312 @@
|
||||
# Task6-1 Context: Proposal 1 - Unlocked Mana Type Capacity
|
||||
|
||||
## Overview
|
||||
Gathered context for implementing Proposal 1: Unlocked Mana Type Capacity. This proposal likely relates to either:
|
||||
1. Increasing the mana capacity (max) of unlocked mana types
|
||||
2. Increasing the number of mana types that can be unlocked
|
||||
|
||||
## 1. Existing Prestige Upgrade Structure
|
||||
|
||||
**File:** `src/lib/game/constants/prestige.ts`
|
||||
|
||||
### PrestigeDef Interface (from `src/lib/game/types/skills.ts`)
|
||||
```typescript
|
||||
export interface PrestigeDef {
|
||||
name: string;
|
||||
desc: string;
|
||||
max: number;
|
||||
cost: number;
|
||||
}
|
||||
```
|
||||
|
||||
### Current Prestige Upgrades (relevant to mana/elements)
|
||||
```typescript
|
||||
export const PRESTIGE_DEF: Record<string, PrestigeDef> = {
|
||||
// ... other upgrades ...
|
||||
|
||||
// Existing elemental capacity upgrade
|
||||
elementalAttune: {
|
||||
name: "Elemental Attunement",
|
||||
desc: "+25 elemental mana cap",
|
||||
max: 10,
|
||||
cost: 600
|
||||
},
|
||||
|
||||
// Starting elemental mana upgrade
|
||||
elemStart: {
|
||||
name: "Elem. Start",
|
||||
desc: "Start with 5 of each unlocked element",
|
||||
max: 3,
|
||||
cost: 800
|
||||
},
|
||||
|
||||
// ... other upgrades ...
|
||||
};
|
||||
```
|
||||
|
||||
**Key observations:**
|
||||
- All prestige upgrades use the same structure: name, description, max level, cost
|
||||
- `elementalAttune` already provides +25 elemental mana cap per level (max 10 levels = +250 cap)
|
||||
- `elemStart` gives starting elemental mana when a new loop begins
|
||||
- Upgrades are stored as `Record<string, number>` where the value is the current level
|
||||
|
||||
---
|
||||
|
||||
## 2. How Mana Type Unlocks Are Tracked
|
||||
|
||||
### Element State Structure
|
||||
**File:** `src/lib/game/types/elements.ts`
|
||||
```typescript
|
||||
export interface ElementState {
|
||||
current: number;
|
||||
max: number;
|
||||
unlocked: boolean;
|
||||
}
|
||||
```
|
||||
|
||||
### Elements Storage in GameState
|
||||
**File:** `src/lib/game/types/game.ts`
|
||||
```typescript
|
||||
export interface GameState {
|
||||
// ...
|
||||
elements: Record<string, ElementState>;
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### Base Unlocked Elements
|
||||
**File:** `src/lib/game/constants/elements.ts`
|
||||
```typescript
|
||||
export const BASE_UNLOCKED_ELEMENTS = ['transference'];
|
||||
```
|
||||
|
||||
**Key observations:**
|
||||
- Only `transference` starts unlocked by default
|
||||
- All other 12 elements (fire, water, air, earth, light, dark, death, metal, sand, lightning, crystal, stellar, void) start locked
|
||||
- Elements have a boolean `unlocked` field - no partial unlocking or limits on number of unlocked types
|
||||
|
||||
### Unlocking New Elements
|
||||
**File:** `src/lib/game/store.ts` (lines 2176-2195)
|
||||
```typescript
|
||||
unlockElement: (element: string) => {
|
||||
const state = get();
|
||||
if (state.elements[element]?.unlocked) return;
|
||||
|
||||
const cost = 500;
|
||||
if (state.rawMana < cost) return;
|
||||
|
||||
// ELEMENTAL_AFFINITY: New elements start with 10 capacity
|
||||
const effects = getUnifiedEffects(state.skillUpgrades, state.skillTiers);
|
||||
const newElementMax = hasSpecial(effects, SPECIAL_EFFECTS.ELEMENTAL_AFFINITY) ? 10 : 0;
|
||||
|
||||
set({
|
||||
rawMana: state.rawMana - cost,
|
||||
elements: {
|
||||
...state.elements,
|
||||
[element]: { ...state.elements[element], unlocked: true, max: newElementMax },
|
||||
},
|
||||
log: [`✨ ${ELEMENTS[element].name} affinity unlocked!`, ...state.log.slice(0, 49)],
|
||||
});
|
||||
},
|
||||
```
|
||||
|
||||
**Key observations:**
|
||||
- Unlocking an element costs 500 raw mana
|
||||
- When unlocked, the element gets `unlocked: true`
|
||||
- New elements start with `max: 0` unless ELEMENTAL_AFFINITY special effect is active (then max: 10)
|
||||
- No limit on the NUMBER of elements that can be unlocked - players can unlock all 12+ types
|
||||
|
||||
### Element Definitions
|
||||
**File:** `src/lib/game/constants/elements.ts`
|
||||
|
||||
Total element types:
|
||||
- **Base (7):** fire, water, air, earth, light, dark, death
|
||||
- **Utility (1):** transference
|
||||
- **Composite (3):** metal, sand, lightning
|
||||
- **Exotic (3):** crystal, stellar, void
|
||||
|
||||
**Total: 14 element types** (13 unlockable + transference which starts unlocked)
|
||||
|
||||
---
|
||||
|
||||
## 3. How Mana Capacity Is Applied
|
||||
|
||||
### computeElementMax Function
|
||||
**File:** `src/lib/game/store.ts` (lines 415-432)
|
||||
```typescript
|
||||
export function computeElementMax(
|
||||
state: Pick<GameState, 'skills' | 'prestigeUpgrades' | 'skillUpgrades' | 'skillTiers'>,
|
||||
effects?: ComputedEffects | UnifiedEffects,
|
||||
element?: string
|
||||
): number {
|
||||
const pu = state.prestigeUpgrades;
|
||||
const base = 10 + (state.skills.elemAttune || 0) * 50 + (pu.elementalAttune || 0) * 25;
|
||||
|
||||
// Apply upgrade effects if provided
|
||||
if (effects) {
|
||||
let bonus = effects.elementCapBonus; // Global bonus
|
||||
|
||||
// Add per-element bonus if element is specified and available
|
||||
if (element && (effects as UnifiedEffects).perElementCapBonus) {
|
||||
const perElementBonus = (effects as UnifiedEffects).perElementCapBonus[element];
|
||||
if (perElementBonus) {
|
||||
bonus += perElementBonus;
|
||||
}
|
||||
}
|
||||
|
||||
return Math.floor((base + bonus) * effects.elementCapMultiplier);
|
||||
}
|
||||
return base;
|
||||
}
|
||||
```
|
||||
|
||||
**Key observations:**
|
||||
- Base capacity formula: `10 + (elemAttune skill levels * 50) + (elementalAttune prestige levels * 25)`
|
||||
- Effects system can modify capacity via:
|
||||
- `elementCapBonus`: Global flat bonus
|
||||
- `elementCapMultiplier`: Global multiplier
|
||||
- `perElementCapBonus[element]`: Per-element flat bonus (from equipment)
|
||||
- **Currently, all unlocked elements share the SAME max capacity** (calculated once and applied to all)
|
||||
- The `perElementCapBonus` exists but is currently only used for equipment effects
|
||||
|
||||
### Usage in Store Initialization
|
||||
**File:** `src/lib/game/store.ts` (lines 670-710)
|
||||
```typescript
|
||||
function makeInitial(overrides: Partial<GameState> = {}): GameState {
|
||||
const pu = overrides.prestigeUpgrades || {};
|
||||
// ...
|
||||
const elemMax = computeElementMax(
|
||||
{ skills: overrides.skills || {}, prestigeUpgrades: pu, skillUpgrades: overrides.skillUpgrades, skillTiers: overrides.skillTiers },
|
||||
effects
|
||||
);
|
||||
|
||||
const elements: Record<string, { current: number; max: number; unlocked: boolean }> = {};
|
||||
Object.keys(ELEMENTS).forEach((k) => {
|
||||
const isUnlocked = BASE_UNLOCKED_ELEMENTS.includes(k);
|
||||
let startAmount = 0;
|
||||
|
||||
// Start with some elemental mana if elemStart upgrade
|
||||
if (isUnlocked && pu.elemStart) {
|
||||
startAmount = pu.elemStart * 5;
|
||||
}
|
||||
|
||||
elements[k] = {
|
||||
current: overrides.elements?.[k]?.current ?? startAmount,
|
||||
max: elemMax, // <-- Same max for ALL elements
|
||||
unlocked: isUnlocked,
|
||||
};
|
||||
});
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
**Key observation:** The same `elemMax` is applied to ALL elements regardless of type or unlock status.
|
||||
|
||||
---
|
||||
|
||||
## 4. Save Data Structure (Cross-Loop Persistence)
|
||||
|
||||
### GameState Prestige-Related Fields
|
||||
**File:** `src/lib/game/types/game.ts`
|
||||
```typescript
|
||||
export interface GameState {
|
||||
// ...
|
||||
|
||||
// Prestige
|
||||
insight: number;
|
||||
totalInsight: number;
|
||||
prestigeUpgrades: Record<string, number>;
|
||||
memorySlots: number;
|
||||
memories: string[];
|
||||
|
||||
// Elements
|
||||
elements: Record<string, ElementState>;
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### What Persists Through Loops
|
||||
**File:** `src/lib/game/store.ts` (lines 2255-2290)
|
||||
```typescript
|
||||
startNewLoop: () => {
|
||||
const state = get();
|
||||
const insightGained = state.loopInsight || calcInsight(state);
|
||||
const total = state.insight + insightGained;
|
||||
|
||||
// ... (keep some spells through temporal memory)
|
||||
|
||||
const newState = makeInitial({
|
||||
loopCount: state.loopCount + 1,
|
||||
insight: total,
|
||||
totalInsight: (state.totalInsight || 0) + insightGained,
|
||||
prestigeUpgrades: state.prestigeUpgrades, // <-- PERSISTS
|
||||
memories: state.memories,
|
||||
skills: state.skills,
|
||||
manaHeartBonus: newHeartBonus,
|
||||
});
|
||||
|
||||
// ...
|
||||
set(newState);
|
||||
},
|
||||
```
|
||||
|
||||
**Key observations:**
|
||||
- `prestigeUpgrades` (all upgrade levels) persist through loops
|
||||
- `insight` and `totalInsight` persist and accumulate
|
||||
- `elements` are NOT persisted - they are reinitialized in `makeInitial()`
|
||||
- On new loop, element unlocks are lost (player must re-unlock elements)
|
||||
- `BASE_UNLOCKED_ELEMENTS` and `pu.elemStart` determine which elements start unlocked/have starting mana
|
||||
|
||||
### Purchasing Prestige Upgrades
|
||||
**File:** `src/lib/game/store.ts` (lines 2235-2250)
|
||||
```typescript
|
||||
doPrestige: (id: string) => {
|
||||
// ...
|
||||
const lvl = state.prestigeUpgrades[id] || 0;
|
||||
if (lvl >= pd.max || state.insight < pd.cost) return;
|
||||
|
||||
const newPU = { ...state.prestigeUpgrades, [id]: lvl + 1 };
|
||||
set({
|
||||
insight: state.insight - pd.cost,
|
||||
prestigeUpgrades: newPU,
|
||||
// ...
|
||||
});
|
||||
},
|
||||
```
|
||||
|
||||
**Key observations:**
|
||||
- Upgrades purchased with `insight` currency
|
||||
- Each level costs the same amount (flat cost model)
|
||||
- Max level check prevents over-purchasing
|
||||
- New upgrade level is saved to `prestigeUpgrades` record
|
||||
|
||||
---
|
||||
|
||||
## Summary of Key Points for Task6-1
|
||||
|
||||
1. **Prestige upgrades** follow a simple structure: `{ name, desc, max, cost }`
|
||||
2. **Element capacity** is currently global (same for all elements), calculated from:
|
||||
- Base: 10
|
||||
- Skill: `elemAttune` levels × 50
|
||||
- Prestige: `elementalAttune` levels × 25
|
||||
- Effects: bonuses and multipliers
|
||||
3. **Element unlocks** are per-type (boolean), cost 500 raw mana each
|
||||
4. **No limit** on number of unlocked element types currently exists
|
||||
5. **Per-element capacity** bonuses exist in effects system (`perElementCapBonus`) but aren't widely used
|
||||
6. **Cross-loop persistence:** prestige upgrades and insight persist; element unlocks do NOT persist
|
||||
7. **14 total element types** available (13 unlockable + transference)
|
||||
|
||||
## Questions for Implementation
|
||||
|
||||
Depending on what "Unlocked Mana Type Capacity" means:
|
||||
|
||||
**If it means increasing capacity (max mana) of unlocked types:**
|
||||
- Could add a new prestige upgrade similar to `elementalAttune`
|
||||
- Could modify the `computeElementMax` function
|
||||
- Could add per-element capacity tracking
|
||||
|
||||
**If it means increasing the NUMBER of types that can be unlocked:**
|
||||
- Need to add a limit/cap on unlocked types (currently unlimited)
|
||||
- Need to add a prestige upgrade to increase this limit
|
||||
- Need to modify `unlockElement` to check against the limit
|
||||
Reference in New Issue
Block a user