From a9d2864b35cf16715de025d62dde352bbd540716 Mon Sep 17 00:00:00 2001 From: Michele Rossi Date: Sun, 3 May 2026 19:30:11 +0200 Subject: [PATCH] Update READMEs --- README.md | 319 +++++++++++++++++++++++++++++++++---------------- core/README.md | 135 +++++++++++++++++++++ 2 files changed, 352 insertions(+), 102 deletions(-) diff --git a/README.md b/README.md index e4c8269..3834af6 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# Idles - Godot 4.6 Idle Game +# Idles — Godot 4.6 Idle Game -A modular idle/incremental game prototype featuring a decoupled buff system, multi-target buffs with wildcard support, and goal-based progression. +A modular idle/incremental game prototype featuring a decoupled buff system, multi-target buffs with wildcard support, goal-based progression, prestige resets, and production-driven research. ## Quick Start @@ -22,11 +22,11 @@ A modular idle/incremental game prototype featuring a decoupled buff system, mul | System | Location | Purpose | |--------|----------|---------| | **BigNumber** | `core/big_number.gd` | Handles huge numbers (mantissa + exponent) for idle game math | +| **LevelGameState** | `core/level_game_state.gd` | Central state authority — currencies, generators, buffs, goals, research, persistence | | **Currency** | `core/currency/`, `core/currency_catalogue.gd` | Catalog system for all in-game currencies | -| **LevelGameState** | `core/level_game_state.gd` | Central state authority (currencies, generators, buffs, persistence, research) | | **Generators** | `core/generator/` | Production mechanics (auto cycles, clicks, purchases) | -| **Buffs** | `core/generator/generator_buff_data.gd` | Global multipliers with multi-target support | -| **Goals** | `core/goals/` | Unlock conditions for generators and buffs | +| **Buffs** | `core/generator/generator_buff_data.gd` | Global, multi-target buffs applied to generators | +| **Goals** | `core/goals/` | Unlock conditions and achievement tracking | | **Prestige** | `core/prestige/` | Reset-with-bonus mechanics | | **Research** | `core/research/` | XP-based progression with production multipliers | @@ -34,165 +34,280 @@ A modular idle/incremental game prototype featuring a decoupled buff system, mul | Node | Script | Description | |------|--------|-------------| -| `LevelGameState` | `core/level_game_state.gd` | Central state management, save/load | -| `CurrencyCatalogue` | `core/currency_catalogue.gd` | Currency catalog resource | -| `PrestigeManager` | `core/prestige/prestige_manager.gd` | Prestige system | +| `LevelGameState` | `core/level_game_state.gd` | Central state management, save/load, catalogs, all signals | +| `PrestigeManager` | `core/prestige/prestige_manager.gd` | Prestige calculation, reset orchestration | +| `CurrencyGenerator` | `core/generator/currency_generator.gd` | Per-generator production, purchasing, buff interaction | +| `GeneratorPanel` | `core/generator/generator_container.gd` | UI panel for a single generator | + +**Important:** There are **no autoload singletons**. `LevelGameState` and `PrestigeManager` are regular nodes instantiated in the scene (e.g., `docs/gyms/tiny_sword/tiny_sword.tscn`). All code accesses them through `@export` references or `get_node()`, never through an autoload name like `/root/GameState`. ### Data Directory Structure +All gameplay resources (`.tres` files) live under: + ``` -sandbox/ -├── currencies/ # Currency resources (gold, food, wood, worker, etc.) -├── generators/ # Generator configuration (.tres files) -├── buffs/ # Buff definitions with target_ids -├── goals/ # Unlock goal definitions -└── prestige/ # Prestige configuration +docs/gyms/tiny_sword/ +├── currencies/ # Currency resources (gold, food, wood, worker, etc.) +├── buildings/ # Generator configuration data +│ ├── farm/ # farm_generator.tres +│ ├── forestry/ # forestry_generator.tres +│ ├── gold_mine/ # gold_mine_generator.tres +│ ├── monastery/ # monastery_generator.tres +│ └── alchemy_tower/ # alchemy_tower_data.tres + recipes/ +├── buffs/ # Buff definitions with target_ids +├── goals/ # Unlock goal definitions +├── prestige/ # Prestige configuration (primary_prestige.tres) +├── research/ # Research track data +└── test/ # (reserved for tests) ``` -## Current Architecture (Post-Refactoring) +Game art assets live separately in: -### Decoupled Buff System +``` +sandbox/tiny_swords/ +├── Buildings/ # Building sprites +├── Terrain/ # Tilesets and decorations +├── UI Elements/ # UI assets +└── Units/ # Unit sprites +``` -Buffs are now **globally registered** and **multi-target capable**: +## Core Documentation -- **Global Levels**: Buff levels are shared across all targets -- **Multi-Target**: A single buff resource can affect multiple generators -- **Wildcards**: `target_ids: ["*"]` applies to all current + future generators -- **Auto-Discovery**: Buffs loaded from `res://sandbox/buffs/` at runtime -- **Goal-Based Activation**: Buffs unlock automatically when goals are met +Each `core/` subsystem has its own detailed README: -### Key Design Decisions +| Module | Documentation | +|--------|--------------| +| Core overview | `core/README.md` | +| Currency system | `core/currency/README.md` | +| Generator system | `core/generator/README.md` | +| Goals system | `core/goals/README.md` | +| Prestige system | `core/prestige/README.md` | +| Research system | `core/research/README.md` | -| Feature | Implementation | -|---------|----------------| -| **Buff Levels** | Global (shared across all targets) | -| **Buff Registry** | Auto-discovered from `res://sandbox/buffs/` | -| **Wildcards** | `["*"]` matches all current AND future generators | -| **Default State** | Inactive until goal met | -| **Persistence** | Buff definitions serialized to save | -| **Prestige** | All buff levels reset to 0 | -| **Activation** | Automatic via goals, no manual toggle | +## Buff System -### API Changes +Buffs are **globally registered** and **multi-target capable**: + +- **Global Levels**: Each buff has a single level shared across all generators it targets. +- **Multi-Target**: A single buff resource can affect multiple generators. Controlled by the `target_ids` array on `GeneratorBuffData`. +- **Wildcards**: A `target_ids` value of `["*"]` applies the buff to all current AND future generators. +- **Catalog-Based Loading**: Buffs are defined in a `BuffCatalogue` resource (`@export var buff_catalogue: BuffCatalogue` on `LevelGameState`). No runtime directory scanning is performed. +- **Goal-Based Activation**: Buffs with an `unlock_goal` automatically unlock when their goal is completed. +- **Multiplicative Stacking**: Multiple buffs of the same kind on a generator stack multiplicatively (`buff1 × buff2 × …`). +- **Buff Kinds**: `AUTO_PRODUCTION_MULTIPLIER`, `MANUAL_CLICK_MULTIPLIER`, `RESOURCE_PURCHASE`, `RESEARCH_XP_MULTIPLIER` + +### Lifecycle + +| Event | What happens | +|-------|-------------| +| **Default state** | Inactive (locked) until its unlock goal is met | +| **Goal met** | Buff auto-unlocks, becomes purchasable | +| **Purchased** | Level increments, effect applies to all target generators | +| **Prestige reset** | All buff levels reset to 0, all buffs re-locked | + +### API (on LevelGameState) -**Old API (per-generator state):** ```gdscript -GameState.register_generator_buff(generator_id, buff_id, level) -GameState.get_generator_buff_level(generator_id, buff_id) +LevelGameState.register_buff(buff: GeneratorBuffData) +LevelGameState.get_buff(buff_id: StringName) -> GeneratorBuffData +LevelGameState.get_buff_level(buff_id: StringName) -> int +LevelGameState.get_buffs_for_generator(generator_id: StringName) -> Array[GeneratorBuffData] +LevelGameState.get_effective_multiplier(generator_id: StringName, kind: int) -> float +LevelGameState.set_buff_level(buff_id: StringName, level: int) +LevelGameState.is_buff_active(buff_id: StringName) -> bool ``` -**New API (global buff state):** +## Goals System + +Goals track player progress and unlock generators or buffs when requirements are met. + +- **Automatic or Manual**: Goals define their own `unlock_behavior` (`AUTOMATIC` or `MANUAL`). +- **Logarithmic Progress**: Progress bars use log-scale for visual feedback on huge numbers. +- **Prestige Reset**: All goals are reset to incomplete on prestige. Previously unlocked generators re-lock until their goal is met again. + +### Signals on LevelGameState + ```gdscript -GameState.register_buff(buff: GeneratorBuffData) -GameState.get_buff(buff_id) -> GeneratorBuffData -GameState.get_buffs_for_generator(generator_id) -> Array[GeneratorBuffData] -GameState.get_effective_multiplier(generator_id, kind) -> float +goal_completed(goal_id: StringName) +goal_progress_changed(goal_id: StringName, progress: float) ``` +## Prestige System + +Prestige resets run progress in exchange for a permanent production multiplier. + +- **Configuration**: `PrestigeConfig` resource at `docs/gyms/tiny_sword/prestige/primary_prestige.tres` +- **Basis types**: `LIFETIME_TOTAL` (single currency), `RUN_TOTAL`, `RUN_MAX`, `ALL_CURRENCIES` (sum of all tracked currencies) +- **Formula types**: `POWER` (`scale × ratio^exponent`) or `TRIANGULAR_INVERSE` +- **Multiplier modes**: `ADDITIVE` or `MULTIPLICATIVE_POWER` + +### What gets reset on prestige + +| State | Behavior | +|-------|----------| +| **Current currency balances** | Reset to 0 (except prestige currency if configured) | +| **Generator states** (owned, unlocked) | Cleared | +| **Buff levels** | All reset to 0, all re-locked | +| **Goals** | All reset to incomplete | +| **Research XP and levels** | All cleared to 0 | +| **Lifetime currency totals** | Preserved | +| **All-time currency acquired** | Preserved | +| **Prestige points earned** | Preserved (permanent) | + +## Research System + +XP-based progression tied to generator output. Research tracks level up automatically, granting permanent production multipliers to their associated generator. + +- **XP Formula**: `XP = currency_produced × xp_per_currency_produced × buff_multiplier` +- **Level Formula**: `xp_required(n) = base_xp_required × growth_multiplier^(n-1)` +- **Buffs**: The `RESEARCH_XP_MULTIPLIER` buff kind increases XP gain for a research track. +- **Prestige Reset**: All research XP and levels reset to 0. + ## Save Format **Version 5 (current):** + ```json { "save_format_version": 5, "currencies": { - "": { - "current": {"m": float, "e": int}, - "total": {"m": float, "e": int}, - "all_time": {"m": float, "e": int} - } + "": { + "current": {"m": 1.0, "e": 0}, + "total": {"m": 1.0, "e": 0}, + "all_time": {"m": 1.0, "e": 0} + } }, - "generator_states": { ... }, - "buff_levels": { "farm_flux": 5 }, - "buff_unlocked": { "farm_flux": true }, - "buff_active": { "farm_flux": true }, - "goals": { "completed": ["goal_id"] }, - "research_xp": { "": {"m": float, "e": int} }, - "research_levels": { "": int }, + "generator_states": { + "": { + "owned": 0, + "purchased_count": 0, + "unlocked": false, + "available": false + } + }, + "buff_levels": { "": 0 }, + "buff_unlocked": { "": false }, + "buff_active": { "": false }, + "goals": { "completed": [""] }, + "research_xp": { "": {"m": 0.0, "e": 0} }, + "research_levels": { "": 0 }, + "research_workers": 0, + "prestige_state": { … }, "last_save_time": 1234567890 } ``` -**Note**: Save format version 5 includes research XP as BigNumber serialization. +Notes: +- All large numbers are serialized as `{"m": mantissa_float, "e": exponent_int}` (BigNumber format). +- `prestige_state` is an external section managed by `PrestigeManager`, not by `LevelGameState` directly. +- `research_workers` tracks workers assigned to research across all tracks. +- Save file is written to `user://level_save.json`. + +## Save/Load + +- **Save**: `LevelGameState.save_game()` serializes all state to JSON. Called on prestige reset (via `PrestigeManager`) and when manually triggered. +- **Load**: `LevelGameState.load_game()` deserializes from disk during `_ready()`. Handles version migration from v2+. +- **External sections**: Other systems (e.g. `PrestigeManager`) can attach their data via `set_external_save_data()` / `get_external_save_data()`. ## Development ### Adding New Content +All gameplay resources go under `docs/gyms/tiny_sword/` (not `sandbox/` — that's for art assets only). + **New Currency:** -1. Create `res://sandbox/currencies/.tres` (Currency resource) +1. Create `res://docs/gyms/tiny_sword/currencies/.tres` (extend `Currency`) 2. Set `id`, `display_name`, `icon` -3. Add to `CurrencyCatalogue` resource +3. Add to the `CurrencyCatalogue` resource (`ts_currency_catalogue.tres`) **New Generator:** -1. Create `res://sandbox/generators/.tres` (CurrencyGeneratorData) -2. Configure production stats, costs, purchase currency -3. Optionally add `unlock_goal` -4. Add `CurrencyGenerator` node to scene +1. Create `res://docs/gyms/tiny_sword/buildings//_generator.tres` (extend `CurrencyGeneratorData`) +2. Configure production stats, costs, purchase currency, optional `unlock_goal` +3. Add a `CurrencyGenerator` node to the scene referencing this data **New Buff:** -1. Create `res://sandbox/buffs/.tres` (GeneratorBuffData) -2. Set `target_ids` (e.g., `["farm", "forestry"]` or `["*"]`) -3. Configure effect, cost, unlock goal -4. Add to `BuffCatalogue` resource +1. Create `res://docs/gyms/tiny_sword/buffs/.tres` (extend `GeneratorBuffData`) +2. Set `target_ids` (e.g., `["farm", "forestry"]` or `["*"]` for all generators) +3. Configure `kind`, `effect_increment`, cost, and optional `unlock_goal` +4. Add to the `BuffCatalogue` resource (`ts_buff_catalogue.tres`) **New Research:** -1. Create `res://sandbox/research/.tres` (ResearchData) -2. Link to generator via `generator_id` -3. Configure XP and multiplier parameters -4. Add to `ResearchCatalogue` resource +1. Create `res://docs/gyms/tiny_sword/research/.tres` (extend `ResearchData`) +2. Link to a generator via `generator_id` +3. Configure `xp_per_currency_produced`, `base_xp_required`, `xp_growth_multiplier`, `multiplier_per_level` +4. Add to the `ResearchCatalogue` resource (`ts_research_catalogue.tres`) -### Testing +### BigNumber System -```bash -# Run project and test manually -"$GODOT_BIN" --path . +- **Representation**: `mantissa: float` × `10^exponent: int` +- **Range**: Values up to ~10^308 before float precision loss +- **Operations**: `add()`, `subtract()`, `multiply()`, `divide()`, `compare_to()`, `add_in_place()` +- **Serialization**: `.serialize()` → `{"m": float, "e": int}`; `BigNumber.deserialize(dict)` → `BigNumber` +- **Display**: `.to_string_suffix(decimals: int)` → `"1.50K"`, `"3.21M"`, etc. -# No automated test framework committed yet -# (GUT not included in repository) +### Production Formulas + +``` +Cost for N items: base × coefficient^owned × (coefficient^n − 1) / (coefficient − 1) +Max affordable: floor(log(currency × (coefficient − 1) / (base × coefficient^owned) + 1) / log(coefficient)) +Production/cycle: initial_productivity × owned × run_mult × milestone_mult × purchased_mult +Production/second: production/cycle / initial_time +``` + +## Project Structure + +``` +core/ # Engine code (all .gd scripts) +├── big_number.gd # BigNumber class +├── level_game_state.gd # Central state manager (LevelGameState node) +├── buff_catalogue.gd # Buff catalogue resource +├── currency_catalogue.gd # Currency catalogue resource +├── goal_catalogue.gd # Goal catalogue resource +├── currency/ # Currency resource definition +├── generator/ # Generator, buff data, UI panel, research buff calculator +├── goals/ # Goal/requirement resources +├── prestige/ # Prestige manager, config, panel +└── research/ # Research catalogue, panel, row UI + +docs/gyms/tiny_sword/ # Playable game content (.tres resources) +├── currencies/ # Currency definitions +├── buildings/ # Generator configs +├── buffs/ # Buff definitions +├── goals/ # Goal definitions +├── prestige/ # Prestige config +├── research/ # Research configs +└── test/ # (reserved for tests) + +sandbox/tiny_swords/ # Art assets (sprites, tilesets, UI textures) +└── Buildings/, Terrain/, UI Elements/, Units/ + +docs/museums/ # Prototype/demo scenes (big_number_museum) ``` ## Known Issues / TODOs -- [ ] Manual save triggers not implemented (auto-save needed) -- [ ] `GeneratorPanel` has missing `mouse_entered`/`mouse_exited` handlers -- [ ] Some buffs configured locked with no unlock path +- [ ] Periodic auto-save not implemented (saves only on prestige and manual trigger) - [ ] No automated test suite - -## Current Status - -Research system fully implemented with: -- XP-based progression tied to production -- Auto-leveling with BigNumber support -- Buff multipliers for XP gain -- Save format version 5 with research persistence +- [ ] Some buffs may be configured with no unlock path ## Technical Details -### BigNumber System -- **Representation**: Mantissa (float) + Exponent (int) -- **Range**: Handles values up to ~10^308 (float precision limit) -- **Operations**: Add, subtract, multiply, divide, compare -- **Optimization**: `add_in_place()` for performance-critical loops - -### Production Formulas -``` -Cost for N items: base * ratio^k * ((ratio^n - 1) / (ratio - 1)) -Max affordable: log((currency*(ratio-1)/(base*ratio^k)) + 1) / log(ratio) -Production: base_productivity * owned * run_mult * milestone_mult * purchased_mult -``` - ### Buff Multiplier Stacking -Multiple buffs of the same kind stack **multiplicatively**: + +Multiple buffs of the same kind on the same generator stack **multiplicatively**: + ``` -total_multiplier = buff1_effect * buff2_effect * buff3_effect * ... +total_multiplier = buff1_effect(level) × buff2_effect(level) × buff3_effect(level) × … ``` +### Prestige Multiplier Application + +Generators apply prestige multipliers automatically. The prestige multiplier is fetched from `PrestigeManager.get_total_multiplier()` and multiplied into the effective production rate alongside research and buff multipliers. + ## License -Internal prototype - no external license applied. +Internal prototype — no external license applied. --- -*Last updated: Refactoring plan documented* *Godot version: 4.6* diff --git a/core/README.md b/core/README.md index 8645767..e0f6d8c 100644 --- a/core/README.md +++ b/core/README.md @@ -37,6 +37,141 @@ core/ 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