# Per-Level GameState Architecture Refactoring Plan ## Overview Convert the current autoload-based global GameState system to a per-level instance-based architecture where each level scene has its own `LevelGameState` node with references to catalogues (resources) and its own save file. ## Current Architecture (To Be Replaced) ``` project.godot autoloads: ├─ BuffDatabase (autoload) ├─ CurrencyDatabase (autoload) ├─ GameState (autoload) └─ PrestigeManager (autoload) tiny_sword.tscn: └─ Uses GameState autoload directly from any node ``` **Problems:** - Level scenes implicitly depend on global autoloads - Cannot have multiple levels with independent state - Catalogues auto-discover from filesystem (tightly coupled) - Save file path is hardcoded globally ## Target Architecture ``` tiny_sword.tscn (root node) ├─ LevelGameState (Node) │ ├─ @export var currency_catalogue: CurrencyCatalogue (Resource) │ ├─ @export var buff_catalogue: BuffCatalogue (Resource) │ ├─ @export var goal_catalogue: GoalCatalogue (Resource) │ ├─ @export var save_file_path: String = "user://tiny_sword_save.json" │ └─ PrestigeManager (Node child) │ └─ @export var game_state: LevelGameState ├─ World (Node2D) │ ├─ GoldMine (CurrencyGenerator) │ │ └─ @onready var game_state = find_parent("LevelGameState") │ └─ ...other generators └─ UI (Control) ├─ CurrencyTile (each: @export var game_state: LevelGameState) └─ ... ``` ## Catalogue Resources All catalogues are `Resource` subclasses with explicit arrays populated in the editor: ### CurrencyCatalogue ```gdscript class_name CurrencyCatalogue extends Resource @export var currencies: Array[Currency] = [] func get_currency_by_id(id: StringName) -> Currency func get_all_ids() -> Array[StringName] ``` **Location:** `res://docs/gyms/tiny_sword/currencies/tiny_sword_catalogue.tres` ### BuffCatalogue ```gdscript class_name BuffCatalogue extends Resource @export var buffs: Array[GeneratorBuffData] = [] func get_buff_by_id(id: StringName) -> GeneratorBuffData func get_all_ids() -> Array[StringName] func get_buffs_for_generator(gen_id: StringName) -> Array[GeneratorBuffData] ``` **Location:** `res://docs/gyms/tiny_sword/buffs/tiny_sword_buffs.tres` ### GoalCatalogue ```gdscript class_name GoalCatalogue extends Resource @export var goals: Array[GoalData] = [] func get_goal_by_id(id: StringName) -> GoalData func get_all_ids() -> Array[StringName] ``` **Location:** `res://docs/gyms/tiny_sword/goals/tiny_sword_goals.tres` --- ## Implementation Steps ### Step 1: Create Catalogue Classes **Files to create:** 1. `core/currency_catalogue.gd` 2. `core/buff_catalogue.gd` 3. `core/goal_catalogue.gd` **Purpose:** Resource classes that hold arrays of game data and provide lookup methods. --- ### Step 2: Create LevelGameState **File to create:** `core/level_game_state.gd` **Key changes from current `game_state.gd`:** | Current | New | |---------|-----| | `extends Node` (autoload) | `extends Node` (instance) | | `CurrencyDatabase.get_known_currency_ids()` | `currency_catalogue.get_all_ids()` | | `BuffDatabase` calls | `buff_catalogue` calls | | Static `file_name` | `@export var save_file_path: String` | | Filesystem goal discovery | `goal_catalogue.goals` array | | Buff auto-registration from BuffDatabase | Buff registration from `buff_catalogue` | **New properties:** ```gdscript @export var currency_catalogue: CurrencyCatalogue @export var buff_catalogue: BuffCatalogue @export var goal_catalogue: GoalCatalogue @export var save_file_path: String = "user://level_save.json" ``` **Methods to port:** - All currency methods (`add_currency`, `spend_currency`, `get_currency_amount_by_id`, etc.) - All generator methods (`register_generator`, `get_generator_state`, etc.) - All buff methods (`register_buff`, `get_buff_level`, `get_effective_multiplier`, etc.) - All goal methods (`register_goal`, `is_goal_completed`, `evaluate_all_goals`, etc.) - Save/load (`save_game()`, `load_game()`) with configurable path - Prestige methods (`reset_for_prestige`, `emit_currency_changed_for_all`) --- ### Step 3: Convert PrestigeManager **File to modify:** `core/prestige/prestige_manager.gd` **Changes:** - Remove autoload registration from `project.godot` - Add `@export var game_state: LevelGameState` - Change `/root/PrestigeManager` lookup to use direct reference - Make it a child node of `LevelGameState` in scenes **Key fix in `_get_prestige_multiplier()`:** ```gdscript # Old: get_node_or_null("/root/PrestigeManager") # New: Direct access through injected reference ``` --- ### Step 4: Update CurrencyGenerator **File to modify:** `core/generator/currency_generator.gd` **Changes:** - Replace all `GameState.` calls with `game_state.` calls - Add `@onready var game_state: LevelGameState = find_parent("LevelGameState")` - Update signal connections: `game_state.currency_changed.connect(...)` - Update `_get_prestige_multiplier()` to use injected reference --- ### Step 5: Update CurrencyTile **File to modify:** `currency_label.gd` **Changes:** ```gdscript @export var game_state: LevelGameState # ← NEW func _ready() -> void: if game_state == null: push_error("CurrencyTile '%s' missing game_state reference" % name) return _currency_id = game_state.get_currency_id(currency) # ... rest of initialization ``` --- ### Step 6: Update project.godot **Remove autoloads:** ```ini # REMOVE these lines: # BuffDatabase="*res://core/buff_database.gd" # CurrencyDatabase="*res://core/currency_database.gd" # GameState="*uid://d2j7tvlgxr2jp" # PrestigeManager="*res://core/prestige/prestige_manager.gd" ``` --- ### Step 7: Update tiny_sword.tscn **Add LevelGameState node:** ```ini [node name="LevelGameState" type="Node" parent="."] currency_catalogue = ExtResource("currency_catalogue_tres") buff_catalogue = ExtResource("buff_catalogue_tres") goal_catalogue = ExtResource("goal_catalogue_tres") save_file_path = "user://tiny_sword_save.json" ``` **Add PrestigeManager as child:** ```ini [node name="PrestigeManager" type="Node" parent="LevelGameState"] game_state = NodePath("..") # Reference to LevelGameState ``` **Wire up children:** - Each `CurrencyTile`: `game_state = LevelGameState` - Each `CurrencyGenerator`: auto-discovers via `find_parent()` --- ### Step 8: Create Catalogue Resources **Create `.tres` files via Godot editor:** 1. `res://docs/gyms/tiny_sword/currencies/tiny_sword_catalogue.tres` - `currencies = [gold, worker, food, wood]` 2. `res://docs/gyms/tiny_sword/buffs/tiny_sword_buffs.tres` - `buffs = [all relevant buffs for this level]` 3. `res://docs/gyms/tiny_sword/goals/tiny_sword_goals.tres` - `goals = [all relevant goals for this level]` --- ### Step 9: Clean Up **Files to delete:** - `core/currency_database.gd` (replaced by `CurrencyCatalogue`) - `core/buff_database.gd` (replaced by `BuffCatalogue`) - `core/game_state.gd` (replaced by `level_game_state.gd`) --- ## File Summary ### Files to Create (4) | File | Purpose | |------|---------| | `core/currency_catalogue.gd` | Resource with `Array[Currency]` + lookup methods | | `core/buff_catalogue.gd` | Resource with `Array[GeneratorBuffData]` + lookup methods | | `core/goal_catalogue.gd` | Resource with `Array[GoalData]` + lookup methods | | `core/level_game_state.gd` | Complete GameState implementation without autoload dependencies | ### Files to Modify (4) | File | Changes | |------|---------| | `core/prestige/prestige_manager.gd` | Remove autoload, add `@export var game_state` | | `core/generator/currency_generator.gd` | Add `find_parent()` lookup, replace `GameState.` | | `currency_label.gd` | Add `@export var game_state: LevelGameState` | | `project.godot` | Remove 4 autoloads | ### Files to Delete (3) | File | Reason | |------|--------| | `core/currency_database.gd` | Replaced by `CurrencyCatalogue` | | `core/buff_database.gd` | Replaced by `BuffCatalogue` | | `core/game_state.gd` | Replaced by `level_game_state.gd` | ### Scene Updates | File | Changes | |------|---------| | `tiny_sword.tscn` | Add `LevelGameState` node, add `PrestigeManager` child, wire catalogues | | Create `.tres` resources | `tiny_sword_catalogue.tres`, `tiny_sword_buffs.tres`, `tiny_sword_goals.tres` | --- ## Benefits 1. **Decoupled levels**: Each level can exist independently without global dependencies 2. **Multiple levels**: Can support multiple levels with independent state (future feature) 3. **Explicit dependencies**: All dependencies are visible in the inspector 4. **Resource-based catalogues**: Content is defined as editor-authorable resources 5. **Per-level saves**: Each level has its own save file path 6. **Better testing**: Easier to test individual levels in isolation --- ## Risks and Considerations 1. **Signal connections**: UI components must be manually wired to `LevelGameState` instance 2. **PrestigeManager access**: Must ensure proper reference injection 3. **Catalogue population**: Catalogue resources must be created and populated in editor 4. **Migration**: Existing save files may need migration logic --- ## Implementation Order 1. Create catalogue classes (3 files) 2. Create level_game_state.gd (the core refactor) 3. Update prestige_manager.gd (remove autoload dependency) 4. Update currency_generator.gd (add game_state injection) 5. Update currency_label.gd (add game_state export) 6. Update project.godot (remove autoloads) 7. Create catalogue resources (.tres files via editor) 8. Update tiny_sword.tscn (wire everything together) 9. Delete old files (cleanup) --- ## Status - [ ] Step 1: Create catalogue classes - [ ] Step 2: Create level_game_state.gd - [ ] Step 3: Update prestige_manager.gd - [ ] Step 4: Update currency_generator.gd - [ ] Step 5: Update currency_label.gd - [ ] Step 6: Update project.godot - [ ] Step 7: Create catalogue resources (manual editor work) - [ ] Step 8: Update tiny_sword.tscn (manual editor work) - [ ] Step 9: Delete old files