Decouples core systems from centralized globals in favor of catalogue-based architecture and data-driven content. Key changes: - Prestige: node-based buff tech tree, graph panel, progress bar - Research: multi-worker system, per-generator research, xp generation - Ascension: meta-currency layer via multi-currency prestige resets - Buffs: refactored as generator-independent via catalogues - UI: currency panel, edge-scrolling camera + zoom, current-goal panel - Alchemy Tower: crafting building with recipe/cost system - Testing: 7 test suites (ascension, prestige, research, goals) - Content: migrated from hardcoded idles/ to gym format (tiny_sword)
9.2 KiB
Core Module Documentation
Overview
The core/ directory contains the fundamental systems for the idle game. It provides:
- Numeric Foundation:
BigNumberfor handling huge idle game values - Game State Management:
LevelGameStatefor all persistent state - Currency System:
CurrencyCatalogueand tracking for multiple currency types - Generator System: Automated resource production with scaling costs
- Buff System: Upgrades that modify generator behavior
- Goal System: Achievement tracking and unlock triggers
- Prestige System: Rebirth mechanics with permanent multipliers
- Research System: XP-based progression with production multipliers
Architecture
core/
├── big_number.gd # Core numeric API
├── level_game_state.gd # Main state management (Node)
├── currency/ # Currency data structures + CurrencyCatalogue
├── currency_catalogue.gd # Currency catalogue resource
├── buff_catalogue.gd # Buff catalogue resource
├── goal_catalogue.gd # Goal catalogue resource
├── generator/ # Generator logic and data
├── goals/ # Goal/achievement system
├── prestige/ # Prestige/rebirth system
└── research/ # Research XP and progression
Data Flow
- Game Start:
LevelGameState._ready()initializes currencies, loads save - Player Action: Generator bought →
LevelGameStateupdated → signals emitted - UI Reaction: Listeners respond to signals → update display
- Goal Check: Currency changed → goals evaluated → buffs unlocked
- Save:
LevelGameState.save_game()→ JSON touser://level_save.json
Production Multiplier Chain
All multipliers compose multiplicatively from callee to caller. The entry point is
_grant_cycle_income() in CurrencyGenerator.
Entry: CurrencyGenerator._grant_cycle_income() (currency_generator.gd:134)
var effective = get_effective_auto_run_multiplier()
var per_cycle = data.production_per_cycle(owned, effective, purchased_count)
var produced = per_cycle × cycle_count
Layer 1 — CurrencyGeneratorData.production_per_cycle() (currency_generator_data.gd:117)
production_per_cycle = initial_productivity × owned × run_multiplier × milestone_mult × purchased_mult
| Sub-multiplier | Method | Formula |
|---|---|---|
| milestone_mult | milestone_multiplier_total(owned) |
milestone_multiplier ^ floor(owned / milestone_step) |
| purchased_mult | purchased_multiplier(purchased) |
1.0 + purchased × (purchased_boost_percent / 100) |
Layer 2 — CurrencyGenerator.get_effective_auto_run_multiplier() (currency_generator.gd:357)
This is the run_multiplier value passed into Layer 1 above:
effective_run_multiplier = run_multiplier × research_multiplier × auto_production_buffs × prestige_multiplier
2a. run_multiplier
Exported float on the CurrencyGenerator node. Default 1.0. A scene-level tweak.
2b. research_multiplier → LevelGameState.get_research_multiplier(id) (level_game_state.gd:488) → ResearchData.get_multiplier_for_level(level) (research/research_data.gd:52)
research_multiplier = base_multiplier + (multiplier_per_level × research_level)
Level is auto-calculated from accumulated BigNumber XP. Both constants are exports on ResearchData.
2c. auto_production_buffs → LevelGameState.get_effective_multiplier(generator_id, AUTO_PRODUCTION_MULTIPLIER) (level_game_state.gd:441)
# For every active buff of kind AUTO_PRODUCTION_MULTIPLIER targeting this generator:
mult = 1.0
for buff in buffs:
if buff.active and buff.kind == AUTO_PRODUCTION_MULTIPLIER:
mult *= buff.get_effect_multiplier(level)
GeneratorBuffData.get_effect_multiplier(level) (generator_buff_data.gd:97):
buff_effect = base_effect + (effect_increment × level)
Multiple buffs of the same kind stack multiplicatively: ∏ (base_effect_i + increment_i × level_i).
2d. prestige_multiplier → PrestigeManager.get_total_multiplier() (prestige_manager.gd:107)
Configured via PrestigeConfig.multiplier_mode:
| Mode | Formula |
|---|---|
ADDITIVE |
base + prestige_points × multiplier_per_prestige |
MULTIPLICATIVE_POWER |
base × (1 + multiplier_per_prestige) ^ (prestige_points ^ exponent) |
Layer 3 — Production per second
production_per_second = production_per_cycle / maxf(initial_time, 0.0001)
(currency_generator_data.gd:128)
Full Formula (one production cycle)
produced = initial_productivity × owned
# --- effective_run_multiplier ---
× run_multiplier
× (base_multiplier + multiplier_per_level × research_level)
× ∏(base_effect_i + effect_increment_i × buff_level_i) [auto-production buffs]
× prestige_multiplier [from PrestigeManager]
# --- milestone & purchased ---
× milestone_multiplier ^ floor(owned / milestone_step)
× (1.0 + purchased_count × purchased_boost_percent / 100)
Manual Click Formula (separate path)
_try_grant_click_currency() (currency_generator.gd:460):
click_reward = (click_mantissa × 10^click_exponent) × ∏ buff_effect_i [MANUAL_CLICK_MULTIPLIER buffs]
Uses the same get_effective_multiplier() loop filtered to BuffKind.MANUAL_CLICK_MULTIPLIER.
Research, prestige, milestone, and purchased multipliers are not applied to clicks.
Research XP Awarded (production side-effect)
Awarded inside _grant_cycle_income() when the generator has an associated ResearchData:
base_xp = produced_currency × xp_per_currency_produced
scaled_xp = base_xp × (1.0 + research_workers × worker_scaling_factor)
actual_xp = scaled_xp × ∏ buff_effect_i [RESEARCH_XP_MULTIPLIER buffs]
Buff multiplier for XP is applied later in LevelGameState.add_research_xp()
via ResearchBuffCalculator.apply_buffs() (core/generator/research_buff_calculator.gd).
Cascade Diagram
┌─────────────────────────────────┐
│ run_multiplier (node export) │
│ research_multiplier (XP level) │─── effective_run_multiplier ──┐
│ auto-production buffs (∏) │ │
│ prestige_multiplier │ │
└─────────────────────────────────┘ │
▼
production_per_cycle = initial_productivity × owned × effective_run_multiplier × milestone × purchased
│
▼
production_per_second = production_per_cycle / initial_time
Save Format
Version: 5
{
"save_format_version": 5,
"currencies": {
"<currency_id>": {
"current": {"m": float, "e": int},
"total": {"m": float, "e": int},
"all_time": {"m": float, "e": int}
}
},
"generator_states": {
"<generator_id>": {
"owned": int,
"purchased_count": int,
"unlocked": bool,
"available": bool
}
},
"buff_levels": {
"<buff_id>": int
},
"buff_unlocked": {
"<buff_id>": true
},
"buff_active": {
"<buff_id>": true
},
"goals": {
"completed": ["<goal_id>", ...]
},
"research_xp": {
"<research_id>": {"m": float, "e": int}
},
"research_levels": {
"<research_id>": int
},
"last_save_time": unix_timestamp
}
Key Signals
| Signal | Emitted When |
|---|---|
currency_changed(currency_id, new_amount) |
Any currency balance changes |
generator_state_changed(generator_id, state) |
Generator owned/unlocked changes |
generator_buff_level_changed(gen_id, buff_id, level) |
Buff level increased |
buff_unlocked_changed(buff_id, unlocked) |
Buff unlock state changed |
goal_completed(goal_id) |
Goal criteria met |
BigNumber Serialization
All large numbers use scientific notation internally:
# Value = mantissa * 10^exponent
var num = BigNumber.new(1.5, 24) # 1.5e24
var dict = num.serialize() # {"m": 1.5, "e": 24}
var restored = BigNumber.deserialize(dict)
Extending the Core
Adding a New Currency
- Create
res://sandbox/currencies/<name>.tresextendingCurrency - Set
id,display_name, andicon - Add to
CurrencyCatalogueresource assigned toLevelGameState
Adding a New Generator
- Create
res://sandbox/generators/<name>.tresextendingCurrencyGeneratorData - Configure costs, production, and unlock goals
- Instance
CurrencyGeneratorscene with this data
Adding a New Buff
- Create
res://sandbox/buffs/<name>.tresextendingGeneratorBuffData - Set
target_ids,kind,effect_increment, andcost - Add to
BuffCatalogueresource assigned toLevelGameState
Adding New Research
- Create
res://sandbox/research/<name>.tresextendingResearchData - Link to a generator via
generator_id - Add to
ResearchCatalogueresource assigned toLevelGameState
See Also
currency/- Currency data structuresgenerator/- Generator production systemgoals/- Achievement systemprestige/- Rebirth mechanics