--- a/net/minecraft/server/level/ChunkMap.java
+++ b/net/minecraft/server/level/ChunkMap.java
@@ -1,11 +1,13 @@
 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;
-import com.google.common.collect.ImmutableList.Builder;
 import com.google.gson.JsonElement;
 import com.mojang.datafixers.DataFixer;
 import com.mojang.logging.LogUtils;
@@ -18,16 +20,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.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.longs.Long2ObjectMap.Entry;
+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;
@@ -50,6 +54,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.BuiltInRegistries;
@@ -88,9 +93,11 @@
 import net.minecraft.world.level.chunk.storage.ChunkStorage;
 import net.minecraft.world.level.chunk.storage.RegionStorageInfo;
 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;
+import net.minecraft.world.level.levelgen.blending.BlendingData;
 import net.minecraft.world.level.levelgen.structure.StructureStart;
 import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager;
 import net.minecraft.world.level.storage.DimensionDataStorage;
@@ -99,7 +106,12 @@
 import org.apache.commons.lang3.mutable.MutableBoolean;
 import org.slf4j.Logger;
 
+// CraftBukkit start
+import org.bukkit.craftbukkit.generator.CustomChunkGenerator;
+// 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;
@@ -110,91 +122,236 @@
     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();
+    public final ChunkMap.ChunkDistanceManager distanceManager;
+    public final AtomicInteger tickingGenerated; // Paper - public
     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;
     public int serverViewDistance;
-    private WorldGenContext worldGenContext;
+    private WorldGenContext worldGenContext; public final WorldGenContext getWorldGenContext() { return this.worldGenContext; } // Paper - rewrite chunk system
 
-    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(
-            new RegionStorageInfo(session.getLevelId(), world.dimension(), "chunk"),
-            session.getDimensionPath(world.dimension()).resolve("region"),
-            dataFixer,
-            dsync
-        );
+    // Paper start - distance maps
+    private final com.destroystokyo.paper.util.misc.PooledLinkedHashSets<ServerPlayer> pooledLinkedPlayerHashSets = new com.destroystokyo.paper.util.misc.PooledLinkedHashSets<>();
+    // Paper start - use distance map to optimise tracker
+    public static boolean isLegacyTrackingEntity(Entity entity) {
+        return entity.isLegacyTrackingEntity;
+    }
+
+    // inlined EnumMap, TrackingRange.TrackingRangeType
+    static final org.spigotmc.TrackingRange.TrackingRangeType[] TRACKING_RANGE_TYPES = org.spigotmc.TrackingRange.TrackingRangeType.values();
+    public final com.destroystokyo.paper.util.misc.PlayerAreaMap[] playerEntityTrackerTrackMaps;
+    final int[] entityTrackerTrackRanges;
+    public final int getEntityTrackerRange(final int ordinal) {
+        return this.entityTrackerTrackRanges[ordinal];
+    }
+
+    private int convertSpigotRangeToVanilla(final int vanilla) {
+        return net.minecraft.server.MinecraftServer.getServer().getScaledTrackingDistance(vanilla);
+    }
+    // Paper end - use distance map to optimise tracker
+
+    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
+        // Paper start - use distance map to optimise entity tracker
+        for (int i = 0, len = TRACKING_RANGE_TYPES.length; i < len; ++i) {
+            com.destroystokyo.paper.util.misc.PlayerAreaMap trackMap = this.playerEntityTrackerTrackMaps[i];
+            int trackRange = this.entityTrackerTrackRanges[i];
+
+            trackMap.add(player, chunkX, chunkZ, Math.min(trackRange, io.papermc.paper.chunk.system.ChunkSystem.getSendViewDistance(player)));
+        }
+        // Paper end - use distance map to optimise entity tracker
+    }
+
+    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
+        // Paper start - use distance map to optimise tracker
+        for (int i = 0, len = TRACKING_RANGE_TYPES.length; i < len; ++i) {
+            this.playerEntityTrackerTrackMaps[i].remove(player);
+        }
+        // Paper end - use distance map to optimise tracker
+        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 start - use distance map to optimise entity tracker
+        for (int i = 0, len = TRACKING_RANGE_TYPES.length; i < len; ++i) {
+            com.destroystokyo.paper.util.misc.PlayerAreaMap trackMap = this.playerEntityTrackerTrackMaps[i];
+            int trackRange = this.entityTrackerTrackRanges[i];
+
+            trackMap.update(player, chunkX, chunkZ, Math.min(trackRange, io.papermc.paper.chunk.system.ChunkSystem.getSendViewDistance(player)));
+        }
+        // Paper end - use distance map to optimise entity tracker
+    }
+    // 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(new RegionStorageInfo(session.getLevelId(), world.dimension(), "chunk"), 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();
         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);
+        RegistryAccess iregistrycustom = world.registryAccess();
+        long j = world.getSeed();
+
+        // CraftBukkit start - SPIGOT-7051: It's a rigged game! Use delegate for random state creation, otherwise it is not so random.
+        ChunkGenerator randomGenerator = chunkGenerator;
+        if (randomGenerator instanceof CustomChunkGenerator customChunkGenerator) {
+            randomGenerator = customChunkGenerator.getDelegate();
+        }
+        if (randomGenerator instanceof NoiseBasedChunkGenerator chunkgeneratorabstract) {
+            // CraftBukkit end
+            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(
-            new RegionStorageInfo(session.getLevelId(), world.dimension(), "poi"), path.resolve("poi"), dataFixer, dsync, registryAccess, world
-        );
+        this.poiManager = new PoiManager(new RegionStorageInfo(session.getLevelId(), world.dimension(), "poi"), path.resolve("poi"), dataFixer, dsync, iregistrycustom, world);
         this.setServerViewDistance(viewDistance);
         this.worldGenContext = new WorldGenContext(world, chunkGenerator, structureTemplateManager, this.lightEngine);
+        // 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 - use distance map to optimise entity tracker
+        this.playerEntityTrackerTrackMaps = new com.destroystokyo.paper.util.misc.PlayerAreaMap[TRACKING_RANGE_TYPES.length];
+        this.entityTrackerTrackRanges = new int[TRACKING_RANGE_TYPES.length];
+
+        org.spigotmc.SpigotWorldConfig spigotWorldConfig = this.level.spigotConfig;
+
+        for (int ordinal = 0, len = TRACKING_RANGE_TYPES.length; ordinal < len; ++ordinal) {
+            org.spigotmc.TrackingRange.TrackingRangeType trackingRangeType = TRACKING_RANGE_TYPES[ordinal];
+            int configuredSpigotValue;
+            switch (trackingRangeType) {
+                case PLAYER:
+                    configuredSpigotValue = spigotWorldConfig.playerTrackingRange;
+                    break;
+                case ANIMAL:
+                    configuredSpigotValue = spigotWorldConfig.animalTrackingRange;
+                    break;
+                case MONSTER:
+                    configuredSpigotValue = spigotWorldConfig.monsterTrackingRange;
+                    break;
+                case MISC:
+                    configuredSpigotValue = spigotWorldConfig.miscTrackingRange;
+                    break;
+                case OTHER:
+                    configuredSpigotValue = spigotWorldConfig.otherTrackingRange;
+                    break;
+                case ENDERDRAGON:
+                    configuredSpigotValue = EntityType.ENDER_DRAGON.clientTrackingRange() * 16;
+                    break;
+                case DISPLAY:
+                    configuredSpigotValue = spigotWorldConfig.displayTrackingRange;
+                    break;
+                default:
+                    throw new IllegalStateException("Missing case for enum " + trackingRangeType);
+            }
+            configuredSpigotValue = convertSpigotRangeToVanilla(configuredSpigotValue);
+
+            int trackRange = (configuredSpigotValue >>> 4) + ((configuredSpigotValue & 15) != 0 ? 1 : 0);
+            this.entityTrackerTrackRanges[ordinal] = trackRange;
+
+            this.playerEntityTrackerTrackMaps[ordinal] = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets);
+        }
+        // Paper end - use distance map to optimise entity tracker
     }
 
+    // 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;
     }
@@ -208,44 +365,76 @@
     }
 
     public void debugReloadGenerator() {
-        DataResult<JsonElement> dataResult = ChunkGenerator.CODEC.encodeStart(JsonOps.INSTANCE, this.generator);
-        DataResult<ChunkGenerator> dataResult2 = dataResult.flatMap(json -> ChunkGenerator.CODEC.parse(JsonOps.INSTANCE, json));
-        dataResult2.ifSuccess(
-            chunkGenerator -> {
-                this.generator = chunkGenerator;
-                this.worldGenContext = new WorldGenContext(
-                    this.worldGenContext.level(), chunkGenerator, this.worldGenContext.structureManager(), this.worldGenContext.lightEngine()
-                );
-            }
-        );
+        DataResult<JsonElement> dataresult = ChunkGenerator.CODEC.encodeStart(JsonOps.INSTANCE, this.generator);
+        DataResult<ChunkGenerator> dataresult1 = dataresult.flatMap((jsonelement) -> {
+            return ChunkGenerator.CODEC.parse(JsonOps.INSTANCE, jsonelement);
+        });
+
+        dataresult1.ifSuccess((chunkgenerator) -> {
+            this.generator = chunkgenerator;
+            this.worldGenContext = new WorldGenContext(this.worldGenContext.level(), chunkgenerator, this.worldGenContext.structureManager(), this.worldGenContext.lightEngine());
+        });
     }
 
-    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 - Optional per player mob spawns
+    public void updatePlayerMobTypeMap(final 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(final ServerPlayer player, final net.minecraft.world.entity.MobCategory mobCategory) {
+        return player.mobCounts[mobCategory.ordinal()] + player.mobBackoffCounts[mobCategory.ordinal()]; // Paper - per player mob count backoff
+    }
+    // Paper end - Optional per player mob spawns
+
+    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() {
@@ -254,535 +443,302 @@
 
     @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: §" + chunkStatus.getIndex() + chunkStatus + "§r\n";
-            }
+            String s = playerchunk.getTicketLevel() + "\n";
+            ChunkStatus chunkstatus = playerchunk.getLastAvailableStatus();
+            ChunkAccess ichunkaccess = playerchunk.getLastAvailable();
 
-            if (chunkAccess != null) {
-                string = string + "Ch: §" + chunkAccess.getStatus().getIndex() + chunkAccess.getStatus() + "§r\n";
+            if (chunkstatus != null) {
+                s = s + "St: \u00a7" + chunkstatus.getIndex() + String.valueOf(chunkstatus) + "\u00a7r\n";
             }
 
-            FullChunkStatus fullChunkStatus = chunkHolder.getFullStatus();
-            string = string + '§' + fullChunkStatus.ordinal() + fullChunkStatus;
-            return string + "§r";
-        }
-    }
-
-    private CompletableFuture<ChunkResult<List<ChunkAccess>>> getChunkRangeFuture(
-        ChunkHolder centerChunk, int margin, IntFunction<ChunkStatus> distanceToStatus
-    ) {
-        if (margin == 0) {
-            ChunkStatus chunkStatus = distanceToStatus.apply(0);
-            return centerChunk.getOrScheduleFuture(chunkStatus, this).thenApply(chunkResult -> chunkResult.map(List::of));
-        } else {
-            List<CompletableFuture<ChunkResult<ChunkAccess>>> 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));
-                    ChunkPos chunkPos2 = new ChunkPos(i + l, j + k);
-                    long n = chunkPos2.toLong();
-                    ChunkHolder chunkHolder = this.getUpdatingChunkIfPresent(n);
-                    if (chunkHolder == null) {
-                        return CompletableFuture.completedFuture(ChunkResult.error(() -> "Unloaded " + chunkPos2));
-                    }
-
-                    ChunkStatus chunkStatus2 = distanceToStatus.apply(m);
-                    CompletableFuture<ChunkResult<ChunkAccess>> completableFuture = chunkHolder.getOrScheduleFuture(chunkStatus2, this);
-                    list2.add(chunkHolder);
-                    list.add(completableFuture);
-                }
+            if (ichunkaccess != null) {
+                s = s + "Ch: \u00a7" + ichunkaccess.getStatus().getIndex() + String.valueOf(ichunkaccess.getStatus()) + "\u00a7r\n";
             }
 
-            CompletableFuture<List<ChunkResult<ChunkAccess>>> completableFuture2 = Util.sequence(list);
-            CompletableFuture<ChunkResult<List<ChunkAccess>>> completableFuture3 = completableFuture2.thenApply(
-                chunks -> {
-                    List<ChunkAccess> listx = Lists.newArrayList();
-                    int l = 0;
+            FullChunkStatus fullchunkstatus = playerchunk.getFullStatus();
 
-                    for (ChunkResult<ChunkAccess> chunkResult : chunks) {
-                        if (chunkResult == null) {
-                            throw this.debugFuturesAndCreateReportedException(new IllegalStateException("At least one of the chunk futures were null"), "n/a");
-                        }
+            s = s + String.valueOf('\u00a7') + fullchunkstatus.ordinal() + String.valueOf(fullchunkstatus);
+            return s + "\u00a7r";
+        }
+    }
 
-                        ChunkAccess chunkAccess = chunkResult.orElse(null);
-                        if (chunkAccess == null) {
-                            int mx = l;
-                            return ChunkResult.error(
-                                () -> "Unloaded " + new ChunkPos(i + mx % (margin * 2 + 1), j + mx / (margin * 2 + 1)) + " " + chunkResult.getError()
-                            );
-                        }
+    private CompletableFuture<ChunkResult<List<ChunkAccess>>> getChunkRangeFuture(ChunkHolder centerChunk, int margin, IntFunction<ChunkStatus> distanceToStatus) {
+        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
+    }
 
-                        listx.add(chunkAccess);
-                        l++;
-                    }
+    public ReportedException debugFuturesAndCreateReportedException(IllegalStateException exception, String details) {
+        StringBuilder stringbuilder = new StringBuilder();
+        Consumer<ChunkHolder> consumer = (playerchunk) -> {
+            playerchunk.getAllFutures().forEach((pair) -> {
+                ChunkStatus chunkstatus = (ChunkStatus) pair.getFirst();
+                CompletableFuture<ChunkResult<ChunkAccess>> completablefuture = (CompletableFuture) pair.getSecond();
 
-                    return ChunkResult.of(listx);
+                if (completablefuture != null && completablefuture.isDone() && completablefuture.join() == null) {
+                    stringbuilder.append(playerchunk.getPos()).append(" - status: ").append(chunkstatus).append(" future: ").append(completablefuture).append(System.lineSeparator());
                 }
-            );
 
-            for (ChunkHolder chunkHolder2 : list2) {
-                chunkHolder2.addSaveDependency("getChunkRangeFuture " + chunkPos + " " + margin, completableFuture3);
-            }
+            });
+        };
 
-            return completableFuture3;
-        }
-    }
+        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");
 
-    public ReportedException debugFuturesAndCreateReportedException(IllegalStateException exception, String details) {
-        StringBuilder stringBuilder = new StringBuilder();
-        Consumer<ChunkHolder> consumer = chunkHolder -> chunkHolder.getAllFutures()
-                .forEach(
-                    pair -> {
-                        ChunkStatus chunkStatus = pair.getFirst();
-                        CompletableFuture<ChunkResult<ChunkAccess>> completableFuture = pair.getSecond();
-                        if (completableFuture != null && completableFuture.isDone() && completableFuture.join() == null) {
-                            stringBuilder.append(chunkHolder.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);
+        crashreportsystemdetails.setDetail("Details", (Object) details);
+        crashreportsystemdetails.setDetail("Futures", (Object) stringbuilder);
+        return new ReportedException(crashreport);
     }
 
     public CompletableFuture<ChunkResult<LevelChunk>> prepareEntityTickingChunk(ChunkHolder chunk) {
-        return this.getChunkRangeFuture(chunk, 2, distance -> ChunkStatus.FULL)
-            .thenApplyAsync(chunkResult -> chunkResult.map(chunks -> (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
     }
 
-    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 -> chunk instanceof ImposterProtoChunk || chunk instanceof LevelChunk)
-                    .filter(this::save)
-                    .forEach(chunk -> mutableBoolean.setTrue());
-            } while (mutableBoolean.isTrue());
+    // Paper start - rewrite chunk system
+    protected void saveIncrementally() {
+        this.level.chunkTaskScheduler.chunkHolderManager.autoSave(); // Paper - rewrite chunk system
+    }
+    // Paper end - - rewrite chunk system
 
-            this.processUnloads(() -> true);
-            this.flushWorker();
-        } else {
-            this.visibleChunkMap.values().forEach(this::saveChunkIfNeeded);
-        }
+    protected void saveAllChunks(boolean flush) {
+        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();
+        this.level.chunkTaskScheduler.chunkHolderManager.processUnloads(); // Paper - rewrite chunk system
 
-        while (k < 20 && shouldKeepTicking.getAsBoolean() && objectIterator.hasNext()) {
-            if (this.saveChunkIfNeeded(objectIterator.next())) {
-                k++;
-            }
-        }
     }
 
     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) {
-                        this.level.unload(levelChunk);
-                    }
-
-                    this.lightEngine.updateChunkStatus(chunk.getPos());
-                    this.lightEngine.tryScheduleUpdate();
-                    this.progressListener.onStatusChange(chunk.getPos(), 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<ChunkResult<ChunkAccess>> schedule(ChunkHolder holder, ChunkStatus requiredStatus) {
-        ChunkPos chunkPos = holder.getPos();
-        if (requiredStatus == ChunkStatus.EMPTY) {
-            return this.scheduleChunkLoad(chunkPos).thenApply(ChunkResult::of);
-        } else {
-            if (requiredStatus == ChunkStatus.LIGHT) {
-                this.distanceManager.addTicket(TicketType.LIGHT, chunkPos, ChunkLevel.byStatus(ChunkStatus.LIGHT), chunkPos);
-            }
-
-            if (!requiredStatus.hasLoadDependencies()) {
-                ChunkAccess chunkAccess = holder.getOrScheduleFuture(requiredStatus.getParent(), this).getNow(ChunkHolder.UNLOADED_CHUNK).orElse(null);
-                if (chunkAccess != null && chunkAccess.getStatus().isOrAfter(requiredStatus)) {
-                    CompletableFuture<ChunkAccess> completableFuture = requiredStatus.load(
-                        this.worldGenContext, chunk -> this.protoChunkToFullChunk(holder, chunk), chunkAccess
-                    );
-                    this.progressListener.onStatusChange(chunkPos, requiredStatus);
-                    return completableFuture.thenApply(ChunkResult::of);
-                }
-            }
-
-            return this.scheduleChunkGeneration(holder, requiredStatus);
-        }
+        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
     }
 
     private CompletableFuture<ChunkAccess> scheduleChunkLoad(ChunkPos pos) {
-        return this.readChunk(pos).thenApply(nbt -> nbt.filter(nbt2 -> {
-                boolean bl = isChunkDataValid(nbt2);
-                if (!bl) {
-                    LOGGER.error("Chunk file at {} is missing level data, skipping", 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 chunkAccess;
-            } else {
-                return this.createEmptyChunk(pos);
-            }
-        }, this.mainThreadExecutor).exceptionallyAsync(throwable -> 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 ChunkAccess handleChunkLoadFailure(Throwable throwable, ChunkPos chunkPos) {
-        Throwable throwable2 = throwable instanceof CompletionException completionException ? completionException.getCause() : throwable;
-        Throwable throwable3 = throwable2 instanceof ReportedException reportedException ? reportedException.getCause() : throwable2;
-        boolean bl = throwable3 instanceof Error;
-        boolean bl2 = throwable3 instanceof IOException || throwable3 instanceof NbtException;
-        if (!bl) {
-            if (!bl2) {
+        Throwable throwable1;
+
+        if (throwable instanceof CompletionException completionexception) {
+            throwable1 = completionexception.getCause();
+        } else {
+            throwable1 = throwable;
+        }
+
+        Throwable throwable2 = throwable1;
+
+        if (throwable2 instanceof ReportedException reportedexception) {
+            throwable1 = reportedexception.getCause();
+        } else {
+            throwable1 = throwable2;
+        }
+
+        Throwable throwable3 = throwable1;
+        boolean flag = throwable3 instanceof Error;
+        boolean flag1 = throwable3 instanceof IOException || throwable3 instanceof NbtException;
+
+        if (!flag) {
+            if (!flag1) {
+                ;
             }
 
-            LOGGER.error("Couldn't load chunk {}", chunkPos, throwable3);
+            ChunkMap.LOGGER.error("Couldn't load chunk {}", chunkPos, throwable3);
             this.level.getServer().reportChunkLoadFailure(chunkPos);
             return this.createEmptyChunk(chunkPos);
         } else {
-            CrashReport crashReport = CrashReport.forThrowable(throwable, "Exception loading chunk");
-            CrashReportCategory crashReportCategory = crashReport.addCategory("Chunk being loaded");
-            crashReportCategory.setDetail("pos", chunkPos);
+            CrashReport crashreport = CrashReport.forThrowable(throwable, "Exception loading chunk");
+            CrashReportCategory crashreportsystemdetails = crashreport.addCategory("Chunk being loaded");
+
+            crashreportsystemdetails.setDetail("pos", (Object) chunkPos);
             this.markPositionReplaceable(chunkPos);
-            throw new ReportedException(crashReport);
+            throw new ReportedException(crashreport);
         }
     }
 
     private ChunkAccess createEmptyChunk(ChunkPos chunkPos) {
         this.markPositionReplaceable(chunkPos);
-        return new ProtoChunk(chunkPos, UpgradeData.EMPTY, this.level, this.level.registryAccess().registryOrThrow(Registries.BIOME), 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, ChunkType type) {
-        return this.chunkTypeCache.put(pos.toLong(), (byte)(type == ChunkType.PROTOCHUNK ? -1 : 1));
+        return this.chunkTypeCache.put(pos.toLong(), (byte) (type == ChunkType.PROTOCHUNK ? -1 : 1));
     }
 
     private CompletableFuture<ChunkResult<ChunkAccess>> scheduleChunkGeneration(ChunkHolder holder, ChunkStatus requiredStatus) {
-        ChunkPos chunkPos = holder.getPos();
-        CompletableFuture<ChunkResult<List<ChunkAccess>>> completableFuture = this.getChunkRangeFuture(
-            holder, requiredStatus.getRange(), distance -> this.getDependencyStatus(requiredStatus, distance)
-        );
-        this.level.getProfiler().incrementCounter(() -> "chunkGenerate " + requiredStatus);
-        Executor executor = task -> this.worldgenMailbox.tell(ChunkTaskPriorityQueueSorter.message(holder, task));
-        return completableFuture.thenComposeAsync(
-            chunkResult -> {
-                List<ChunkAccess> list = chunkResult.orElse(null);
-                if (list == null) {
-                    this.releaseLightTicket(chunkPos);
-                    return CompletableFuture.completedFuture(ChunkResult.error(chunkResult::getError));
-                } else {
-                    try {
-                        ChunkAccess chunkAccess = list.get(list.size() / 2);
-                        CompletableFuture<ChunkAccess> completableFuturex;
-                        if (chunkAccess.getStatus().isOrAfter(requiredStatus)) {
-                            completableFuturex = requiredStatus.load(
-                                this.worldGenContext, chunkAccessx -> this.protoChunkToFullChunk(holder, chunkAccessx), chunkAccess
-                            );
-                        } else {
-                            completableFuturex = requiredStatus.generate(
-                                this.worldGenContext, executor, chunkAccessx -> this.protoChunkToFullChunk(holder, chunkAccessx), list
-                            );
-                        }
-
-                        this.progressListener.onStatusChange(chunkPos, requiredStatus);
-                        return completableFuturex.thenApply(ChunkResult::of);
-                    } catch (Exception var10) {
-                        var10.getStackTrace();
-                        CrashReport crashReport = CrashReport.forThrowable(var10, "Exception generating new chunk");
-                        CrashReportCategory crashReportCategory = crashReport.addCategory("Chunk to be generated");
-                        crashReportCategory.setDetail("Status being generated", () -> BuiltInRegistries.CHUNK_STATUS.getKey(requiredStatus).toString());
-                        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);
-                    }
-                }
-            },
-            executor
-        );
+        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
     }
 
     protected void releaseLightTicket(ChunkPos pos) {
-        this.mainThreadExecutor
-            .tell(
-                Util.name(
-                    () -> this.distanceManager.removeTicket(TicketType.LIGHT, pos, ChunkLevel.byStatus(ChunkStatus.LIGHT), pos),
-                    () -> "release light ticket " + pos
-                )
-            );
+        this.mainThreadExecutor.tell(Util.name(() -> {
+            this.distanceManager.removeTicket(TicketType.LIGHT, pos, ChunkLevel.byStatus(ChunkStatus.LIGHT), pos);
+        }, () -> {
+            return "release light ticket " + String.valueOf(pos);
+        }));
     }
 
-    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(null); // CraftBukkit - add Bukkit remove cause
+                    needsRemoval = true;
+                }
+                if (!server.isSpawningAnimals() && (entity instanceof net.minecraft.world.entity.animal.Animal || entity instanceof net.minecraft.world.entity.animal.WaterAnimal)) {
+                    entity.discard(null); // CraftBukkit - add Bukkit remove cause
+                    needsRemoval = true;
+                }
+                checkDupeUUID(world, entity); // Paper - duplicate uuid resolving
+                return !needsRemoval;
+            }), position); // Paper - rewrite chunk system
+            // CraftBukkit end
         }
+
     }
 
-    private CompletableFuture<ChunkAccess> protoChunkToFullChunk(ChunkHolder chunkHolder, ChunkAccess chunkAccess) {
-        return CompletableFuture.supplyAsync(() -> {
-            ChunkPos chunkPos = chunkHolder.getPos();
-            ProtoChunk protoChunk2 = (ProtoChunk)chunkAccess;
-            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));
-            }
+    private CompletableFuture<ChunkAccess> protoChunkToFullChunk(ChunkHolder playerchunk, ChunkAccess ichunkaccess) {
+        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
+    }
 
-            levelChunk.setFullStatus(() -> ChunkLevel.fullStatus(chunkHolder.getTicketLevel()));
-            levelChunk.runPostLoad();
-            if (this.entitiesInLevel.add(chunkPos.toLong())) {
-                levelChunk.setLoaded(true);
-                levelChunk.registerAllBlockEntitiesAfterLevelLoad();
-                levelChunk.registerTickContainerInLevel(this.level);
-            }
+    // Paper start - duplicate uuid resolving
+    // 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;
+        }
+
+        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
+        ) {
+            entity.discard(null);
+            return true;
+        }
+        if (!other.isRemoved()) {
+            switch (mode) {
+                case SAFE_REGEN: {
+                    entity.setUUID(java.util.UUID.randomUUID());
+                    break;
+                }
+                case DELETE: {
+                    entity.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD);
+                    return true;
+                }
+                default:
+                    break;
+            }
+        }
+        return false;
     }
+    // Paper end - duplicate uuid resolving
 
     public CompletableFuture<ChunkResult<LevelChunk>> prepareTickingChunk(ChunkHolder holder) {
-        CompletableFuture<ChunkResult<List<ChunkAccess>>> completableFuture = this.getChunkRangeFuture(holder, 1, distance -> ChunkStatus.FULL);
-        CompletableFuture<ChunkResult<LevelChunk>> completableFuture2 = completableFuture.<ChunkResult<LevelChunk>>thenApplyAsync(
-                chunkResult -> chunkResult.map(cs -> (LevelChunk)cs.get(cs.size() / 2)),
-                task -> this.mainThreadMailbox.tell(ChunkTaskPriorityQueueSorter.message(holder, task))
-            )
-            .thenApplyAsync(chunkResult -> chunkResult.ifSuccess(chunk -> {
-                    chunk.postProcessGeneration();
-                    this.level.startTickingChunk(chunk);
-                    CompletableFuture<?> completableFuturex = holder.getChunkSendSyncFuture();
-                    if (completableFuturex.isDone()) {
-                        this.onChunkReadyToSend(chunk);
-                    } else {
-                        completableFuturex.thenAcceptAsync(v -> this.onChunkReadyToSend(chunk), this.mainThreadExecutor);
-                    }
-                }), this.mainThreadExecutor);
-        completableFuture2.handle((chunkResult, throwable) -> {
-            this.tickingGenerated.getAndIncrement();
-            return null;
-        });
-        return completableFuture2;
+        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
     }
 
     private void onChunkReadyToSend(LevelChunk chunk) {
-        ChunkPos chunkPos = chunk.getPos();
+        throw new UnsupportedOperationException(); // Paper - rewrite player chunk loader
 
-        for (ServerPlayer serverPlayer : this.playerMap.getAllPlayers()) {
-            if (serverPlayer.getChunkTrackingView().contains(chunkPos)) {
-                markChunkPendingToSend(serverPlayer, chunk);
-            }
-        }
     }
 
     public CompletableFuture<ChunkResult<LevelChunk>> prepareAccessibleChunk(ChunkHolder holder) {
-        return this.getChunkRangeFuture(holder, 1, ChunkStatus::getStatusAroundFullChunk)
-            .thenApplyAsync(
-                chunkResult -> chunkResult.map(chunks -> (LevelChunk)chunks.get(chunks.size() / 2)),
-                task -> this.mainThreadMailbox.tell(ChunkTaskPriorityQueueSorter.message(holder, task))
-            );
+        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
     }
 
     public int getTickingGenerated() {
@@ -790,234 +746,241 @@
     }
 
     private boolean saveChunkIfNeeded(ChunkHolder chunkHolder) {
-        if (!chunkHolder.wasAccessibleSinceLastSave()) {
-            return false;
-        } else {
-            ChunkAccess chunkAccess = chunkHolder.getChunkToSave().getNow(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() != 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).exceptionallyAsync(throwable -> {
-                    this.level.getServer().reportChunkSaveFailure(chunkPos);
-                    return null;
-                }, this.mainThreadExecutor);
-                this.markPosition(chunkPos, chunkStatus.getChunkType());
-                return true;
-            } catch (Exception var5) {
-                LOGGER.error("Failed to save chunk {},{}", chunkPos.x, chunkPos.z, var5);
-                this.level.getServer().reportChunkSaveFailure(chunkPos);
-                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(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
+    }
 
-            ChunkType chunkType = ChunkSerializer.getChunkTypeFromTag(compoundTag);
-            return this.markPosition(pos, chunkType) == 1;
+    public void setServerViewDistance(int watchDistance) { // Paper - public
+        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
         }
+
     }
 
-    protected void setServerViewDistance(int watchDistance) {
-        int i = Mth.clamp(watchDistance, 2, 32);
-        if (i != this.serverViewDistance) {
-            this.serverViewDistance = i;
-            this.distanceManager.updatePlayerTickets(this.serverViewDistance);
+    // Paper start - replace player loader system
+    public void setTickViewDistance(int distance) {
+        this.level.playerChunkLoader.setTickDistance(distance);
+    }
 
-            for (ServerPlayer serverPlayer : this.playerMap.getAllPlayers()) {
-                this.updateChunkTracking(serverPlayer);
-            }
-        }
+    public void setSendViewDistance(int distance) {
+        this.level.playerChunkLoader.setSendDistance(distance);
     }
+    // Paper end - replace player loader system
 
-    int getPlayerViewDistance(ServerPlayer player) {
-        return Mth.clamp(player.requestedViewDistance(), 2, this.serverViewDistance);
+    public int getPlayerViewDistance(ServerPlayer player) { // Paper - public
+        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 (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 -> chunk instanceof LevelChunk ? Optional.of((LevelChunk)chunk) : Optional.empty());
-            csvOutput.writeRow(
-                chunkPos.x,
-                chunkPos.z,
-                chunkHolder.getTicketLevel(),
-                optional.isPresent(),
-                optional.map(ChunkAccess::getStatus).orElse(null),
-                optional2.map(LevelChunk::getFullStatus).orElse(null),
-                printFuture(chunkHolder.getFullChunkFuture()),
-                printFuture(chunkHolder.getTickingChunkFuture()),
-                printFuture(chunkHolder.getEntityTickingChunkFuture()),
-                this.distanceManager.getTicketDebugString(l),
-                this.anyPlayerCloseEnoughForSpawning(chunkPos),
-                optional2.<Integer>map(chunk -> chunk.getBlockEntities().size()).orElse(0),
-                tickingTracker.getTicketDebugString(l),
-                tickingTracker.getLevel(l),
-                optional2.<Integer>map(chunk -> chunk.getBlockTicks().count()).orElse(0),
-                optional2.<Integer>map(chunk -> chunk.getFluidTicks().count()).orElse(0)
-            );
-        }
+        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
     }
 
     private static String printFuture(CompletableFuture<ChunkResult<LevelChunk>> future) {
         try {
-            ChunkResult<LevelChunk> chunkResult = future.getNow(null);
-            if (chunkResult != null) {
-                return chunkResult.isSuccess() ? "done" : "unloaded";
-            } else {
-                return "not completed";
-            }
-        } catch (CompletionException var2) {
-            return "failed " + var2.getCause().getMessage();
-        } catch (CancellationException var3) {
+            ChunkResult<LevelChunk> chunkresult = (ChunkResult) future.getNow(null); // CraftBukkit - decompile error
+
+            return chunkresult != null ? (chunkresult.isSuccess() ? "done" : "unloaded") : "not completed";
+        } 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 {
+        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()
+            );
+        }
+        return super.readSync(chunkcoordintpair);
+    }
+
+    @Override
+    public CompletableFuture<Void> write(ChunkPos chunkcoordintpair, CompoundTag nbttagcompound) throws IOException {
+        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 null;
+        }
+        super.write(chunkcoordintpair, nbttagcompound);
+        return null;
+    }
+    // Paper end
+
     private CompletableFuture<Optional<CompoundTag>> readChunk(ChunkPos chunkPos) {
-        return this.read(chunkPos).thenApplyAsync(nbt -> 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, this.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);
+        if (nbttagcompound == null) {
+            return null;
+        }
+
+        nbttagcompound = this.upgradeChunkTag(nbttagcompound, pos); // CraftBukkit
+        if (nbttagcompound == null) {
+            return null;
+        }
+
+        this.updateChunkStatusOnDisk(pos, nbttagcompound);
+
+        return nbttagcompound;
+    }
+
+    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 - Cache chunk status on disk
+
+    public boolean anyPlayerCloseEnoughForSpawning(ChunkPos pos) { // Paper - public
+        // Spigot start
+        return this.anyPlayerCloseEnoughForSpawning(pos, false);
+    }
+
+    boolean anyPlayerCloseEnoughForSpawning(ChunkPos chunkcoordintpair, boolean reducedRange) {
+        int chunkRange = this.level.spigotConfig.mobSpawnRange;
+        chunkRange = (chunkRange > this.level.spigotConfig.viewDistance) ? (byte) this.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 - 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 - PlayerNaturallySpawnCreaturesEvent
+            } 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 {
             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);
                 }
             }
 
@@ -1025,12 +988,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.0;
+            double d0 = ChunkMap.euclideanDistanceSquared(chunkcoordintpair, entityplayer);
+
+            return d0 < range; // Spigot
         }
     }
 
@@ -1039,218 +1003,279 @@
     }
 
     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());
-            } else {
-                trackedEntity.updatePlayer(player);
-            }
-        }
+        // Paper - delay this logic for the entity tracker tick, no need to duplicate it
 
-        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);
-        if (player.getChunkTrackingView() instanceof ChunkTrackingView.Positioned positioned
-            && 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 positioned
-                && (!(chunkTrackingView instanceof ChunkTrackingView.Positioned positioned2) || !positioned2.center().equals(positioned.center()))) {
-                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();
-        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;
+        }
+        // Paper end - ignore and warn about illegal addEntity calls instead of crashing server
+        if (entity instanceof ServerPlayer && ((ServerPlayer) entity).supressTrackerForLogin) return; // Paper - Fire PlayerJoinEvent when Player is actually ready; Delay adding to tracker until after list packets
         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());
-                    if (entity instanceof ServerPlayer serverPlayer) {
-                        this.updatePlayerStatus(serverPlayer, true);
+                    ChunkMap.TrackedEntity playerchunkmap_entitytracker = new ChunkMap.TrackedEntity(entity, i, j, entitytypes.trackDeltas());
 
-                        for (ChunkMap.TrackedEntity trackedEntity2 : this.entityMap.values()) {
-                            if (trackedEntity2.entity != serverPlayer) {
-                                trackedEntity2.updatePlayer(serverPlayer);
+                    entity.tracker = playerchunkmap_entitytracker; // Paper - Fast access to tracker
+                    this.entityMap.put(entity.getId(), playerchunkmap_entitytracker);
+                    playerchunkmap_entitytracker.updatePlayers(entity.getPlayersInTrackRange()); // Paper - don't search all players
+                    if (entity instanceof ServerPlayer) {
+                        ServerPlayer entityplayer = (ServerPlayer) entity;
+
+                        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);
                             }
                         }
                     }
+
                 }
             }
         }
     }
 
     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 entityplayer) {
+            this.updatePlayerStatus(entityplayer, false);
+            ObjectIterator objectiterator = this.entityMap.values().iterator();
 
-            for (ChunkMap.TrackedEntity trackedEntity : this.entityMap.values()) {
-                trackedEntity.removePlayer(serverPlayer);
+            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 start - optimised tracker
+    private final void processTrackQueue() {
+        this.level.timings.tracker1.startTiming();
+        try {
+            for (TrackedEntity tracker : this.entityMap.values()) {
+                // update tracker entry
+                tracker.updatePlayers(tracker.entity.getPlayersInTrackRange());
+            }
+        } finally {
+            this.level.timings.tracker1.stopTiming();
         }
 
+
+        this.level.timings.tracker2.startTiming();
+        try {
+            for (TrackedEntity tracker : this.entityMap.values()) {
+                tracker.serverEntity.sendChanges();
+            }
+        } finally {
+            this.level.timings.tracker2.stopTiming();
+        }
+    }
+    // Paper end - optimised tracker
+
+    protected void tick() {
+        // Paper start - optimized tracker
+        if (true) {
+            this.processTrackQueue();
+            return;
+        }
+        // Paper end - optimized tracker
         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 chunk1) {
+                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 -> new ArrayList<>()).add(levelChunk2);
+            Iterator iterator1 = this.getPlayers(chunkcoordintpair, false).iterator();
+
+            while (iterator1.hasNext()) {
+                ServerPlayer entityplayer = (ServerPlayer) iterator1.next();
+
+                ((List) map.computeIfAbsent(entityplayer, (entityplayer1) -> {
+                    return new ArrayList();
+                })).add(chunk);
             }
         }
 
-        map.forEach((player, chunksx) -> player.connection.send(ClientboundChunksBiomesPacket.forChunks((List<LevelChunk>)chunksx)));
+        map.forEach((entityplayer1, list1) -> {
+            entityplayer1.connection.send(ClientboundChunksBiomesPacket.forChunks(list1));
+        });
     }
 
     public PoiManager getPoiManager() {
@@ -1266,23 +1291,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(final Executor workerExecutor, final Executor mainThreadExecutor) {
-            super(workerExecutor, mainThreadExecutor);
+    public class ChunkDistanceManager extends DistanceManager { // Paper - public
+
+        protected ChunkDistanceManager(final Executor workerExecutor, final Executor mainThreadExecutor) {
+            super(workerExecutor, mainThreadExecutor, ChunkMap.this); // Paper
         }
 
         @Override
         protected boolean isChunkToRemove(long pos) {
-            return ChunkMap.this.toDrop.contains(pos);
+            throw new UnsupportedOperationException(); // Paper - rewrite chunk system
         }
 
         @Nullable
@@ -1293,77 +1313,149 @@
 
         @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 - Perf: optimise map impl
 
-        public TrackedEntity(final Entity entity, final int maxDistance, final int tickInterval, final boolean alwaysUpdateVelocity) {
-            this.serverEntity = new ServerEntity(ChunkMap.this.level, entity, tickInterval, alwaysUpdateVelocity, this::broadcast);
+        public TrackedEntity(final Entity entity, final int i, final int j, final 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
+        // Paper start - use distance map to optimise tracker
+        com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> lastTrackerCandidates;
+
+        final void updatePlayers(com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> newTrackerCandidates) {
+            com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> oldTrackerCandidates = this.lastTrackerCandidates;
+            this.lastTrackerCandidates = newTrackerCandidates;
+
+            if (newTrackerCandidates != null) {
+                Object[] rawData = newTrackerCandidates.getBackingSet();
+                for (int i = 0, len = rawData.length; i < len; ++i) {
+                    Object raw = rawData[i];
+                    if (!(raw instanceof ServerPlayer)) {
+                        continue;
+                    }
+                    ServerPlayer player = (ServerPlayer)raw;
+                    this.updatePlayer(player);
+                }
+            }
+
+            if (oldTrackerCandidates == newTrackerCandidates) {
+                // this is likely the case.
+                // means there has been no range changes, so we can just use the above for tracking.
+                return;
+            }
+
+            // stuff could have been removed, so we need to check the trackedPlayers set
+            // for players that were removed
+
+            for (ServerPlayerConnection conn : this.seenBy.toArray(new ServerPlayerConnection[0])) { // avoid CME
+                if (newTrackerCandidates == null || !newTrackerCandidates.contains(conn.getPlayer())) {
+                    this.updatePlayer(conn.getPlayer());
+                }
+            }
+        }
+        // Paper end - use distance map to optimise tracker
+
         public boolean equals(Object object) {
-            return object instanceof ChunkMap.TrackedEntity && ((ChunkMap.TrackedEntity)object).entity.getId() == this.entity.getId();
+            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);
             }
+
         }
 
         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);
             }
+
         }
 
         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());
+                // Paper start - remove allocation of Vec3D here
+                // Vec3 vec3d = player.position().subtract(this.entity.position());
+                double vec3d_dx = player.getX() - this.entity.getX();
+                double vec3d_dz = player.getZ() - this.entity.getZ();
+                // Paper end - remove allocation of Vec3D here
                 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_dx * vec3d_dx + vec3d_dz * vec3d_dz; // Paper
+                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 - Configurable entity tracking range by 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 - Configurable entity tracking range by Y
+
+                // CraftBukkit start - respect vanish API
+                if (flag && !player.getBukkitEntity().canSee(this.entity.getBukkitEntity())) { // Paper - only consider hits
+                    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 - entity tracking events
+                        this.serverEntity.onPlayerAdd(); // Paper - fix desync when a player is added to the tracker
                     }
                 } else if (this.seenBy.remove(player.connection)) {
                     this.serverEntity.removePairing(player);
                 }
+
             }
         }
 
@@ -1373,9 +1465,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;
                 }
@@ -1385,9 +1481,14 @@
         }
 
         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);
             }
+
         }
     }
 }
