26.2+ Clients:
Entity’s interpolation is set to 3 on default
26.3+ Clients:
Entity’s interpolation should be set to (updateInterval + 1) ticks
For example, player’s updateInterval is 2, so its interpolation should be 3 ticks. But it seems that the actual updateInterval is set to 2 (maybe even lower).
This issue makes all kinds of entities move inconsistently in visual.
Here is a link about the effect of this issue:
Linked issues
is duplicated by 1
Attachments
Comments 3
I traced the issue to the 26.3 entity interpolation refactor, In class net/minecraft/world/entity/SteppedInterpolationHandler
Current 26.3 code:
private SteppedInterpolationHandler(final Entity entity) {
super(entity, entity.getType().updateInterval());
}The interpolation length is therefore equal to updateInterval. However, the client should interpolate for updateInterval + 1 ticks.
Player is registered in:
net/minecraft.world.entity.EntityTypes
with:
.updateInterval(2)
Therefore, the current code interpolates player movement for only 2 ticks instead of 3.
I guess expected is:
private SteppedInterpolationHandler(final Entity entity) {
super(entity, entity.getType().updateInterval() + 1);
}In 26.2 , the default interpolation length was hard-coded to 3 ticks:
public InterpolationHandler(final Entity entity) {
this(entity, 3, null);
}The 26.3 refactor replaced this fixed value with updateInterval, but omitted the +1 . This causes player’s actual updateInterval is 2.
BTW The existing net.minecraft.client.player.RemotePlayer.lerpMotion() implementation already uses updateInterval() + 1 for velocity interpolation. But this seems unrelate to this issue.
There are several problems with the current version of the stepped interpolation and using updateInterval() + 1 instead of updateInterval() is not going to fix the most notable stutters. Please make sure to first quickly read my report MC-311976 on this (mostly same) issue.
The first issue is that the vanilla server seemingly already sends move entity packets using the new PositionPath.Stepped packet protocol. This is especially noticeable when hitting an entity currently in rest, as the server will sometimes sent stepped move entity packets with a tickOffset of only 1 or 2, making any changes on the client useless in this regard as they overwrite the interpolationSteps value.
Another issue i mention is that entities can get stuck interpolating on every client tick even though they are not being moved at all.
For a complete fix one must change the interpolationSteps value to updateInterval() + 1 as proposed, must stop the server from sending the move entity packets with PositionSteps of small tickOffset and adjust the following methods in a way similar like I did (I do not guarantee that these are completely free of bugs, but at least entities move smoothly °_°),
In SteppedInterpolationHandler:
protected void doInterpolate() {
float tick = this.entity.level().getRelativeTickSpeed();
this.interpolationData.advance(tick, this.interpolationSteps);
PositionAndRotation target = this.interpolationData.getNewPositionAndRotation();
this.entity.setPos(target.position());
this.entity.setRot(target.yRot(), target.xRot());
}
and in SteppedInterpolationHandler.InterpolationData:
private void setStartingPoint(Vec3 position, float yRot, float xRot) {
this.lastStepPosRot.set(position, yRot, xRot);
this.currentStepTicks = 0.0F;
}private void advance(float ticks, final int interpolationSteps) {
float targetSpeed = Math.max(this.remainingTicks / interpolationSteps, 1.0F);
this.interpolationSpeed = Mth.lerp(1.0F / interpolationSteps, this.interpolationSpeed, targetSpeed);
ticks *= this.interpolationSpeed;
this.currentStepTicks += ticks;
this.remainingTicks -= ticks;
while (!this.remainingSteps.isEmpty()) {
SteppedInterpolationHandler.Step step = this.remainingSteps.getFirst();
int offset = step.tickOffset;
if (this.currentStepTicks < offset) {
return;
}
this.currentStepTicks -= offset;
this.lastStepPosRot.set(step.position(), step.yRot(), step.xRot());
this.remainingSteps.removeFirst();
}
this.remainingTicks = 0.0F;
}
private PositionAndRotation getNewPositionAndRotation() {
if (this.remainingSteps.isEmpty()) {
return this;
}
SteppedInterpolationHandler.Step step = this.remainingSteps.getFirst();
float a = this.currentStepTicks / step.tickOffset;
return PositionAndRotation.of(
this.lastStepPosRot.position().lerp(step.position(), a),
Mth.rotLerp(a, this.lastStepPosRot.yRot(), step.yRot()),
Mth.lerp(a, this.lastStepPosRot.xRot(), step.xRot())
);
}
I think it makes sense structurally to have all of the core logic in the advance method only and avoid the problem with checking for having cleared the full path twice in this way. We set the starting value of currentStepTicks to 0 instead of 1 and instead run the advance() method before getting the new position and rotation.
Thank you for helping us improve Minecraft! We saved your files: