Fix: ghost entity in EntityStore after vanished player disconnects#59
Open
Dimotai wants to merge 1 commit intoEliteScouter:mainfrom
Open
Fix: ghost entity in EntityStore after vanished player disconnects#59Dimotai wants to merge 1 commit intoEliteScouter:mainfrom
Dimotai wants to merge 1 commit intoEliteScouter:mainfrom
Conversation
cb6f866 to
33c235d
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem 1 — Ghost entity after disconnect
When a player uses /vanish and performs cross-world teleports, disconnecting leaves a ghost entity in EntityStore$UUIDSystem. On reconnect, onEntityAdded detects a duplicate UUID and calls removeEntity(), invalidating the Ref that deferred tasks (e.g. SoulboundContainerGuard.registerPlayer, UsageRulesService.doPlayerJoinSetup) use. The player gets kicked with "Player removed from world" and cannot rejoin without a full server restart. Deleting player data files does not help — the ghost entity lives in the runtime UUID→entity map in RAM.
Root cause: onPlayerLeave() only cleaned up internal tracking (vanishedPlayers set, playerStoreRefs map) but never reversed engine-level vanish effects. Cross-world teleports while vanished leave residual HiddenPlayersManager entries that persist even after un-vanishing, interfering with the engine's entity removal pipeline. The bug occurs even when the player un-vanishes before disconnecting.
Fix: onPlayerLeave() now unconditionally calls updateVisibilityForAll(playerId, false) on every disconnect. showPlayer() is a no-op if the player isn't hidden, so zero cost for normal players. If the player is currently vanished, the Invulnerable component is also removed (respecting GodService state).
Problem 2 — Live entity corruption on rapid vanish toggle
Rapidly toggling /v (e.g. twice within 6 seconds) corrupts the player entity while still connected. The Player component becomes null, breaking all commands, movement, and map tracking. The player sees "Player is not in valid world" and "Cannot invoke Player.getUuid() because player is null".
Root cause: updateMobImmunity() had a "synchronous" code path that called store.putComponent(ref, Invulnerable) directly on the ForkJoinPool thread where commands execute — not the WorldThread. Rapid toggles caused concurrent putComponent/removeComponent calls racing with the WorldThread, corrupting the ECS archetype and nulling the Player component.
Fix (two parts):
updateMobImmunity() now always defers component mutations to the world thread via world.execute(), using the caller-provided ref when available for freshness.
toggleVanish() now has a per-player reentrance guard (toggleInProgress set) that ignores a second toggle if one is already in progress for that UUID.