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
- Entity starts intersecting/overlapping the retracting piston base volume.
- Retract tick computes push slice (
0.5) and applies+0.01bias. - Retract source-piston fix path can apply another forward-biased correction that also includes
+0.01. - 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.