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:
ResolveAtrewinds 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
| Actor | On this page |
|---|---|
schema-author | declares predicted vs authoritative-only fields; sets prediction window |
room-owner | resolves hits at a historical state; rewinds the world |
player | predicts 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:
| Mechanism | What it does | Runs on | When it is wrong |
|---|---|---|---|
| Predicting your own motion | applies the declared model to your own input without waiting for the server | the client | a correction, replayed and smoothed |
| Showing other players | draws other entities between the states that arrive | the client | a visible jerk |
| Lag compensation | rewinds targets to the moment the shooter saw | the server | somebody dies unfairly |
Four presets, and "no prediction" is one of them.
| Preset | Predicts your own | Compensates | Smooths others |
|---|---|---|---|
| shooter | yes | in a window of roughly a second and a half | yes |
| arcade | yes | no | yes |
| observer | no | no | yes |
| no prediction | no | no | no — 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.
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 predictedAttribute-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[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":
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 interpolatedexport 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 interpolatedAttribute-style declaration in Go is still open (O-1) — the samples land when the carrier is decided.
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.
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:
Trajectory call: a collision-aware forecast the server and the aim preview sharevar arc = room.Prediction.Trajectory(from, velocity, steps: 30); // collision-awareconst arc = room.prediction.trajectory(from, velocity, { steps: 30 }); // collision-awarearc = room.prediction.trajectory(origin, velocity, steps=30) # collision-awareAttribute-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.var arc = room.Prediction.Trajectory(from, velocity, steps: 30); // collision-awareThe 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.
| Declares | What it is |
|---|---|
predictable aspects | which ones the client may step ahead of the authority |
divergence threshold | below 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 entities | interpolation between arrived states, or extrapolation |
interpolation delay | how far the display of others lags, declared rather than tuned by feel |
extrapolation window | beyond it an entity is marked stale and extrapolation ceases |
compensation window | how far back a rewind may reach, and it is managed |
What is rewound, and what deliberately is not.
| What it is | |
|---|---|
rewound | the positions and orientations of targets, and the geometry of dynamic obstacles where the type declares them historical |
not rewound | life state — the dead do not revive in order to be shot — and ownership, score and inventory |
the rule behind the split | the decision is taken in the past; the effect is applied in the present |
| Always | What it is |
|---|---|
authoritative state names the input it saw | it carries the number of the last input applied, which is what makes reconciliation exact rather than approximate |
divergence | is observable: the client knows its prediction was corrected, instead of quietly drifting |
a view time | is 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 point | the same constraint as everywhere else in the contract |
one history ring, two consumers | reconciliation 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.