180 lines
8.9 KiB
Markdown
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.
|