Aller au contenu principal
Le jeu en direct

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 : ResolveAt ramè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

ActeurCe qu’il fait sur cette page
schema-authordéclare les champs prédits et ceux qui ne relèvent que de l’autorité ; fixe la fenêtre de prédiction
room-ownerrésout les tirs sur un état passé ; rembobine le monde
playerpré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écanismeCe qu’il faitS’exécute surQuand il se trompe
Prédire son propre mouvementapplique le modèle déclaré à vos propres entrées sans attendre le serveurle clientune correction, rejouée puis lissée
Afficher les autres joueursdessine les autres entités entre les états qui arriventle clientune saccade visible
Lag Compensationrembobine les cibles jusqu’à l’instant qu’a vu le tireurle serveurquelqu’un meurt injustement

Quatre presets, et « no prediction » en fait partie.

PresetPrédit votre propre mouvementCompenseLisse les autres
shooterouidans une fenêtre d’environ une seconde et demieoui
arcadeouinonoui
observernonnonoui
no predictionnonnonnon — 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.

Prédiction déclarée sur 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 predicted
Bientôt — Go

La 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
Unity C# is the same C# API here — les mêmes attributs compilent dans Unity, sur la base 2021.3 — aucune syntaxe C# 12 ici
[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 » :

Validation des tirs dans un seul hook : 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 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
Bientôt — Go

La déclaration par attributs en Go n’est pas encore tranchée (O-1). Les exemples arriveront quand la forme d’écriture sera choisie.

S’exécute hors du moteur

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.

S’exécute hors du moteur

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.

Un seul appel Trajectory : une prévision qui tient compte des collisions, partagée par le serveur et l’aperçu de visée
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
Bientôt — Go

La 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.
Unity C# is the same C# API here — fonctionne tel quel dans Unity avec les types générés
var arc = room.Prediction.Trajectory(from, velocity, steps: 30);   // collision-aware

Le 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éclareCe que c’est
aspects prédictiblesceux que le client peut faire avancer avant l’autorité
seuil de divergenceen 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 distantesinterpolation entre les états reçus, ou extrapolation
délai d'interpolationle retard d’affichage des autres, déclaré plutôt qu’ajusté au feeling
fenêtre d'extrapolationau-delà, une entité est marquée périmée et l’extrapolation s’arrête
fenêtre de compensationjusqu’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 partagela décision se prend dans le passé ; l’effet s’applique dans le présent
ToujoursCe que c’est
l'état faisant autorité nomme l'entrée qu'il a vueil 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
divergenceelle est observable : le client sait que sa prédiction a été corrigée, au lieu de dériver sans rien dire
un instant de vuec’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 flottantela même contrainte que partout ailleurs dans le contrat
un seul anneau d'historique, deux consommateursla 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.