--- a/net/minecraft/server/level/ChunkMap.java
+++ b/net/minecraft/server/level/ChunkMap.java
@@ -1,7 +1,10 @@
 package net.minecraft.server.level;
 
+import co.aikar.timings.Timing; // Paper
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableList.Builder;
 import com.google.common.collect.Iterables;
+import com.google.common.collect.ComparisonChain; // Paper
 import com.google.common.collect.Lists;
 import com.google.common.collect.Queues;
 import com.google.common.collect.Sets;
@@ -18,16 +21,18 @@
 import it.unimi.dsi.fastutil.longs.Long2LongMap;
 import it.unimi.dsi.fastutil.longs.Long2LongOpenHashMap;
 import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap;
-import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
+import it.unimi.dsi.fastutil.longs.Long2ObjectMap.Entry;
 import it.unimi.dsi.fastutil.longs.LongIterator;
 import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
 import it.unimi.dsi.fastutil.longs.LongSet;
+import it.unimi.dsi.fastutil.objects.ObjectBidirectionalIterator;
 import it.unimi.dsi.fastutil.objects.ObjectIterator;
 import java.io.IOException;
 import java.io.Writer;
 import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
@@ -38,6 +43,7 @@
 import java.util.concurrent.CancellationException;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CompletionException;
+import java.util.concurrent.CompletionStage;
 import java.util.concurrent.Executor;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.BooleanSupplier;
@@ -50,6 +56,7 @@
 import net.minecraft.CrashReportCategory;
 import net.minecraft.ReportedException;
 import net.minecraft.Util;
+import net.minecraft.core.HolderGetter;
 import net.minecraft.core.RegistryAccess;
 import net.minecraft.core.SectionPos;
 import net.minecraft.core.registries.Registries;
@@ -83,6 +90,7 @@
 import net.minecraft.world.level.chunk.storage.ChunkSerializer;
 import net.minecraft.world.level.chunk.storage.ChunkStorage;
 import net.minecraft.world.level.entity.ChunkStatusUpdateListener;
+import net.minecraft.world.level.entity.EntityAccess;
 import net.minecraft.world.level.levelgen.NoiseBasedChunkGenerator;
 import net.minecraft.world.level.levelgen.NoiseGeneratorSettings;
 import net.minecraft.world.level.levelgen.RandomState;
@@ -94,8 +102,12 @@
 import net.minecraft.world.phys.Vec3;
 import org.apache.commons.lang3.mutable.MutableBoolean;
 import org.slf4j.Logger;
+import org.bukkit.craftbukkit.v1_20_R2.generator.CustomChunkGenerator;
+import org.bukkit.entity.Player;
+// CraftBukkit end
 
 public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider {
+
     private static final byte CHUNK_TYPE_REPLACEABLE = -1;
     private static final byte CHUNK_TYPE_UNKNOWN = 0;
     private static final byte CHUNK_TYPE_FULL = 1;
@@ -106,67 +118,158 @@
     public static final int MIN_VIEW_DISTANCE = 2;
     public static final int MAX_VIEW_DISTANCE = 32;
     public static final int FORCED_TICKET_LEVEL = ChunkLevel.byStatus(FullChunkStatus.ENTITY_TICKING);
-    public final Long2ObjectLinkedOpenHashMap<ChunkHolder> updatingChunkMap = new Long2ObjectLinkedOpenHashMap<>();
-    public volatile Long2ObjectLinkedOpenHashMap<ChunkHolder> visibleChunkMap = this.updatingChunkMap.clone();
-    private final Long2ObjectLinkedOpenHashMap<ChunkHolder> pendingUnloads = new Long2ObjectLinkedOpenHashMap<>();
-    private final LongSet entitiesInLevel = new LongOpenHashSet();
+    // Paper - rewrite chunk system
     public final ServerLevel level;
     private final ThreadedLevelLightEngine lightEngine;
-    private final BlockableEventLoop<Runnable> mainThreadExecutor;
+    public final BlockableEventLoop<Runnable> mainThreadExecutor; // Paper - public
     public ChunkGenerator generator;
     private final RandomState randomState;
     private final ChunkGeneratorStructureState chunkGeneratorState;
     public final Supplier<DimensionDataStorage> overworldDataStorage;
     private final PoiManager poiManager;
-    public final LongSet toDrop = new LongOpenHashSet();
+    // Paper - rewrite chunk system
     private boolean modified;
-    private final ChunkTaskPriorityQueueSorter queueSorter;
-    private final ProcessorHandle<ChunkTaskPriorityQueueSorter.Message<Runnable>> worldgenMailbox;
-    private final ProcessorHandle<ChunkTaskPriorityQueueSorter.Message<Runnable>> mainThreadMailbox;
+    // Paper - rewrite chunk system
     public final ChunkProgressListener progressListener;
     private final ChunkStatusUpdateListener chunkStatusListener;
-    public final ChunkMap.DistanceManager distanceManager;
-    private final AtomicInteger tickingGenerated = new AtomicInteger();
-    private final StructureTemplateManager structureTemplateManager;
+    public final ChunkMap.ChunkDistanceManager distanceManager;
+    private final AtomicInteger tickingGenerated;
+    public final StructureTemplateManager structureTemplateManager; // Paper - rewrite chunk system
     private final String storageName;
-    private final PlayerMap playerMap = new PlayerMap();
-    public final Int2ObjectMap<ChunkMap.TrackedEntity> entityMap = new Int2ObjectOpenHashMap<>();
-    private final Long2ByteMap chunkTypeCache = new Long2ByteOpenHashMap();
-    private final Long2LongMap chunkSaveCooldowns = new Long2LongOpenHashMap();
-    private final Queue<Runnable> unloadQueue = Queues.newConcurrentLinkedQueue();
+    private final PlayerMap playerMap;
+    public final Int2ObjectMap<ChunkMap.TrackedEntity> entityMap;
+    private final Long2ByteMap chunkTypeCache;
+    private final Long2LongMap chunkSaveCooldowns;
+    private final Queue<Runnable> unloadQueue;
     private int serverViewDistance;
 
+    // Paper - rewrite chunk system
+
+    // Paper start - distance maps
+    private final com.destroystokyo.paper.util.misc.PooledLinkedHashSets<ServerPlayer> pooledLinkedPlayerHashSets = new com.destroystokyo.paper.util.misc.PooledLinkedHashSets<>();
+
+    void addPlayerToDistanceMaps(ServerPlayer player) {
+        int chunkX = io.papermc.paper.util.MCUtil.getChunkCoordinate(player.getX());
+        int chunkZ = io.papermc.paper.util.MCUtil.getChunkCoordinate(player.getZ());
+        // Note: players need to be explicitly added to distance maps before they can be updated
+        this.nearbyPlayers.addPlayer(player);
+        this.level.playerChunkLoader.addPlayer(player); // Paper - replace chunk loader
+    }
+
+    void removePlayerFromDistanceMaps(ServerPlayer player) {
+        int chunkX = io.papermc.paper.util.MCUtil.getChunkCoordinate(player.getX());
+        int chunkZ = io.papermc.paper.util.MCUtil.getChunkCoordinate(player.getZ());
+        // Note: players need to be explicitly added to distance maps before they can be updated
+        this.nearbyPlayers.removePlayer(player);
+        this.level.playerChunkLoader.removePlayer(player); // Paper - replace chunk loader
+        this.playerMobSpawnMap.remove(player); // Paper - optimise chunk tick iteration
+    }
+
+    void updateMaps(ServerPlayer player) {
+        int chunkX = io.papermc.paper.util.MCUtil.getChunkCoordinate(player.getX());
+        int chunkZ = io.papermc.paper.util.MCUtil.getChunkCoordinate(player.getZ());
+        // Note: players need to be explicitly added to distance maps before they can be updated
+        this.nearbyPlayers.tickPlayer(player);
+        this.level.playerChunkLoader.updatePlayer(player); // Paper - replace chunk loader
+    }
+    // Paper end
+    // Paper start
+    public final List<io.papermc.paper.chunk.SingleThreadChunkRegionManager> regionManagers = new java.util.ArrayList<>();
+    public final io.papermc.paper.chunk.SingleThreadChunkRegionManager dataRegionManager;
+
+    public static final class DataRegionData implements io.papermc.paper.chunk.SingleThreadChunkRegionManager.RegionData {
+    }
+
+    public static final class DataRegionSectionData implements io.papermc.paper.chunk.SingleThreadChunkRegionManager.RegionSectionData {
+
+        @Override
+        public void removeFromRegion(final io.papermc.paper.chunk.SingleThreadChunkRegionManager.RegionSection section,
+                                     final io.papermc.paper.chunk.SingleThreadChunkRegionManager.Region from) {
+            final DataRegionSectionData sectionData = (DataRegionSectionData)section.sectionData;
+            final DataRegionData fromData = (DataRegionData)from.regionData;
+        }
+
+        @Override
+        public void addToRegion(final io.papermc.paper.chunk.SingleThreadChunkRegionManager.RegionSection section,
+                                final io.papermc.paper.chunk.SingleThreadChunkRegionManager.Region oldRegion,
+                                final io.papermc.paper.chunk.SingleThreadChunkRegionManager.Region newRegion) {
+            final DataRegionSectionData sectionData = (DataRegionSectionData)section.sectionData;
+            final DataRegionData oldRegionData = oldRegion == null ? null : (DataRegionData)oldRegion.regionData;
+            final DataRegionData newRegionData = (DataRegionData)newRegion.regionData;
+        }
+    }
+
+    public final ChunkHolder getUnloadingChunkHolder(int chunkX, int chunkZ) {
+        return null; // Paper - rewrite chunk system
+    }
+    public final io.papermc.paper.util.player.NearbyPlayers nearbyPlayers;
+    // Paper end
+    // Paper start - optimise chunk tick iteration
+    public final it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<ChunkHolder> needsChangeBroadcasting = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>();
+    public final com.destroystokyo.paper.util.misc.PlayerAreaMap playerMobSpawnMap = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets);
+    // Paper end - optimise chunk tick iteration
+
     public ChunkMap(ServerLevel world, LevelStorageSource.LevelStorageAccess session, DataFixer dataFixer, StructureTemplateManager structureTemplateManager, Executor executor, BlockableEventLoop<Runnable> mainThreadExecutor, LightChunkGetter chunkProvider, ChunkGenerator chunkGenerator, ChunkProgressListener worldGenerationProgressListener, ChunkStatusUpdateListener chunkStatusChangeListener, Supplier<DimensionDataStorage> persistentStateManagerFactory, int viewDistance, boolean dsync) {
         super(session.getDimensionPath(world.dimension()).resolve("region"), dataFixer, dsync);
+        // Paper - rewrite chunk system
+        this.tickingGenerated = new AtomicInteger();
+        this.playerMap = new PlayerMap();
+        this.entityMap = new Int2ObjectOpenHashMap();
+        this.chunkTypeCache = new Long2ByteOpenHashMap();
+        this.chunkSaveCooldowns = new Long2LongOpenHashMap();
+        this.unloadQueue = Queues.newConcurrentLinkedQueue();
         this.structureTemplateManager = structureTemplateManager;
         Path path = session.getDimensionPath(world.dimension());
+
         this.storageName = path.getFileName().toString();
         this.level = world;
         this.generator = chunkGenerator;
-        RegistryAccess registryAccess = world.registryAccess();
-        long l = world.getSeed();
-        if (chunkGenerator instanceof NoiseBasedChunkGenerator noiseBasedChunkGenerator) {
-            this.randomState = RandomState.create(noiseBasedChunkGenerator.generatorSettings().value(), registryAccess.lookupOrThrow(Registries.NOISE), l);
+        // CraftBukkit start - SPIGOT-7051: It's a rigged game! Use delegate for random state creation, otherwise it is not so random.
+        if (chunkGenerator instanceof CustomChunkGenerator) {
+            chunkGenerator = ((CustomChunkGenerator) chunkGenerator).getDelegate();
+        }
+        // CraftBukkit end
+        RegistryAccess iregistrycustom = world.registryAccess();
+        long j = world.getSeed();
+
+        if (chunkGenerator instanceof NoiseBasedChunkGenerator) {
+            NoiseBasedChunkGenerator chunkgeneratorabstract = (NoiseBasedChunkGenerator) chunkGenerator;
+
+            this.randomState = RandomState.create((NoiseGeneratorSettings) chunkgeneratorabstract.generatorSettings().value(), (HolderGetter) iregistrycustom.lookupOrThrow(Registries.NOISE), j);
         } else {
-            this.randomState = RandomState.create(NoiseGeneratorSettings.dummy(), registryAccess.lookupOrThrow(Registries.NOISE), l);
+            this.randomState = RandomState.create(NoiseGeneratorSettings.dummy(), (HolderGetter) iregistrycustom.lookupOrThrow(Registries.NOISE), j);
         }
 
-        this.chunkGeneratorState = chunkGenerator.createState(registryAccess.lookupOrThrow(Registries.STRUCTURE_SET), this.randomState, l);
+        this.chunkGeneratorState = chunkGenerator.createState(iregistrycustom.lookupOrThrow(Registries.STRUCTURE_SET), this.randomState, j, world.spigotConfig); // Spigot
         this.mainThreadExecutor = mainThreadExecutor;
-        ProcessorMailbox<Runnable> processorMailbox = ProcessorMailbox.create(executor, "worldgen");
-        ProcessorHandle<Runnable> processorHandle = ProcessorHandle.of("main", mainThreadExecutor::tell);
+        // Paper - rewrite chunk system
+
+        Objects.requireNonNull(mainThreadExecutor);
+        // Paper - rewrite chunk system
+
         this.progressListener = worldGenerationProgressListener;
         this.chunkStatusListener = chunkStatusChangeListener;
-        ProcessorMailbox<Runnable> processorMailbox2 = ProcessorMailbox.create(executor, "light");
-        this.queueSorter = new ChunkTaskPriorityQueueSorter(ImmutableList.of(processorMailbox, processorHandle, processorMailbox2), executor, Integer.MAX_VALUE);
-        this.worldgenMailbox = this.queueSorter.getProcessor(processorMailbox, false);
-        this.mainThreadMailbox = this.queueSorter.getProcessor(processorHandle, false);
-        this.lightEngine = new ThreadedLevelLightEngine(chunkProvider, this, this.level.dimensionType().hasSkyLight(), processorMailbox2, this.queueSorter.getProcessor(processorMailbox2, false));
-        this.distanceManager = new ChunkMap.DistanceManager(executor, mainThreadExecutor);
+        // Paper - rewrite chunk system
+
+        // Paper - rewrite chunk system
+        this.lightEngine = new ThreadedLevelLightEngine(chunkProvider, this, this.level.dimensionType().hasSkyLight(), null, null); // Paper - rewrite chunk system
+        this.distanceManager = new ChunkMap.ChunkDistanceManager(executor, mainThreadExecutor);
         this.overworldDataStorage = persistentStateManagerFactory;
-        this.poiManager = new PoiManager(path.resolve("poi"), dataFixer, dsync, registryAccess, world);
+        this.poiManager = new PoiManager(path.resolve("poi"), dataFixer, dsync, iregistrycustom, world);
         this.setServerViewDistance(viewDistance);
+        // Paper start
+        this.dataRegionManager = new io.papermc.paper.chunk.SingleThreadChunkRegionManager(this.level, 2, (1.0 / 3.0), 1, 6, "Data", DataRegionData::new, DataRegionSectionData::new);
+        this.regionManagers.add(this.dataRegionManager);
+        this.nearbyPlayers = new io.papermc.paper.util.player.NearbyPlayers(this.level);
+        // Paper end
+    }
+
+    // Paper start
+    // always use accessor, so folia can override
+    public final io.papermc.paper.util.player.NearbyPlayers getNearbyPlayers() {
+        return this.nearbyPlayers;
     }
+    // Paper end
 
     protected ChunkGenerator generator() {
         return this.generator;
@@ -181,41 +284,76 @@
     }
 
     public void debugReloadGenerator() {
-        DataResult<JsonElement> dataResult = ChunkGenerator.CODEC.encodeStart(JsonOps.INSTANCE, this.generator);
-        DataResult<ChunkGenerator> dataResult2 = dataResult.flatMap((json) -> {
-            return ChunkGenerator.CODEC.parse(JsonOps.INSTANCE, json);
+        DataResult<JsonElement> dataresult = ChunkGenerator.CODEC.encodeStart(JsonOps.INSTANCE, this.generator);
+        DataResult<ChunkGenerator> dataresult1 = dataresult.flatMap((jsonelement) -> {
+            return ChunkGenerator.CODEC.parse(JsonOps.INSTANCE, jsonelement);
         });
-        dataResult2.result().ifPresent((chunkGenerator) -> {
-            this.generator = chunkGenerator;
+
+        dataresult1.result().ifPresent((chunkgenerator) -> {
+            this.generator = chunkgenerator;
         });
     }
 
-    private static double euclideanDistanceSquared(ChunkPos pos, Entity entity) {
-        double d = (double)SectionPos.sectionToBlockCoord(pos.x, 8);
-        double e = (double)SectionPos.sectionToBlockCoord(pos.z, 8);
-        double f = d - entity.getX();
-        double g = e - entity.getZ();
-        return f * f + g * g;
+    // Paper start
+    public void updatePlayerMobTypeMap(Entity entity) {
+        if (!this.level.paperConfig().entities.spawning.perPlayerMobSpawns) {
+            return;
+        }
+        int index = entity.getType().getCategory().ordinal();
+
+        final com.destroystokyo.paper.util.maplist.ReferenceList<ServerPlayer> inRange =
+            this.getNearbyPlayers().getPlayers(entity.chunkPosition(), io.papermc.paper.util.player.NearbyPlayers.NearbyMapType.TICK_VIEW_DISTANCE);
+        if (inRange == null) {
+            return;
+        }
+        final Object[] backingSet = inRange.getRawData();
+        for (int i = 0, len = inRange.size(); i < len; i++) {
+            ++((ServerPlayer)backingSet[i]).mobCounts[index];
+        }
+    }
+
+    // Paper start - per player mob count backoff
+    public void updateFailurePlayerMobTypeMap(int chunkX, int chunkZ, net.minecraft.world.entity.MobCategory mobCategory) {
+        if (!this.level.paperConfig().entities.spawning.perPlayerMobSpawns) {
+            return;
+        }
+        int idx = mobCategory.ordinal();
+        final com.destroystokyo.paper.util.maplist.ReferenceList<ServerPlayer> inRange =
+            this.getNearbyPlayers().getPlayersByChunk(chunkX, chunkZ, io.papermc.paper.util.player.NearbyPlayers.NearbyMapType.TICK_VIEW_DISTANCE);
+        if (inRange == null) {
+            return;
+        }
+        final Object[] backingSet = inRange.getRawData();
+        for (int i = 0, len = inRange.size(); i < len; i++) {
+            ++((ServerPlayer)backingSet[i]).mobBackoffCounts[idx];
+        }
+    }
+    // Paper end - per player mob count backoff
+
+    public int getMobCountNear(ServerPlayer entityPlayer, net.minecraft.world.entity.MobCategory mobCategory) {
+        return entityPlayer.mobCounts[mobCategory.ordinal()] + entityPlayer.mobBackoffCounts[mobCategory.ordinal()]; // Paper - per player mob count backoff
+    }
+    // Paper end
+
+    public static double euclideanDistanceSquared(ChunkPos pos, Entity entity) { // Paper - optimise chunk iteration - public
+        double d0 = (double) SectionPos.sectionToBlockCoord(pos.x, 8);
+        double d1 = (double) SectionPos.sectionToBlockCoord(pos.z, 8);
+        double d2 = d0 - entity.getX();
+        double d3 = d1 - entity.getZ();
+
+        return d2 * d2 + d3 * d3;
     }
 
     boolean isChunkTracked(ServerPlayer player, int chunkX, int chunkZ) {
-        return player.getChunkTrackingView().contains(chunkX, chunkZ) && !player.connection.chunkSender.isPending(ChunkPos.asLong(chunkX, chunkZ));
+        // Paper start - rewrite player chunk loader
+        return this.level.playerChunkLoader.isChunkSent(player, chunkX, chunkZ);
+        // Paper end - rewrite player chunk loader
     }
 
     private boolean isChunkOnTrackedBorder(ServerPlayer player, int chunkX, int chunkZ) {
-        if (!this.isChunkTracked(player, chunkX, chunkZ)) {
-            return false;
-        } else {
-            for(int i = -1; i <= 1; ++i) {
-                for(int j = -1; j <= 1; ++j) {
-                    if ((i != 0 || j != 0) && !this.isChunkTracked(player, chunkX + i, chunkZ + j)) {
-                        return true;
-                    }
-                }
-            }
-
-            return false;
-        }
+        // Paper start - rewrite player chunk loader
+        return this.level.playerChunkLoader.isChunkSent(player, chunkX, chunkZ, true);
+        // Paper end - rewrite player chunk loader
     }
 
     protected ThreadedLevelLightEngine getLightEngine() {
@@ -224,378 +362,168 @@
 
     @Nullable
     protected ChunkHolder getUpdatingChunkIfPresent(long pos) {
-        return this.updatingChunkMap.get(pos);
+        // Paper start - rewrite chunk system
+        io.papermc.paper.chunk.system.scheduling.NewChunkHolder holder = this.level.chunkTaskScheduler.chunkHolderManager.getChunkHolder(pos);
+        return holder == null ? null : holder.vanillaChunkHolder;
+        // Paper end - rewrite chunk system
     }
 
     @Nullable
     public ChunkHolder getVisibleChunkIfPresent(long pos) {
-        return this.visibleChunkMap.get(pos);
+        // Paper start - rewrite chunk system
+        io.papermc.paper.chunk.system.scheduling.NewChunkHolder holder = this.level.chunkTaskScheduler.chunkHolderManager.getChunkHolder(pos);
+        return holder == null ? null : holder.vanillaChunkHolder;
+        // Paper end - rewrite chunk system
     }
 
     protected IntSupplier getChunkQueueLevel(long pos) {
-        return () -> {
-            ChunkHolder chunkHolder = this.getVisibleChunkIfPresent(pos);
-            return chunkHolder == null ? ChunkTaskPriorityQueue.PRIORITY_LEVEL_COUNT - 1 : Math.min(chunkHolder.getQueueLevel(), ChunkTaskPriorityQueue.PRIORITY_LEVEL_COUNT - 1);
-        };
+        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
     }
 
     public String getChunkDebugData(ChunkPos chunkPos) {
-        ChunkHolder chunkHolder = this.getVisibleChunkIfPresent(chunkPos.toLong());
-        if (chunkHolder == null) {
+        ChunkHolder playerchunk = this.getVisibleChunkIfPresent(chunkPos.toLong());
+
+        if (playerchunk == null) {
             return "null";
         } else {
-            String string = chunkHolder.getTicketLevel() + "\n";
-            ChunkStatus chunkStatus = chunkHolder.getLastAvailableStatus();
-            ChunkAccess chunkAccess = chunkHolder.getLastAvailable();
-            if (chunkStatus != null) {
-                string = string + "St: \u00a7" + chunkStatus.getIndex() + chunkStatus + "\u00a7r\n";
-            }
+            String s = playerchunk.getTicketLevel() + "\n";
+            ChunkStatus chunkstatus = playerchunk.getLastAvailableStatus();
+            ChunkAccess ichunkaccess = playerchunk.getLastAvailable();
 
-            if (chunkAccess != null) {
-                string = string + "Ch: \u00a7" + chunkAccess.getStatus().getIndex() + chunkAccess.getStatus() + "\u00a7r\n";
+            if (chunkstatus != null) {
+                s = s + "St: \u00a7" + chunkstatus.getIndex() + chunkstatus + "\u00a7r\n";
             }
 
-            FullChunkStatus fullChunkStatus = chunkHolder.getFullStatus();
-            string = string + String.valueOf('\u00a7') + fullChunkStatus.ordinal() + fullChunkStatus;
-            return string + "\u00a7r";
-        }
-    }
-
-    private CompletableFuture<Either<List<ChunkAccess>, ChunkHolder.ChunkLoadingFailure>> getChunkRangeFuture(ChunkHolder centerChunk, int margin, IntFunction<ChunkStatus> distanceToStatus) {
-        if (margin == 0) {
-            ChunkStatus chunkStatus = distanceToStatus.apply(0);
-            return centerChunk.getOrScheduleFuture(chunkStatus, this).thenApply((chunk) -> {
-                return chunk.mapLeft(List::of);
-            });
-        } else {
-            List<CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>>> list = new ArrayList<>();
-            List<ChunkHolder> list2 = new ArrayList<>();
-            ChunkPos chunkPos = centerChunk.getPos();
-            int i = chunkPos.x;
-            int j = chunkPos.z;
-
-            for(int k = -margin; k <= margin; ++k) {
-                for(int l = -margin; l <= margin; ++l) {
-                    int m = Math.max(Math.abs(l), Math.abs(k));
-                    final ChunkPos chunkPos2 = new ChunkPos(i + l, j + k);
-                    long n = chunkPos2.toLong();
-                    ChunkHolder chunkHolder = this.getUpdatingChunkIfPresent(n);
-                    if (chunkHolder == null) {
-                        return CompletableFuture.completedFuture(Either.right(new ChunkHolder.ChunkLoadingFailure() {
-                            @Override
-                            public String toString() {
-                                return "Unloaded " + chunkPos2;
-                            }
-                        }));
-                    }
-
-                    ChunkStatus chunkStatus2 = distanceToStatus.apply(m);
-                    CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> completableFuture = chunkHolder.getOrScheduleFuture(chunkStatus2, this);
-                    list2.add(chunkHolder);
-                    list.add(completableFuture);
-                }
+            if (ichunkaccess != null) {
+                s = s + "Ch: \u00a7" + ichunkaccess.getStatus().getIndex() + ichunkaccess.getStatus() + "\u00a7r\n";
             }
 
-            CompletableFuture<List<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>>> completableFuture2 = Util.sequence(list);
-            CompletableFuture<Either<List<ChunkAccess>, ChunkHolder.ChunkLoadingFailure>> completableFuture3 = completableFuture2.thenApply((chunks) -> {
-                List<ChunkAccess> list = Lists.newArrayList();
-                int l = 0;
-
-                for(final Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure> either : chunks) {
-                    if (either == null) {
-                        throw this.debugFuturesAndCreateReportedException(new IllegalStateException("At least one of the chunk futures were null"), "n/a");
-                    }
-
-                    Optional<ChunkAccess> optional = either.left();
-                    if (optional.isEmpty()) {
-                        final int m = l;
-                        return Either.right(new ChunkHolder.ChunkLoadingFailure() {
-                            @Override
-                            public String toString() {
-                                return "Unloaded " + new ChunkPos(i + m % (margin * 2 + 1), j + m / (margin * 2 + 1)) + " " + either.right().get();
-                            }
-                        });
-                    }
-
-                    list.add(optional.get());
-                    ++l;
-                }
+            FullChunkStatus fullchunkstatus = playerchunk.getFullStatus();
 
-                return Either.left(list);
-            });
+            s = s + String.valueOf('\u00a7') + fullchunkstatus.ordinal() + fullchunkstatus;
+            return s + "\u00a7r";
+        }
+    }
 
-            for(ChunkHolder chunkHolder2 : list2) {
-                chunkHolder2.addSaveDependency("getChunkRangeFuture " + chunkPos + " " + margin, completableFuture3);
-            }
+    // Paper start
+    public final int getEffectiveViewDistance() {
+        return this.serverViewDistance;
+    }
+    // Paper end
 
-            return completableFuture3;
-        }
+    private CompletableFuture<Either<List<ChunkAccess>, ChunkHolder.ChunkLoadingFailure>> getChunkRangeFuture(ChunkHolder centerChunk, int margin, IntFunction<ChunkStatus> distanceToStatus) {
+        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
     }
 
     public ReportedException debugFuturesAndCreateReportedException(IllegalStateException exception, String details) {
-        StringBuilder stringBuilder = new StringBuilder();
-        Consumer<ChunkHolder> consumer = (chunkHolder) -> {
-            chunkHolder.getAllFutures().forEach((pair) -> {
-                ChunkStatus chunkStatus = pair.getFirst();
-                CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> completableFuture = pair.getSecond();
-                if (completableFuture != null && completableFuture.isDone() && completableFuture.join() == null) {
-                    stringBuilder.append((Object)chunkHolder.getPos()).append(" - status: ").append((Object)chunkStatus).append(" future: ").append((Object)completableFuture).append(System.lineSeparator());
+        StringBuilder stringbuilder = new StringBuilder();
+        Consumer<ChunkHolder> consumer = (playerchunk) -> {
+            playerchunk.getAllFutures().forEach((pair) -> {
+                ChunkStatus chunkstatus = (ChunkStatus) pair.getFirst();
+                CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> completablefuture = (CompletableFuture) pair.getSecond();
+
+                if (completablefuture != null && completablefuture.isDone() && completablefuture.join() == null) {
+                    stringbuilder.append(playerchunk.getPos()).append(" - status: ").append(chunkstatus).append(" future: ").append(completablefuture).append(System.lineSeparator());
                 }
 
             });
         };
-        stringBuilder.append("Updating:").append(System.lineSeparator());
-        this.updatingChunkMap.values().forEach(consumer);
-        stringBuilder.append("Visible:").append(System.lineSeparator());
-        this.visibleChunkMap.values().forEach(consumer);
-        CrashReport crashReport = CrashReport.forThrowable(exception, "Chunk loading");
-        CrashReportCategory crashReportCategory = crashReport.addCategory("Chunk loading");
-        crashReportCategory.setDetail("Details", details);
-        crashReportCategory.setDetail("Futures", stringBuilder);
-        return new ReportedException(crashReport);
+
+        stringbuilder.append("Updating:").append(System.lineSeparator());
+        io.papermc.paper.chunk.system.ChunkSystem.getUpdatingChunkHolders(this.level).forEach(consumer); // Paper
+        stringbuilder.append("Visible:").append(System.lineSeparator());
+        io.papermc.paper.chunk.system.ChunkSystem.getVisibleChunkHolders(this.level).forEach(consumer); // Paper
+        CrashReport crashreport = CrashReport.forThrowable(exception, "Chunk loading");
+        CrashReportCategory crashreportsystemdetails = crashreport.addCategory("Chunk loading");
+
+        crashreportsystemdetails.setDetail("Details", (Object) details);
+        crashreportsystemdetails.setDetail("Futures", (Object) stringbuilder);
+        return new ReportedException(crashreport);
     }
 
     public CompletableFuture<Either<LevelChunk, ChunkHolder.ChunkLoadingFailure>> prepareEntityTickingChunk(ChunkHolder chunk) {
-        return this.getChunkRangeFuture(chunk, 2, (distance) -> {
-            return ChunkStatus.FULL;
-        }).thenApplyAsync((either) -> {
-            return either.mapLeft((chunks) -> {
-                return (LevelChunk)chunks.get(chunks.size() / 2);
-            });
-        }, this.mainThreadExecutor);
+        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
     }
 
     @Nullable
-    ChunkHolder updateChunkScheduling(long pos, int level, @Nullable ChunkHolder holder, int i) {
-        if (!ChunkLevel.isLoaded(i) && !ChunkLevel.isLoaded(level)) {
-            return holder;
-        } else {
-            if (holder != null) {
-                holder.setTicketLevel(level);
-            }
-
-            if (holder != null) {
-                if (!ChunkLevel.isLoaded(level)) {
-                    this.toDrop.add(pos);
-                } else {
-                    this.toDrop.remove(pos);
-                }
-            }
-
-            if (ChunkLevel.isLoaded(level) && holder == null) {
-                holder = this.pendingUnloads.remove(pos);
-                if (holder != null) {
-                    holder.setTicketLevel(level);
-                } else {
-                    holder = new ChunkHolder(new ChunkPos(pos), level, this.level, this.lightEngine, this.queueSorter, this);
-                }
-
-                this.updatingChunkMap.put(pos, holder);
-                this.modified = true;
-            }
-
-            return holder;
-        }
+    ChunkHolder updateChunkScheduling(long pos, int level, @Nullable ChunkHolder holder, int k) {
+        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
     }
 
     @Override
     public void close() throws IOException {
-        try {
-            this.queueSorter.close();
-            this.poiManager.close();
-        } finally {
-            super.close();
-        }
+        throw new UnsupportedOperationException("Use ServerChunkCache#close"); // Paper - rewrite chunk system
+    }
 
+    // Paper start - rewrite chunk system
+    protected void saveIncrementally() {
+        this.level.chunkTaskScheduler.chunkHolderManager.autoSave(); // Paper - rewrite chunk system
     }
+    // Paper end - - rewrite chunk system
 
     protected void saveAllChunks(boolean flush) {
-        if (flush) {
-            List<ChunkHolder> list = this.visibleChunkMap.values().stream().filter(ChunkHolder::wasAccessibleSinceLastSave).peek(ChunkHolder::refreshAccessibility).toList();
-            MutableBoolean mutableBoolean = new MutableBoolean();
-
-            do {
-                mutableBoolean.setFalse();
-                list.stream().map((chunkHolder) -> {
-                    CompletableFuture<ChunkAccess> completableFuture;
-                    do {
-                        completableFuture = chunkHolder.getChunkToSave();
-                        this.mainThreadExecutor.managedBlock(completableFuture::isDone);
-                    } while(completableFuture != chunkHolder.getChunkToSave());
-
-                    return completableFuture.join();
-                }).filter((chunk) -> {
-                    return chunk instanceof ImposterProtoChunk || chunk instanceof LevelChunk;
-                }).filter(this::save).forEach((chunk) -> {
-                    mutableBoolean.setTrue();
-                });
-            } while(mutableBoolean.isTrue());
-
-            this.processUnloads(() -> {
-                return true;
-            });
-            this.flushWorker();
-        } else {
-            this.visibleChunkMap.values().forEach(this::saveChunkIfNeeded);
-        }
-
+        this.level.chunkTaskScheduler.chunkHolderManager.saveAllChunks(flush, false, false); // Paper - rewrite chunk system
     }
 
     protected void tick(BooleanSupplier shouldKeepTicking) {
-        ProfilerFiller profilerFiller = this.level.getProfiler();
-        profilerFiller.push("poi");
+        ProfilerFiller gameprofilerfiller = this.level.getProfiler();
+
+        try (Timing ignored = this.level.timings.poiUnload.startTiming()) { // Paper
+        gameprofilerfiller.push("poi");
         this.poiManager.tick(shouldKeepTicking);
-        profilerFiller.popPush("chunk_unload");
+        } // Paper
+        gameprofilerfiller.popPush("chunk_unload");
         if (!this.level.noSave()) {
+            try (Timing ignored = this.level.timings.chunkUnload.startTiming()) { // Paper
             this.processUnloads(shouldKeepTicking);
+            } // Paper
         }
 
-        profilerFiller.pop();
+        gameprofilerfiller.pop();
     }
 
     public boolean hasWork() {
-        return this.lightEngine.hasLightWork() || !this.pendingUnloads.isEmpty() || !this.updatingChunkMap.isEmpty() || this.poiManager.hasWork() || !this.toDrop.isEmpty() || !this.unloadQueue.isEmpty() || this.queueSorter.hasWork() || this.distanceManager.hasTickets();
+        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
     }
 
     private void processUnloads(BooleanSupplier shouldKeepTicking) {
-        LongIterator longIterator = this.toDrop.iterator();
-
-        for(int i = 0; longIterator.hasNext() && (shouldKeepTicking.getAsBoolean() || i < 200 || this.toDrop.size() > 2000); longIterator.remove()) {
-            long l = longIterator.nextLong();
-            ChunkHolder chunkHolder = this.updatingChunkMap.remove(l);
-            if (chunkHolder != null) {
-                this.pendingUnloads.put(l, chunkHolder);
-                this.modified = true;
-                ++i;
-                this.scheduleUnload(l, chunkHolder);
-            }
-        }
-
-        int j = Math.max(0, this.unloadQueue.size() - 2000);
-
-        Runnable runnable;
-        while((shouldKeepTicking.getAsBoolean() || j > 0) && (runnable = this.unloadQueue.poll()) != null) {
-            --j;
-            runnable.run();
-        }
-
-        int k = 0;
-        ObjectIterator<ChunkHolder> objectIterator = this.visibleChunkMap.values().iterator();
-
-        while(k < 20 && shouldKeepTicking.getAsBoolean() && objectIterator.hasNext()) {
-            if (this.saveChunkIfNeeded(objectIterator.next())) {
-                ++k;
-            }
-        }
+        this.level.chunkTaskScheduler.chunkHolderManager.processUnloads(); // Paper - rewrite chunk system
 
     }
 
     private void scheduleUnload(long pos, ChunkHolder holder) {
-        CompletableFuture<ChunkAccess> completableFuture = holder.getChunkToSave();
-        completableFuture.thenAcceptAsync((chunk) -> {
-            CompletableFuture<ChunkAccess> completableFuture2 = holder.getChunkToSave();
-            if (completableFuture2 != completableFuture) {
-                this.scheduleUnload(pos, holder);
-            } else {
-                if (this.pendingUnloads.remove(pos, holder) && chunk != null) {
-                    if (chunk instanceof LevelChunk) {
-                        ((LevelChunk)chunk).setLoaded(false);
-                    }
-
-                    this.save(chunk);
-                    if (this.entitiesInLevel.remove(pos) && chunk instanceof LevelChunk) {
-                        LevelChunk levelChunk = (LevelChunk)chunk;
-                        this.level.unload(levelChunk);
-                    }
-
-                    this.lightEngine.updateChunkStatus(chunk.getPos());
-                    this.lightEngine.tryScheduleUpdate();
-                    this.progressListener.onStatusChange(chunk.getPos(), (ChunkStatus)null);
-                    this.chunkSaveCooldowns.remove(chunk.getPos().toLong());
-                }
-
-            }
-        }, this.unloadQueue::add).whenComplete((void_, throwable) -> {
-            if (throwable != null) {
-                LOGGER.error("Failed to save chunk {}", holder.getPos(), throwable);
-            }
-
-        });
+        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
     }
 
     protected boolean promoteChunkMap() {
-        if (!this.modified) {
-            return false;
-        } else {
-            this.visibleChunkMap = this.updatingChunkMap.clone();
-            this.modified = false;
-            return true;
-        }
+        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
     }
 
     public CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> schedule(ChunkHolder holder, ChunkStatus requiredStatus) {
-        ChunkPos chunkPos = holder.getPos();
-        if (requiredStatus == ChunkStatus.EMPTY) {
-            return this.scheduleChunkLoad(chunkPos);
-        } else {
-            if (requiredStatus == ChunkStatus.LIGHT) {
-                this.distanceManager.addTicket(TicketType.LIGHT, chunkPos, ChunkLevel.byStatus(ChunkStatus.LIGHT), chunkPos);
-            }
-
-            if (!requiredStatus.hasLoadDependencies()) {
-                Optional<ChunkAccess> optional = holder.getOrScheduleFuture(requiredStatus.getParent(), this).getNow(ChunkHolder.UNLOADED_CHUNK).left();
-                if (optional.isPresent() && optional.get().getStatus().isOrAfter(requiredStatus)) {
-                    CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> completableFuture = requiredStatus.load(this.level, this.structureTemplateManager, this.lightEngine, (chunk) -> {
-                        return this.protoChunkToFullChunk(holder);
-                    }, optional.get());
-                    this.progressListener.onStatusChange(chunkPos, requiredStatus);
-                    return completableFuture;
-                }
-            }
-
-            return this.scheduleChunkGeneration(holder, requiredStatus);
-        }
+        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
     }
 
     private CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> scheduleChunkLoad(ChunkPos pos) {
-        return this.readChunk(pos).thenApply((nbt) -> {
-            return nbt.filter((nbt2) -> {
-                boolean bl = isChunkDataValid(nbt2);
-                if (!bl) {
-                    LOGGER.error("Chunk file at {} is missing level data, skipping", (Object)pos);
-                }
-
-                return bl;
-            });
-        }).thenApplyAsync((nbt) -> {
-            this.level.getProfiler().incrementCounter("chunkLoad");
-            if (nbt.isPresent()) {
-                ChunkAccess chunkAccess = ChunkSerializer.read(this.level, this.poiManager, pos, nbt.get());
-                this.markPosition(pos, chunkAccess.getStatus().getChunkType());
-                return Either.left(chunkAccess);
-            } else {
-                return Either.left(this.createEmptyChunk(pos));
-            }
-        }, this.mainThreadExecutor).exceptionallyAsync((throwable) -> {
-            return this.handleChunkLoadFailure(throwable, pos);
-        }, this.mainThreadExecutor);
+        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
     }
 
-    private static boolean isChunkDataValid(CompoundTag nbt) {
+    public static boolean isChunkDataValid(CompoundTag nbt) { // Paper - async chunk loading
         return nbt.contains("Status", 8);
     }
 
     private Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure> handleChunkLoadFailure(Throwable throwable, ChunkPos chunkPos) {
-        if (throwable instanceof ReportedException reportedException) {
-            Throwable throwable2 = reportedException.getCause();
-            if (!(throwable2 instanceof IOException)) {
+        if (throwable instanceof ReportedException) {
+            ReportedException reportedexception = (ReportedException) throwable;
+            Throwable throwable1 = reportedexception.getCause();
+
+            if (!(throwable1 instanceof IOException)) {
                 this.markPositionReplaceable(chunkPos);
-                throw reportedException;
+                throw reportedexception;
             }
 
-            LOGGER.error("Couldn't load chunk {}", chunkPos, throwable2);
+            ChunkMap.LOGGER.error("Couldn't load chunk {}", chunkPos, throwable1);
         } else if (throwable instanceof IOException) {
-            LOGGER.error("Couldn't load chunk {}", chunkPos, throwable);
+            ChunkMap.LOGGER.error("Couldn't load chunk {}", chunkPos, throwable);
         }
 
         return Either.left(this.createEmptyChunk(chunkPos));
@@ -603,62 +531,19 @@
 
     private ChunkAccess createEmptyChunk(ChunkPos chunkPos) {
         this.markPositionReplaceable(chunkPos);
-        return new ProtoChunk(chunkPos, UpgradeData.EMPTY, this.level, this.level.registryAccess().registryOrThrow(Registries.BIOME), (BlendingData)null);
+        return new ProtoChunk(chunkPos, UpgradeData.EMPTY, this.level, this.level.registryAccess().registryOrThrow(Registries.BIOME), (BlendingData) null);
     }
 
     private void markPositionReplaceable(ChunkPos pos) {
-        this.chunkTypeCache.put(pos.toLong(), (byte)-1);
+        this.chunkTypeCache.put(pos.toLong(), (byte) -1);
     }
 
     private byte markPosition(ChunkPos pos, ChunkStatus.ChunkType type) {
-        return this.chunkTypeCache.put(pos.toLong(), (byte)(type == ChunkStatus.ChunkType.PROTOCHUNK ? -1 : 1));
+        return this.chunkTypeCache.put(pos.toLong(), (byte) (type == ChunkStatus.ChunkType.PROTOCHUNK ? -1 : 1));
     }
 
     private CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> scheduleChunkGeneration(ChunkHolder holder, ChunkStatus requiredStatus) {
-        ChunkPos chunkPos = holder.getPos();
-        CompletableFuture<Either<List<ChunkAccess>, ChunkHolder.ChunkLoadingFailure>> completableFuture = this.getChunkRangeFuture(holder, requiredStatus.getRange(), (distance) -> {
-            return this.getDependencyStatus(requiredStatus, distance);
-        });
-        this.level.getProfiler().incrementCounter(() -> {
-            return "chunkGenerate " + requiredStatus;
-        });
-        Executor executor = (task) -> {
-            this.worldgenMailbox.tell(ChunkTaskPriorityQueueSorter.message(holder, task));
-        };
-        return completableFuture.thenComposeAsync((either) -> {
-            return either.map((chunks) -> {
-                try {
-                    ChunkAccess chunkAccess = chunks.get(chunks.size() / 2);
-                    CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> completableFuture;
-                    if (chunkAccess.getStatus().isOrAfter(requiredStatus)) {
-                        completableFuture = requiredStatus.load(this.level, this.structureTemplateManager, this.lightEngine, (chunkAccess) -> {
-                            return this.protoChunkToFullChunk(holder);
-                        }, chunkAccess);
-                    } else {
-                        completableFuture = requiredStatus.generate(executor, this.level, this.generator, this.structureTemplateManager, this.lightEngine, (chunk) -> {
-                            return this.protoChunkToFullChunk(holder);
-                        }, chunks);
-                    }
-
-                    this.progressListener.onStatusChange(chunkPos, requiredStatus);
-                    return completableFuture;
-                } catch (Exception var9) {
-                    var9.getStackTrace();
-                    CrashReport crashReport = CrashReport.forThrowable(var9, "Exception generating new chunk");
-                    CrashReportCategory crashReportCategory = crashReport.addCategory("Chunk to be generated");
-                    crashReportCategory.setDetail("Location", String.format(Locale.ROOT, "%d,%d", chunkPos.x, chunkPos.z));
-                    crashReportCategory.setDetail("Position hash", ChunkPos.asLong(chunkPos.x, chunkPos.z));
-                    crashReportCategory.setDetail("Generator", this.generator);
-                    this.mainThreadExecutor.execute(() -> {
-                        throw new ReportedException(crashReport);
-                    });
-                    throw new ReportedException(crashReport);
-                }
-            }, (unloaded) -> {
-                this.releaseLightTicket(chunkPos);
-                return CompletableFuture.completedFuture(Either.right(unloaded));
-            });
-        }, executor);
+        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
     }
 
     protected void releaseLightTicket(ChunkPos pos) {
@@ -669,109 +554,98 @@
         }));
     }
 
-    private ChunkStatus getDependencyStatus(ChunkStatus centerChunkTargetStatus, int distance) {
-        ChunkStatus chunkStatus;
+    public static ChunkStatus getDependencyStatus(ChunkStatus centerChunkTargetStatus, int distance) { // Paper -> public, static
+        ChunkStatus chunkstatus1;
+
         if (distance == 0) {
-            chunkStatus = centerChunkTargetStatus.getParent();
+            chunkstatus1 = centerChunkTargetStatus.getParent();
         } else {
-            chunkStatus = ChunkStatus.getStatusAroundFullChunk(ChunkStatus.getDistance(centerChunkTargetStatus) + distance);
+            chunkstatus1 = ChunkStatus.getStatusAroundFullChunk(ChunkStatus.getDistance(centerChunkTargetStatus) + distance);
         }
 
-        return chunkStatus;
+        return chunkstatus1;
     }
 
-    private static void postLoadProtoChunk(ServerLevel world, List<CompoundTag> nbt) {
+    public static void postLoadProtoChunk(ServerLevel world, List<CompoundTag> nbt, ChunkPos position) { // Paper - public and add chunk position parameter
         if (!nbt.isEmpty()) {
-            world.addWorldGenChunkEntities(EntityType.loadEntitiesRecursive(nbt, world));
+            // CraftBukkit start - these are spawned serialized (DefinedStructure) and we don't call an add event below at the moment due to ordering complexities
+            world.addWorldGenChunkEntities(EntityType.loadEntitiesRecursive(nbt, world).filter((entity) -> {
+                boolean needsRemoval = false;
+                net.minecraft.server.dedicated.DedicatedServer server = world.getCraftServer().getServer();
+                if (!server.areNpcsEnabled() && entity instanceof net.minecraft.world.entity.npc.Npc) {
+                    entity.discard();
+                    needsRemoval = true;
+                }
+                if (!server.isSpawningAnimals() && (entity instanceof net.minecraft.world.entity.animal.Animal || entity instanceof net.minecraft.world.entity.animal.WaterAnimal)) {
+                    entity.discard();
+                    needsRemoval = true;
+                }
+                checkDupeUUID(world, entity); // Paper
+                return !needsRemoval;
+            }), position); // Paper - rewrite chunk system
+            // CraftBukkit end
         }
 
     }
 
     private CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> protoChunkToFullChunk(ChunkHolder chunkHolder) {
-        CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> completableFuture = chunkHolder.getFutureIfPresentUnchecked(ChunkStatus.FULL.getParent());
-        return completableFuture.thenApplyAsync((either) -> {
-            ChunkStatus chunkStatus = ChunkLevel.generationStatus(chunkHolder.getTicketLevel());
-            return !chunkStatus.isOrAfter(ChunkStatus.FULL) ? ChunkHolder.UNLOADED_CHUNK : either.mapLeft((protoChunk) -> {
-                ChunkPos chunkPos = chunkHolder.getPos();
-                ProtoChunk protoChunk2 = (ProtoChunk)protoChunk;
-                LevelChunk levelChunk;
-                if (protoChunk2 instanceof ImposterProtoChunk) {
-                    levelChunk = ((ImposterProtoChunk)protoChunk2).getWrapped();
-                } else {
-                    levelChunk = new LevelChunk(this.level, protoChunk2, (chunk) -> {
-                        postLoadProtoChunk(this.level, protoChunk2.getEntities());
-                    });
-                    chunkHolder.replaceProtoChunk(new ImposterProtoChunk(levelChunk, false));
-                }
+        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
+    }
 
-                levelChunk.setFullStatus(() -> {
-                    return ChunkLevel.fullStatus(chunkHolder.getTicketLevel());
-                });
-                levelChunk.runPostLoad();
-                if (this.entitiesInLevel.add(chunkPos.toLong())) {
-                    levelChunk.setLoaded(true);
-                    levelChunk.registerAllBlockEntitiesAfterLevelLoad();
-                    levelChunk.registerTickContainerInLevel(this.level);
-                }
+    // Paper start
+    // rets true if to prevent the entity from being added
+    public static boolean checkDupeUUID(ServerLevel level, Entity entity) {
+        io.papermc.paper.configuration.WorldConfiguration.Entities.Spawning.DuplicateUUID.DuplicateUUIDMode mode = level.paperConfig().entities.spawning.duplicateUuid.mode;
+        if (mode != io.papermc.paper.configuration.WorldConfiguration.Entities.Spawning.DuplicateUUID.DuplicateUUIDMode.WARN
+            && mode != io.papermc.paper.configuration.WorldConfiguration.Entities.Spawning.DuplicateUUID.DuplicateUUIDMode.DELETE
+            && mode != io.papermc.paper.configuration.WorldConfiguration.Entities.Spawning.DuplicateUUID.DuplicateUUIDMode.SAFE_REGEN) {
+            return false;
+        }
+        Entity other = level.getEntity(entity.getUUID());
 
-                return levelChunk;
-            });
-        }, (task) -> {
-            this.mainThreadMailbox.tell(ChunkTaskPriorityQueueSorter.message(task, chunkHolder.getPos().toLong(), chunkHolder::getTicketLevel));
-        });
-    }
+        if (other == null || other == entity) {
+            return false;
+        }
 
-    public CompletableFuture<Either<LevelChunk, ChunkHolder.ChunkLoadingFailure>> prepareTickingChunk(ChunkHolder holder) {
-        CompletableFuture<Either<List<ChunkAccess>, ChunkHolder.ChunkLoadingFailure>> completableFuture = this.getChunkRangeFuture(holder, 1, (distance) -> {
-            return ChunkStatus.FULL;
-        });
-        CompletableFuture<Either<LevelChunk, ChunkHolder.ChunkLoadingFailure>> completableFuture2 = completableFuture.thenApplyAsync((chunks) -> {
-            return chunks.mapLeft((cs) -> {
-                return (LevelChunk)cs.get(cs.size() / 2);
-            });
-        }, (task) -> {
-            this.mainThreadMailbox.tell(ChunkTaskPriorityQueueSorter.message(holder, task));
-        }).thenApplyAsync((either) -> {
-            return either.ifLeft((chunk) -> {
-                chunk.postProcessGeneration();
-                this.level.startTickingChunk(chunk);
-                CompletableFuture<?> completableFuture = holder.getChunkSendSyncFuture();
-                if (completableFuture.isDone()) {
-                    this.onChunkReadyToSend(chunk);
-                } else {
-                    completableFuture.thenAcceptAsync((v) -> {
-                        this.onChunkReadyToSend(chunk);
-                    }, this.mainThreadExecutor);
+        if (mode == io.papermc.paper.configuration.WorldConfiguration.Entities.Spawning.DuplicateUUID.DuplicateUUIDMode.SAFE_REGEN && other != null && !other.isRemoved()
+            && Objects.equals(other.getEncodeId(), entity.getEncodeId())
+            && entity.getBukkitEntity().getLocation().distance(other.getBukkitEntity().getLocation()) < level.paperConfig().entities.spawning.duplicateUuid.safeRegenDeleteRange
+        ) {
+            if (ServerLevel.DEBUG_ENTITIES) LOGGER.warn("[DUPE-UUID] Duplicate UUID found used by " + other + ", deleted entity " + entity + " because it was near the duplicate and likely an actual duplicate. See https://github.com/PaperMC/Paper/issues/1223 for discussion on what this is about.");
+            entity.discard();
+            return true;
+        }
+        if (other != null && !other.isRemoved()) {
+            switch (mode) {
+                case SAFE_REGEN: {
+                    entity.setUUID(java.util.UUID.randomUUID());
+                    if (ServerLevel.DEBUG_ENTITIES) LOGGER.warn("[DUPE-UUID] Duplicate UUID found used by " + other + ", regenerated UUID for " + entity + ". See https://github.com/PaperMC/Paper/issues/1223 for discussion on what this is about.");
+                    break;
                 }
-
-            });
-        }, this.mainThreadExecutor);
-        completableFuture2.handle((chunk, throwable) -> {
-            this.tickingGenerated.getAndIncrement();
-            return null;
-        });
-        return completableFuture2;
+                case DELETE: {
+                    if (ServerLevel.DEBUG_ENTITIES) LOGGER.warn("[DUPE-UUID] Duplicate UUID found used by " + other + ", deleted entity " + entity + ". See https://github.com/PaperMC/Paper/issues/1223 for discussion on what this is about.");
+                    entity.discard();
+                    return true;
+                }
+                default:
+                    if (ServerLevel.DEBUG_ENTITIES) LOGGER.warn("[DUPE-UUID] Duplicate UUID found used by " + other + ", doing nothing to " + entity + ". See https://github.com/PaperMC/Paper/issues/1223 for discussion on what this is about.");
+                    break;
+            }
+        }
+        return false;
+    }
+    // Paper end
+    public CompletableFuture<Either<LevelChunk, ChunkHolder.ChunkLoadingFailure>> prepareTickingChunk(ChunkHolder holder) {
+        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
     }
 
     private void onChunkReadyToSend(LevelChunk chunk) {
-        ChunkPos chunkPos = chunk.getPos();
-
-        for(ServerPlayer serverPlayer : this.playerMap.getAllPlayers()) {
-            if (serverPlayer.getChunkTrackingView().contains(chunkPos)) {
-                markChunkPendingToSend(serverPlayer, chunk);
-            }
-        }
+        throw new UnsupportedOperationException(); // Paper - rewrite player chunk loader
 
     }
 
     public CompletableFuture<Either<LevelChunk, ChunkHolder.ChunkLoadingFailure>> prepareAccessibleChunk(ChunkHolder holder) {
-        return this.getChunkRangeFuture(holder, 1, ChunkStatus::getStatusAroundFullChunk).thenApplyAsync((either) -> {
-            return either.mapLeft((chunks) -> {
-                return (LevelChunk)chunks.get(chunks.size() / 2);
-            });
-        }, (task) -> {
-            this.mainThreadMailbox.tell(ChunkTaskPriorityQueueSorter.message(holder, task));
-        });
+        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
     }
 
     public int getTickingGenerated() {
@@ -779,209 +653,251 @@
     }
 
     private boolean saveChunkIfNeeded(ChunkHolder chunkHolder) {
-        if (!chunkHolder.wasAccessibleSinceLastSave()) {
-            return false;
-        } else {
-            ChunkAccess chunkAccess = chunkHolder.getChunkToSave().getNow((ChunkAccess)null);
-            if (!(chunkAccess instanceof ImposterProtoChunk) && !(chunkAccess instanceof LevelChunk)) {
-                return false;
-            } else {
-                long l = chunkAccess.getPos().toLong();
-                long m = this.chunkSaveCooldowns.getOrDefault(l, -1L);
-                long n = System.currentTimeMillis();
-                if (n < m) {
-                    return false;
-                } else {
-                    boolean bl = this.save(chunkAccess);
-                    chunkHolder.refreshAccessibility();
-                    if (bl) {
-                        this.chunkSaveCooldowns.put(l, n + 10000L);
-                    }
-
-                    return bl;
-                }
-            }
-        }
+        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
     }
 
     public boolean save(ChunkAccess chunk) {
-        this.poiManager.flush(chunk.getPos());
-        if (!chunk.isUnsaved()) {
-            return false;
-        } else {
-            chunk.setUnsaved(false);
-            ChunkPos chunkPos = chunk.getPos();
-
-            try {
-                ChunkStatus chunkStatus = chunk.getStatus();
-                if (chunkStatus.getChunkType() != ChunkStatus.ChunkType.LEVELCHUNK) {
-                    if (this.isExistingChunkFull(chunkPos)) {
-                        return false;
-                    }
-
-                    if (chunkStatus == ChunkStatus.EMPTY && chunk.getAllStarts().values().stream().noneMatch(StructureStart::isValid)) {
-                        return false;
-                    }
-                }
-
-                this.level.getProfiler().incrementCounter("chunkSave");
-                CompoundTag compoundTag = ChunkSerializer.write(this.level, chunk);
-                this.write(chunkPos, compoundTag);
-                this.markPosition(chunkPos, chunkStatus.getChunkType());
-                return true;
-            } catch (Exception var5) {
-                LOGGER.error("Failed to save chunk {},{}", chunkPos.x, chunkPos.z, var5);
-                return false;
-            }
-        }
+        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
     }
 
     private boolean isExistingChunkFull(ChunkPos pos) {
-        byte b = this.chunkTypeCache.get(pos.toLong());
-        if (b != 0) {
-            return b == 1;
-        } else {
-            CompoundTag compoundTag;
-            try {
-                compoundTag = this.readChunk(pos).join().orElse((CompoundTag)null);
-                if (compoundTag == null) {
-                    this.markPositionReplaceable(pos);
-                    return false;
-                }
-            } catch (Exception var5) {
-                LOGGER.error("Failed to read chunk {}", pos, var5);
-                this.markPositionReplaceable(pos);
-                return false;
-            }
+        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
+    }
 
-            ChunkStatus.ChunkType chunkType = ChunkSerializer.getChunkTypeFromTag(compoundTag);
-            return this.markPosition(pos, chunkType) == 1;
-        }
+    // Paper start - replace player loader system
+    public void setTickViewDistance(int distance) {
+        this.level.playerChunkLoader.setTickDistance(distance);
     }
 
-    protected void setServerViewDistance(int watchDistance) {
-        int i = Mth.clamp(watchDistance, 2, 32);
-        if (i != this.serverViewDistance) {
-            this.serverViewDistance = i;
-            this.distanceManager.updatePlayerTickets(this.serverViewDistance);
+    public void setSendViewDistance(int distance) {
+        this.level.playerChunkLoader.setSendDistance(distance);
+    }
+    // Paper end - replace player loader system
 
-            for(ServerPlayer serverPlayer : this.playerMap.getAllPlayers()) {
-                this.updateChunkTracking(serverPlayer);
-            }
+    public void setServerViewDistance(int watchDistance) { // Paper - replace player loader system
+        int j = Mth.clamp(watchDistance, 2, 32);
+
+        if (j != this.serverViewDistance) {
+            this.serverViewDistance = j;
+            this.level.playerChunkLoader.setLoadDistance(this.serverViewDistance + 1); // Paper - replace player loader system
         }
 
     }
 
     int getPlayerViewDistance(ServerPlayer player) {
-        return Mth.clamp(player.requestedViewDistance(), 2, this.serverViewDistance);
+        return io.papermc.paper.chunk.system.ChunkSystem.getSendViewDistance(player); // Paper - per player view distance
     }
 
     private void markChunkPendingToSend(ServerPlayer player, ChunkPos pos) {
-        LevelChunk levelChunk = this.getChunkToSend(pos.toLong());
-        if (levelChunk != null) {
-            markChunkPendingToSend(player, levelChunk);
-        }
+        throw new UnsupportedOperationException(); // Paper - per player view distance
 
     }
 
     private static void markChunkPendingToSend(ServerPlayer player, LevelChunk chunk) {
-        player.connection.chunkSender.markChunkPendingToSend(chunk);
+        throw new UnsupportedOperationException(); // Paper - rewrite player chunk loader
     }
 
     private static void dropChunk(ServerPlayer player, ChunkPos pos) {
-        player.connection.chunkSender.dropChunk(player, pos);
+        // Paper - rewrite player chunk loader
     }
 
     @Nullable
     public LevelChunk getChunkToSend(long pos) {
-        ChunkHolder chunkHolder = this.getVisibleChunkIfPresent(pos);
-        return chunkHolder == null ? null : chunkHolder.getChunkToSend();
+        ChunkHolder playerchunk = this.getVisibleChunkIfPresent(pos);
+
+        return playerchunk == null ? null : playerchunk.getChunkToSend();
     }
 
     public int size() {
-        return this.visibleChunkMap.size();
+        return io.papermc.paper.chunk.system.ChunkSystem.getVisibleChunkHolderCount(this.level); // Paper
     }
 
-    public net.minecraft.server.level.DistanceManager getDistanceManager() {
+    public DistanceManager getDistanceManager() {
         return this.distanceManager;
     }
 
     protected Iterable<ChunkHolder> getChunks() {
-        return Iterables.unmodifiableIterable(this.visibleChunkMap.values());
+        return Iterables.unmodifiableIterable(io.papermc.paper.chunk.system.ChunkSystem.getVisibleChunkHolders(this.level)); // Paper
     }
 
     void dumpChunks(Writer writer) throws IOException {
-        CsvOutput csvOutput = CsvOutput.builder().addColumn("x").addColumn("z").addColumn("level").addColumn("in_memory").addColumn("status").addColumn("full_status").addColumn("accessible_ready").addColumn("ticking_ready").addColumn("entity_ticking_ready").addColumn("ticket").addColumn("spawning").addColumn("block_entity_count").addColumn("ticking_ticket").addColumn("ticking_level").addColumn("block_ticks").addColumn("fluid_ticks").build(writer);
-        TickingTracker tickingTracker = this.distanceManager.tickingTracker();
-
-        for(Long2ObjectMap.Entry<ChunkHolder> entry : this.visibleChunkMap.long2ObjectEntrySet()) {
-            long l = entry.getLongKey();
-            ChunkPos chunkPos = new ChunkPos(l);
-            ChunkHolder chunkHolder = entry.getValue();
-            Optional<ChunkAccess> optional = Optional.ofNullable(chunkHolder.getLastAvailable());
-            Optional<LevelChunk> optional2 = optional.flatMap((chunk) -> {
-                return chunk instanceof LevelChunk ? Optional.of((LevelChunk)chunk) : Optional.empty();
-            });
-            csvOutput.writeRow(chunkPos.x, chunkPos.z, chunkHolder.getTicketLevel(), optional.isPresent(), optional.map(ChunkAccess::getStatus).orElse((ChunkStatus)null), optional2.map(LevelChunk::getFullStatus).orElse((FullChunkStatus)null), printFuture(chunkHolder.getFullChunkFuture()), printFuture(chunkHolder.getTickingChunkFuture()), printFuture(chunkHolder.getEntityTickingChunkFuture()), this.distanceManager.getTicketDebugString(l), this.anyPlayerCloseEnoughForSpawning(chunkPos), optional2.map((chunk) -> {
-                return chunk.getBlockEntities().size();
-            }).orElse(0), tickingTracker.getTicketDebugString(l), tickingTracker.getLevel(l), optional2.map((chunk) -> {
-                return chunk.getBlockTicks().count();
-            }).orElse(0), optional2.map((chunk) -> {
-                return chunk.getFluidTicks().count();
-            }).orElse(0));
-        }
-
+        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
     }
 
     private static String printFuture(CompletableFuture<Either<LevelChunk, ChunkHolder.ChunkLoadingFailure>> future) {
         try {
-            Either<LevelChunk, ChunkHolder.ChunkLoadingFailure> either = future.getNow((Either<LevelChunk, ChunkHolder.ChunkLoadingFailure>)null);
-            return either != null ? either.map((chunk) -> {
+            Either<LevelChunk, ChunkHolder.ChunkLoadingFailure> either = (Either) future.getNow(null); // CraftBukkit - decompile error
+
+            return either != null ? (String) either.map((chunk) -> {
                 return "done";
-            }, (unloaded) -> {
+            }, (playerchunk_failure) -> {
                 return "unloaded";
             }) : "not completed";
-        } catch (CompletionException var2) {
-            return "failed " + var2.getCause().getMessage();
-        } catch (CancellationException var3) {
+        } catch (CompletionException completionexception) {
+            return "failed " + completionexception.getCause().getMessage();
+        } catch (CancellationException cancellationexception) {
             return "cancelled";
         }
     }
 
+    // Paper start - Asynchronous chunk io
+    @Nullable
+    @Override
+    public CompoundTag readSync(ChunkPos chunkcoordintpair) throws IOException {
+        // Paper start - rewrite chunk system
+        if (!io.papermc.paper.chunk.system.io.RegionFileIOThread.isRegionFileThread()) {
+            return io.papermc.paper.chunk.system.io.RegionFileIOThread.loadData(
+                this.level, chunkcoordintpair.x, chunkcoordintpair.z, io.papermc.paper.chunk.system.io.RegionFileIOThread.RegionFileType.CHUNK_DATA,
+                io.papermc.paper.chunk.system.io.RegionFileIOThread.getIOBlockingPriorityForCurrentThread()
+            );
+        }
+        // Paper end - rewrite chunk system
+        return super.readSync(chunkcoordintpair);
+    }
+
+    @Override
+    public void write(ChunkPos chunkcoordintpair, CompoundTag nbttagcompound) throws IOException {
+        // Paper start - rewrite chunk system
+        if (!io.papermc.paper.chunk.system.io.RegionFileIOThread.isRegionFileThread()) {
+            io.papermc.paper.chunk.system.io.RegionFileIOThread.scheduleSave(
+                this.level, chunkcoordintpair.x, chunkcoordintpair.z, nbttagcompound,
+                io.papermc.paper.chunk.system.io.RegionFileIOThread.RegionFileType.CHUNK_DATA);
+            return;
+        }
+        // Paper end - rewrite chunk system
+        super.write(chunkcoordintpair, nbttagcompound);
+    }
+    // Paper end
+
     private CompletableFuture<Optional<CompoundTag>> readChunk(ChunkPos chunkPos) {
-        return this.read(chunkPos).thenApplyAsync((nbt) -> {
-            return nbt.map(this::upgradeChunkTag);
-        }, Util.backgroundExecutor());
+        // Paper start - Cache chunk status on disk
+        try {
+            return CompletableFuture.completedFuture(Optional.ofNullable(this.readConvertChunkSync(chunkPos)));
+        } catch (Throwable thr) {
+            return CompletableFuture.failedFuture(thr);
+        }
+        // Paper end - Cache chunk status on disk
     }
 
-    private CompoundTag upgradeChunkTag(CompoundTag nbt) {
-        return this.upgradeChunkTag(this.level.dimension(), this.overworldDataStorage, nbt, this.generator.getTypeNameForDataFixer());
+    // CraftBukkit start
+    private CompoundTag upgradeChunkTag(CompoundTag nbttagcompound, ChunkPos chunkcoordintpair) {
+        return this.upgradeChunkTag(this.level.getTypeKey(), this.overworldDataStorage, nbttagcompound, this.generator.getTypeNameForDataFixer(), chunkcoordintpair, level);
+        // CraftBukkit end
     }
 
-    boolean anyPlayerCloseEnoughForSpawning(ChunkPos pos) {
-        if (!this.distanceManager.hasPlayersNearby(pos.toLong())) {
+    // Paper start - Cache chunk status on disk
+    @Nullable
+    public CompoundTag readConvertChunkSync(ChunkPos pos) throws IOException {
+        CompoundTag nbttagcompound = this.readSync(pos);
+        // Paper start - Cache chunk status on disk
+        if (nbttagcompound == null) {
+            return null;
+        }
+
+        nbttagcompound = this.upgradeChunkTag(nbttagcompound, pos); // CraftBukkit
+        if (nbttagcompound == null) {
+            return null;
+        }
+
+        this.updateChunkStatusOnDisk(pos, nbttagcompound);
+
+        return nbttagcompound;
+        // Paper end
+    }
+
+    // Paper start - chunk status cache "api"
+    public ChunkStatus getChunkStatusOnDiskIfCached(ChunkPos chunkPos) {
+        net.minecraft.world.level.chunk.storage.RegionFile regionFile = regionFileCache.getRegionFileIfLoaded(chunkPos);
+
+        return regionFile == null ? null : regionFile.getStatusIfCached(chunkPos.x, chunkPos.z);
+    }
+
+    public ChunkStatus getChunkStatusOnDisk(ChunkPos chunkPos) throws IOException {
+        net.minecraft.world.level.chunk.storage.RegionFile regionFile = regionFileCache.getRegionFile(chunkPos, true);
+
+        if (regionFile == null || !regionFileCache.chunkExists(chunkPos)) {
+            return null;
+        }
+
+        ChunkStatus status = regionFile.getStatusIfCached(chunkPos.x, chunkPos.z);
+
+        if (status != null) {
+            return status;
+        }
+
+        this.readChunk(chunkPos);
+
+        return regionFile.getStatusIfCached(chunkPos.x, chunkPos.z);
+    }
+
+    public void updateChunkStatusOnDisk(ChunkPos chunkPos, @Nullable CompoundTag compound) throws IOException {
+        net.minecraft.world.level.chunk.storage.RegionFile regionFile = regionFileCache.getRegionFile(chunkPos, false);
+
+        regionFile.setStatus(chunkPos.x, chunkPos.z, ChunkSerializer.getStatus(compound));
+    }
+
+    public ChunkAccess getUnloadingChunk(int chunkX, int chunkZ) {
+        ChunkHolder chunkHolder = io.papermc.paper.chunk.system.ChunkSystem.getUnloadingChunkHolder(this.level, chunkX, chunkZ);
+        return chunkHolder == null ? null : chunkHolder.getAvailableChunkNow();
+    }
+    // Paper end
+
+    public boolean anyPlayerCloseEnoughForSpawning(ChunkPos pos) { // Paper - public
+        // Spigot start
+        return this.anyPlayerCloseEnoughForSpawning(pos, false);
+    }
+
+    boolean anyPlayerCloseEnoughForSpawning(ChunkPos chunkcoordintpair, boolean reducedRange) {
+        int chunkRange = level.spigotConfig.mobSpawnRange;
+        chunkRange = (chunkRange > level.spigotConfig.viewDistance) ? (byte) level.spigotConfig.viewDistance : chunkRange;
+        chunkRange = (chunkRange > 8) ? 8 : chunkRange;
+
+        final int finalChunkRange = chunkRange; // Paper for lambda below
+        //double blockRange = (reducedRange) ? Math.pow(chunkRange << 4, 2) : 16384.0D; // Paper - use from event
+        double blockRange = 16384.0D; // Paper
+        // Spigot end
+        if (!this.distanceManager.hasPlayersNearby(chunkcoordintpair.toLong())) {
             return false;
         } else {
-            for(ServerPlayer serverPlayer : this.playerMap.getAllPlayers()) {
-                if (this.playerIsCloseEnoughForSpawning(serverPlayer, pos)) {
-                    return true;
+            Iterator iterator = this.playerMap.getAllPlayers().iterator();
+
+            ServerPlayer entityplayer;
+
+            do {
+                if (!iterator.hasNext()) {
+                    return false;
                 }
-            }
 
-            return false;
+                entityplayer = (ServerPlayer) iterator.next();
+                // Paper start - add PlayerNaturallySpawnCreaturesEvent
+                com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent event;
+                blockRange = 16384.0D;
+                if (reducedRange) {
+                    event = entityplayer.playerNaturallySpawnedEvent;
+                    if (event == null || event.isCancelled()) return false;
+                    blockRange = (double) ((event.getSpawnRadius() << 4) * (event.getSpawnRadius() << 4));
+                }
+                // Paper end
+            } while (!this.playerIsCloseEnoughForSpawning(entityplayer, chunkcoordintpair, blockRange)); // Spigot
+
+            return true;
         }
     }
 
     public List<ServerPlayer> getPlayersCloseForSpawning(ChunkPos pos) {
-        long l = pos.toLong();
-        if (!this.distanceManager.hasPlayersNearby(l)) {
+        long i = pos.toLong();
+
+        if (!this.distanceManager.hasPlayersNearby(i)) {
             return List.of();
         } else {
-            ImmutableList.Builder<ServerPlayer> builder = ImmutableList.builder();
+            Builder<ServerPlayer> builder = ImmutableList.builder();
+            Iterator iterator = this.playerMap.getAllPlayers().iterator();
 
-            for(ServerPlayer serverPlayer : this.playerMap.getAllPlayers()) {
-                if (this.playerIsCloseEnoughForSpawning(serverPlayer, pos)) {
-                    builder.add(serverPlayer);
+            while (iterator.hasNext()) {
+                ServerPlayer entityplayer = (ServerPlayer) iterator.next();
+
+                if (this.playerIsCloseEnoughForSpawning(entityplayer, pos, 16384.0D)) { // Spigot
+                    builder.add(entityplayer);
                 }
             }
 
@@ -989,12 +905,13 @@
         }
     }
 
-    private boolean playerIsCloseEnoughForSpawning(ServerPlayer player, ChunkPos pos) {
-        if (player.isSpectator()) {
+    private boolean playerIsCloseEnoughForSpawning(ServerPlayer entityplayer, ChunkPos chunkcoordintpair, double range) { // Spigot
+        if (entityplayer.isSpectator()) {
             return false;
         } else {
-            double d = euclideanDistanceSquared(pos, player);
-            return d < 16384.0D;
+            double d0 = ChunkMap.euclideanDistanceSquared(chunkcoordintpair, entityplayer);
+
+            return d0 < range; // Spigot
         }
     }
 
@@ -1003,143 +920,138 @@
     }
 
     void updatePlayerStatus(ServerPlayer player, boolean added) {
-        boolean bl = this.skipPlayer(player);
-        boolean bl2 = this.playerMap.ignoredOrUnknown(player);
+        boolean flag1 = this.skipPlayer(player);
+        boolean flag2 = this.playerMap.ignoredOrUnknown(player);
+
         if (added) {
-            this.playerMap.addPlayer(player, bl);
+            this.playerMap.addPlayer(player, flag1);
             this.updatePlayerPos(player);
-            if (!bl) {
-                this.distanceManager.addPlayer(SectionPos.of(player), player);
+            if (!flag1) {
+                this.distanceManager.addPlayer(SectionPos.of((EntityAccess) player), player);
             }
 
-            player.setChunkTrackingView(ChunkTrackingView.EMPTY);
-            this.updateChunkTracking(player);
+            // Paper - handled by player chunk loader
+            this.addPlayerToDistanceMaps(player); // Paper - distance maps
         } else {
-            SectionPos sectionPos = player.getLastSectionPos();
+            SectionPos sectionposition = player.getLastSectionPos();
+
             this.playerMap.removePlayer(player);
-            if (!bl2) {
-                this.distanceManager.removePlayer(sectionPos, player);
+            if (!flag2) {
+                this.distanceManager.removePlayer(sectionposition, player);
             }
 
-            this.applyChunkTrackingView(player, ChunkTrackingView.EMPTY);
+            this.removePlayerFromDistanceMaps(player); // Paper - distance maps
+            // Paper - handled by player chunk loader
         }
 
     }
 
     private void updatePlayerPos(ServerPlayer player) {
-        SectionPos sectionPos = SectionPos.of(player);
-        player.setLastSectionPos(sectionPos);
+        SectionPos sectionposition = SectionPos.of((EntityAccess) player);
+
+        player.setLastSectionPos(sectionposition);
     }
 
     public void move(ServerPlayer player) {
-        for(ChunkMap.TrackedEntity trackedEntity : this.entityMap.values()) {
-            if (trackedEntity.entity == player) {
-                trackedEntity.updatePlayers(this.level.players());
+        ObjectIterator objectiterator = this.entityMap.values().iterator();
+
+        while (objectiterator.hasNext()) {
+            ChunkMap.TrackedEntity playerchunkmap_entitytracker = (ChunkMap.TrackedEntity) objectiterator.next();
+
+            if (playerchunkmap_entitytracker.entity == player) {
+                playerchunkmap_entitytracker.updatePlayers(this.level.players());
             } else {
-                trackedEntity.updatePlayer(player);
+                playerchunkmap_entitytracker.updatePlayer(player);
             }
         }
 
-        SectionPos sectionPos = player.getLastSectionPos();
-        SectionPos sectionPos2 = SectionPos.of(player);
-        boolean bl = this.playerMap.ignored(player);
-        boolean bl2 = this.skipPlayer(player);
-        boolean bl3 = sectionPos.asLong() != sectionPos2.asLong();
-        if (bl3 || bl != bl2) {
+        SectionPos sectionposition = player.getLastSectionPos();
+        SectionPos sectionposition1 = SectionPos.of((EntityAccess) player);
+        boolean flag = this.playerMap.ignored(player);
+        boolean flag1 = this.skipPlayer(player);
+        boolean flag2 = sectionposition.asLong() != sectionposition1.asLong();
+
+        if (flag2 || flag != flag1) {
             this.updatePlayerPos(player);
-            if (!bl) {
-                this.distanceManager.removePlayer(sectionPos, player);
+            if (!flag) {
+                this.distanceManager.removePlayer(sectionposition, player);
             }
 
-            if (!bl2) {
-                this.distanceManager.addPlayer(sectionPos2, player);
+            if (!flag1) {
+                this.distanceManager.addPlayer(sectionposition1, player);
             }
 
-            if (!bl && bl2) {
+            if (!flag && flag1) {
                 this.playerMap.ignorePlayer(player);
             }
 
-            if (bl && !bl2) {
+            if (flag && !flag1) {
                 this.playerMap.unIgnorePlayer(player);
             }
 
-            this.updateChunkTracking(player);
+            // Paper - replaced by PlayerChunkLoader
         }
 
+        this.updateMaps(player); // Paper - distance maps
     }
 
     private void updateChunkTracking(ServerPlayer player) {
-        ChunkPos chunkPos = player.chunkPosition();
-        int i = this.getPlayerViewDistance(player);
-        ChunkTrackingView var5 = player.getChunkTrackingView();
-        if (var5 instanceof ChunkTrackingView.Positioned positioned) {
-            if (positioned.center().equals(chunkPos) && positioned.viewDistance() == i) {
-                return;
-            }
-        }
-
-        this.applyChunkTrackingView(player, ChunkTrackingView.of(chunkPos, i));
+        throw new UnsupportedOperationException(); // Paper - replaced by PlayerChunkLoader
     }
 
     private void applyChunkTrackingView(ServerPlayer player, ChunkTrackingView chunkFilter) {
-        if (player.level() == this.level) {
-            ChunkTrackingView chunkTrackingView = player.getChunkTrackingView();
-            if (chunkFilter instanceof ChunkTrackingView.Positioned) {
-                label15: {
-                    ChunkTrackingView.Positioned positioned = (ChunkTrackingView.Positioned)chunkFilter;
-                    if (chunkTrackingView instanceof ChunkTrackingView.Positioned) {
-                        ChunkTrackingView.Positioned positioned2 = (ChunkTrackingView.Positioned)chunkTrackingView;
-                        if (positioned2.center().equals(positioned.center())) {
-                            break label15;
-                        }
-                    }
-
-                    player.connection.send(new ClientboundSetChunkCacheCenterPacket(positioned.center().x, positioned.center().z));
-                }
-            }
-
-            ChunkTrackingView.difference(chunkTrackingView, chunkFilter, (chunkPos) -> {
-                this.markChunkPendingToSend(player, chunkPos);
-            }, (chunkPos) -> {
-                dropChunk(player, chunkPos);
-            });
-            player.setChunkTrackingView(chunkFilter);
-        }
+        throw new UnsupportedOperationException(); // Paper - replaced by PlayerChunkLoader
     }
 
     @Override
     public List<ServerPlayer> getPlayers(ChunkPos chunkPos, boolean onlyOnWatchDistanceEdge) {
-        Set<ServerPlayer> set = this.playerMap.getAllPlayers();
-        ImmutableList.Builder<ServerPlayer> builder = ImmutableList.builder();
-
-        for(ServerPlayer serverPlayer : set) {
-            if (onlyOnWatchDistanceEdge && this.isChunkOnTrackedBorder(serverPlayer, chunkPos.x, chunkPos.z) || !onlyOnWatchDistanceEdge && this.isChunkTracked(serverPlayer, chunkPos.x, chunkPos.z)) {
-                builder.add(serverPlayer);
-            }
+        // Paper start - per player view distance
+        ChunkHolder holder = this.getVisibleChunkIfPresent(chunkPos.toLong());
+        if (holder == null) {
+            return new java.util.ArrayList<>();
+        } else {
+            return holder.getPlayers(onlyOnWatchDistanceEdge);
         }
-
-        return builder.build();
+        // Paper end - per player view distance
     }
 
     public void addEntity(Entity entity) {
+        org.spigotmc.AsyncCatcher.catchOp("entity track"); // Spigot
+        // Paper start - ignore and warn about illegal addEntity calls instead of crashing server
+        if (!entity.valid || entity.level() != this.level || this.entityMap.containsKey(entity.getId())) {
+            LOGGER.error("Illegal ChunkMap::addEntity for world " + this.level.getWorld().getName()
+                + ": " + entity  + (this.entityMap.containsKey(entity.getId()) ? " ALREADY CONTAINED (This would have crashed your server)" : ""), new Throwable());
+            return;
+        }
+        if (entity instanceof ServerPlayer && ((ServerPlayer) entity).supressTrackerForLogin) return; // Delay adding to tracker until after list packets
+        // Paper end
         if (!(entity instanceof EnderDragonPart)) {
-            EntityType<?> entityType = entity.getType();
-            int i = entityType.clientTrackingRange() * 16;
+            EntityType<?> entitytypes = entity.getType();
+            int i = entitytypes.clientTrackingRange() * 16;
+            i = org.spigotmc.TrackingRange.getEntityTrackingRange(entity, i); // Spigot
+
             if (i != 0) {
-                int j = entityType.updateInterval();
+                int j = entitytypes.updateInterval();
+
                 if (this.entityMap.containsKey(entity.getId())) {
-                    throw (IllegalStateException)Util.pauseInIde(new IllegalStateException("Entity is already tracked!"));
+                    throw (IllegalStateException) Util.pauseInIde(new IllegalStateException("Entity is already tracked!"));
                 } else {
-                    ChunkMap.TrackedEntity trackedEntity = new ChunkMap.TrackedEntity(entity, i, j, entityType.trackDeltas());
-                    this.entityMap.put(entity.getId(), trackedEntity);
-                    trackedEntity.updatePlayers(this.level.players());
+                    ChunkMap.TrackedEntity playerchunkmap_entitytracker = new ChunkMap.TrackedEntity(entity, i, j, entitytypes.trackDeltas());
+
+                    entity.tracker = playerchunkmap_entitytracker; // Paper - Fast access to tracker
+                    this.entityMap.put(entity.getId(), playerchunkmap_entitytracker);
+                    playerchunkmap_entitytracker.updatePlayers(this.level.players());
                     if (entity instanceof ServerPlayer) {
-                        ServerPlayer serverPlayer = (ServerPlayer)entity;
-                        this.updatePlayerStatus(serverPlayer, true);
+                        ServerPlayer entityplayer = (ServerPlayer) entity;
 
-                        for(ChunkMap.TrackedEntity trackedEntity2 : this.entityMap.values()) {
-                            if (trackedEntity2.entity != serverPlayer) {
-                                trackedEntity2.updatePlayer(serverPlayer);
+                        this.updatePlayerStatus(entityplayer, true);
+                        ObjectIterator objectiterator = this.entityMap.values().iterator();
+
+                        while (objectiterator.hasNext()) {
+                            ChunkMap.TrackedEntity playerchunkmap_entitytracker1 = (ChunkMap.TrackedEntity) objectiterator.next();
+
+                            if (playerchunkmap_entitytracker1.entity != entityplayer) {
+                                playerchunkmap_entitytracker1.updatePlayer(entityplayer);
                             }
                         }
                     }
@@ -1150,93 +1062,122 @@
     }
 
     protected void removeEntity(Entity entity) {
-        if (entity instanceof ServerPlayer serverPlayer) {
-            this.updatePlayerStatus(serverPlayer, false);
+        org.spigotmc.AsyncCatcher.catchOp("entity untrack"); // Spigot
+        if (entity instanceof ServerPlayer) {
+            ServerPlayer entityplayer = (ServerPlayer) entity;
 
-            for(ChunkMap.TrackedEntity trackedEntity : this.entityMap.values()) {
-                trackedEntity.removePlayer(serverPlayer);
+            this.updatePlayerStatus(entityplayer, false);
+            ObjectIterator objectiterator = this.entityMap.values().iterator();
+
+            while (objectiterator.hasNext()) {
+                ChunkMap.TrackedEntity playerchunkmap_entitytracker = (ChunkMap.TrackedEntity) objectiterator.next();
+
+                playerchunkmap_entitytracker.removePlayer(entityplayer);
             }
         }
 
-        ChunkMap.TrackedEntity trackedEntity2 = this.entityMap.remove(entity.getId());
-        if (trackedEntity2 != null) {
-            trackedEntity2.broadcastRemoved();
-        }
+        ChunkMap.TrackedEntity playerchunkmap_entitytracker1 = (ChunkMap.TrackedEntity) this.entityMap.remove(entity.getId());
 
+        if (playerchunkmap_entitytracker1 != null) {
+            playerchunkmap_entitytracker1.broadcastRemoved();
+        }
+        entity.tracker = null; // Paper - We're no longer tracked
     }
 
     protected void tick() {
-        for(ServerPlayer serverPlayer : this.playerMap.getAllPlayers()) {
-            this.updateChunkTracking(serverPlayer);
-        }
+        // Paper - replaced by PlayerChunkLoader
 
         List<ServerPlayer> list = Lists.newArrayList();
-        List<ServerPlayer> list2 = this.level.players();
+        List<ServerPlayer> list1 = this.level.players();
+        ObjectIterator objectiterator = this.entityMap.values().iterator();
+        level.timings.tracker1.startTiming(); // Paper
+
+        ChunkMap.TrackedEntity playerchunkmap_entitytracker;
+
+        while (objectiterator.hasNext()) {
+            playerchunkmap_entitytracker = (ChunkMap.TrackedEntity) objectiterator.next();
+            SectionPos sectionposition = playerchunkmap_entitytracker.lastSectionPos;
+            SectionPos sectionposition1 = SectionPos.of((EntityAccess) playerchunkmap_entitytracker.entity);
+            boolean flag = !Objects.equals(sectionposition, sectionposition1);
+
+            if (flag) {
+                playerchunkmap_entitytracker.updatePlayers(list1);
+                Entity entity = playerchunkmap_entitytracker.entity;
 
-        for(ChunkMap.TrackedEntity trackedEntity : this.entityMap.values()) {
-            SectionPos sectionPos = trackedEntity.lastSectionPos;
-            SectionPos sectionPos2 = SectionPos.of(trackedEntity.entity);
-            boolean bl = !Objects.equals(sectionPos, sectionPos2);
-            if (bl) {
-                trackedEntity.updatePlayers(list2);
-                Entity entity = trackedEntity.entity;
                 if (entity instanceof ServerPlayer) {
-                    list.add((ServerPlayer)entity);
+                    list.add((ServerPlayer) entity);
                 }
 
-                trackedEntity.lastSectionPos = sectionPos2;
+                playerchunkmap_entitytracker.lastSectionPos = sectionposition1;
             }
 
-            if (bl || this.distanceManager.inEntityTickingRange(sectionPos2.chunk().toLong())) {
-                trackedEntity.serverEntity.sendChanges();
+            if (flag || this.distanceManager.inEntityTickingRange(sectionposition1.chunk().toLong())) {
+                playerchunkmap_entitytracker.serverEntity.sendChanges();
             }
         }
+        level.timings.tracker1.stopTiming(); // Paper
 
         if (!list.isEmpty()) {
-            for(ChunkMap.TrackedEntity trackedEntity2 : this.entityMap.values()) {
-                trackedEntity2.updatePlayers(list);
+            objectiterator = this.entityMap.values().iterator();
+
+            level.timings.tracker2.startTiming(); // Paper
+            while (objectiterator.hasNext()) {
+                playerchunkmap_entitytracker = (ChunkMap.TrackedEntity) objectiterator.next();
+                playerchunkmap_entitytracker.updatePlayers(list);
             }
+            level.timings.tracker2.stopTiming(); // Paper
         }
 
     }
 
     public void broadcast(Entity entity, Packet<?> packet) {
-        ChunkMap.TrackedEntity trackedEntity = this.entityMap.get(entity.getId());
-        if (trackedEntity != null) {
-            trackedEntity.broadcast(packet);
+        ChunkMap.TrackedEntity playerchunkmap_entitytracker = (ChunkMap.TrackedEntity) this.entityMap.get(entity.getId());
+
+        if (playerchunkmap_entitytracker != null) {
+            playerchunkmap_entitytracker.broadcast(packet);
         }
 
     }
 
     protected void broadcastAndSend(Entity entity, Packet<?> packet) {
-        ChunkMap.TrackedEntity trackedEntity = this.entityMap.get(entity.getId());
-        if (trackedEntity != null) {
-            trackedEntity.broadcastAndSend(packet);
+        ChunkMap.TrackedEntity playerchunkmap_entitytracker = (ChunkMap.TrackedEntity) this.entityMap.get(entity.getId());
+
+        if (playerchunkmap_entitytracker != null) {
+            playerchunkmap_entitytracker.broadcastAndSend(packet);
         }
 
     }
 
     public void resendBiomesForChunks(List<ChunkAccess> chunks) {
-        Map<ServerPlayer, List<LevelChunk>> map = new HashMap<>();
+        Map<ServerPlayer, List<LevelChunk>> map = new HashMap();
+        Iterator iterator = chunks.iterator();
 
-        for(ChunkAccess chunkAccess : chunks) {
-            ChunkPos chunkPos = chunkAccess.getPos();
-            LevelChunk levelChunk2;
-            if (chunkAccess instanceof LevelChunk levelChunk) {
-                levelChunk2 = levelChunk;
+        while (iterator.hasNext()) {
+            ChunkAccess ichunkaccess = (ChunkAccess) iterator.next();
+            ChunkPos chunkcoordintpair = ichunkaccess.getPos();
+            LevelChunk chunk;
+
+            if (ichunkaccess instanceof LevelChunk) {
+                LevelChunk chunk1 = (LevelChunk) ichunkaccess;
+
+                chunk = chunk1;
             } else {
-                levelChunk2 = this.level.getChunk(chunkPos.x, chunkPos.z);
+                chunk = this.level.getChunk(chunkcoordintpair.x, chunkcoordintpair.z);
             }
 
-            for(ServerPlayer serverPlayer : this.getPlayers(chunkPos, false)) {
-                map.computeIfAbsent(serverPlayer, (player) -> {
+            Iterator iterator1 = this.getPlayers(chunkcoordintpair, false).iterator();
+
+            while (iterator1.hasNext()) {
+                ServerPlayer entityplayer = (ServerPlayer) iterator1.next();
+
+                ((List) map.computeIfAbsent(entityplayer, (entityplayer1) -> {
                     return new ArrayList();
-                }).add(levelChunk2);
+                })).add(chunk);
             }
         }
 
-        map.forEach((player, chunksx) -> {
-            player.connection.send(ClientboundChunksBiomesPacket.forChunks(chunksx));
+        map.forEach((entityplayer1, list1) -> {
+            entityplayer1.connection.send(ClientboundChunksBiomesPacket.forChunks(list1));
         });
     }
 
@@ -1253,24 +1194,18 @@
     }
 
     public void waitForLightBeforeSending(ChunkPos centerPos, int radius) {
-        int i = radius + 1;
-        ChunkPos.rangeClosed(centerPos, i).forEach((pos) -> {
-            ChunkHolder chunkHolder = this.getVisibleChunkIfPresent(pos.toLong());
-            if (chunkHolder != null) {
-                chunkHolder.addSendDependency(this.lightEngine.waitForPendingTasks(pos.x, pos.z));
-            }
-
-        });
+        // Paper - rewrite player chunk loader
     }
 
-    class DistanceManager extends net.minecraft.server.level.DistanceManager {
-        protected DistanceManager(Executor workerExecutor, Executor mainThreadExecutor) {
-            super(workerExecutor, mainThreadExecutor);
+    private class ChunkDistanceManager extends DistanceManager {
+
+        protected ChunkDistanceManager(Executor workerExecutor, Executor mainThreadExecutor) {
+            super(workerExecutor, mainThreadExecutor, ChunkMap.this);
         }
 
         @Override
         protected boolean isChunkToRemove(long pos) {
-            return ChunkMap.this.toDrop.contains(pos);
+            throw new UnsupportedOperationException(); // Paper - rewrite chunk system
         }
 
         @Nullable
@@ -1281,42 +1216,41 @@
 
         @Nullable
         @Override
-        protected ChunkHolder updateChunkScheduling(long pos, int level, @Nullable ChunkHolder holder, int i) {
-            return ChunkMap.this.updateChunkScheduling(pos, level, holder, i);
+        protected ChunkHolder updateChunkScheduling(long pos, int level, @Nullable ChunkHolder holder, int k) {
+            return ChunkMap.this.updateChunkScheduling(pos, level, holder, k);
         }
     }
 
     public class TrackedEntity {
+
         public final ServerEntity serverEntity;
         final Entity entity;
         private final int range;
         SectionPos lastSectionPos;
-        public final Set<ServerPlayerConnection> seenBy = Sets.newIdentityHashSet();
+        public final Set<ServerPlayerConnection> seenBy = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(); // Paper - optimise map impl
 
-        public TrackedEntity(Entity entity, int maxDistance, int tickInterval, boolean alwaysUpdateVelocity) {
-            this.serverEntity = new ServerEntity(ChunkMap.this.level, entity, tickInterval, alwaysUpdateVelocity, this::broadcast);
+        public TrackedEntity(Entity entity, int i, int j, boolean flag) {
+            this.serverEntity = new ServerEntity(ChunkMap.this.level, entity, j, flag, this::broadcast, this.seenBy); // CraftBukkit
             this.entity = entity;
-            this.range = maxDistance;
-            this.lastSectionPos = SectionPos.of(entity);
+            this.range = i;
+            this.lastSectionPos = SectionPos.of((EntityAccess) entity);
         }
 
-        @Override
         public boolean equals(Object object) {
-            if (object instanceof ChunkMap.TrackedEntity) {
-                return ((ChunkMap.TrackedEntity)object).entity.getId() == this.entity.getId();
-            } else {
-                return false;
-            }
+            return object instanceof ChunkMap.TrackedEntity ? ((ChunkMap.TrackedEntity) object).entity.getId() == this.entity.getId() : false;
         }
 
-        @Override
         public int hashCode() {
             return this.entity.getId();
         }
 
         public void broadcast(Packet<?> packet) {
-            for(ServerPlayerConnection serverPlayerConnection : this.seenBy) {
-                serverPlayerConnection.send(packet);
+            Iterator iterator = this.seenBy.iterator();
+
+            while (iterator.hasNext()) {
+                ServerPlayerConnection serverplayerconnection = (ServerPlayerConnection) iterator.next();
+
+                serverplayerconnection.send(packet);
             }
 
         }
@@ -1324,19 +1258,24 @@
         public void broadcastAndSend(Packet<?> packet) {
             this.broadcast(packet);
             if (this.entity instanceof ServerPlayer) {
-                ((ServerPlayer)this.entity).connection.send(packet);
+                ((ServerPlayer) this.entity).connection.send(packet);
             }
 
         }
 
         public void broadcastRemoved() {
-            for(ServerPlayerConnection serverPlayerConnection : this.seenBy) {
-                this.serverEntity.removePairing(serverPlayerConnection.getPlayer());
+            Iterator iterator = this.seenBy.iterator();
+
+            while (iterator.hasNext()) {
+                ServerPlayerConnection serverplayerconnection = (ServerPlayerConnection) iterator.next();
+
+                this.serverEntity.removePairing(serverplayerconnection.getPlayer());
             }
 
         }
 
         public void removePlayer(ServerPlayer player) {
+            org.spigotmc.AsyncCatcher.catchOp("player tracker clear"); // Spigot
             if (this.seenBy.remove(player.connection)) {
                 this.serverEntity.removePairing(player);
             }
@@ -1344,18 +1283,43 @@
         }
 
         public void updatePlayer(ServerPlayer player) {
+            org.spigotmc.AsyncCatcher.catchOp("player tracker update"); // Spigot
             if (player != this.entity) {
-                Vec3 vec3 = player.position().subtract(this.entity.position());
+                Vec3 vec3d = player.position().subtract(this.entity.position());
                 int i = ChunkMap.this.getPlayerViewDistance(player);
-                double d = (double)Math.min(this.getEffectiveRange(), i * 16);
-                double e = vec3.x * vec3.x + vec3.z * vec3.z;
-                double f = d * d;
-                boolean bl = e <= f && this.entity.broadcastToPlayer(player) && ChunkMap.this.isChunkTracked(player, this.entity.chunkPosition().x, this.entity.chunkPosition().z);
-                if (bl) {
+                double d0 = (double) Math.min(this.getEffectiveRange(), i * 16);
+                double d1 = vec3d.x * vec3d.x + vec3d.z * vec3d.z;
+                double d2 = d0 * d0;
+                boolean flag = d1 <= d2 && this.entity.broadcastToPlayer(player) && ChunkMap.this.isChunkTracked(player, this.entity.chunkPosition().x, this.entity.chunkPosition().z);
+                // Paper start - check Y
+                if (flag && level.paperConfig().entities.trackingRangeY.enabled) {
+                    double rangeY = level.paperConfig().entities.trackingRangeY.get(this.entity, -1);
+                    if (rangeY != -1) {
+                        double vec3d_dy = player.getY() - this.entity.getY();
+                        flag = vec3d_dy * vec3d_dy <= rangeY * rangeY;
+                    }
+                }
+                // Paper end - check Y
+
+                // CraftBukkit start - respect vanish API
+                if (!player.getBukkitEntity().canSee(this.entity.getBukkitEntity())) {
+                    flag = false;
+                }
+                // CraftBukkit end
+                if (flag) {
                     if (this.seenBy.add(player.connection)) {
+                        // Paper start - entity tracking events
+                        if (io.papermc.paper.event.player.PlayerTrackEntityEvent.getHandlerList().getRegisteredListeners().length == 0 || new io.papermc.paper.event.player.PlayerTrackEntityEvent(player.getBukkitEntity(), this.entity.getBukkitEntity()).callEvent()) {
                         this.serverEntity.addPairing(player);
+                        }
+                        // Paper end
                     }
                 } else if (this.seenBy.remove(player.connection)) {
+                    // Paper start - entity tracking events
+                    if (io.papermc.paper.event.player.PlayerUntrackEntityEvent.getHandlerList().getRegisteredListeners().length > 0) {
+                        new io.papermc.paper.event.player.PlayerUntrackEntityEvent(player.getBukkitEntity(), this.entity.getBukkitEntity()).callEvent();
+                    }
+                    // Paper end
                     this.serverEntity.removePairing(player);
                 }
 
@@ -1368,9 +1332,13 @@
 
         private int getEffectiveRange() {
             int i = this.range;
+            Iterator iterator = this.entity.getIndirectPassengers().iterator();
 
-            for(Entity entity : this.entity.getIndirectPassengers()) {
+            while (iterator.hasNext()) {
+                Entity entity = (Entity) iterator.next();
                 int j = entity.getType().clientTrackingRange() * 16;
+                j = org.spigotmc.TrackingRange.getEntityTrackingRange(entity, j); // Paper
+
                 if (j > i) {
                     i = j;
                 }
@@ -1380,8 +1348,12 @@
         }
 
         public void updatePlayers(List<ServerPlayer> players) {
-            for(ServerPlayer serverPlayer : players) {
-                this.updatePlayer(serverPlayer);
+            Iterator iterator = players.iterator();
+
+            while (iterator.hasNext()) {
+                ServerPlayer entityplayer = (ServerPlayer) iterator.next();
+
+                this.updatePlayer(entityplayer);
             }
 
         }
