Skip to content

Prediction

Client-side prediction in GAS lets the owning client execute gameplay actions immediately without waiting for server confirmation, then reconciles when the server responds. This is what makes abilities feel responsive in networked games -- press a button, see the result instantly, even with 100ms of latency.

The prediction system is built around one core concept: the Prediction Key.

FPredictionKey

An FPredictionKey is a unique ID generated on the client and sent to the server. It ties together a predicted action and all of its side effects so they can be confirmed or rolled back as a unit.

Key properties:

  • Generated by the client when starting a predicted action (typically ability activation)
  • Sent to the server with the activation RPC
  • The server associates its authoritative side effects with the same key
  • Replication is asymmetric: keys replicate client → server always, but server → client only to the originating client. All other clients receive an invalid (0) key.

This asymmetry is intentional -- only the predicting client needs to reconcile.

What Gets Predicted

System Predicted? Notes
Ability activation Yes The core predicted action
Chained ability activation Partial With caveats (dependent keys)
Triggered events Yes
GE application (attribute mods) Yes Modifier-based only, not Executions
GE application (tag grants) Yes
Gameplay Cues Yes Both from GEs and standalone
Montages Yes Via ability system's montage replication
Character Movement Yes Built into UCharacterMovement, not GAS
GE removal No
GE periodic ticks No
Execution Calculations No Executions run server-only
Spawned actors (from abilities) Partial Can be predicted with extra work

Execution Calculations aren't predicted

This is a common surprise. If your damage formula uses an ExecCalc, the damage calculation only runs on the server. The client can predict the ability activation and the GE application, but not the calculated damage value. The health change arrives via replication. For instant-feel damage, some projects use modifiers instead of ExecCalcs for the predicted portion.

The Prediction Window

A prediction window is the scope during which predicted side effects are valid. Here's how it works:

  1. Client calls TryActivateAbility → a new FPredictionKey is generated
  2. Client calls ServerTryActivateAbility → the key is sent to the server
  3. Client calls ActivateAbility locally (predictively) → this is the prediction window
  4. Everything that happens in the ActivateAbility callstack gets associated with the prediction key
  5. ActivateAbility returns → the prediction window closes

The prediction window is one callstack

Anything that happens after ActivateAbility returns is outside the prediction window. This means: timers, delays, latent Blueprint nodes, and anything on the next frame are not predicted. If your ability does ActivateAbility → Delay 0.5s → ApplyEffect, the effect application is not predicted.

FScopedPredictionWindow

The engine uses FScopedPredictionWindow to manage prediction windows. It's an RAII object that sets up and tears down the prediction context:

// Inside the ASC's ability activation flow:
FScopedPredictionWindow ScopedPrediction(AbilitySystemComponent, PredictionKey);
// Everything in this scope is predicted with this key
Ability->ActivateAbility(...);
// Scope ends, prediction window closes

You can also create scoped windows manually in ability code for dependent predictions:

{
    FScopedPredictionWindow ScopedPrediction(
        AbilitySystemComponent, true /* create new scoped key */);

    // This predicted action uses a dependent key
    ASC->ApplyGameplayEffectToSelf(Effect, Level, Context);
}

The Prediction Flow

Here's the full flow for a predicted ability activation:

CLIENT                                  SERVER
──────                                  ──────
TryActivateAbility()
  │ Generate PredictionKey = 42
  │ Call ServerTryActivateAbility(42)  ──→  ServerTryActivateAbility(42)
  │                                          │ Can activate? Check costs, tags, etc.
  │ (Don't wait for server)                  │
  ▼                                          │
ActivateAbility()  ←─ prediction window      │
  │ Apply GE (predicted, key=42)             │
  │ Play cue (predicted, key=42)             │
  │ Play montage (predicted)                 │
  ▼                                          │
  (ability continues locally)                ▼
                                        ActivateAbility() on server
                                          │ Apply GE (authoritative, key=42)
                                          │ Play cue (key=42)
                                          │ Play montage
                                        ClientActivateAbilitySucceed(42) ──→ Client
  ▼ (property replication catches up)
ReplicatedPredictionKey matches 42
  │ Side effects with key 42 are "caught up"
  │ Predicted GE is removed (server's replicated version takes over)
  │ Predicted cue is suppressed (already playing)
Reconciled -- client and server agree

Rejection Flow

If the server rejects the activation:

SERVER: ClientActivateAbilityFailed(42) ──→ Client
Client receives rejection
  │ Kill the ability immediately
  │ Roll back all side effects with key 42:
  │   - Remove predicted GE
  │   - Remove predicted cue
  │   - Stop predicted montage
State reverts to pre-prediction

Predicted GameplayEffect Handling

When a GE is applied predictively:

  1. Client applies the GE with PredictionKey = 42 → creates a local FActiveGameplayEffect
  2. Server applies the same GE with PredictionKey = 42 → replicates down to client
  3. Client receives the replicated GE, sees it has PredictionKey = 42, checks for a local match
  4. Match found → skip "on applied" events (no duplicate cue, no duplicate tag grant)
  5. Briefly, two copies of the effect exist (predicted + replicated)
  6. When ReplicatedPredictionKey catches up to 42, the predicted copy is removed
  7. The replicated copy remains as the authoritative state

Attribute Prediction

Attributes are replicated as standard UPROPERTYs, so predicting them requires special handling. GAS uses delta prediction: instead of predicting "I have 90 mana," the client predicts "-10 mana from whatever the server says." Instant GE modifications are treated as infinite-duration modifiers during the prediction window.

When the server confirms and the replicated attribute arrives, the predicted delta is removed, and the replicated value takes over seamlessly.

Predicted Actor Spawning

Abilities can predict spawning actors (projectiles, etc.) but it requires coordination:

  1. The ability spawns the actor locally during the prediction window
  2. The server also spawns the actor
  3. The server's replicated actor arrives on the client
  4. The client needs to match and merge the predicted and replicated versions

This is handled through UAbilityTask_SpawnActor and the prediction key system, but it's one of the trickier aspects of GAS prediction.

Common Pitfalls

Pitfall 1: Predicting Outside the Window

void UMyAbility::ActivateAbility(...)
{
    // Inside prediction window
    ApplyEffect(CostEffect); // Predicted

    GetWorld()->GetTimerManager().SetTimer(Handle, [this]()
    {
        // OUTSIDE prediction window -- NOT predicted!
        ApplyEffect(DamageEffect); // Server-only
    }, 0.5f, false);
}

Fix: Apply all predicted effects in the initial callstack, or use FScopedPredictionWindow with dependent keys.

Pitfall 2: ExecCalc Mismatch

Client predicts ability activation and GE application, but the ExecCalc only runs on the server. The client's predicted health doesn't change, then suddenly jumps when the server's calculated damage arrives.

Fix: For instant damage feedback, use modifier-based GEs for the predicted portion. Or accept the slight delay and design your cue feedback to mask it.

Pitfall 3: Prediction Key Already Used

If you try to reuse a prediction key (e.g., activating another ability in the same frame), you'll get unexpected behavior.

Fix: Each predicted action should have its own unique key. The system handles this automatically for standard ability activation.

Pitfall 4: Client Applies Effect, Server Doesn't

If the client's tag state differs from the server's (due to latency), the client might predict an effect application that the server rejects because of tag requirements.

Fix: Design your tag requirements to be lenient during prediction, or accept the rollback. Keep tag-based blocks simple and predictable.

Pitfall 5: Forgetting That GE Removal Isn't Predicted

If your ability removes a buff predictively, the client removes it immediately, but the server might not process the removal for a round trip. During that window, the client sees the buff gone while the server still has it.

Fix: GE removal not being predicted is a known limitation. For critical removal timing, use server-authoritative flow.

Key Source File

The GameplayPrediction.h header contains an extensive comment block (100+ lines) that documents the entire prediction architecture. It's the definitive reference -- read it if you need to understand the system deeply.