Appearance
Persistent effects
Mass Forge Persistent Effects provide opt-in finite or infinite lifecycle instances for actorless and represented Mass entities. An instance can act as a queryable timer/marker, execute an Instant Effect periodically, or do both.
Opening the asset shows live validation, its periodic dependency, and searchable saved referencers with direct navigation. See GAMEPLAY_ASSET_AUTHORING_GUIDE.md.
Setup
- Add Mass Forge Persistent Effects Trait to every Mass Entity Config that may receive persistent effects. Also add Mass Forge Gameplay Tag Trait when using requirements, immunity blockers, or granted tags.
- For periodic behavior, create a Mass Forge Instant Effect containing the attribute and/or counted Gameplay Tag pulse.
- Create a Mass Forge Persistent Effect Data Asset and assign a stable
EffectId. - Choose Duration or Infinite, select a stacking policy, then optionally configure persistent attribute modifiers, a period, and a pulse effect.
- Call
Apply Mass Forge Persistent Effectat a safe point, orQueue Mass Forge Persistent Effectwhen the caller may overlap Mass processing.

This complete asset is the reusable authoring unit: reapplication refreshes the existing handle, the lifecycle lasts five seconds, and the referenced Instant Effect executes once per second. The green authoring overview proves the saved asset and dependency validate together.
Each participating entity has 16 fixed active-instance slots. The trait is opt-in, so entities that do not need lifecycle effects pay no fragment cost.
Lifetime policies
- Duration requires a positive finite
DurationSecondsvalue. The instance expires automatically after its active time and publishes an expiration event. - Infinite has no automatic expiration. Remove it explicitly by its returned handle or let entity destruction invalidate it.
An effect with a zero period is a lightweight lifecycle marker. It remains queryable, can own lifecycle tags, and emits apply/removal events without periodically changing attributes.
Persistent attribute modifiers
Add entries to AttributeModifiers when an effect should contribute a reversible stat change for its complete lifetime. Each contribution targets a normal schema attribute and uses one aggregation channel:
- Add contributions are summed.
- Multiply contributions are multiplied together.
- Override selects one winner. Higher
OverridePrioritywins; equal priority is resolved by the newest active handle, making the result stable and explainable.
The effective value is calculated as (Base + Additive Sum) * Multiplicative Product, followed by the winning override when one exists, then the attribute schema clamp. Removing, replacing, cancelling, refreshing, or expiring an instance rebuilds the remaining aggregate from snapshotted per-handle contributions; Mass Forge never tries to reverse a modifier with lossy inverse arithmetic.
Modifier magnitudes can use the same context-parameter formula as Instant Effects and can add one sampled source or target attribute term. Persistent lifetime contributions always resolve that term when the instance applies, preserving a compact allocation-free aggregate. Later source/target changes, parameter changes, or Data Asset edits cannot mutate an existing contribution. Missing and non-finite parameters, invalid capture entities, unknown attributes, missing schema/attribute fragments, aggregate overflow, and capacity exhaustion return explicit apply results before lifecycle state changes.
The Persistent Effects Trait includes storage for 24 distinct modified attributes per entity in addition to the 16 lifecycle slots. Multiple active contributions to the same attribute consume one aggregate slot. This fixed bound keeps the hot path allocation-free; exceeding it returns Modifier Capacity Exceeded.
The final PrePhysics modifier processor reconciles direct Mass writes after regeneration and reapplies the active aggregate. The derived-attribute processor writes its calculated result into the aggregate's base, so persistent modifiers remain effective on derived targets and dependent derived rules observe the effective modified value. Blueprint/subsystem changes continue to operate on the visible effective value; an active override keeps presentation fixed while preserving underlying base movement for when the override leaves.
Periodic execution
Set PeriodSeconds to at least 0.001 seconds and assign a valid Mass Forge Instant Effect to PeriodicEffect. Every pulse uses the Instant Effect's complete atomic transaction contract, including attribute bounds, counted tag operations, structured failures, and change events.
Enable Execute Periodic Effect On Application to run pulse 1 during application. If that first pulse fails, the persistent application returns Initial Application Failed, exposes the complete Instant Effect result, and leaves no active instance. A successful first pulse is followed by the next pulse one full period later.
Scheduled pulse failures are reported through On Persistent Effect Period; they do not silently remove the lifecycle instance. This lets a project decide whether a temporary failure should cancel, pause, or simply skip that pulse.
Deterministic timing and catch-up
Finite effects count only time within their authored lifetime. If a frame crosses several period boundaries, every due pulse is scheduled in order. A pulse exactly on the duration boundary is included before expiration.
To prevent one hitch from monopolizing the game thread, one instance executes at most 64 catch-up pulses per subsystem tick. Remaining pulses stay in the compact slot as backlog and execute on later ticks; they are not discarded. A finite instance whose lifetime has ended remains internally pending until its earned backlog is drained, then emits one expiration event.
If a scheduler tick encounters active Mass processing, elapsed time is accumulated and applied on the next safe tick rather than lost.
The scheduler uses a world-local deadline min-heap. It advances only effects whose next pulse, expiration, or catch-up deadline is due, and it never queries or iterates the complete Mass entity population. A separate generation-safe ownership check scales with active persistent handles so destroyed owners are still retired on the next phase. Refresh and extension use revisioned lazy rescheduling; obsolete heap entries are harmless and compacted when their ratio grows.
Use Get Persistent Effect Scheduler Stats on the Persistent Effect subsystem to inspect active handles, schedules, heap entries, last-phase due work, ownership checks, and stale entries from Blueprint. See the persistent scheduler architecture for the exact runtime, determinism, compaction, diagnostics, and verification contract.
Stacking policies
StackKey controls which active instances interact. If it is empty, Mass Forge uses EffectId, so the default setup groups applications of the same asset. An explicit key lets different effect assets participate in one shared stack.
- Independent always creates a new handle, subject to the entity's 16-slot capacity.
- Capped Independent creates separate handles until
MaxStacksis reached, then returnsStack Limit Reachedwithout executing an initial pulse or changing state. - Refresh Existing reuses the first matching handle and resets its remaining duration to the incoming duration. It requires a finite Duration effect.
- Extend Existing reuses the first matching handle and adds the incoming duration to its remaining lifetime, saturating safely. It requires a finite Duration effect.
- Replace Existing removes every matching active instance and creates one incoming instance with a new handle.
- Strongest Wins compares
StackStrength. A strictly stronger incoming effect replaces every matching instance; an equal or weaker application returnsStronger Or Equal Effect Activeand identifies the winning handle without changing state.
Refresh and extend update the active definition, strength, periodic effect, copied context, and snapshotted modifier payload. Enable Reset Period On Stack when a successful refresh or extension should discard partial progress toward the next pulse. Already-earned catch-up backlog is preserved when the incoming effect remains periodic; switching to a non-periodic marker clears obsolete pulse timing and backlog. If execute-on-application is enabled, the initial pulse must succeed before any merge or replacement commits; a failure restores the exact active stack.
The application result exposes StackDisposition and the resulting StackCount. Successful applications publish On Persistent Effect Stacked; new and replacement instances also publish On Persistent Effect Applied. Handles retired by replacement publish On Persistent Effect Removed with Replaced.
Lifecycle tags, immunity, and cancellation
Persistent Effect assets can participate in the project's normal Gameplay Tag vocabulary without imposing plugin-specific tag names:
EffectTagsclassify an active effect for cancellation queries, such asEffect.DebufforEffect.Debuff.Fire.RequiredTargetTagsmust all match before application. Parent requirements match owned child tags.BlockedTargetTagsreject application when any matches. These are the normal authoring point for immunity tags such asState.Immune.Fire.GrantedTagsadd one counted tag contribution per active instance. The exact contribution is removed on manual removal, expiration, replacement, or cancellation.CancelEffectsWithTagscancels every active instance whose classification matches any requested tag. A broad parent therefore supports dispel or cleanse families.
Application gates run before any state changes. Cancellations, old grant removal, incoming grants, and an optional execute-on-application pulse are projected as one transaction. A missing required tag, immunity blocker, tag-capacity error, invalid pulse, or other transaction failure leaves the original handles, attributes, and counted tags intact.
Refresh and extend reconcile their reused handle's grants against the incoming definition without removing shared tags. Independent instances each contribute their own count, so removing one source preserves the other sources. Granted and classification containers are snapshotted per active handle; later edits to the Data Asset cannot cause cleanup to remove a different tag set.
The apply result identifies a failed tag and underlying tag result when relevant, and reports CancelledEffectCount on success. Cancelled handles publish On Persistent Effect Removed with Cancelled.
Ability integration
Ability Data Assets can author SelfPersistentEffects and TargetPersistentEffects. Add the Persistent Effects Trait to every possible recipient; target persistent outcomes automatically make the ability require a valid target. Ability parameters and source identity flow into each stored effect context.
Costs, self/target Instant Effects, self Persistent Effects, and target Persistent Effects share one rollback boundary. Applications execute in that deterministic order. If any persistent outcome fails, Mass Forge restores cooldowns, shared cooldowns, charges, attributes, counted tags, cancelled or replaced instances, compact timer slots, payload snapshots, and the next handle ID. Provisional events are discarded. A successful ability result returns persistent handles in authored order and the aggregate cancellation count; a failure identifies the list index, source/target subject, underlying persistent result, and relevant tag details.
Handles, queries, and removal
Every successful application returns a world-unique Mass Forge Persistent Effect Handle. Independent policies create handles; refresh and extend deliberately return the reused handle.
Query Mass Forge Persistent Effectreturns identity, stack key, strength, current stack count, snapshotted classification/granted tags, persistent modifier contribution count, duration policy, remaining time, period, time to the next period, and completed pulse count.Get Active Mass Forge Persistent Effectslists every visible instance in compact fragment order.Remove Mass Forge Persistent Effectremoves exactly one instance by handle.
The matching queued nodes defer application or exact-handle removal into one bounded FIFO stream. An accepted receipt contains a positive request ID; bind On Persistent Effect Request Completed and match its unified completion payload, which identifies Apply versus Remove and carries the full corresponding result. A rejected submission uses request ID 0 and never emits a completion. Definition identity and complete entity generation are revalidated at execution.
Queued requests execute at the start of the Persistent Effects phase, before that phase advances durations, due pulses, and expiration. Consequently, a queued removal accepted before a boundary can suppress that handle's otherwise-due pulse. Work enqueued by a Persistent Effect completion callback waits for the next coordinator tick. Configure Max Queued Persistent Effect Requests Per Frame and Max Pending Queued Persistent Effect Requests under Project Settings > Mass Forge > Global Attribute Schemas > Runtime.
Removal publishes On Persistent Effect Removed with Manual, Expired, Replaced, Cancelled, or Entity Invalidated. Applying beyond the 16-instance capacity returns No Free Persistent Effect Slot without changing state.
Scheduling is owned by the shared coordinator rather than an independent subsystem tick. Due work is sorted by complete entity handle and persistent handle; earned pulses run before same-boundary expiration, and carried pulse backlog delays expiration until it drains. The complete cancellation and callback precedence is defined in the deterministic gameplay ordering contract.
Persistent handles contain the owning entity generation. Destroyed owners are retired on the next valid subsystem pass, including a zero-delta cleanup pass, and cannot leak into a recycled entity index. See the entity lifecycle safety contract.
Durations and periods use scaled gameplay delta and stop under normal world pause. Entity-state snapshots serialize exact active instances from format 3 onward with projected duration, period phase/backlog, reversible modifier bases, owned tags, portable context, and soft definition identities; format 4 additionally preserves an active Ability Lifecycle. Restore allocates fresh handles; external effect-source entity handles and Actor/hit payloads do not cross worlds. See the durable persistence guide and time, travel, and save contract.
For a combined debug view, Capture Mass Forge Entity Inspection includes all visible persistent instances in monotonically increasing handle order alongside the entity's attributes, tags, abilities, and cooldown groups. See ENTITY_INSPECTION_GUIDE.md.
Context policy
The supplied effect context is copied when the instance is applied. Source/target entity handles, Actor references, source identity, origin, and scalar parameters therefore remain stable inputs for later pulses. Persistent modifier magnitudes are resolved at application.
Periodic Instant Effect modifiers can add an attribute-backed magnitude term and select one explicit policy:
- Snapshot At Application captures the source or target attribute before lifecycle tags, initial pulses, cancellations, or other state changes commit. The captured values remain in the stored context for every later pulse.
- Evaluate On Execution validates the capture at application, then reads the current source or recipient value before each pulse. If an entity or attribute later becomes unavailable, that pulse reports the normal detailed failure and the lifecycle instance remains active.
Snapshot storage is bounded to 32 unique subject/attribute pairs per active context. Duplicate captures share one stored value. Capture failures identify Source or Target, the attribute ID, and the underlying attribute access result. All modifiers in one pulse observe capture state from before that pulse's atomic writes.
The subsystem owns the shared Data Asset and context payload centrally while active. The Mass fragment stores only bounded handles and timing state, so no Actor or UObject is required per entity.
Blueprint events
On Persistent Effect Appliedfires after an instance becomes visible.On Persistent Effect Stackedfires after every successful add, refresh, extension, or replacement and reports the disposition and resulting stack count.On Persistent Effect Periodfires after every pulse attempt and includes its sequence number and full Instant Effect result.On Persistent Effect Removedfires once for manual removal, expiration, replacement, cancellation, or invalidated entity ownership.On Persistent Effect Removed With Contextis its additive, source-aware companion. It fires for the same removal and includes the application context snapshotted before the instance is retired. Existing bindings toOn Persistent Effect Removedremain compatible.On Persistent Effect Request Completedfires once after each accepted queued Apply or Remove reaches a terminal result; successful lifecycle events are published first.
Get the world subsystem through Get Mass Forge Persistent Effect Subsystem to bind these delegates. C++ integrations can bind the corresponding Native delegate mirrors without dynamic dispatch.
Current scope
Persistent effects support independent, capped, refresh, extend, replace, and strongest-wins behavior, reversible additive/multiplicative/priority-override attribute aggregation, target tag gates, lifecycle-granted counted tags, classification-based cancellation, transactional initial-pulse rollback, fully transactional ability-authored self/target applications, bounded deferred Apply/Remove commands, durable entity-state save/load, and owner-only authoritative network baselines. Ability prediction is presentation-only and never predicts a Persistent Effect mutation. Additional game-specific Mass LOD timing policies remain project work.