Saltar al contenido principal
El juego en vivo

Esta página aún no está traducida: se muestra la versión en inglés.

Prediction & Lag Compensation

Predicción hacia adelante y compensación hacia atrás, sobre datos que llevan el momento real de cada evento. El cliente se siente instantáneo, el servidor sigue teniendo la razón y los impactos se juzgan en la línea de tiempo del tirador.

Cuándo usarlo

  • La entrada tiene que sentirse instantánea aun con latencia, mientras el servidor mantiene la autoridad: predices hacia adelante y reconcilias cuando hay divergencia.
  • Los impactos se tienen que juzgar en la línea de tiempo del tirador: ResolveAt rebobina las hitboxes al tick de vista informado.
  • Los arcos de apuntado y los marcadores de aterrizaje tienen que coincidir con el resultado: cliente y servidor pronostican la misma Trajectory.
  • Los campos críticos para el juego nunca deben hacer rollback: declara qué se predice y qué espera al servidor.
  • Hay que ajustar el rubber banding: ventanas y tolerancias por entidad, y telemetría de predicciones fallidas (misprediction).
  • No lo uses si la latencia no molesta. Los juegos por turnos o lentos funcionan bien con los deltas de Data & Subscriptions.

Quién hace qué

ActorQué hace en esta página
schema-authordeclara qué campos se predicen y cuáles son solo autoritativos; define la ventana de predicción
room-ownerresuelve impactos sobre un estado histórico; rebobina el mundo
playerpredice y reconcilia el movimiento; se suscribe a las correcciones

A qué salas se aplica

Este módulo se ejecuta donde la plataforma avanza la simulación: en las salas (room) declaradas con Host = "Backend". Si tu propio servidor de juego es dueño de la simulación (PlayServ como metaservidor), el movimiento, las colisiones y la predicción se quedan del lado del motor, y esta página describe la alternativa alojada en la plataforma, no un requisito. Consulta Quién ejecuta el tick.

En resumen

La palabra abarca tres mecanismos distintos, con autoridades y formas de fallar diferentes, y cada uno tiene su propio artículo:

MecanismoQué haceDónde se ejecutaCuando falla
Predecir tu propio movimientoaplica el modelo declarado a tu propia entrada sin esperar al servidorel clienteuna corrección, reproducida y suavizada
Cómo mostrar a otros jugadoresdibuja las demás entidades entre los estados que van llegandoel clienteun salto visible
Lag Compensationrebobina los objetivos al momento que vio el tiradorel servidoralguien muere injustamente

Cuatro presets, y “no prediction” es uno de ellos.

PresetPredice lo tuyoCompensaSuaviza a los demás
shootersíen una ventana de aproximadamente un segundo y mediosí
arcadesínosí
observernonosí
no predictionnonono — el estado llega de la autoridad con una ventana de interpolación declarada

Los juegos por turnos, los de estrategia y la mayoría de los títulos para celular no quieren ninguna predicción; al declararlo, el cliente muestra el estado tal como es en lugar de adivinarlo.

El módulo no tiene modelo de movimiento, tabla de reacciones, geometría ni ventana de historial propios. Eso les corresponde a Locomotion, Collision, Map y Entity, respectivamente. La predicción los aplica antes o los lee hacia atrás; nunca declara una segunda copia.

Predicción declarada en Tank: campos predichos, Hp solo autoritativo y una ventana de 8 ticks hacia adelante y 64 de rebobinado
[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
Próximamente — Go

Todavía no está decidido cómo declarar con atributos en Go (O-1). Los ejemplos llegarán cuando se elija la forma de escribirlo.

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 — los mismos atributos compilan en Unity con la versión base 2021.3 — aquí no hay sintaxis de C# 12
[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 resolución con compensación de lag responde “¿dónde estaba cada uno cuando se disparó este tiro?”:

Validación de impactos en un solo hook: ResolveAt(shooterViewTick) rebobina las hitboxes a lo que veía el tirador
[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
Próximamente — Go

Todavía no está decidido cómo declarar con atributos en Go (O-1). Los ejemplos llegarán cuando se elija la forma de escribirlo.

Se ejecuta fuera del motor

Un hook es una función en la nube: se ejecuta en la plataforma, no en el motor. Escríbelo en C#, TypeScript o Python — Unreal se suscribe a los eventos resultantes. Están planeadas funciones escritas desde el motor: gráficos de Blueprint y C++ (traducido a un destino de script validado por la plataforma). Ambos se analizan y se suben como cualquier declaración; la ejecución se queda en la plataforma.

Se ejecuta fuera del motor

Un hook es una función en la nube: se ejecuta en la plataforma, no en el motor. Escríbelo en C#, TypeScript o Python — Unity se suscribe a los eventos resultantes.

Pronóstico de trayectoria compartido por el servidor y el cliente (arcos de apuntado, marcadores de aterrizaje). El pronóstico es una operación de este módulo, extrapolada contra el conjunto de obstáculos del mapa, así que ambos lados dibujan el mismo arco a partir de las mismas entradas:

Una sola llamada a Trajectory: un pronóstico que tiene en cuenta las colisiones y que comparten el servidor y la vista previa de apuntado
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
Próximamente — Go

Todavía no está decidido cómo declarar con atributos en Go (O-1). Los ejemplos llegarán cuando se elija la forma de escribirlo.

// 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 — se ejecuta tal cual en Unity con los tipos generados
var arc = room.Prediction.Trajectory(from, velocity, steps: 30);   // collision-aware

El modelo

La predictibilidad se declara en un aspecto — y un aspecto que cambia solo la autoridad, con reglas que el cliente no tiene, no puede marcarse como predecible: eso es una falla de validación en el deploy, no una sorpresa en runtime.

Qué se declara.

Qué se declaraQué es
aspectos predeciblescuáles puede avanzar el cliente antes que la autoridad
umbral de divergenciapor debajo, la corrección se suaviza; por encima, se acepta el estado de la autoridad tal como llega. Se declara y es managed: no es una perilla que un diseñador toque a diario
modo de visualización de entidades remotasinterpolación entre los estados que llegaron, o extrapolación
retardo de interpolacióncuánto se atrasa la visualización de los demás; se declara, no se ajusta a ojo
ventana de extrapolaciónmás allá de ella, la entidad se marca como obsoleta y la extrapolación se detiene
ventana de compensaciónhasta dónde puede llegar un rebobinado hacia atrás; es managed

Qué se rebobina y qué no, a propósito.

Qué es
se rebobinalas posiciones y orientaciones de los objetivos, y la geometría de los obstáculos dinámicos cuando el tipo la declara histórica
no se rebobinael estado de vida — los muertos no reviven para que les disparen —, la propiedad, la puntuación y el inventario
la regla detrás de la divisiónla decisión se toma en el pasado; el efecto se aplica en el presente
SiempreQué es
el estado autoritativo indica qué entrada violleva el número de la última entrada aplicada, y por eso la reconciliación es exacta y no aproximada
la divergenciaes observable: el cliente sabe que su predicción se corrigió, en lugar de desviarse en silencio
un tiempo de vistaes una afirmación, no un hecho: el momento que el actor dice haber visto. Fuera de la ventana, la plataforma rechaza en lugar de extrapolar: extrapolar en silencio sería un regalo para un tramposo, que solo tendría que enviar un tiempo más antiguo
un rebobinado no promete reproducibilidad en punto flotantela misma restricción que en el resto del contrato
un búfer circular de historial, dos consumidoresla reconciliación y las consultas con compensación de lag leen la pista de historial instantáneo de la entidad. El rebobinado le corresponde a este módulo y no a los módulos que se rebobinan: el búfer circular restaura las poses del tick en disputa y luego se le hace a las colisiones su pregunta habitual de superposición sobre esas poses — no guardan historial propio ni saben nada de un “tick de vista”
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

Solo los campos declarados como predichos hacen rollback; una deriva mínima se suaviza, y una divergencia real rebobina y vuelve a reproducir las entradas.

Errores

  • Un tiempo de vista fuera de la ventana es un conflicto: envía uno actual.
  • Una solicitud de historial fuera de la ventana también es un conflicto.
  • Dos defectos de declaración se detectan en el deploy: una ventana de compensación más grande que el búfer de historial, y un aspecto marcado como predecible cuando el cliente no tiene reglas para predecirlo. Ninguno de los dos puede llegar a una partida en vivo.
  • Hay dos cosas que no son errores, y ambas son observables. Un búfer de entrada lleno suspende la predicción hasta la confirmación en lugar de descartar entradas en silencio; y una divergencia por encima del umbral significa que se acepta el estado de la autoridad tal como llega, que es la corrección declarada y no una falla.

Límites

Los números llegarán con el capítulo de límites de la plataforma; lo que pasa en cada tope ya está definido.

  • La ventana de compensación: más allá, un rechazo, nunca extrapolación.
  • La ventana de extrapolación para los demás: la entidad se marca como obsoleta y la extrapolación se detiene.
  • El búfer de entradas sin confirmar: la predicción se suspende hasta la confirmación; las entradas nunca se descartan en silencio.
  • La profundidad del historial para un rebobinado: no menor que la ventana de compensación; se comprueba en el deploy.
  • La frecuencia de acciones que llevan un tiempo de vista: un rate limit con plazo.
  • Entidades predichas a la vez por cliente: por encima del tope no se predice, y eso es una degradación declarada, no un rechazo.