PaperMC / PaperMC/Paper

Players in spectator mode can be teleported via Entity#teleport while spectating another entity, causing client-server desync

Open
#13,473 4 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

status: accepted version: 1.21.11
Dominant language
Java
Stars
12.7k
Forks
3.5k
Avg merge
3d 13h
Merged PRs (30d)
11

Description

Expected behavior

When a player that is spectating an entity is teleported, the player stops spectating the entity, and is teleported to the location.
It would also be nice to have an option to ignore the teleport and keep the player spectating but that's for another issue.

Observed/Actual behavior

The player is teleported, but continues to spectate the entity. This breaks something most of the time, but exactly what breaks is inconsistent.

I think what happens most of the time is that the spectator is teleported, and its new location is synced with the client, but then the server-side spectator location override kicks in, which sets the player's location without syncing to the client (see nms ServerPlayer#tick), causing a nasty desync which leaves the camera in unloaded chunks. Sometimes the client can pull itself out of this state, sometimes the camera entity needs to change for it to sync up again.

Steps/models to reproduce
  • Switch to spectator mode
  • Click on an entity to start spectating it
  • While still spectating the entity, teleport yourself (but not the entity) a significant distance via Entity#teleport

I've been using this quick and dirty plugin to teleport:

package me.lucyydotp.paper_tp_bug;

import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents;
import org.bukkit.entity.Player;
import org.bukkit.plugin.java.JavaPlugin;

public class PaperTeleportBug extends JavaPlugin {
    @Override
    public void onEnable() {
        getLifecycleManager().registerEventHandler(LifecycleEvents.COMMANDS, c -> {
            c.registrar().register("tpbug", (source, args) -> {
                if (!(source.getExecutor() instanceof Player player)) return;
                player.teleport(player.getLocation().add(500, 0, 500));
                player.sendMessage("Teleported! New server-side location is %.3f %.3f %.3f".formatted(player.getLocation().getX(), player.getLocation().getY(), player.getLocation().getZ()));
            });
        });
    }
}

And a fabric mixin to see what's going on client-side (F3 shows the camera position by default): https://gist.github.com/lucyydotp/dbe1cec43e9ebd0e5f3b4d55b789bcf1

Plugin and Datapack List

None other than the above repro plugin.

Paper version

This server is running Paper version 1.21.11-69-main@94d0c97 (2025-12-30T20:33:30Z) (Implementing API version 1.21.11-R0.1-SNAPSHOT)

Other

Vanilla solves this problem by just stopping spectating on teleport (nms ServerPlayer#teleportTo), but that method isn't used by paper.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reproducing the issue with the provided PaperTeleportBug plugin while watching the client-side camera behavior. Read nms ServerPlayer#tick and ServerPlayer#teleportTo, then trace how Entity#teleport handles a player spectating another entity. Done means teleporting the spectator stops spectating and keeps the client and server locations synchronized.

Written by the indexing model from the issue text.

Assessment

Tech stack
java
Domain
api, backend
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.