Files
idle/core/prestige/TECH_SPEC.md
2026-03-22 10:50:37 +01:00

180 lines
8.9 KiB
Markdown

# Prestige System - Technical Specification
## Document Status
1. Version: 1.0
2. Date: 2026-03-21
3. Scope: Add a configurable prestige reset loop with editor-authored curve math and permanent run multipliers.
## Problem Statement
The prototype currently has growth, buffs, and unlock goals, but no reset loop that converts late-run progress into long-term power.
Without prestige, the economy has no designed "ladder climb" cycle and balancing eventually relies only on direct cost/production tuning.
## Design Goals
1. Support a prestige reset that grants a permanent currency based on player progress.
2. Keep prestige math data-driven and editable in the Godot inspector for balancing iteration.
3. Support both major prestige archetypes from Kongregate's analysis:
- Lifetime or max-stat based curves (diminishing gains when resetting at the same point repeatedly).
- Since-reset/run-only curves (same run depth gives similar rewards each reset).
4. Integrate with existing systems (`GameState`, `BigNumber`, generator/buff runtime) without hardcoding one formula.
5. Persist all prestige state in `user://idle_save.json` alongside existing runtime data.
## Non-Goals (For First Iteration)
1. Multiple prestige currencies.
2. Prestige skill tree/shop UI.
3. Offline simulation changes beyond applying the resulting permanent multiplier.
## Kongregate-Informed Curve Model
From "The Math of Idle Games, Part III", prestige formulas can be modeled as transformed progress stats with fractional exponents.
Core variables:
1. `delta_p`: prestige currency gained on reset.
2. `p`: total prestige currency owned.
3. `c_L`: lifetime currency earned.
4. `c_M`: max currency reached in a run.
5. `c_R`: currency earned since last reset.
Supported formula families for this system:
1. Power curve (generic): `f(x) = scale * (x / threshold)^exponent`.
2. Triangular inverse sqrt (Realm Grinder style): solve `x = threshold * p(p+1)/2` for `p`.
3. Log-like compression (future option, disabled by default).
Behavior groups:
1. Cumulative-stat formulas (`c_L`, `c_M`) compute target total prestige then grant `target_total - current_total`.
2. Run-only formulas (`c_R`) compute direct gain per run.
## Proposed Runtime Architecture
1. Add `PrestigeManager` autoload (`core/prestige/prestige_manager.gd`) as the single authority for prestige state and reset flow.
2. Add `PrestigeConfig` resource (`core/prestige/prestige_config.gd`) authored in inspector and loaded from data assets.
3. `GameState` remains owner of currencies/generator state; `PrestigeManager` orchestrates reset and applies permanent multiplier.
4. Generators read the active prestige multiplier through one accessor (`PrestigeManager.get_total_multiplier()`), then multiply existing `run_multiplier` behavior.
## Data Model
### PrestigeConfig (Resource)
Editor-authored resource fields (all exported):
1. `id: StringName`.
2. `display_name: String`.
3. `prestige_currency_id: StringName` (logical id, not regular wallet currency).
4. `source_currency_id: StringName` (which gameplay currency powers prestige calculation).
5. `basis: BasisType` enum:
- `LIFETIME_TOTAL`
- `RUN_TOTAL`
- `RUN_MAX`
6. `formula: FormulaType` enum:
- `POWER`
- `TRIANGULAR_INVERSE`
7. `threshold_mantissa: float` and `threshold_exponent: int` (BigNumber-like threshold).
8. `scale: float`.
9. `exponent: float` (used by `POWER`).
10. `flat_bonus: float`.
11. `minimum_gain: int`.
12. `rounding_mode: RoundingMode` enum (`FLOOR`, `ROUND`, `CEIL`).
13. `allow_prestige_without_gain: bool`.
14. Multiplier tuning:
- `multiplier_mode: MultiplierMode` enum (`ADDITIVE`, `MULTIPLICATIVE_POWER`).
- `base_multiplier: float`.
- `multiplier_per_prestige: float`.
- `multiplier_exponent: float`.
### Prestige Runtime State
Persisted by `PrestigeManager`:
1. `total_prestige_earned: BigNumber`.
2. `current_prestige_unspent: BigNumber`.
3. `prestige_resets_count: int`.
4. `run_start_total_source_currency: BigNumber`.
5. `run_peak_source_currency: BigNumber`.
6. `last_reset_time: int`.
## Formula Specification
### 1) Basis Value Extraction
Given configured `source_currency_id`:
1. `LIFETIME_TOTAL`: `basis_value = GameState.get_total_currency_acquired_by_id(source_currency_id)`.
2. `RUN_TOTAL`: `basis_value = lifetime_now - run_start_total_source_currency`.
3. `RUN_MAX`: `basis_value = run_peak_source_currency`.
### 2) Raw Gain
1. POWER: `raw = scale * pow(basis_value / threshold, exponent) + flat_bonus`.
2. TRIANGULAR_INVERSE:
- `k = basis_value / threshold`
- `target = (sqrt(1 + 8k) - 1) / 2`
- if basis is cumulative (`LIFETIME_TOTAL`, `RUN_MAX` interpreted as cumulative target), `raw = target - total_prestige_earned`
- if basis is run-only, `raw = target`
### 3) Post-Processing
1. Clamp negative to zero.
2. Apply configured rounding.
3. Enforce `minimum_gain` when basis passed threshold and gain > 0.
### 4) Multiplier Application
`get_total_multiplier()` returns one of:
1. Additive mode: `base_multiplier + total_prestige_earned * multiplier_per_prestige`.
2. Multiplicative power mode: `base_multiplier * pow(1 + multiplier_per_prestige, pow(total_prestige_earned, multiplier_exponent))`.
Implementation note: compute large-number powers via `log10` transforms against `BigNumber` to avoid float overflow.
## Reset Flow
1. Player presses prestige button.
2. `PrestigeManager.calculate_pending_gain()` computes gain from configured curve.
3. If gain is zero and `allow_prestige_without_gain` is false, block reset.
4. On confirm:
- Add gain to `total_prestige_earned` and `current_prestige_unspent`.
- Increment `prestige_resets_count`.
- Reset runtime progression (`GameState` currencies, generator owned counts, generator availability that should restart locked).
- Preserve permanent unlocks only if explicitly marked as persistent in future extensions.
- Reinitialize run-tracking values (`run_start_total_source_currency`, `run_peak_source_currency`).
- Emit `prestige_performed(gain, total_prestige)` signal.
## GameState Integration
1. Add APIs for reset-safe state initialization (single call from `PrestigeManager` instead of duplicating clear logic).
2. Keep total-acquired currency behavior explicit:
- Option A (recommended): reset current currencies and generator ownership, keep lifetime totals for `LIFETIME_TOTAL` basis.
- Option B: clear totals too and rely on dedicated lifetime accumulator in prestige state.
3. Ensure generator/buff registrations are idempotent after reset (`_ensure_registered` paths already support this pattern).
## Editor Authoring And Balancing Workflow
1. Create `idles/prestige/primary_prestige.tres` using `PrestigeConfig`.
2. Designers tune only exported fields in inspector (no code edits).
3. Provide presets matching Kongregate article examples:
- Realm Grinder-like: `basis=RUN_MAX`, `formula=TRIANGULAR_INVERSE`, `threshold=1e12`.
- AdCap-like: `basis=LIFETIME_TOTAL`, `formula=POWER`, `scale=150`, `threshold=1e15`, `exponent=0.5`.
- Cookie Clicker-like: `basis=LIFETIME_TOTAL`, `formula=POWER`, `threshold=1e12`, `exponent=0.333333`.
- Egg Inc-like: `basis=RUN_TOTAL`, `formula=POWER`, `threshold=1e6`, `exponent=0.14`.
4. Balance target guidance:
- For sqrt-like curves, doubling prestige generally needs roughly 3x-4x deeper runs.
- For cube-root curves, doubling prestige tends toward ~8x deeper runs.
- For ~1/7 exponent run curves, doubling prestige needs ~128x deeper runs.
## UI Requirements
1. Prestige panel must display:
- current prestige total
- pending gain (`delta_p`)
- resulting multiplier after reset
- basis stat currently used (`c_L`, `c_R`, or `c_M` equivalent)
2. Reset CTA disabled when pending gain is zero and config disallows zero-gain resets.
3. Confirmation dialog explicitly lists what resets and what is kept.
## Persistence
1. Extend save payload with `prestige_state` object.
2. Serialize all `BigNumber` values with existing `BigNumber.serialize()` pattern.
3. During load, validate dictionaries and apply defaults if fields are missing.
4. Maintain backward compatibility with pre-prestige saves by creating default prestige state when absent.
## Validation Plan
1. Headless parse: `"$GODOT_BIN" --headless --path . --quit`.
2. Script checks once implemented:
- `"$GODOT_BIN" --headless --check-only --script res://core/prestige/prestige_manager.gd`
- `"$GODOT_BIN" --headless --check-only --script res://core/prestige/prestige_config.gd`
3. Manual smoke cases:
- same reset point in cumulative mode yields reduced/zero incremental gain.
- same reset point in run-only mode yields roughly same gain.
- multiplier updates production immediately after reset.
- prestige state survives save/load.
## Implementation Milestones
1. Introduce data classes (`PrestigeConfig`) and one authored `.tres` profile.
2. Implement `PrestigeManager` with gain math + persistence.
3. Add reset integration entrypoint in `GameState`.
4. Wire generator production multiplier to prestige.
5. Add initial prestige UI panel and confirmation flow.