Piston pull float alignment (Paper 1.21.5)

Description

When a piston retracts and a tnt (or gravel or entity of the same size) gets pulled inside or is inside it gets float aligned against the block in front

Reason

The retracting source-piston path intentionally applies an extra +0.01 displacement multiple times, which is enough to place entities slightly into the block in front of the piston. That is the float-alignment state you described for (Piston Air Block).

Key code evidence

Path: net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java (33-36)

private static final int TICKS_TO_EXTEND = 2;
private static final double PUSH_OFFSET = 0.01;
public static final double TICK_MOVEMENT = 0.51;

Path: net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java (328-331)

float f = blockEntity.progress + 0.5F;
moveCollidedEntities(level, pos, f, blockEntity);
moveStuckEntities(level, pos, f, blockEntity);
blockEntity.progress = f;

Path: net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java (114-117, 175-179)

Direction movementDirection = piston.getMovementDirection();
double d = partialTick - piston.progress;
...
d4 = Math.min(d4, d) + 0.01;
moveEntityByPiston(movementDirection, entity, d4, movementDirection);
if (!piston.extending && piston.isSourcePiston) {
    fixEntityWithinPistonBase(pos, entity, movementDirection, d);
}

d is the current movement slice (normally 0.5 in this tick logic), then another +0.01 is applied before moving the entity.

Path: net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java (251-260)

if (boundingBox.intersects(aabb)) {
    Direction opposite = dir.getOpposite();
    double d = getMovement(aabb, opposite, boundingBox) + 0.01;
    double d1 = getMovement(aabb, opposite, boundingBox.intersect(aabb)) + 0.01;
    if (Math.abs(d - d1) < 0.01) {
        d = Math.min(d, progress) + 0.01;
        moveEntityByPiston(dir, entity, d, opposite);
    }
}

This is the retract-only "fix entity within piston base" path. It explicitly pushes the entity toward the piston front (opposite) and again adds +0.01.

Path: net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java (220-222)

public Direction getMovementDirection() {
    return this.extending ? this.direction : this.direction.getOpposite();
}

For retracting pistons, movementDirection is opposite the piston facing. fixEntityWithinPistonBase then uses dir.getOpposite() to push toward the facing/front side.

Path: net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java (186-192, 369-372)

NOCLIP.set(noClipDirection);
entity.move(MoverType.PISTON, new Vec3(...));
...
Direction direction = NOCLIP.get();
if (this.progress < 1.0 && direction == this.getMovementDirection()) {
    return collisionShape;
}

The move is done in piston move mode with thread-local noclip shaping for moving-piston collision behavior during that move.

Why the setup produces float alignment

  1. Entity starts intersecting/overlapping the retracting piston base volume.
  2. Retract tick computes push slice (0.5) and applies +0.01 bias.
  3. Retract source-piston fix path can apply another forward-biased correction that also includes +0.01.
  4. Net result: the entity is placed slightly into the front block side instead of perfectly flush.

Root cause statement

The float alignment is not random rounding noise; it is created by explicit piston displacement biases (+0.01) in both collided-entity handling and retracting source-piston base correction. Those biases can place entities in a persistent slight-overlap state at the front block boundary.