Files
idle/core/README.md
Michele Rossi 81a4058b04 systems rework: buffs, prestige graph, research, modular architecture
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)
2026-05-07 20:36:21 +00:00

267 lines
9.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Core Module Documentation
## Overview
The `core/` directory contains the fundamental systems for the idle game. It provides:
- **Numeric Foundation**: `BigNumber` for handling huge idle game values
- **Game State Management**: `LevelGameState` for all persistent state
- **Currency System**: `CurrencyCatalogue` and 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
1. **Game Start**: `LevelGameState._ready()` initializes currencies, loads save
2. **Player Action**: Generator bought → `LevelGameState` updated → signals emitted
3. **UI Reaction**: Listeners respond to signals → update display
4. **Goal Check**: Currency changed → goals evaluated → buffs unlocked
5. **Save**: `LevelGameState.save_game()` → JSON to `user://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`)
```gdscript
# 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
```json
{
"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:
```gdscript
# 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
1. Create `res://sandbox/currencies/<name>.tres` extending `Currency`
2. Set `id`, `display_name`, and `icon`
3. Add to `CurrencyCatalogue` resource assigned to `LevelGameState`
### Adding a New Generator
1. Create `res://sandbox/generators/<name>.tres` extending `CurrencyGeneratorData`
2. Configure costs, production, and unlock goals
3. Instance `CurrencyGenerator` scene with this data
### Adding a New Buff
1. Create `res://sandbox/buffs/<name>.tres` extending `GeneratorBuffData`
2. Set `target_ids`, `kind`, `effect_increment`, and `cost`
3. Add to `BuffCatalogue` resource assigned to `LevelGameState`
### Adding New Research
1. Create `res://sandbox/research/<name>.tres` extending `ResearchData`
2. Link to a generator via `generator_id`
3. Add to `ResearchCatalogue` resource assigned to `LevelGameState`
## See Also
- `currency/` - Currency data structures
- `generator/` - Generator production system
- `goals/` - Achievement system
- `prestige/` - Rebirth mechanics