Repository navigation
01 Prototype Tech Effect System
github-actions[bot] edited this page Jun 3, 2026
·
2 revisions
As-built record for the prototype implementation (SH-41, SH-43). This document covers what was built and where it lives. The design authority is designs/effect-system/README.md.
classDiagram
class ItemDefinition {
+key: String
+role: StringName
+display_name: String
+descriptions: String[]
+base_cost: int
+cost_scaling: float
+max_level: int
+effects: Effect[]
+fixture: Fixture
+kit_rate_override: float
+get_effects_for_level(level: int) Effect[]
+get_key() String
}
class Partner {
+key: String
+relationship_level: int
+effects: Effect[]
+get_effects_for_level(level: int) Effect[]
}
class Effect {
+trigger: Trigger
+conditions: Condition[]
+outcomes: Outcome[]
+min_active_level: int
+max_active_level: Variant
}
class Trigger {
<<sub-resource>>
+type: StringName
+parameters: Dictionary
}
class Condition {
<<sub-resource>>
+type: StringName
+parameters: Dictionary
}
class Outcome {
<<sub-resource>>
+type: StringName
+parameters: Dictionary
}
class ItemManager {
+items: ItemDefinition[]
-progression: ProgressionData
-effect_manager: EffectManager
+get_level(item_key: String) int
+calculate_cost(item_key: String) int
+can_purchase(item_key: String) bool
+purchase(item_key: String) bool
+remove_level(item_key: String)
+get_soul_balance() int
+add_soul(points: int)
+subtract_soul(points: int)
}
class EffectManager {
-effect_state: EffectState
+get_stat(key: StringName) float
+get_base_stat(key: StringName) float
+get_percentage_offset(key: StringName) float
+is_game_state_active(state: StringName) bool
+register_source(source: ItemDefinition, level: int)
+unregister_source(source: ItemDefinition)
-clear_source(source_key: String)
-apply_effect(effect: Effect, source_key: String, level: int)
}
class EffectState {
<<internal>>
-base_values: Dictionary[StringName, float]
-add_modifiers: Array[StatModifier]
-percentage_modifiers: Array[StatModifier]
-multiply_modifiers: Array[StatModifier]
-active_states: Dictionary[StringName, String]
+register_base_values(values: Dictionary)
+get_stat(key: StringName) float
+get_base_stat(key: StringName) float
+get_percentage_offset(key: StringName) float
+add_modifier(modifier: StatModifier)
+remove_modifiers_by_source(source_key: String)
+clear_temporary_modifiers()
+set_state(state: StringName, source_key: String)
+clear_state(state: StringName)
+is_state_active(state: StringName) bool
}
class StatModifier {
<<internal>>
+source_key: String
+stat_key: StringName
+operation: Operation
+value: float
+range_stat_key: StringName
+temporary: bool
}
ItemManager "1" --> "0..*" ItemDefinition
ItemManager "1" --> "1" EffectManager
ItemDefinition "1" --> "1..*" Effect
Partner "1" --> "1..*" Effect
Effect "1" --> "1" Trigger
Effect "1" --> "0..*" Condition
Effect "1" --> "1..*" Outcome
EffectManager "1" --> "1" EffectState
EffectState "1" --> "0..*" StatModifier
| Class | Role |
|---|---|
ItemDefinition |
Pure data template (Resource): display info, cost formula, effect definitions, authored role, optional fixture
|
Partner |
NPC companion providing effects scaled by relationship level |
Effect |
One trigger + conditions + outcomes rule, gated by min/max active level |
Trigger |
When the effect fires. Type is a StringName, not an enum |
Condition |
Optional gate that must pass before outcomes execute |
Outcome |
What happens when the effect fires |
ItemManager |
Ownership and economy: tracks what the player owns, levels, and friendship balance. Delegates stat effects to EffectManager on level change |
EffectManager |
Evaluation engine and public stat API: registers/unregisters sources, dispatches outcomes by trigger type, delegates stat queries to EffectState
|
EffectState |
Internal to EffectManager: holds base values from GameRules.BASE_STATS, modifier arrays (one per operation type), oscillations, and named game states. get_stat includes temporary modifiers; get_base_stat excludes them |
StatModifier |
One stat change: additive, percentage, or multiplicative. temporary flag marks modifiers cleared on miss. range_stat_key defers value resolution to query time |
ItemDefinition is a pure data template; level is stored per-player in ProgressionData, keyed by item key. ItemManager calls EffectManager.unregister_source then register_source on every level change.
| Class | Godot type | Location |
|---|---|---|
ItemManager |
Autoload (Node) | res://scripts/items/item_manager.gd |
EffectManager |
Autoload (Node) | res://scripts/items/effect/effect_manager.gd |
EffectState |
RefCounted (internal to EffectManager) | res://scripts/items/effect/effect_state.gd |
ItemDefinition |
Resource | res://resources/items/*.tres |
Effect |
Resource | res://scripts/items/effect/effect.gd |
Trigger |
Resource (sub-resource) | Inline in Effect resource |
Condition |
Resource (sub-resource) | Inline in Effect resource |
Outcome |
Resource (sub-resource) | Inline in Effect resource |
StatModifier |
RefCounted | Created at runtime by EffectManager
|
GameRules |
RefCounted (static constants) | res://scripts/core/game_rules.gd |
Partner |
Resource (planned) | res://data/partners/ |
Effects, items, and partners are .tres resource files. Authored in data, loaded at runtime.
- All stat keys exposed via
GameRules.BASE_STATSand queried throughItemManager.get_stat(). - Event dispatch:
ItemManager.process_event()fires registered effects with matching triggers.Game.gdwires ball signals (at_max_speed_changed,missed) to dispatchon_max_speed_reachedandon_miss. - Signal-to-trigger wiring: game signals (
ball_missed,paddle_hit,streak_changed, and the like) are translated intoprocess_eventcalls with the matching TriggerType, so the effect system never subscribes to raw gameplay signals itself. - Named game states tracked in
EffectStateviaset_state()/clear_state()/is_state_active(). - Ball reads speed limits every physics frame via
BallEffectProcessor._sync_speed_limits(), enabling dynamic stat changes to take effect immediately. -
GameRules.BALL_SPEED_MINandBALL_SPEED_MAXconstants removed; replaced by stat-driven values. - Percentage modifier operation: values summed into a single offset, then applied as
result *= (1.0 + total_offset). Evaluation order: add, then percentage, then multiply. -
paddle_size_minbase stat (50.0) clamps the paddle so percentage reductions cannot shrink it below a playable size.
- Causality items need temporary outcome expiry (timer or per-frame tick) for
multiply_stat_temporary. - Multi-ball requires a ball spawner and a reference list of active balls in the scene.
clear_extra_ballsremoves all but the original.
Contributing
The game
The Cast
Process
Reference