7.9 KiB
Goal-Based Generator Unlocks - Technical Specification
Document Status
- Version: 1.0
- Date: 2026-03-14
- Scope: Unlock generators when goal thresholds are reached using one or more currencies.
- Context sources: Amp threads
T-019ce93a-d410-70ad-aaa0-4697dafb0148andT-019c846f-2556-75bc-a1aa-fb26cd9ff52a.
Problem Statement
The project already supports generator state (unlocked + available) in GameState, but unlock
transitions are currently configured only by static defaults (starts_unlocked, starts_available).
We need progression goals so that a locked generator becomes usable only after the player reaches specific currency quantities. Goals must support one or more currencies per objective.
Current Baseline
- Generator state is persisted in
GameState.generator_stateswith keysowned,purchased_count,unlocked,available. CurrencyGeneratoralready exposesis_unlocked,is_available, andis_available_to_player()and uses them to gate click, auto-production, and purchases.GeneratorContainerlistens toGameState.generator_state_changedand updates button enabled/disabled state.- Save/load sanitization already handles generator state schema evolution safely.
This means the unlock feature can be added without changing core purchase/production mechanics.
Goals And Non-Goals
Goals
- Define unlock goals that depend on one or multiple currency thresholds.
- Evaluate goals automatically when relevant currency values change.
- Unlock and make target generator available exactly once when a goal is met.
- Keep behavior deterministic after save/load.
Non-Goals
- Adding quests, timed objectives, or repeatable missions.
- Adding reward types other than generator unlock/availability.
- Rebalancing generator economy values in this feature.
Functional Requirements
- A goal targets exactly one generator (
target_generator_id). - A goal contains one or more currency requirements (
requirements[]). - Goal completion rule is logical AND across requirements (all thresholds must be met).
- Supported currencies must map to existing
GameState.CurrencyTypevalues (gold,gems). - When completed, a goal sets both:
GameState.set_generator_unlocked(target, true)GameState.set_generator_available(target, true)
- Goal completion must be idempotent (re-evaluating does not re-apply side effects).
- Goal checks must run:
- at runtime on
currency_changed - once at startup after loading save data
- at runtime on
- Invalid goal entries (unknown currency, missing target id, malformed amount) must be ignored with a warning, not a crash.
Data Model
New Config Resources
Goals are now authored as typed resources instead of JSON:
res://core/goals/goal_requirement_data.gd(GoalRequirementData)res://core/goals/goal_data.gd(GoalData)res://core/generator-unlock-goals/generator_unlock_goal_data.gd(GeneratorUnlockGoalData)
Example assets:
res://idles/goals/magic_total_30.tres(goal definition)res://idles/goals/generator_unlock_knowledge.tres(goal target binding)
Notes:
GoalData.idmust be unique and stable.GoalData.requirementsmust contain at least one validGoalRequirementData.GoalRequirementDatastores amount as mantissa/exponent to preserveBigNumbersemantics.
Runtime Structures (GDScript)
Runtime data comes from resource classes:
GoalRequirementDatacurrency: Resourceamount_mantissa: floatamount_exponent: int
GoalDataid: StringNamerequirements: Array[Resource](validated asGoalRequirementData)
GeneratorUnlockGoalDatatarget_generator_id: StringNamegoal: Resource(validated asGoalData)
No persistent completed_goals storage is required for v1 because completion is derived from generator unlock state.
System Design
New Runtime Component
Add a scene-level controller script (recommended name: generator_unlock_system.gd) responsible for:
- Loading + validating exported
GeneratorUnlockGoalDataresources. - Subscribing to currency change signals.
- Evaluating pending goals.
- Triggering generator unlock transitions.
Placement options:
- Attach to the main gameplay scene root (preferred for prototype).
- Promote to autoload later if multiple gameplay scenes require shared unlock logic.
Evaluation Flow
- On
_ready():- load goals
- connect to
GameState.currency_changed - run
evaluate_all_goals()once
- On currency change:
- run
evaluate_all_goals()(or an optimized subset)
- run
- For each goal:
- skip if target generator already unlocked and available
- verify all requirement thresholds
- if satisfied, unlock target generator and emit debug/info log
Pseudocode
func _evaluate_goal(goal: UnlockGoal) -> bool:
if GameState.is_generator_unlocked(goal.target_generator_id) and GameState.is_generator_available(goal.target_generator_id):
return false
for requirement in goal.requirements:
var current: BigNumber = _get_currency_amount(requirement.currency)
if current.is_less_than(requirement.amount):
return false
GameState.set_generator_unlocked(goal.target_generator_id, true)
GameState.set_generator_available(goal.target_generator_id, true)
return true
UI/UX Behavior
- Existing
GeneratorContainerbehavior already prevents interaction while locked. - Optional v1.1 enhancement: show a compact "Unlock requirement" line for locked generators.
- On unlock, UI updates automatically through existing
generator_state_changedsignal path.
Error Handling And Validation
- File open failure: log warning and disable unlock processing for that session.
- JSON parse failure or wrong root type: log warning and disable unlock processing.
- Invalid goal entries: skip only invalid entries, continue loading valid ones.
- Duplicate goal IDs: keep first entry, ignore duplicates with warning.
- Unknown
target_generator_id: allow config load, but warn when evaluation cannot find/resolve state.
Save/Load Behavior
- Unlock result persists naturally via existing generator state persistence.
- On load, if a goal was completed previously, no duplicate effect occurs because state is already unlocked.
- If config thresholds are reduced in future versions, startup evaluation can unlock additional generators from existing balances.
Performance Considerations
- v1 can safely evaluate all goals on every currency change (small goal count).
- If goals scale up, add currency-to-goal index to evaluate only affected goals.
- BigNumber comparisons are lightweight for this usage profile.
Implementation Plan
- Add
GoalDataandGeneratorUnlockGoalDataresources with initial goals. - Implement
generator_unlock_system.gdloader/validator/evaluator. - Attach unlock system to gameplay scene (
generator_museum.tscnor current active scene). - Configure at least one generator as initially locked (
starts_unlocked = false,starts_available = false) in its.tresdata. - Add runtime logs for unlock events and invalid config entries.
- Run headless project parse smoke check.
Acceptance Criteria
- A locked generator becomes usable immediately after all configured currency thresholds are reached.
- Goals with multiple requirements unlock only when every requirement is satisfied.
- Unlock state persists after save/load and app restart.
- No crashes occur when goals file is missing or malformed.
- Existing generator purchase/production behavior is unchanged for already unlocked generators.
Manual Test Cases
- Start with a generator configured as locked; verify buy buttons are disabled.
- Grant required currency via debug controls; verify automatic unlock and enabled buttons.
- Save/restart; verify generator remains unlocked.
- Use a multi-currency goal; verify partial progress does not unlock.
- Corrupt one goal entry in JSON; verify warning is logged and other valid goals still work.