Lagback: Difference between revisions

From Minecraft Parkour Wiki
Content added Content deleted
m (correction)
(complete explanation)
Line 1: Line 1:
A lagback is an occurrence of the player getting teleported back to a previous position. It can be abused to clip inside walls, or jump higher in a similar fashion as [[Blip|Blips]], though this practice may be a bannable offense on multiplayer servers. In this article, we focus on singleplayer 1.8.
A lagback is an occurrence of the player getting teleported back to a previous position. It can be abused to clip inside walls, or jump higher in a similar fashion as [[Blip|Blips]], though this practice may be a bannable offense on multiplayer servers. In this article, we focus on singleplayer 1.8.


== Known causes for lagback ==


Here are the known triggers for lagbacks:
* Being stuck in a block.
* Colliding with a boat.
* Colliding with a boat.
* Falling and sneaking at the last moment.
* Falling and sneaking at the last moment.
* Moving inside a cobweb too quickly.
* Moving inside a cobweb too quickly.
* Moving through a [[Stepping|steppable]] block with an obscene amount of speed (Speed 100, for example).
* Moving through a [[Stepping|steppable]] block with an obscene amount of speed (Speed 100, for example).
* Moving close to a wall.
* Moving quickly against a wall.


== Explanation ==
== Explanation ==
There are two main ways to trigger a lagback:
In singleplayer, the lagback itself is caused by this portion of code (in class NetHandlerPlayServer):<syntaxhighlight lang="java" line="1">
* Intersecting with a collision box (stuck in a block, or colliding with a boat).
* Moving "''wrongly''".
The first method is pretty self-explanatory. As for the second method, one must first understand how player movement is processed server-side (even in singleplayer, the game runs on an integrated server). Each tick, the client calculates their next position, and sends a packet to the server. The server attempts to validate the player's movement by recalculating it from their last position, which should theoretically result in the same position. However, the server forgets to undo the side effects of the player's original movement, which may cause a discrepancy between the player's expected and actual positions; the discrepancy is considered significant if the horizontal distance between the two is strictly more than 0.25m, in which case the player is lagged back to their previous position.

Side effects that aren't undone prior to the validation process include:

* The <code>onGround</code> flag being set to <code>true</code> when landing.
* The inWeb flag being set to <code>false</code> when colliding with a [[cobweb]].
* The various <code>collided</code> flags being set to <code>true</code> when colliding with a wall or ceiling.

In short, the player's movement is essentially calculated twice each tick, but the side effects of the first calculation may influence the result of the second. If the player moves fast enough, the server thinks the player moves wrongly, and triggers a lagback.

== Code ==
In singleplayer, lagbacks are caused by this section of code (heavily simplified, we ignore exceptional cases such as flying, sleeping, teleporting, respawning, mounting an entity...):<syntaxhighlight lang="java" line="1">
//in NetHandlerPlayServer.java
public void processPlayer(PacketPlayer packetIn)
public void processPlayer(PacketPlayer packetIn)
{
{
//heavily simplified, ignores exceptional cases (flying, sleeping, teleporting, , noclip, mounted, respawning...)
double posY_original = this.playerEntity.posY;
double posY_original = this.playerEntity.posY;
this.lastPosX = this.playerEntity.posX;
this.lastPosX = this.playerEntity.posX;
this.lastPosY = this.playerEntity.posY;
this.lastPosY = this.playerEntity.posY;
this.lastPosZ = this.playerEntity.posZ;
this.lastPosZ = this.playerEntity.posZ;
double posX = packetIn.getPositionX();
double next_posX = packetIn.getPositionX();
double posY = packetIn.getPositionY();
double next_posY = packetIn.getPositionY();
double posZ = packetIn.getPositionZ();
double next_posZ = packetIn.getPositionZ();
float yaw = this.playerEntity.rotationYaw;
float yaw = this.playerEntity.rotationYaw;
float pitch = this.playerEntity.rotationPitch;
float pitch = this.playerEntity.rotationPitch;
if (packetIn.getRotating()) {yaw = packetIn.getYaw(); pitch = packetIn.getPitch();}
if (packetIn.getRotating()) {yaw = packetIn.getYaw(); pitch = packetIn.getPitch();}


//checks whether the player is starting from inside a collision box: in this case, there is no lagback
this.playerEntity.onUpdateEntity();
this.playerEntity.setPositionAndRotation(this.lastPosX, this.lastPosY, this.lastPosZ, yaw, pitch);
double deltaX = posX - this.playerEntity.posX;
double deltaY = posY - this.playerEntity.posY;
double deltaZ = posZ - this.playerEntity.posZ;

boolean noCollisionInside = worldserver.getCollidingBoundingBoxes(this.playerEntity, this.playerEntity.getEntityBoundingBox().contract(0.0625, 0.0625, 0.0625).isEmpty();
boolean noCollisionInside = worldserver.getCollidingBoundingBoxes(this.playerEntity, this.playerEntity.getEntityBoundingBox().contract(0.0625, 0.0625, 0.0625).isEmpty();


//begin movement verification
if (this.playerEntity.onGround && !packetIn.isOnGround() && deltaY > 0.0D)
this.playerEntity.jump();
double deltaX = next_posX - this.playerEntity.posX;
double deltaY = next_posY - this.playerEntity.posY;
double deltaZ = next_posZ - this.playerEntity.posZ;


if (this.playerEntity.onGround && !packetIn.isOnGround() && deltaY > 0.0)
this.playerEntity.jump();
this.playerEntity.moveEntity(deltaX, deltaY, deltaZ);
this.playerEntity.moveEntity(deltaX, deltaY, deltaZ);
this.playerEntity.onGround = packetIn.isOnGround();
this.playerEntity.onGround = packetIn.isOnGround();
//calculates error between the expected and actual positions
double errorX = posX - this.playerEntity.posX;
double errorZ = posZ - this.playerEntity.posZ;
double errorX = next_posX - this.playerEntity.posX;
double errorZ = next_posZ - this.playerEntity.posZ;
double error_squared = errorX * errorX + errorZ * errorZ;
double error_squared = errorX * errorX + errorZ * errorZ;
boolean movedWrongly = (error_squared > 0.0625 && !this.playerEntity.theItemInWorldManager.isCreative());
boolean movedWrongly = (error_squared > 0.0625 && !this.playerEntity.theItemInWorldManager.isCreative());


//sets the player to their actual position, and checks for collisions
this.playerEntity.setPositionAndRotation(posX, posY, posZ, yaw, pitch);
this.playerEntity.setPositionAndRotation(next_posX, next_posY, next_posZ, yaw, pitch);
boolean noCollisionInside_updated = worldserver.getCollidingBoundingBoxes(this.playerEntity, this.playerEntity.getEntityBoundingBox().contract(0.0625, 0.0625, 0.0625).isEmpty();
boolean next_noCollisionInside = worldserver.getCollidingBoundingBoxes(this.playerEntity, this.playerEntity.getEntityBoundingBox().contract(0.0625, 0.0625, 0.0625).isEmpty();


//detects a desync - "lagbacks" the player to their previous position
//detects an error: player moved wrongly, or moved inside a collision box
if (noCollisionInside && (movedWrongly || !noCollisionInside_updated))
if (noCollisionInside && (movedWrongly || !next_noCollisionInside))
{
{
//lags the player back to their previous position
this.setPlayerLocation(this.lastPosX, this.lastPosY, this.lastPosZ, yaw, pitch);
this.setPlayerLocation(this.lastPosX, this.lastPosY, this.lastPosZ, yaw, pitch);
return;
return;
}
}


this.playerEntity.onGround = packetIn.isOnGround();
this.playerEntity.handleFalling(this.playerEntity.posY - posY_original, packetIn.isOnGround());
this.playerEntity.handleFalling(this.playerEntity.posY - posY_original, packetIn.isOnGround());
}
}
</syntaxhighlight>Note that the handling of fall damage is not processed if the player gets lagbacked. This may cause the player to suffer unexpectedly high fall damage when they eventually land.
</syntaxhighlight>There are essentially two ways to trigger a lagback:

* Moving "''wrongly''".
* Intersecting with a collision box (only seems to happen when colliding with boats, but this can also make the player move ''wrongly'').

The next section shall be dedicated to detailing how the player can move ''wrongly''.

== Moving wrongly ==
The player is considered to have moved ''wrongly'' if their expected motion differs significantly from their actual motion (difference of at least 0.25m). The problem lies in the fact that the player is moved twice with the same velocity during the process, which should give the same motion, but some actions performed in the first execution influence the second execution (notably sneaking and moving in a cobweb).

Revision as of 10:06, 6 September 2022

A lagback is an occurrence of the player getting teleported back to a previous position. It can be abused to clip inside walls, or jump higher in a similar fashion as Blips, though this practice may be a bannable offense on multiplayer servers. In this article, we focus on singleplayer 1.8.


Here are the known triggers for lagbacks:

  • Being stuck in a block.
  • Colliding with a boat.
  • Falling and sneaking at the last moment.
  • Moving inside a cobweb too quickly.
  • Moving through a steppable block with an obscene amount of speed (Speed 100, for example).
  • Moving quickly against a wall.

Explanation

There are two main ways to trigger a lagback:

  • Intersecting with a collision box (stuck in a block, or colliding with a boat).
  • Moving "wrongly".

The first method is pretty self-explanatory. As for the second method, one must first understand how player movement is processed server-side (even in singleplayer, the game runs on an integrated server). Each tick, the client calculates their next position, and sends a packet to the server. The server attempts to validate the player's movement by recalculating it from their last position, which should theoretically result in the same position. However, the server forgets to undo the side effects of the player's original movement, which may cause a discrepancy between the player's expected and actual positions; the discrepancy is considered significant if the horizontal distance between the two is strictly more than 0.25m, in which case the player is lagged back to their previous position.

Side effects that aren't undone prior to the validation process include:

  • The onGround flag being set to true when landing.
  • The inWeb flag being set to false when colliding with a cobweb.
  • The various collided flags being set to true when colliding with a wall or ceiling.

In short, the player's movement is essentially calculated twice each tick, but the side effects of the first calculation may influence the result of the second. If the player moves fast enough, the server thinks the player moves wrongly, and triggers a lagback.

Code

In singleplayer, lagbacks are caused by this section of code (heavily simplified, we ignore exceptional cases such as flying, sleeping, teleporting, respawning, mounting an entity...):

//in NetHandlerPlayServer.java
public void processPlayer(PacketPlayer packetIn)
{
    double posY_original = this.playerEntity.posY;
    this.lastPosX = this.playerEntity.posX;
    this.lastPosY = this.playerEntity.posY;
    this.lastPosZ = this.playerEntity.posZ;
    double next_posX = packetIn.getPositionX();
    double next_posY = packetIn.getPositionY();
    double next_posZ = packetIn.getPositionZ();
    float yaw = this.playerEntity.rotationYaw;
    float pitch = this.playerEntity.rotationPitch;
    if (packetIn.getRotating()) {yaw = packetIn.getYaw(); pitch = packetIn.getPitch();}

    //checks whether the player is starting from inside a collision box: in this case, there is no lagback
    boolean noCollisionInside = worldserver.getCollidingBoundingBoxes(this.playerEntity, this.playerEntity.getEntityBoundingBox().contract(0.0625, 0.0625, 0.0625).isEmpty();

    //begin movement verification
    double deltaX = next_posX - this.playerEntity.posX;
    double deltaY = next_posY - this.playerEntity.posY;
    double deltaZ = next_posZ - this.playerEntity.posZ;

    if (this.playerEntity.onGround && !packetIn.isOnGround() && deltaY > 0.0)
        this.playerEntity.jump();
    this.playerEntity.moveEntity(deltaX, deltaY, deltaZ);
    this.playerEntity.onGround = packetIn.isOnGround();
    
    //calculates error between the expected and actual positions
    double errorX = next_posX - this.playerEntity.posX;
    double errorZ = next_posZ - this.playerEntity.posZ;
    double error_squared = errorX * errorX + errorZ * errorZ;
    boolean movedWrongly = (error_squared > 0.0625 && !this.playerEntity.theItemInWorldManager.isCreative());

    //sets the player to their actual position, and checks for collisions
    this.playerEntity.setPositionAndRotation(next_posX, next_posY, next_posZ, yaw, pitch);
    boolean next_noCollisionInside = worldserver.getCollidingBoundingBoxes(this.playerEntity, this.playerEntity.getEntityBoundingBox().contract(0.0625, 0.0625, 0.0625).isEmpty();

    //detects an error: player moved wrongly, or moved inside a collision box
    if (noCollisionInside && (movedWrongly || !next_noCollisionInside))
    {
        //lags the player back to their previous position
        this.setPlayerLocation(this.lastPosX, this.lastPosY, this.lastPosZ, yaw, pitch);
        return;
    }

    this.playerEntity.handleFalling(this.playerEntity.posY - posY_original, packetIn.isOnGround());
}

Note that the handling of fall damage is not processed if the player gets lagbacked. This may cause the player to suffer unexpectedly high fall damage when they eventually land.