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:
ResolveAtrebobina 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é
| Actor | Qué hace en esta página |
|---|---|
schema-author | declara qué campos se predicen y cuáles son solo autoritativos; define la ventana de predicción |
room-owner | resuelve impactos sobre un estado histórico; rebobina el mundo |
player | predice 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:
| Mecanismo | Qué hace | Dónde se ejecuta | Cuando falla |
|---|---|---|---|
| Predecir tu propio movimiento | aplica el modelo declarado a tu propia entrada sin esperar al servidor | el cliente | una corrección, reproducida y suavizada |
| Cómo mostrar a otros jugadores | dibuja las demás entidades entre los estados que van llegando | el cliente | un salto visible |
| Lag Compensation | rebobina los objetivos al momento que vio el tirador | el servidor | alguien muere injustamente |
Cuatro presets, y “no prediction” es uno de ellos.
| Preset | Predice lo tuyo | Compensa | Suaviza a los demás |
|---|---|---|---|
| shooter | sí | en una ventana de aproximadamente un segundo y medio | sí |
| arcade | sí | no | sí |
| observer | no | no | sí |
| no prediction | no | no | no — 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.
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 predictedTodaví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[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?”:
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 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 interpolatedTodavía no está decidido cómo declarar con atributos en Go (O-1). Los ejemplos llegarán cuando se elija la forma de escribirlo.
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.
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:
Trajectory: un pronóstico que tiene en cuenta las colisiones y que comparten el servidor y la vista previa de apuntadovar 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-awareTodaví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.var arc = room.Prediction.Trajectory(from, velocity, steps: 30); // collision-awareEl 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 declara | Qué es |
|---|---|
aspectos predecibles | cuáles puede avanzar el cliente antes que la autoridad |
umbral de divergencia | por 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 remotas | interpolación entre los estados que llegaron, o extrapolación |
retardo de interpolación | cuánto se atrasa la visualización de los demás; se declara, no se ajusta a ojo |
ventana de extrapolación | más allá de ella, la entidad se marca como obsoleta y la extrapolación se detiene |
ventana de compensación | hasta dónde puede llegar un rebobinado hacia atrás; es managed |
Qué se rebobina y qué no, a propósito.
| Qué es | |
|---|---|
se rebobina | las 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 rebobina | el 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ón | la decisión se toma en el pasado; el efecto se aplica en el presente |
| Siempre | Qué es |
|---|---|
el estado autoritativo indica qué entrada vio | lleva el número de la última entrada aplicada, y por eso la reconciliación es exacta y no aproximada |
la divergencia | es observable: el cliente sabe que su predicción se corrigió, en lugar de desviarse en silencio |
un tiempo de vista | es 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 flotante | la misma restricción que en el resto del contrato |
un búfer circular de historial, dos consumidores | la 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.