🧠 Status Effects System — Mental Model¶
This page explains how to think about Status Effects in RevFramework.
It is not an API reference.
It is a conceptual map that helps you decide:
- Where does this behaviour belong?
- Which extension point should I use?
- What should I never touch?
If you understand this page, the API will feel obvious.
🎯 The Core Idea¶
Status Effects are controller-driven state mutations applied to actors over time.
A status is not “add a buff component”.
It is a runtime effect instance managed by a controller, with explicit rules for:
- stacking
- timing
- potency scaling
- resistance
- immunity
- authority
Everything in the system exists to protect that execution model.
🧱 The Single Orchestrator¶
StatusEffectController¶
There is exactly one authority on status effect truth:
StatusEffectController
It owns:
- the active effect list
- effect lifecycle
- ticking and expiry
- stacking rules
- immunity checks
- potency and resistance evaluation
- runtime events
The controller is the only authority that may mutate status state.
If something modifies status state outside the controller, it is a bug.
Nothing else should:
- tick status effects
- mutate the active list
- remove effects directly
- modify controller state
If something outside the controller attempts to do these things directly, the system boundaries have likely been violated.
🔄 The Status Lifecycle¶
Every status effect follows the same conceptual lifecycle:
Apply
↓
Tick
↓
Refresh (optional)
↓
Expire / Remove
This lifecycle is always driven externally by the controller.
Effects do not advance themselves through this lifecycle.
Each stage has clear responsibilities.
▶️ Stage 1: Apply¶
Question: “A new effect is being applied — what should happen?”
During application the controller:
- checks immunity
- evaluates stacking rules
- applies potency scaling
- applies duration scaling
- calls the effect's
Apply()method
The effect instance becomes part of the controller's active set.
What belongs here¶
- stacking decisions
- potency modifiers
- duration scaling
- source attribution
What does NOT belong here¶
- UI updates
- spawning visual effects outside the FX seam
- direct mutation of other gameplay systems
Application is controlled by the controller.
Application is a state transition, not gameplay execution.
Effects should not perform gameplay logic here beyond initialization.
⏱️ Stage 2: Tick (Runtime Progress)¶
Active effects advance through controller-driven ticks.
The controller calls:
effect.Tick(target, deltaTime)
Effects never tick themselves.
All runtime behaviour happens during Tick.
This keeps behaviour consistent, controllable, and testable.
Why this matters¶
Centralized ticking guarantees:
- deterministic behaviour
- authority control
- testability
- predictable stacking interactions
The effect only implements behaviour — the controller owns time.
🔁 Stage 3: Refresh¶
Some stacking rules refresh the timer of an existing effect.
Examples:
| Stacking Rule | Behaviour |
|---|---|
| Replace | Remove existing effect, apply new |
| Refresh | Reset timer on existing instance |
| Stack | Add a second instance |
Refresh logic is controller-driven so stacking behaviour remains deterministic.
❌ Stage 4: Expire / Remove¶
An effect leaves the system when:
- its timer reaches zero
- it is explicitly removed
- it is dispelled or cleansed
The controller then:
- calls
Remove()on the effect - removes it from the active set
- emits lifecycle events
Effects should clean up any side-effects during Remove.
🧩 Effect Instances vs Definitions¶
Status Effects support two ways of defining behaviour.
Runtime Effect Instances¶
Effects are runtime objects implementing:
IStatusEffect
They contain:
- behaviour
- timers
- stacking rules
Example:
PoisonStatus
SlowStatus
BurnStatus
These instances live inside the controller.
Definition Assets¶
Definitions are ScriptableObject data assets.
StatusEffectDefinitionBase
They exist to:
- configure effects in data
- allow designers to author effects
- provide reusable definitions for abilities or items
Definitions build runtime instances via:
BuildEffect()
Definitions never contain runtime behaviour.
🏷️ Effect Identity¶
Every effect has a typed identifier:
StatusId
The identifier is used for:
- stacking decisions
- queries
- UI display
- registry construction
- integration logic
Prefer StatusId over raw strings in gameplay code.
🔌 Extension Points¶
Status Effects are designed to be extended through capability interfaces.
These allow behaviour to be layered without modifying the controller.
Extension points are additive.
They do not replace controller behaviour — they modify it.
Effect Capability Interfaces¶
Optional interfaces effects may implement:
| Interface | Purpose |
|---|---|
IAdjustableMagnitude |
Allow potency scaling |
IStatusMetadata |
Expose tags and dispel rules |
IDispellable |
Allow removal via dispel |
These describe what the effect supports.
Receiver-Side Modifiers¶
Actors may influence incoming effects.
Attach components implementing:
| Interface | Behaviour |
|---|---|
IStatusPotency |
Amplifies effect magnitude |
IStatusResistance |
Reduces duration |
IStatusImmunity |
Blocks effects entirely |
These are evaluated by the controller when effects are applied.
⚖️ Potency vs Resistance¶
These are often confused.
Potency¶
Controls how strong the effect is.
Examples:
- stronger poison damage
- stronger slow multiplier
- stronger vulnerability modifier
Implemented through IStatusPotency.
Resistance¶
Controls how long the effect lasts.
Examples:
- reduced stun duration
- shorter poison effects
- partial immunity to CC
Implemented through IStatusResistance.
⏱️ Time Sources¶
The controller advances time through a time provider abstraction.
ITimeSource
This allows:
- scaled gameplay time
- unscaled UI time
- paused systems
- custom time providers
Time is injected, not hardcoded.
🔐 Authority (Optional)¶
Status mutation can be gated by:
IStatusAuthority
This allows systems such as:
- multiplayer servers
- replay simulations
- deterministic lockstep systems
to control who may tick or mutate statuses.
When authority is denied:
- effects do not tick
- effects do not apply
- effects do not mutate
🧠 Built-In Status Effects¶
RevFramework includes several built-in effect implementations:
- Poison
- Burn
- Slow
- Haste
- Stun
- Vulnerability
- Thorns
Additional effects may appear when optional integrations are present.
Effects are allowed to do nothing if their required integrations are not present.
They still:
- exist
- tick
- expire normally
🔗 Integrations¶
The Status Effects system can integrate with other RevFramework modules.
For example:
Health Integration
Adds effects such as:
RegenStatusShieldStatus
These live in:
RevFramework > Integrations > StatusEffects > HealthIntegration
The base system does not depend on Health, but can integrate when available.
🎨 UI Representation¶
Status effects may be represented visually through UI components such as:
StatusBuffBarStatusIconViewStatusIconLibrary
These are optional helpers built on top of controller events.
🚫 What Never Belongs Where¶
- Effects should never tick themselves
- Effects should never modify the controller’s internal list
- UI should never mutate status state
- Gameplay systems should not bypass the controller
- Definitions should never contain runtime logic
If you feel the need to break these rules, you are likely using the wrong extension point.
🧠 The One-Sentence Model¶
Status Effects are controller-managed runtime effect instances applied over time and modified through a layered pipeline.
TL;DR¶
- One controller owns status truth
- Effects are runtime instances
- Lifecycle: apply → tick → refresh → expire
- Potency changes strength
- Resistance changes duration
- Extension seams keep the system modular
- Integrations layer behaviour across gameplay systems