16 KiB
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
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
@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
signal prestige_state_changed(total_prestige, pending_gain)
signal prestige_performed(gain, total_prestige)
signal prestige_threshold_changed(next_threshold)
State Variables
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
# 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):
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:
# 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:
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:
multiplier = base_multiplier + (total_prestige * multiplier_per_prestige)
# e.g., 1.0 + (5 * 0.05) = 1.25 (25% bonus)
Multiplicative Power Mode:
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
- Player clicks "Prestige" button
perform_prestige()called- Gain calculated and added to total
- Prestige currency granted via
game_state.add_currency_by_id() game_state.reset_for_prestige()called (preserves prestige currency, resets generators, currencies, buff levels, goals)_reset_all_buff_levels()— clears generator buff levels (NOT prestige buff unlocks)- Generator runtime state reinitialized
- Run tracking reinitialized
game_state.emit_currency_changed_for_all()— triggers UI refresh- Save triggered
PrestigePanel
UI component for prestige interaction.
Displays
_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():
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:
{
"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:
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):
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
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
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
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
@export var prestige_buff_catalogue: PrestigeBuffCatalogue
var _prestige_buff_unlocked: Dictionary = {} # {StringName: bool} — node_id → unlocked
- Never cleared by
reset_for_prestige()(unlike_buff_levelswhich 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
# 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
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_idis empty: all matching nodes are included (global effect) - If
target_idis set: only nodes with empty or matchingtarget_idare included
Multiplier Usage In Generators
Generators should call get_prestige_buff_multiplier() with their generator ID:
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 asfalse.
UI — PrestigeBuffGraphPanel
A PanelContainer-based separate screen.
- Layout: Tiered rows (
HBoxContainerper tier), sorted bytierthenx_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_unlockedto 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