Lagback

From Minecraft Parkour Wiki
This page is a translated version of the page Lagback and the translation is 100% complete.
Other languages:

Lagbackとは、プレイヤーが以前の位置にテレポートで戻される現象である。悪用することで、壁にめり込んだりBlipと同様により高くジャンプしたりできるが、マルチプレイヤーサーバーでは実行が禁止されている場合がある。このページでは1.8のシングルプレイヤーに焦点を当てているが、ほとんどの情報はマルチプレイヤー及び1.9以降にも関連している。

lagbackのトリガーとして知られている動作は以下の通り:

  • ブロックに埋まる。
  • ボートにぶつかる。
  • クモの巣の中で素早く移動する。
  • 充分な速度で、スニークしながら着地する。
  • stepping発動可能なブロックを、超高速(移動速度上昇100など)で通り抜ける。
  • 壁に向かって素早く移動する。


一般的な説明

lagbackを引き起こす方法は、大きく分けて2つある:

  • 当たり判定と重なる(ブロックに埋まる、ボートにぶつかるなど)。
  • 不正に」移動する。

1つ目の方法については、説明の必要はないだろう。

2つ目の方法については、まずプレイヤーの動きがサーバー側でどのように処理されているかを理解する必要がある(シングルプレイヤーであっても、ゲームは統合サーバー上で動作している)。
毎tick、クライアントはプレイヤーの次の位置を計算し、サーバーにパケットを送信する。サーバーは、プレイヤーの動きを前回の位置から再計算することで検証する。この時、理論上は計算結果は同じ位置になるはずである。しかし、サーバーはプレイヤーの移動による副作用を元に戻さず計算するため、プレイヤーの予想される位置と実際の位置にズレが生じることがある。2つの計算結果のズレは、水平方向の距離が0.25mを超えていると重大と判断され、その場合、プレイヤーは前回の位置に戻される。

検証の過程で元に戻されない副作用には以下が含まれる:

  • 着地時にonGroundフラグがtrueに設定される(スニークの挙動が変わる)。
  • クモの巣に接触時にinWebフラグがfalseに設定される。
  • 壁や天井に衝突時に各種collidedフラグがtrueに設定される。

つまり、プレイヤーの動きは1tickに2回計算されるが、1回目の計算による副作用が2回目の計算結果に影響を与える可能性がある。プレイヤーの動きが十分に速ければ、サーバーはプレイヤーが不正に移動したと判断し、lagbackを発生させる。


具体的なトリガーの説明

当たり判定と重なる

プレイヤーが当たり判定の中に入ることは想定されていない。しかし、サーバーが当たり判定の各方向に0.0625bの猶予を設けているため、プレイヤーのバウンディングボックスはブロックの「外殻」と重なりながらでも通常通り動くことができる。それ以上内側に動くと、lagbackが引き起こされる。

既に完全に当たり判定の内側にいる場合は、動いても戻されることはない。


クモの巣に接触する(WIP)

クモの巣に接触する(tickの終わりに検知される)と、プレイヤーのinWebフラグがtrueに設定される。プレイヤーが移動してinWebtrueになると、すぐにfalseに設定され、プレイヤーの速度にクモの巣の効果が適用される。これにより元の計算と再計算の間にズレが生じ、条件速度が満たされればlagbackが引き起こされる。

高所から落下しスニークしながら着地する

この種類は、以下の必要条件のもとで着地・スニークすることで実現される:

  • 水平方向に0.25m/tより高い速度で移動している。
  • 着地1tick前に地面の最低1ブロック上にいる(1.11+では0.6b)。

スニークのタイミングは正確である必要はないが、早すぎると水平方向の速度が著しく低下する。

垂直方向の速度を-1m/t以上にするために必要なジャンプの高さは-7.3125b。この方法を試すための簡単なセットアップは-9.5b。以下がその例:


スニークグリッチ

スニークグリッチ(もしくはシフトグリッチ)はブロック縁に着地しながらスニークすると発生し、スニークしているにもかかわらず落下してしまうというもの(1.16.2で修正済み)。

スニークグリッチを行う際、サーバーが想定するプレイヤーの動きと実際の動きが異なる。以下がその理由:

  • 最初の計算中、プレイヤーはスニークしているが「地面にいる」わけではないため、ブロックの縁を越えて動く。しかし着地はするため、onGround = trueに設定される。
  • 再計算中、プレイヤーは実際に着地する前に「地面にいる」とみなされ、スニークの挙動が変わる: 地面にいないにもかかわらず、「縁から落ちる」ことができなくなる。

0.25m/t以上の水平方向の速度で、適切な位置でスニークグリッチを行うことで、lagbackを引き起こし、前の位置にテレポートさせられた後にジャンプすることができる。結果として、空中からジャンプを開始することができるという意味で、blip-upのようなジャンプになる。以下はその例:

ジャンプの高さによっては効果のないものもある。現在調査中。


ソースコード

シングルプレイヤーでは、lagbackはソースコードのこの部分によって引き起こされる(大幅に簡略化済み、飛行・睡眠・テレポート・リスポーン・エンティティへの騎乗などの例外的なケースは無視している):
//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();}

    //プレイヤーが当たり判定の内部から動き始めているかをチェック: その場合、lagbackは発生しない
    boolean noCollisionInside = worldserver.getCollidingBoundingBoxes(this.playerEntity, this.playerEntity.getEntityBoundingBox().contract(0.0625, 0.0625, 0.0625).isEmpty();

    //動きの検証を開始
    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();
    
    //予想位置と実際の位置の誤差を算出
    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());

    //プレイヤーを実際の位置に動かし、衝突判定をチェックする
    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();

    //エラーを検出する: プレイヤーが不正に、もしくは当たり判定の内部を移動した
    if (noCollisionInside && (movedWrongly || !next_noCollisionInside))
    {
        //プレイヤーを前の位置に戻す
        this.setPlayerLocation(this.lastPosX, this.lastPosY, this.lastPosZ, yaw, pitch);
        return;
    }

    this.playerEntity.handleFalling(this.playerEntity.posY - posY_original, packetIn.isOnGround());
}
なお、落下ダメージの処理はlagback時には行われない。このため、最終的な落下時に予想外に高い落下ダメージを受けることがある。
1.15(ボートの落下ダメージバグの修正)以降は、ジャンプによりプレイヤーの落下距離がリセットされるため、lagbackと組み合わせ悪用することで、高所からの落下を乗り切ることが可能。