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)
This commit was merged in pull request #1.
This commit is contained in:
266
core/README.md
Normal file
266
core/README.md
Normal file
@@ -0,0 +1,266 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user