Relative teleport commands appear to be measured as relative to the target but applied relative to the context, resulting in unexpected behavior. For example, this function:
tp @s ~5 ~ ~
tp @s ~ ~ ~
Expected behavior: player is not moved at all
Actual behavior: player is moved 5 blocks in the negative X direction
It appears as though the player moves +5X, then the second command tries to put them back to the context position, which is a relative move of -5X from their current position, but it applies that -5X to the context position instead of the player's position, resulting in the player moving from +5X to -5X.
For another example:
tp @s ~ ~ ~5
tp @s ~ ~ ~5
Expected behavior: player is moved 5 blocks in the positive Z direction
Actual behavior: player is not moved at all
Again, it appears that the player moves +5Z, then the second command tries to put them there again, which is a relative move of 0Z from the new position, but it applies that 0Z to the context position, putting the player back where they started.
Attachments
Comments 5
Example 4 has a wrong expected result. The function context should start at the same position for all three commands, which means that the tp @s ~ ~ ~3
at the end should be left unaffected by the previous commands.
So the expected result for Example 4 is that the player should be teleported 3 blocks south, and not have their position changed otherwise.
I can confirm this issue. I've tested it a bit further, and updated the description to be somewhat more clear.
EDIT: also attached a datapack providing the two example functions
Code analysis by @ErrorCraft (text slightly edited by me, and translated mappings from yarn to official):
In ServerPlayer::changeDimension
(which is used even when the dimension is not changed) it first sets the Player's position via Entity::teleportSetPosition
by resolving it. So a y position of -60.0 with a teleport command with ~10 will set the player's position to -50.0. It then calls the ServerGamePacketListenerImpl::teleport
method, which coincidentally calls these same methods! However, since it uses the Player's position, it now uses -50.0 instead of the expected -60.0! So the final y coordinate is -40.0, rather than -50.0, because it applies the offset a second time. However, on the client the player gets moved -50.0, so this actually causes a desync.
Now... What causes it to go back to its original position after executing the command a second time? The Player's y coordinate is now -40.0 and we want to teleport 10 blocks upwards from -60.0, which is -50.0. It calculates the position by subtracting the entity's position from the calculated position. This results in the following calculation: -50.0 - -40.0 which equals -10.0, the exact opposite of what we wanted, which causes it to teleport the Player 10 blocks down twice as described before.
Because this happens in the changeDimension
override in ServerPlayer
, it means that the issue only affects Players, which is something we can observe too if we try it out on another entity like a Pig; The Pig's position is set correctly.
This issue (or two, though one causes the other) is fixed by removing the Entity::teleportSetPosition
call in the ServerPlayer::changeDimension
method.
To reproduce the desync issue, just go in Survival mode and invoke the /teleport ~ ~10 ~
command while standing still and observe the fall damage. Then compare to the damage taken by falling from 20 blocks high. Falling from 10 blocks the fall damage is less.
I also tested the teleport command in different coordination.
Example 4:
Expected: Player is teleported 3 blocks east, 3 blocks south and 3 blocks up from the previous position.
Actual: Player is teleported 3 blocks east, 3 blocks south and 3 blocks down.
Expected: Should be the same expectation.
Actual: Player is teleported 3 blocks west, 3 blocks south and 3 blocks up.
Expected: Player is teleported 3 blocks east, 6 blocks south and 3 blocks up from the previous position.
Actual: Player is teleported 3 blocks west, 3 blocks up, and 3 blocks south and north.
In Example 4, this bug is confirmed.
Example 5:
This works as expected, but if I try to catch the position:
Expected:
Pos[]
follows the player. If players is teleported 3 blocks up,Pos[]
is also 3 blocks up from the previous position,Actual:
Pos[]
is twice as far as the actual position of the player, and it doesn't change withexecute at @s run
heading.In Example 5, we can know that the position caught gets twice as far as expected after teleport commands in a function.