Cette page n’est pas encore traduite : la version anglaise est affichée.
Prediction & Lag Compensation
Prédire vers l’avant, compenser vers l’arrière, sur des données qui portent l’instant réel de chaque événement. Le client réagit instantanément, le serveur garde raison, et les tirs sont jugés dans la chronologie du tireur.
Quand l’utiliser
- L’entrée doit paraître instantanée malgré la latence, tandis que le serveur continue de faire autorité : prédire vers l’avant, réconcilier en cas de divergence.
- Les tirs doivent être jugés dans la chronologie du tireur :
ResolveAtramène les hitboxes au tick de vue annoncé. - Les arcs de visée et les marqueurs d’impact doivent correspondre au résultat : le client et le serveur calculent la même
Trajectory. - Les champs critiques pour le jeu ne doivent jamais subir de rollback : déclarez ce qui se prédit et ce qui attend le serveur.
- L’effet élastique (rubber-banding) demande du réglage : fenêtres et tolérances par entité, télémétrie des erreurs de prédiction.
- Inutile quand la latence ne gêne pas. Les jeux au tour par tour ou lents tournent très bien avec de simples deltas de Data & Subscriptions.
Qui fait quoi
| Acteur | Ce qu’il fait sur cette page |
|---|---|
schema-author | déclare les champs prédits et ceux qui ne relèvent que de l’autorité ; fixe la fenêtre de prédiction |
room-owner | résout les tirs sur un état passé ; rembobine le monde |
player | prédit et réconcilie le mouvement ; s’abonne aux corrections |
Les salles concernées
Ce module fonctionne là où la plateforme fait avancer la simulation : dans les salles (room) déclarées Host = "Backend". Si c’est votre propre serveur de jeu qui possède la simulation (PlayServ en métaserveur), le mouvement, les collisions et la prédiction restent dans le moteur, et cette page décrit l’alternative hébergée par la plateforme, pas une obligation. Voir Qui exécute le tick.
En bref
Le mot recouvre trois mécanismes distincts, avec des autorités et des modes de défaillance différents, et chacun a son article :
| Mécanisme | Ce qu’il fait | S’exécute sur | Quand il se trompe |
|---|---|---|---|
| Prédire son propre mouvement | applique le modèle déclaré à vos propres entrées sans attendre le serveur | le client | une correction, rejouée puis lissée |
| Afficher les autres joueurs | dessine les autres entités entre les états qui arrivent | le client | une saccade visible |
| Lag Compensation | rembobine les cibles jusqu’à l’instant qu’a vu le tireur | le serveur | quelqu’un meurt injustement |
Quatre presets, et « no prediction » en fait partie.
| Preset | Prédit votre propre mouvement | Compense | Lisse les autres |
|---|---|---|---|
| shooter | oui | dans une fenêtre d’environ une seconde et demie | oui |
| arcade | oui | non | oui |
| observer | non | non | oui |
| no prediction | non | non | non — l’état arrive de l’autorité avec une fenêtre d’interpolation déclarée |
Les jeux au tour par tour, les jeux de stratégie et la plupart des titres mobiles n’ont besoin d’aucune prédiction ; la déclarer indique au client d’afficher l’état tel qu’il est au lieu de deviner.
Le module ne possède ni modèle de mouvement, ni table de réactions, ni géométrie, ni fenêtre d’historique à lui. Ils appartiennent respectivement à Locomotion, Collision, Map et Entity. La prédiction les applique plus tôt ou les lit à rebours ; elle n’en déclare jamais une seconde copie.
Tank : champs prédits, Hp réservé à l’autorité, une fenêtre de 8 ticks en avant et 64 en arrière[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 predictedLa déclaration par attributs en Go n’est pas encore tranchée (O-1). Les exemples arriveront quand la forme d’écriture sera choisie.
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
}La résolution avec compensation de latence (lag compensation) répond à la question « où était chacun au moment de ce tir » :
ResolveAt(shooterViewTick) ramène les hitboxes à ce que voyait le tireur[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 interpolatedLa déclaration par attributs en Go n’est pas encore tranchée (O-1). Les exemples arriveront quand la forme d’écriture sera choisie.
Un hook est une fonction cloud : il s’exécute sur la plateforme, pas dans le moteur. Écrivez-le en C#, TypeScript ou Python — Unreal s’abonne aux événements qui en résultent. Des fonctions écrites dans le moteur sont prévues : graphes Blueprint et C++ (traduit vers une cible de script validée par la plateforme). Les deux sont analysées et publiées comme n’importe quelle déclaration ; l’exécution reste sur la plateforme.
Un hook est une fonction cloud : il s’exécute sur la plateforme, pas dans le moteur. Écrivez-le en C#, TypeScript ou Python — Unity s’abonne aux événements qui en résultent.
La prévision de trajectoire est partagée par le serveur et le client (arcs de visée, marqueurs d’impact). C’est une opération de ce module, extrapolée contre les obstacles de la carte (map) : les deux côtés tracent le même arc à partir des mêmes entrées.
Trajectory : une prévision qui tient compte des collisions, partagée par le serveur et l’aperçu de viséevar 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-awareLa déclaration par attributs en Go n’est pas encore tranchée (O-1). Les exemples arriveront quand la forme d’écriture sera choisie.
// 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-awareLe modèle
La prédictibilité se déclare sur un aspect — et un aspect modifié uniquement par l’autorité, selon des règles que le client n’a pas, ne peut pas être marqué prédictible : c’est un échec de validation au déploiement, pas une surprise au runtime.
Ce qui se déclare.
| Déclare | Ce que c’est |
|---|---|
aspects prédictibles | ceux que le client peut faire avancer avant l’autorité |
seuil de divergence | en dessous, une correction est lissée ; au-dessus, l’état de l’autorité est accepté tel quel. Déclaré, et managed — ce n’est pas un réglage que le game designer touche tous les jours |
mode d'affichage des entités distantes | interpolation entre les états reçus, ou extrapolation |
délai d'interpolation | le retard d’affichage des autres, déclaré plutôt qu’ajusté au feeling |
fenêtre d'extrapolation | au-delà, une entité est marquée périmée et l’extrapolation s’arrête |
fenêtre de compensation | jusqu’où un rembobinage peut remonter ; elle est managed |
Ce qui est rembobiné, et ce qui ne l’est volontairement pas.
| Ce que c’est | |
|---|---|
rembobiné | les positions et orientations des cibles, et la géométrie des obstacles dynamiques quand le type la déclare historisée |
non rembobiné | l’état de vie — les morts ne ressuscitent pas pour se faire tirer dessus —, ainsi que la possession, le score et l’inventaire |
la règle derrière ce partage | la décision se prend dans le passé ; l’effet s’applique dans le présent |
| Toujours | Ce que c’est |
|---|---|
l'état faisant autorité nomme l'entrée qu'il a vue | il porte le numéro de la dernière entrée appliquée, et c’est ce qui rend la réconciliation exacte plutôt qu’approximative |
divergence | elle est observable : le client sait que sa prédiction a été corrigée, au lieu de dériver sans rien dire |
un instant de vue | c’est une affirmation, pas un fait : le moment que l’acteur dit avoir vu. Au-delà de la fenêtre, la plateforme refuse plutôt que d’extrapoler : une extrapolation silencieuse serait un cadeau au tricheur, qui n’aurait qu’à envoyer un instant plus ancien |
un rembobinage ne promet aucune reproductibilité en virgule flottante | la même contrainte que partout ailleurs dans le contrat |
un seul anneau d'historique, deux consommateurs | la réconciliation et les requêtes avec compensation de latence lisent toutes deux la piste d’historique instantané de l’entité. Le rembobinage se fait ici, et non dans les modules rembobinés : l’anneau restaure les poses du tick contesté, puis on pose aux collisions leur question de chevauchement habituelle sur ces poses — elles ne gardent aucun historique et ignorent tout d’un « tick de vue » |
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
Seuls les champs déclarés prédits subissent un rollback ; une petite dérive est lissée, une vraie divergence rembobine et rejoue.
Erreurs
- Un instant de vue hors de la fenêtre est un conflit : envoyez-en un actuel.
- Une requête d’historique hors de la fenêtre est, de même, un conflit.
- Deux défauts de déclaration sont détectés au déploiement : une fenêtre de compensation plus grande que le tampon d’historique, et un aspect marqué prédictible alors que le client n’a pas les règles pour le prédire. Aucun des deux ne peut atteindre un match en cours.
- Deux situations ne sont pas des erreurs, et toutes deux sont observables. Un tampon d’entrées plein suspend la prédiction jusqu’à confirmation au lieu d’écarter des entrées en silence ; et une divergence au-delà du seuil signifie que l’état de l’autorité est accepté tel quel : c’est la correction déclarée, pas une panne.
Limites
Les chiffres arriveront avec le chapitre sur les limites de la plateforme ; ce qui se passe à chaque plafond est déjà fixé.
- La fenêtre de compensation : au-delà, un refus, jamais une extrapolation.
- La fenêtre d’extrapolation pour les autres : l’entité est marquée périmée et l’extrapolation s’arrête.
- Le tampon des entrées non confirmées : la prédiction est suspendue jusqu’à confirmation ; les entrées ne sont jamais écartées en silence.
- La profondeur d’historique pour un rembobinage : au moins égale à la fenêtre de compensation, vérifiée au déploiement.
- La fréquence des actions portant un instant de vue : une limitation de débit, avec une échéance.
- Entités prédites simultanément par client : au-delà du plafond, la prédiction n’a pas lieu, et c’est une dégradation déclarée, pas un refus.