Skip to main content
The live game

Not translated yet — showing English.

Prediction & Lag Compensation

Forward prediction and backward compensation, over data that carries its true event time. The client feels instant, the server stays right, and hits are judged in the shooter's timeline.

When to use it

  • Input must feel instant under latency while the server stays authoritative: predict forward, reconcile on divergence.
  • Hits must be judged in the shooter's timeline: ResolveAt rewinds hitboxes to the reported view tick.
  • Aim arcs and landing markers must match outcomes: client and server forecast the same Trajectory.
  • Game-critical fields must never roll back: declare what predicts and what waits for the server.
  • Rubber-banding needs tuning: per-entity windows, tolerances, and misprediction telemetry.
  • Skip it when latency doesn't hurt. Turn-based or slow games run fine on plain Data deltas.

Who does what

ActorOn this page
schema-authordeclares predicted vs authoritative-only fields; sets prediction window
room-ownerresolves hits at a historical state; rewinds the world
playerpredicts and reconciles motion; subscribes to corrections

Which rooms this applies to

This module runs where the platform steps the simulation: rooms declared Host = "Backend". If your own game server owns the simulation (PlayServ as the metaserver), movement, collision and prediction stay engine-side, and this page describes the platform-hosted alternative rather than a requirement. See Who runs the tick.

At a glance

The word covers three different mechanisms with different authorities and failure modes, and each has its own article:

MechanismWhat it doesRuns onWhen it is wrong
Predicting your own motionapplies the declared model to your own input without waiting for the serverthe clienta correction, replayed and smoothed
Showing other playersdraws other entities between the states that arrivethe clienta visible jerk
Lag compensationrewinds targets to the moment the shooter sawthe serversomebody dies unfairly

Four presets, and "no prediction" is one of them.

PresetPredicts your ownCompensatesSmooths others
shooteryesin a window of roughly a second and a halfyes
arcadeyesnoyes
observernonoyes
no predictionnonono — state arrives from the authority with a declared interpolation window

Turn-based games, strategies and most mobile titles want no prediction at all; declaring it tells the client to show state as it is rather than guess.

The module owns no movement model, no reaction table, no geometry and no history window of its own. Those belong to Locomotion, Collision, Map and Entity respectively. Prediction applies them earlier or reads them backwards; it never declares a second copy.

Prediction declared on Tank: predicted fields, Hp authoritative-only, an 8-forward / 64-rewind window
[Entity("tank")]
[Prediction(ForwardTicks = 8, MaxRewindTicks = 64)]
public class Tank
{
    [Sync, Predicted] public Vector3 Position;                        // rolls back and replays
    [Sync, Predicted] public Vector3 Velocity;

    [Stat(Max = 100), AuthoritativeOnly] public Stat Hp;   // never predicted
}
@Entity('tank')
@Prediction({ forwardTicks: 8, maxRewindTicks: 64 })
export class Tank {
  @Sync() @Predicted() position!: Vector3;                    // rolls back and replays
  @Sync() @Predicted() velocity!: Vector3;

  @Stat({ max: 100 }) @AuthoritativeOnly() hp: Stat;   // never predicted
}
@entity("tank")
@prediction(forward_ticks=8, max_rewind_ticks=64)
class Tank:
    position: Vector3 = sync(predicted=True)                  # rolls back and replays
    velocity: Vector3 = sync(predicted=True)

    hp = stat(max=100, authoritative_only=True)   # never predicted
Coming soon — Go

Attribute-style declaration in Go is still open (O-1) — the samples land when the carrier is decided.

UCLASS(PSEntity = "tank", PSPrediction = (ForwardTicks = 8, MaxRewindTicks = 64))
class UTank : public UObject
{
    GENERATED_BODY()

    UPROPERTY(PSSync = (Predicted = "true")) FVector3f Position;   // rolls back and replays
    UPROPERTY(PSSync = (Predicted = "true")) FVector3f Velocity;

    UPROPERTY(PSStat = (Max = 100, AuthoritativeOnly = "true")) FPSStat Hp;   // never predicted
};
// declarations compile into the same pushed model — playserv push from the UE project or CI
Unity C# is the same C# API here — the same attributes compile in Unity, on the 2021.3 baseline — no C# 12 syntax here
[Entity("tank")]
[Prediction(ForwardTicks = 8, MaxRewindTicks = 64)]
public class Tank
{
    [Sync, Predicted] public Vector3 Position;                        // rolls back and replays
    [Sync, Predicted] public Vector3 Velocity;

    [Stat(Max = 100), AuthoritativeOnly] public Stat Hp;   // never predicted
}

Lag-compensated resolution answers "where was everyone when this shot was fired":

Hit validation in one hook: ResolveAt(shooterViewTick) rewinds hitboxes to the shooter's view
[After(Projectiles.HitReported)]
public static void Validate(HitReport hit) =>
    hit.ResolveAt(hit.ShooterViewTick);    // rewinds hitboxes, sub-tick interpolated
export const validate = after(Projectiles.hitReported, (hit: HitReport) =>
  hit.resolveAt(hit.shooterViewTick));    // rewinds hitboxes, sub-tick interpolated
@after(projectiles.hit_reported)
def validate(hit: HitReport):
    hit.resolve_at(hit.shooter_view_tick)    # rewinds hitboxes, sub-tick interpolated
Coming soon — Go

Attribute-style declaration in Go is still open (O-1) — the samples land when the carrier is decided.

Runs off the engine

A hook is a cloud function: it executes on the platform, not in the engine. Write it in C#, TypeScript or Python — Unreal subscribes to the resulting events. Engine-authored functions are planned: Blueprint graphs, and C++ (translated to a platform-validated script target). Both are analyzed and pushed like any declaration; execution stays on the platform.

Runs off the engine

A hook is a cloud function: it executes on the platform, not in the engine. Write it in C#, TypeScript or Python — Unity subscribes to the resulting events.

Trajectory forecast, shared by server and client (aim arcs, landing markers). The forecast is this module's operation, extrapolated against the map's obstacle set, so both sides draw the same arc from the same inputs:

One Trajectory call: a collision-aware forecast the server and the aim preview share
var arc = room.Prediction.Trajectory(from, velocity, steps: 30);   // collision-aware
const arc = room.prediction.trajectory(from, velocity, { steps: 30 });   // collision-aware
arc = room.prediction.trajectory(origin, velocity, steps=30)   # collision-aware
Coming soon — Go

Attribute-style declaration in Go is still open (O-1) — the samples land when the carrier is decided.

// collision-aware: the arc the platform itself would walk
Room->Prediction->Trajectories->Get(LaunchPosition, LaunchVelocity, /*Steps*/ 30,
    TPSOnResult<FPSTrajectory>::CreateWeakLambda(this, [this](const TPSResult<FPSTrajectory>& Result)
    {
        if (!Result.HasValue()) { return; }
        DrawArc(Result.Value());
    }));
// co_await support ships later: a built-in SDK coroutine wrapper that respects Unreal's GC and threading.
Unity C# is the same C# API here — runs as-is in Unity against the generated types
var arc = room.Prediction.Trajectory(from, velocity, steps: 30);   // collision-aware

The model

Predictability is declared on an aspect — and an aspect changed only by the authority, by rules the client does not have, may not be marked predictable: that is a validation failure at deploy, not a runtime surprise.

What is declared.

DeclaresWhat it is
predictable aspectswhich ones the client may step ahead of the authority
divergence thresholdbelow it a correction is smoothed; above it the authority's state is accepted as it comes. Declared, and managed — not a designer's daily knob
display mode for remote entitiesinterpolation between arrived states, or extrapolation
interpolation delayhow far the display of others lags, declared rather than tuned by feel
extrapolation windowbeyond it an entity is marked stale and extrapolation ceases
compensation windowhow far back a rewind may reach, and it is managed

What is rewound, and what deliberately is not.

What it is
rewoundthe positions and orientations of targets, and the geometry of dynamic obstacles where the type declares them historical
not rewoundlife state — the dead do not revive in order to be shot — and ownership, score and inventory
the rule behind the splitthe decision is taken in the past; the effect is applied in the present
AlwaysWhat it is
authoritative state names the input it sawit carries the number of the last input applied, which is what makes reconciliation exact rather than approximate
divergenceis observable: the client knows its prediction was corrected, instead of quietly drifting
a view timeis a claim, not a fact: the moment the actor says it saw. Beyond the window the platform refuses rather than extrapolating: silent extrapolation is a gift to a cheater, who need only send an older time
a rewind promises no reproducibility over floating pointthe same constraint as everywhere else in the contract
one history ring, two consumersreconciliation and lag-compensated queries both read the entity's instant history track. Rewinding belongs here rather than to the modules being rewound: the ring restores the poses of the tick under dispute, and collision is then asked its ordinary overlap question about those poses — it keeps no history of its own and knows nothing of a "view tick"
client tick:  apply input locally (predict) → buffer it → send, tick-stamped
server tick:  step the same movement model → authoritative state → delta out
client recv:  authoritative state for tick T → if divergence beyond tolerance:
              rewind to T → replay buffered inputs T+1..now → smooth

Only declared-predicted fields ever roll back; tiny drift is smoothed, real divergence rewinds and replays.

Errors

  • A view time beyond the window is a conflict: send a current one.
  • A history request beyond the window is likewise a conflict.
  • Two declaration defects are caught at deploy: a compensation window larger than the history buffer, and an aspect marked predictable when the client has no rules to predict it by. Neither can reach a live match.
  • Two things are not errors, and both are observable. A full input buffer suspends prediction until confirmation rather than discarding inputs silently; and divergence beyond the threshold means the authority's state is accepted as it comes, which is the declared correction and not a fault.

Limits

The numbers arrive with the platform limits chapter; what happens at each ceiling is fixed now.

  • The compensation window: beyond it a refusal, never extrapolation.
  • The extrapolation window for others: the entity is marked stale and extrapolation stops.
  • The unconfirmed-input buffer: prediction is suspended until confirmation; inputs are never discarded silently.
  • The depth of history for a rewind: not less than the compensation window, checked at deploy.
  • The rate of actions carrying a view time: a rate limit with a deadline.
  • Simultaneously predicted entities per client: beyond the cap prediction is not performed, and that is declared degradation rather than a refusal.