Files
idle/PLANNING.md

9.8 KiB

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

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

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

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:

@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():

# 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:

@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:

# 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:

[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:

[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