# Prestige Module Documentation ## Overview The `prestige/` subfolder implements the rebirth/reset system and the prestige buff graph — permanent upgrades purchasable with prestige currency that persist across resets. ## Files | File | Purpose | |------|---------| | `prestige_config.gd` | Configuration resource for prestige rules (`PrestigeConfig`) | | `prestige_manager.gd` | Core prestige state, gain calculation, and reset orchestration (`PrestigeManager`) | | `prestige_buff_node.gd` | Single node in the prestige buff graph (`PrestigeBuffNode`) | | `prestige_buff_catalogue.gd` | Flat list of all prestige buff nodes (`PrestigeBuffCatalogue`) | | `prestige_buff_graph_tile.gd` | Individual tile UI for a buff node (`PrestigeBuffGraphTile`) | | `prestige_buff_graph_tile.tscn` | Tile scene (icon, name, effect, cost, state) | | `prestige_buff_graph_panel.gd` | Full-screen panel displaying the buff graph (`PrestigeBuffGraphPanel`) | | `prestige_panel.gd` | UI panel for prestige reset interaction (`PrestigePanel`) | | `prestige_panel.tscn` | Prestige panel scene | | `prestige_progress_bar.gd` | Progress bar toward next prestige threshold | | `prestige_progress_bar.tscn` | Progress bar scene | | `TECH_SPEC.md` | Detailed technical specification (historical design doc) | ## Architecture ``` LevelGameState (Node) ├── PrestigeManager (Node) │ ├── Tracks prestige currency & reset count │ ├── Calculates pending gain from config │ └── Orchestrates reset via GameState ├── PrestigePanel (UI) │ ├── Shows current/pending prestige & multiplier │ └── Reset button with confirmation dialog ├── PrestigeProgressBar (UI) │ └── Visual progress toward next threshold ├── PrestigeBuffGraphPanel (UI) │ ├── Ascension currency balance │ └── Tiered rows of PrestigeBuffGraphTile nodes └── prestige_buff_catalogue (PrestigeBuffCatalogue) └── Array of PrestigeBuffNode resources ``` PrestigeManager is **not** an autoload — it is a child node of `LevelGameState`. The game state finds it via `find_child("PrestigeManager")` in `_ready()`. --- # Part 1 — Prestige Reset System ## PrestigeConfig Resource defining prestige rules. ### Enums ```gdscript enum BasisType { LIFETIME_TOTAL, # All-time currency acquired (single currency) RUN_TOTAL, # Currency this run (single currency) RUN_MAX, # Peak currency this run (single currency) ALL_CURRENCIES # Sum of all currencies (multi-currency tracking) } enum FormulaType { POWER, # gain = scale * (ratio^exponent) TRIANGULAR_INVERSE # gain based on triangular number inverse } enum RoundingMode { FLOOR, ROUND, CEIL } enum MultiplierMode { ADDITIVE, # +X per prestige MULTIPLICATIVE_POWER # x^(multiplier_per_prestige) } ``` ### Key Properties ```gdscript @export var id: StringName = &"primary" @export var display_name: String = "Ascension" @export var prestige_currency_id: StringName = &"prestige" @export var source_currency_id: StringName = &"magic" # What to measure (ignored for ALL_CURRENCIES) @export var basis: BasisType = LIFETIME_TOTAL @export var formula: FormulaType = POWER # Currency tracking @export var include_currency_ids: Array[StringName] = [] # Empty = all currencies (for ALL_CURRENCIES basis) # Threshold: minimum source currency needed @export var threshold_mantissa: float = 1.0 @export var threshold_exponent: int = 4 # 1e4 = 10,000 # Formula parameters @export var scale: float = 1.0 @export var exponent: float = 0.5 # Square root # Multiplier growth @export var multiplier_per_prestige: float = 0.05 # +5% per prestige @export var multiplier_mode: MultiplierMode = ADDITIVE ``` ## PrestigeManager Child node of `LevelGameState` managing prestige state. ### Signals ```gdscript signal prestige_state_changed(total_prestige, pending_gain) signal prestige_performed(gain, total_prestige) signal prestige_threshold_changed(next_threshold) ``` ### State Variables ```gdscript var total_prestige_earned: BigNumber # Lifetime prestige var current_prestige_unspent: BigNumber # Available to spend var prestige_resets_count: int # How many resets var run_start_total_source_currency: BigNumber # Track run start var run_peak_source_currency: BigNumber # Peak this run ``` ### Key Methods ```gdscript # Query state func get_total_prestige() -> BigNumber func get_current_prestige_unspent() -> BigNumber func calculate_pending_gain() -> BigNumber func can_prestige() -> bool func get_total_multiplier() -> float func get_next_prestige_threshold() -> BigNumber # Perform prestige func perform_prestige() -> bool ``` ### Gain Calculation **Power Formula** (default): ```gdscript ratio = basis_value / threshold raw_gain = scale * (ratio^exponent) + flat_bonus # Cumulative basis subtracts already earned if basis == LIFETIME_TOTAL: gain = raw_gain - total_prestige_earned ``` **Triangular Inverse Formula**: ```gdscript # Based on inverse triangular number: n = (sqrt(1 + 8k) - 1) / 2 k = basis_value / threshold target_total = (sqrt(1 + 8k) - 1) * 0.5 if basis == LIFETIME_TOTAL: gain = target_total - total_prestige_earned ``` **Basis Value Calculation**: ```gdscript if basis == ALL_CURRENCIES: # Sum of all tracked currencies basis_value = sum(get_total_currency_acquired(currency_id) for currency_id in tracked_currencies) elif basis == RUN_TOTAL: # Currency earned since last reset basis_value = lifetime_total - run_start_total elif basis == RUN_MAX: # Peak currency reached this run basis_value = run_peak_currency else: # LIFETIME_TOTAL # All-time currency acquired basis_value = lifetime_total ``` ### Multiplier Application **Additive Mode**: ```gdscript multiplier = base_multiplier + (total_prestige * multiplier_per_prestige) # e.g., 1.0 + (5 * 0.05) = 1.25 (25% bonus) ``` **Multiplicative Power Mode**: ```gdscript growth_base = 1.0 + multiplier_per_prestige multiplier = base_multiplier * (growth_base ^ total_prestige) # e.g., 1.0 * (1.05 ^ 5) = 1.276 (27.6% bonus) ``` ### Prestige Reset Flow 1. Player clicks "Prestige" button 2. `perform_prestige()` called 3. Gain calculated and added to total 4. Prestige currency granted via `game_state.add_currency_by_id()` 5. `game_state.reset_for_prestige()` called (preserves prestige currency, resets generators, currencies, buff levels, goals) 6. `_reset_all_buff_levels()` — clears generator buff levels (NOT prestige buff unlocks) 7. Generator runtime state reinitialized 8. Run tracking reinitialized 9. `game_state.emit_currency_changed_for_all()` — triggers UI refresh 10. Save triggered ## PrestigePanel UI component for prestige interaction. ### Displays ```gdscript _total_value.text # Total prestige earned (e.g. "123.45") _pending_value.text # Gain available now _multiplier_value.text # Current multiplier (e.g. "x2.34") _basis_label.text # Basis + value (e.g. "Total Currency (1.50e6)") ``` ### Buttons - **Reset**: Triggers `perform_prestige()` after confirmation dialog - **Save**: Manually saves game state ## Generator Integration Generators apply prestige multipliers via `PrestigeManager.get_total_multiplier()`: ```gdscript func _get_prestige_multiplier() -> float: var parent: Node = get_parent() var prestige_manager: PrestigeManager = parent.find_child("PrestigeManager", true, false) if prestige_manager: return prestige_manager.get_total_multiplier() return 1.0 ``` This is called from `get_effective_auto_run_multiplier()` alongside research and buff multipliers. In the future, prestige buff multipliers from the graph system will replace or augment this. ## Save Integration Prestige state stored in `LevelGameState` external save: ```json { "prestige_state": { "total_prestige_earned": {"m": 10.0, "e": 0}, "current_prestige_unspent": {"m": 5.0, "e": 0}, "prestige_resets_count": 3, "run_start_total_source_currency": {"m": 1000.0, "e": 0}, "run_peak_source_currency": {"m": 5000.0, "e": 0}, "last_reset_time": 1234567890 } } ``` ## Configuration Example For a typical idle game with single currency: ```gdscript id = &"primary" display_name = "Ascension" source_currency_id = &"magic" basis = LIFETIME_TOTAL threshold_mantissa = 1.0 threshold_exponent = 6 # 1e6 formula = POWER exponent = 0.5 # Square root multiplier_mode = ADDITIVE multiplier_per_prestige = 0.10 ``` For multi-currency tracking (`ALL_CURRENCIES`): ```gdscript id = &"ascension" display_name = "Ascension" prestige_currency_id = &"ascension" basis = ALL_CURRENCIES include_currency_ids = [] # Empty = all currencies from catalogue threshold_mantissa = 1.0 threshold_exponent = 4 # 1e4 formula = POWER exponent = 0.5 multiplier_mode = ADDITIVE multiplier_per_prestige = 0.05 ``` --- # Part 2 — Prestige Buff Graph ## Overview The prestige buff graph is a permanent upgrade system where players spend prestige currency to unlock nodes in a DAG-structured graph. Unlike generator buffs (which reset on prestige), prestige buff unlocks are **permanent** and survive resets. ## PrestigeBuffNode Single node in the graph. Each node is a one-shot unlock — either locked or unlocked, no repeatable levels. ### EffectType Enum ```gdscript enum EffectType { CURRENCY_PRODUCTION_MULTIPLIER, # Multiply currency output by x% BUILDING_CREATION_BONUS, # When a building is created, increase currency gain by x% WORKER_THRESHOLD_MULTIPLIER, # When 10+ workers are assigned to a building, increase currency gain by x% CARRYOVER_BUILDINGS, # After prestige, start with some buildings and workers already assigned UNLOCK_AUTO_BUY_WORKER, # Unlock option to auto-buy workers HOVER_SPEED_BOOST, # Hovering over a place speeds up workers instead of clicking UNLOCK_BUILDING, # Unlock a new building (generator) WORKER_PRODUCTIVITY, # Increase worker productivity by x% RESEARCH_XP_MULTIPLIER, # Increase research XP gain by x% } ``` ### Exported Fields | Field | Type | Notes | |-------|------|-------| | `id` | `StringName` | Unique node identifier | | `display_name` | `String` | UI label | | `description` | `String` (multiline) | Tooltip | | `icon` | `Texture2D` | Icon in graph UI | | `parent_ids` | `Array[StringName]` | Prerequisites (DAG — multiple parents allowed) | | `effect_type` | `EffectType` | What this buff does | | `effect_value` | `float` | Effect magnitude (multiplier or count depending on type) | | `target_id` | `StringName` | Target building/currency for type-specific effects (empty = global) | | `cost_mantissa` | `float` | Prestige currency cost mantissa | | `cost_exponent` | `int` | Prestige currency cost exponent | | `tier` | `int` | Row in graph UI (visual grouping) | | `x_position` | `float` | Column in graph UI | ### Key Methods ```gdscript func get_cost() -> BigNumber # BigNumber from mantissa/exponent func has_parents() -> bool # True if parent_ids is non-empty func all_parents_unlocked(unlocked: Dictionary) -> bool # All prerequisites met? func is_valid() -> bool # Non-empty id and positive cost ``` ## PrestigeBuffCatalogue Flat resource containing all buff nodes. The graph structure emerges from `parent_ids`. | Field | Type | |-------|------| | `nodes` | `Array[PrestigeBuffNode]` | ### Key Methods ```gdscript func get_node_by_id(id: StringName) -> PrestigeBuffNode func get_all_ids() -> Array[StringName] func get_root_nodes() -> Array[PrestigeBuffNode] # Nodes with empty parent_ids func get_children_of(parent_id: StringName) -> Array[PrestigeBuffNode] ``` ## State in LevelGameState ```gdscript @export var prestige_buff_catalogue: PrestigeBuffCatalogue var _prestige_buff_unlocked: Dictionary = {} # {StringName: bool} — node_id → unlocked ``` - Never cleared by `reset_for_prestige()` (unlike `_buff_levels` which IS cleared) - Normalized on load: missing catalogue node IDs are filled as `false` - On initialization, iterates the catalogue and sets any missing entries to `false` ## LevelGameState API ```gdscript # Purchase and query func purchase_prestige_buff(buff_id: StringName) -> bool func can_purchase_prestige_buff(buff_id: StringName) -> bool func is_prestige_buff_unlocked(buff_id: StringName) -> bool func get_prestige_buff_cost(buff_id: StringName) -> BigNumber func get_prestige_currency_id() -> StringName # Returns &"ascension" # Multiplier computation — walks graph, returns combined product from unlocked nodes func get_prestige_buff_multiplier(effect_type: int, target_id: StringName = &"") -> float # Available nodes (prerequisites met, not yet unlocked) func get_available_prestige_buffs() -> Array[PrestigeBuffNode] ``` ### Signal ```gdscript signal prestige_buff_unlocked(buff_id: StringName) ``` Prestige currency balance changes use the existing `currency_changed` signal — no separate signal needed. ## Purchase Flow ``` Player clicks unlock node → can_purchase_prestige_buff(node_id) → Node exists in catalogue? → Not already unlocked? → All parent_ids unlocked? → Enough prestige currency? → YES: spend_currency_by_id("ascension", cost) → _prestige_buff_unlocked[node_id] = true → emit prestige_buff_unlocked → save_game() ``` ## Multiplier Computation `get_prestige_buff_multiplier(effect_type, target_id)` iterates all unlocked nodes matching the given effect type and multiplies their `effect_value` together. Results start at `1.0`. - If `target_id` is empty: all matching nodes are included (global effect) - If `target_id` is set: only nodes with empty or matching `target_id` are included ## Multiplier Usage In Generators Generators should call `get_prestige_buff_multiplier()` with their generator ID: ```gdscript var asc_mult: float = game_state.get_prestige_buff_multiplier( PrestigeBuffNode.EffectType.CURRENCY_PRODUCTION_MULTIPLIER, get_generator_id() ) ``` This replaces the old `_get_prestige_multiplier()` call for prestige buff-driven production bonuses. ## Save Format - Save format version bumped to **8** when prestige buffs were introduced. - Key: `"prestige_buff_unlocked"` → array of unlocked node ID strings: `["gold_boost", "farm_boost"]` - Load deserialization reads the array, sets matching entries to `true`, then calls `_initialize_prestige_buffs()` to fill in missing entries as `false`. ## UI — PrestigeBuffGraphPanel A `PanelContainer`-based separate screen. - **Layout**: Tiered rows (`HBoxContainer` per tier), sorted by `tier` then `x_position` - **Tiles** (`PrestigeBuffGraphTile`): show icon, name, effect description, cost, state - **States**: - **Locked** (greyed out): prerequisites not met - **Available** (highlighted, button enabled): all prerequisites met, can purchase - **Unlocked** (green check, button disabled): already owned - **Balance**: Shows current prestige currency balance at top, refreshes on `currency_changed` - **Graph rebuild**: Rebuilds entire graph on `prestige_buff_unlocked` to reflect newly available children ## Content Files Prestige buff content lives in `docs/gyms/tiny_sword/prestige/`: | File | Purpose | |------|---------| | `prestige_buff_catalogue.tres` | Catalogue resource referencing all buff nodes | | `prestige_buff_gold_boost.tres` | Example: +50% gold mine production | | `prestige_buff_farm_boost.tres` | Example: +50% farm food production | Each `.tres` is a `PrestigeBuffNode` resource with exported fields set in the inspector. ## Prestige Reset And Buffs - **Generator buffs** (`_buff_levels`, `_buff_unlocked`, `_buff_active`): reset to 0/false on prestige - **Prestige buff unlocks** (`_prestige_buff_unlocked`): **never reset** — permanent across all prestige resets