--- a/net/minecraft/server/level/ChunkHolder.java
+++ b/net/minecraft/server/level/ChunkHolder.java
@@ -36,126 +36,203 @@
 import net.minecraft.world.level.chunk.LevelChunkSection;
 import net.minecraft.world.level.chunk.ProtoChunk;
 import net.minecraft.world.level.lighting.LevelLightEngine;
+// CraftBukkit start
+import net.minecraft.server.MinecraftServer;
+// CraftBukkit end
 
 public class ChunkHolder {
+
     public static final Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure> UNLOADED_CHUNK = Either.right(ChunkHolder.ChunkLoadingFailure.UNLOADED);
-    public static final CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> UNLOADED_CHUNK_FUTURE = CompletableFuture.completedFuture(UNLOADED_CHUNK);
+    public static final CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> UNLOADED_CHUNK_FUTURE = CompletableFuture.completedFuture(ChunkHolder.UNLOADED_CHUNK);
     public static final Either<LevelChunk, ChunkHolder.ChunkLoadingFailure> UNLOADED_LEVEL_CHUNK = Either.right(ChunkHolder.ChunkLoadingFailure.UNLOADED);
     private static final Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure> NOT_DONE_YET = Either.right(ChunkHolder.ChunkLoadingFailure.UNLOADED);
-    private static final CompletableFuture<Either<LevelChunk, ChunkHolder.ChunkLoadingFailure>> UNLOADED_LEVEL_CHUNK_FUTURE = CompletableFuture.completedFuture(UNLOADED_LEVEL_CHUNK);
+    private static final CompletableFuture<Either<LevelChunk, ChunkHolder.ChunkLoadingFailure>> UNLOADED_LEVEL_CHUNK_FUTURE = CompletableFuture.completedFuture(ChunkHolder.UNLOADED_LEVEL_CHUNK);
     private static final List<ChunkStatus> CHUNK_STATUSES = ChunkStatus.getStatusList();
     private static final ChunkHolder.FullChunkStatus[] FULL_CHUNK_STATUSES = ChunkHolder.FullChunkStatus.values();
     private static final int BLOCKS_BEFORE_RESEND_FUDGE = 64;
-    private final AtomicReferenceArray<CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>>> futures = new AtomicReferenceArray<>(CHUNK_STATUSES.size());
+    // Paper - rewrite chunk system
     private final LevelHeightAccessor levelHeightAccessor;
-    private volatile CompletableFuture<Either<LevelChunk, ChunkHolder.ChunkLoadingFailure>> fullChunkFuture = UNLOADED_LEVEL_CHUNK_FUTURE;
-    private volatile CompletableFuture<Either<LevelChunk, ChunkHolder.ChunkLoadingFailure>> tickingChunkFuture = UNLOADED_LEVEL_CHUNK_FUTURE;
-    private volatile CompletableFuture<Either<LevelChunk, ChunkHolder.ChunkLoadingFailure>> entityTickingChunkFuture = UNLOADED_LEVEL_CHUNK_FUTURE;
-    private CompletableFuture<ChunkAccess> chunkToSave = CompletableFuture.completedFuture((ChunkAccess)null);
+    // Paper - rewrite chunk system
     @Nullable
-    private final DebugBuffer<ChunkHolder.ChunkSaveDebug> chunkToSaveHistory = null;
-    public int oldTicketLevel;
-    private int ticketLevel;
-    private int queueLevel;
+    private final DebugBuffer<ChunkHolder.ChunkSaveDebug> chunkToSaveHistory;
+    // Paper - rewrite chunk system
     public final ChunkPos pos;
     private boolean hasChangedSections;
     private final ShortSet[] changedBlocksPerSection;
-    private final BitSet blockChangedLightSectionFilter = new BitSet();
-    private final BitSet skyChangedLightSectionFilter = new BitSet();
+    private final BitSet blockChangedLightSectionFilter;
+    private final BitSet skyChangedLightSectionFilter;
     private final LevelLightEngine lightEngine;
     private final ChunkHolder.LevelChangeListener onLevelChange;
     public final ChunkHolder.PlayerProvider playerProvider;
-    private boolean wasAccessibleSinceLastSave;
+    // Paper - rewrite chunk system
     private boolean resendLight;
-    private CompletableFuture<Void> pendingFullStateConfirmation = CompletableFuture.completedFuture((Void)null);
+    // Paper - rewrite chunk system
 
-    public ChunkHolder(ChunkPos pos, int level, LevelHeightAccessor world, LevelLightEngine lightingProvider, ChunkHolder.LevelChangeListener levelUpdateListener, ChunkHolder.PlayerProvider playersWatchingChunkProvider) {
+    private final ChunkMap chunkMap; // Paper
+    // Paper start - no-tick view distance
+    public final LevelChunk getSendingChunk() {
+        // it's important that we use getChunkAtIfLoadedImmediately to mirror the chunk sending logic used
+        // in Chunk's neighbour callback
+        LevelChunk ret = this.chunkMap.level.getChunkSource().getChunkAtIfLoadedImmediately(this.pos.x, this.pos.z);
+        if (ret != null && ret.areNeighboursLoaded(1)) {
+            return ret;
+        }
+        return null;
+    }
+    // Paper end - no-tick view distance
+
+    // Paper start
+    public void onChunkAdd() {
+        // Paper start - optimise anyPlayerCloseEnoughForSpawning
+        long key = net.minecraft.server.MCUtil.getCoordinateKey(this.pos);
+        this.playersInMobSpawnRange = this.chunkMap.playerMobSpawnMap.getObjectsInRange(key);
+        this.playersInChunkTickRange = this.chunkMap.playerChunkTickRangeMap.getObjectsInRange(key);
+        // Paper end - optimise anyPlayerCloseEnoughForSpawning
+        // Paper start - optimise chunk tick iteration
+        if (this.needsBroadcastChanges()) {
+            this.chunkMap.needsChangeBroadcasting.add(this);
+        }
+        // Paper end - optimise chunk tick iteration
+        // Paper start - optimise checkDespawn
+        LevelChunk chunk = this.getFullChunkNowUnchecked();
+        if (chunk != null) {
+            chunk.updateGeneralAreaCache();
+        }
+        // Paper end - optimise checkDespawn
+    }
+
+    public void onChunkRemove() {
+        // Paper start - optimise anyPlayerCloseEnoughForSpawning
+        this.playersInMobSpawnRange = null;
+        this.playersInChunkTickRange = null;
+        // Paper end - optimise anyPlayerCloseEnoughForSpawning
+        // Paper start - optimise chunk tick iteration
+        if (this.needsBroadcastChanges()) {
+            this.chunkMap.needsChangeBroadcasting.remove(this);
+        }
+        // Paper end - optimise chunk tick iteration
+        // Paper start - optimise checkDespawn
+        LevelChunk chunk = this.getFullChunkNowUnchecked();
+        if (chunk != null) {
+            chunk.removeGeneralAreaCache();
+        }
+        // Paper end - optimise checkDespawn
+    }
+    // Paper end
+
+    public final io.papermc.paper.chunk.system.scheduling.NewChunkHolder newChunkHolder; // Paper - rewrite chunk system
+
+    // Paper start - optimise anyPlayerCloseEnoughForSpawning
+    // cached here to avoid a map lookup
+    com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> playersInMobSpawnRange;
+    com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> playersInChunkTickRange;
+    // Paper end - optimise anyPlayerCloseEnoughForSpawning
+
+    public ChunkHolder(ChunkPos pos, LevelHeightAccessor world, LevelLightEngine lightingProvider, ChunkHolder.PlayerProvider playersWatchingChunkProvider, io.papermc.paper.chunk.system.scheduling.NewChunkHolder newChunkHolder) { // Paper - rewrite chunk system
+        this.newChunkHolder = newChunkHolder; // Paper - rewrite chunk system
+        this.chunkToSaveHistory = null;
+        this.blockChangedLightSectionFilter = new BitSet();
+        this.skyChangedLightSectionFilter = new BitSet();
+        // Paper - rewrite chunk system
         this.pos = pos;
         this.levelHeightAccessor = world;
         this.lightEngine = lightingProvider;
-        this.onLevelChange = levelUpdateListener;
+        this.onLevelChange = null; // Paper - rewrite chunk system
         this.playerProvider = playersWatchingChunkProvider;
-        this.oldTicketLevel = ChunkMap.MAX_CHUNK_DISTANCE + 1;
-        this.ticketLevel = this.oldTicketLevel;
-        this.queueLevel = this.oldTicketLevel;
-        this.setTicketLevel(level);
+        // Paper - rewrite chunk system
         this.changedBlocksPerSection = new ShortSet[world.getSectionsCount()];
+        this.chunkMap = (ChunkMap)playersWatchingChunkProvider; // Paper
+    }
+
+    // Paper start
+    public @Nullable ChunkAccess getAvailableChunkNow() {
+        return this.newChunkHolder.getCurrentChunk(); // Paper - rewrite chunk system
+    }
+    // Paper end
+    // CraftBukkit start
+    public LevelChunk getFullChunkNow() {
+        // Paper start - rewrite chunk system
+        ChunkAccess chunk = this.getAvailableChunkNow();
+        if (!this.isFullChunkReady() || !(chunk instanceof LevelChunk)) return null; // instanceof to avoid a race condition on off-main threads
+        return (LevelChunk)chunk;
+        // Paper end - rewrite chunk system
     }
 
+    public LevelChunk getFullChunkNowUnchecked() {
+        // Paper start - rewrite chunk system
+        ChunkAccess chunk = this.getAvailableChunkNow();
+        return chunk instanceof LevelChunk ? (LevelChunk)chunk : null;
+        // Paper end - rewrite chunk system
+    }
+    // CraftBukkit end
+
     public CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> getFutureIfPresentUnchecked(ChunkStatus leastStatus) {
-        CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> completableFuture = this.futures.get(leastStatus.getIndex());
-        return completableFuture == null ? UNLOADED_CHUNK_FUTURE : completableFuture;
+        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
     }
 
     public CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> getFutureIfPresent(ChunkStatus leastStatus) {
-        return getStatus(this.ticketLevel).isOrAfter(leastStatus) ? this.getFutureIfPresentUnchecked(leastStatus) : UNLOADED_CHUNK_FUTURE;
+        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
     }
 
-    public CompletableFuture<Either<LevelChunk, ChunkHolder.ChunkLoadingFailure>> getTickingChunkFuture() {
-        return this.tickingChunkFuture;
+    public final CompletableFuture<Either<LevelChunk, ChunkHolder.ChunkLoadingFailure>> getTickingChunkFuture() { // Paper - final for inline
+        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
     }
 
-    public CompletableFuture<Either<LevelChunk, ChunkHolder.ChunkLoadingFailure>> getEntityTickingChunkFuture() {
-        return this.entityTickingChunkFuture;
+    public final CompletableFuture<Either<LevelChunk, ChunkHolder.ChunkLoadingFailure>> getEntityTickingChunkFuture() { // Paper - final for inline
+        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
     }
 
-    public CompletableFuture<Either<LevelChunk, ChunkHolder.ChunkLoadingFailure>> getFullChunkFuture() {
-        return this.fullChunkFuture;
+    public final CompletableFuture<Either<LevelChunk, ChunkHolder.ChunkLoadingFailure>> getFullChunkFuture() { // Paper - final for inline
+        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
     }
 
     @Nullable
-    public LevelChunk getTickingChunk() {
-        CompletableFuture<Either<LevelChunk, ChunkHolder.ChunkLoadingFailure>> completableFuture = this.getTickingChunkFuture();
-        Either<LevelChunk, ChunkHolder.ChunkLoadingFailure> either = completableFuture.getNow((Either<LevelChunk, ChunkHolder.ChunkLoadingFailure>)null);
-        return either == null ? null : either.left().orElse((LevelChunk)null);
+    public final LevelChunk getTickingChunk() { // Paper - final for inline
+        // Paper start - rewrite chunk system
+        if (!this.isTickingReady()) {
+            return null;
+        }
+        return (LevelChunk)this.getAvailableChunkNow();
+        // Paper end - rewrite chunk system
     }
 
     @Nullable
-    public LevelChunk getFullChunk() {
-        CompletableFuture<Either<LevelChunk, ChunkHolder.ChunkLoadingFailure>> completableFuture = this.getFullChunkFuture();
-        Either<LevelChunk, ChunkHolder.ChunkLoadingFailure> either = completableFuture.getNow((Either<LevelChunk, ChunkHolder.ChunkLoadingFailure>)null);
-        return either == null ? null : either.left().orElse((LevelChunk)null);
+    public final LevelChunk getFullChunk() { // Paper - final for inline
+        // Paper start - rewrite chunk system
+        if (!this.isFullChunkReady()) {
+            return null;
+        }
+        return (LevelChunk)this.getAvailableChunkNow();
+        // Paper end - rewrite chunk system
     }
 
     @Nullable
     public ChunkStatus getLastAvailableStatus() {
-        for(int i = CHUNK_STATUSES.size() - 1; i >= 0; --i) {
-            ChunkStatus chunkStatus = CHUNK_STATUSES.get(i);
-            CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> completableFuture = this.getFutureIfPresentUnchecked(chunkStatus);
-            if (completableFuture.getNow(UNLOADED_CHUNK).left().isPresent()) {
-                return chunkStatus;
-            }
-        }
+        return this.newChunkHolder.getCurrentGenStatus(); // Paper - rewrite chunk system
+    }
 
-        return null;
+    // Paper start
+    public ChunkStatus getChunkHolderStatus() {
+        return this.newChunkHolder.getCurrentGenStatus(); // Paper - rewrite chunk system
     }
+    // Paper end
 
     @Nullable
     public ChunkAccess getLastAvailable() {
-        for(int i = CHUNK_STATUSES.size() - 1; i >= 0; --i) {
-            ChunkStatus chunkStatus = CHUNK_STATUSES.get(i);
-            CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> completableFuture = this.getFutureIfPresentUnchecked(chunkStatus);
-            if (!completableFuture.isCompletedExceptionally()) {
-                Optional<ChunkAccess> optional = completableFuture.getNow(UNLOADED_CHUNK).left();
-                if (optional.isPresent()) {
-                    return optional.get();
-                }
-            }
-        }
-
-        return null;
+        return this.newChunkHolder.getCurrentChunk(); // Paper - rewrite chunk system
     }
 
-    public CompletableFuture<ChunkAccess> getChunkToSave() {
-        return this.chunkToSave;
-    }
+    // Paper - rewrite chunk system
 
     public void blockChanged(BlockPos pos) {
-        LevelChunk levelChunk = this.getTickingChunk();
-        if (levelChunk != null) {
+        LevelChunk chunk = this.getSendingChunk(); // Paper - no-tick view distance
+
+        if (chunk != null) {
             int i = this.levelHeightAccessor.getSectionIndex(pos.getY());
+
+            if (i < 0 || i >= this.changedBlocksPerSection.length) return; // CraftBukkit - SPIGOT-6086, SPIGOT-6296
             if (this.changedBlocksPerSection[i] == null) {
-                this.hasChangedSections = true;
+                this.hasChangedSections = true; this.addToBroadcastMap(); // Paper - optimise chunk tick iteration
                 this.changedBlocksPerSection[i] = new ShortOpenHashSet();
             }
 
@@ -164,21 +241,28 @@
     }
 
     public void sectionLightChanged(LightLayer lightType, int y) {
-        Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure> either = this.getFutureIfPresent(ChunkStatus.FEATURES).getNow((Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>)null);
-        if (either != null) {
-            ChunkAccess chunkAccess = either.left().orElse((ChunkAccess)null);
-            if (chunkAccess != null) {
-                chunkAccess.setUnsaved(true);
-                LevelChunk levelChunk = this.getTickingChunk();
-                if (levelChunk != null) {
-                    int i = this.lightEngine.getMinLightSection();
-                    int j = this.lightEngine.getMaxLightSection();
-                    if (y >= i && y <= j) {
-                        int k = y - i;
+        // Paper start - no-tick view distance
+
+        if (true) {
+            ChunkAccess ichunkaccess = this.getAvailableChunkNow();
+
+            if (ichunkaccess != null) {
+                ichunkaccess.setUnsaved(true);
+                LevelChunk chunk = this.getSendingChunk();
+                // Paper end - no-tick view distance
+
+                if (chunk != null) {
+                    int j = this.lightEngine.getMinLightSection();
+                    int k = this.lightEngine.getMaxLightSection();
+
+                    if (y >= j && y <= k) {
+                    this.addToBroadcastMap(); // Paper - optimise chunk tick iteration
+                        int l = y - j;
+
                         if (lightType == LightLayer.SKY) {
-                            this.skyChangedLightSectionFilter.set(k);
+                            this.skyChangedLightSectionFilter.set(l);
                         } else {
-                            this.blockChangedLightSectionFilter.set(k);
+                            this.blockChangedLightSectionFilter.set(l);
                         }
 
                     }
@@ -187,12 +271,25 @@
         }
     }
 
+    // Paper start - optimise chunk tick iteration
+    public final boolean needsBroadcastChanges() {
+        return this.hasChangedSections || !this.skyChangedLightSectionFilter.isEmpty() || !this.blockChangedLightSectionFilter.isEmpty();
+    }
+
+    private void addToBroadcastMap() {
+        org.spigotmc.AsyncCatcher.catchOp("ChunkHolder update");
+        this.chunkMap.needsChangeBroadcasting.add(this);
+    }
+    // Paper end - optimise chunk tick iteration
+
     public void broadcastChanges(LevelChunk chunk) {
-        if (this.hasChangedSections || !this.skyChangedLightSectionFilter.isEmpty() || !this.blockChangedLightSectionFilter.isEmpty()) {
-            Level level = chunk.getLevel();
+        if (this.needsBroadcastChanges()) { // Paper - moved into above, other logic needs to call
+            Level world = chunk.getLevel();
             int i = 0;
 
-            for(int j = 0; j < this.changedBlocksPerSection.length; ++j) {
+            int j;
+
+            for (j = 0; j < this.changedBlocksPerSection.length; ++j) {
                 i += this.changedBlocksPerSection[j] != null ? this.changedBlocksPerSection[j].size() : 0;
             }
 
@@ -203,26 +300,30 @@
                 this.blockChangedLightSectionFilter.clear();
             }
 
-            for(int k = 0; k < this.changedBlocksPerSection.length; ++k) {
-                ShortSet shortSet = this.changedBlocksPerSection[k];
-                if (shortSet != null) {
-                    int l = this.levelHeightAccessor.getSectionYFromSectionIndex(k);
-                    SectionPos sectionPos = SectionPos.of(chunk.getPos(), l);
-                    if (shortSet.size() == 1) {
-                        BlockPos blockPos = sectionPos.relativeToBlockPos(shortSet.iterator().nextShort());
-                        BlockState blockState = level.getBlockState(blockPos);
-                        this.broadcast(new ClientboundBlockUpdatePacket(blockPos, blockState), false);
-                        this.broadcastBlockEntityIfNeeded(level, blockPos, blockState);
+            for (j = 0; j < this.changedBlocksPerSection.length; ++j) {
+                ShortSet shortset = this.changedBlocksPerSection[j];
+
+                if (shortset != null) {
+                    int k = this.levelHeightAccessor.getSectionYFromSectionIndex(j);
+                    SectionPos sectionposition = SectionPos.of(chunk.getPos(), k);
+
+                    if (shortset.size() == 1) {
+                        BlockPos blockposition = sectionposition.relativeToBlockPos(shortset.iterator().nextShort());
+                        BlockState iblockdata = world.getBlockState(blockposition);
+
+                        this.broadcast(new ClientboundBlockUpdatePacket(blockposition, iblockdata), false);
+                        this.broadcastBlockEntityIfNeeded(world, blockposition, iblockdata);
                     } else {
-                        LevelChunkSection levelChunkSection = chunk.getSection(k);
-                        ClientboundSectionBlocksUpdatePacket clientboundSectionBlocksUpdatePacket = new ClientboundSectionBlocksUpdatePacket(sectionPos, shortSet, levelChunkSection, this.resendLight);
-                        this.broadcast(clientboundSectionBlocksUpdatePacket, false);
-                        clientboundSectionBlocksUpdatePacket.runUpdates((pos, state) -> {
-                            this.broadcastBlockEntityIfNeeded(level, pos, state);
+                        LevelChunkSection chunksection = chunk.getSection(j);
+                        ClientboundSectionBlocksUpdatePacket packetplayoutmultiblockchange = new ClientboundSectionBlocksUpdatePacket(sectionposition, shortset, chunksection, this.resendLight);
+
+                        this.broadcast(packetplayoutmultiblockchange, false);
+                        packetplayoutmultiblockchange.runUpdates((blockposition1, iblockdata1) -> {
+                            this.broadcastBlockEntityIfNeeded(world, blockposition1, iblockdata1);
                         });
                     }
 
-                    this.changedBlocksPerSection[k] = null;
+                    this.changedBlocksPerSection[j] = null;
                 }
             }
 
@@ -238,9 +339,11 @@
     }
 
     private void broadcastBlockEntity(Level world, BlockPos pos) {
-        BlockEntity blockEntity = world.getBlockEntity(pos);
-        if (blockEntity != null) {
-            Packet<?> packet = blockEntity.getUpdatePacket();
+        BlockEntity tileentity = world.getBlockEntity(pos);
+
+        if (tileentity != null) {
+            Packet<?> packet = tileentity.getUpdatePacket();
+
             if (packet != null) {
                 this.broadcast(packet, false);
             }
@@ -249,228 +352,77 @@
     }
 
     public void broadcast(Packet<?> packet, boolean onlyOnWatchDistanceEdge) {
-        this.playerProvider.getPlayers(this.pos, onlyOnWatchDistanceEdge).forEach((serverPlayer) -> {
-            serverPlayer.connection.send(packet);
-        });
-    }
+        // Paper start - per player view distance
+        // there can be potential desync with player's last mapped section and the view distance map, so use the
+        // view distance map here.
+        com.destroystokyo.paper.util.misc.PlayerAreaMap viewDistanceMap = this.chunkMap.playerChunkManager.broadcastMap; // Paper - replace old player chunk manager
+        com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> players = viewDistanceMap.getObjectsInRange(this.pos);
+        if (players == null) {
+            return;
+        }
 
-    public CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> getOrScheduleFuture(ChunkStatus targetStatus, ChunkMap chunkStorage) {
-        int i = targetStatus.getIndex();
-        CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> completableFuture = this.futures.get(i);
-        if (completableFuture != null) {
-            Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure> either = completableFuture.getNow(NOT_DONE_YET);
-            if (either == null) {
-                String string = "value in future for status: " + targetStatus + " was incorrectly set to null at chunk: " + this.pos;
-                throw chunkStorage.debugFuturesAndCreateReportedException(new IllegalStateException("null value previously set for chunk status"), string);
+        Object[] backingSet = players.getBackingSet();
+        for (int i = 0, len = backingSet.length; i < len; ++i) {
+            Object temp = backingSet[i];
+            if (!(temp instanceof ServerPlayer)) {
+                continue;
             }
-
-            if (either == NOT_DONE_YET || either.right().isEmpty()) {
-                return completableFuture;
+            ServerPlayer player = (ServerPlayer)temp;
+            if (!this.chunkMap.playerChunkManager.isChunkSent(player, this.pos.x, this.pos.z, onlyOnWatchDistanceEdge)) {
+                continue;
             }
+            player.connection.send(packet);
         }
-
-        if (getStatus(this.ticketLevel).isOrAfter(targetStatus)) {
-            CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> completableFuture2 = chunkStorage.schedule(this, targetStatus);
-            this.updateChunkToSave(completableFuture2, "schedule " + targetStatus);
-            this.futures.set(i, completableFuture2);
-            return completableFuture2;
-        } else {
-            return completableFuture == null ? UNLOADED_CHUNK_FUTURE : completableFuture;
-        }
-    }
-
-    protected void addSaveDependency(String thenDesc, CompletableFuture<?> then) {
-        if (this.chunkToSaveHistory != null) {
-            this.chunkToSaveHistory.push(new ChunkHolder.ChunkSaveDebug(Thread.currentThread(), then, thenDesc));
-        }
-
-        this.chunkToSave = this.chunkToSave.thenCombine(then, (chunkAccess, object) -> {
-            return chunkAccess;
-        });
+        // Paper end - per player view distance
     }
 
-    private void updateChunkToSave(CompletableFuture<? extends Either<? extends ChunkAccess, ChunkHolder.ChunkLoadingFailure>> then, String thenDesc) {
-        if (this.chunkToSaveHistory != null) {
-            this.chunkToSaveHistory.push(new ChunkHolder.ChunkSaveDebug(Thread.currentThread(), then, thenDesc));
-        }
-
-        this.chunkToSave = this.chunkToSave.thenCombine(then, (chunkAccess, either) -> {
-            return either.map((chunkAccessx) -> {
-                return chunkAccessx;
-            }, (chunkLoadingFailure) -> {
-                return chunkAccess;
-            });
-        });
-    }
+    // Paper - rewrite chunk system
 
     public ChunkHolder.FullChunkStatus getFullStatus() {
-        return getFullChunkStatus(this.ticketLevel);
+        return this.newChunkHolder.getChunkStatus(); // Paper - rewrite chunk system
     }
 
-    public ChunkPos getPos() {
+    public final ChunkPos getPos() { // Paper - final for inline
         return this.pos;
     }
 
-    public int getTicketLevel() {
-        return this.ticketLevel;
-    }
-
-    public int getQueueLevel() {
-        return this.queueLevel;
-    }
-
-    private void setQueueLevel(int level) {
-        this.queueLevel = level;
-    }
-
-    public void setTicketLevel(int level) {
-        this.ticketLevel = level;
-    }
-
-    private void scheduleFullChunkPromotion(ChunkMap chunkMap, CompletableFuture<Either<LevelChunk, ChunkHolder.ChunkLoadingFailure>> completableFuture, Executor executor, ChunkHolder.FullChunkStatus fullChunkStatus) {
-        this.pendingFullStateConfirmation.cancel(false);
-        CompletableFuture<Void> completableFuture2 = new CompletableFuture<>();
-        completableFuture2.thenRunAsync(() -> {
-            chunkMap.onFullChunkStatusChange(this.pos, fullChunkStatus);
-        }, executor);
-        this.pendingFullStateConfirmation = completableFuture2;
-        completableFuture.thenAccept((either) -> {
-            either.ifLeft((levelChunk) -> {
-                completableFuture2.complete((Void)null);
-            });
-        });
-    }
-
-    private void demoteFullChunk(ChunkMap chunkMap, ChunkHolder.FullChunkStatus fullChunkStatus) {
-        this.pendingFullStateConfirmation.cancel(false);
-        chunkMap.onFullChunkStatusChange(this.pos, fullChunkStatus);
+    public final int getTicketLevel() { // Paper - final for inline
+        return this.newChunkHolder.getTicketLevel(); // Paper - rewrite chunk system
     }
 
-    protected void updateFutures(ChunkMap chunkStorage, Executor executor) {
-        ChunkStatus chunkStatus = getStatus(this.oldTicketLevel);
-        ChunkStatus chunkStatus2 = getStatus(this.ticketLevel);
-        boolean bl = this.oldTicketLevel <= ChunkMap.MAX_CHUNK_DISTANCE;
-        boolean bl2 = this.ticketLevel <= ChunkMap.MAX_CHUNK_DISTANCE;
-        ChunkHolder.FullChunkStatus fullChunkStatus = getFullChunkStatus(this.oldTicketLevel);
-        ChunkHolder.FullChunkStatus fullChunkStatus2 = getFullChunkStatus(this.ticketLevel);
-        if (bl) {
-            Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure> either = Either.right(new ChunkHolder.ChunkLoadingFailure() {
-                @Override
-                public String toString() {
-                    return "Unloaded ticket level " + ChunkHolder.this.pos;
-                }
-            });
-
-            for(int i = bl2 ? chunkStatus2.getIndex() + 1 : 0; i <= chunkStatus.getIndex(); ++i) {
-                CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> completableFuture = this.futures.get(i);
-                if (completableFuture == null) {
-                    this.futures.set(i, CompletableFuture.completedFuture(either));
-                }
-            }
-        }
-
-        boolean bl3 = fullChunkStatus.isOrAfter(ChunkHolder.FullChunkStatus.BORDER);
-        boolean bl4 = fullChunkStatus2.isOrAfter(ChunkHolder.FullChunkStatus.BORDER);
-        this.wasAccessibleSinceLastSave |= bl4;
-        if (!bl3 && bl4) {
-            this.fullChunkFuture = chunkStorage.prepareAccessibleChunk(this);
-            this.scheduleFullChunkPromotion(chunkStorage, this.fullChunkFuture, executor, ChunkHolder.FullChunkStatus.BORDER);
-            this.updateChunkToSave(this.fullChunkFuture, "full");
-        }
-
-        if (bl3 && !bl4) {
-            this.fullChunkFuture.complete(UNLOADED_LEVEL_CHUNK);
-            this.fullChunkFuture = UNLOADED_LEVEL_CHUNK_FUTURE;
-        }
-
-        boolean bl5 = fullChunkStatus.isOrAfter(ChunkHolder.FullChunkStatus.TICKING);
-        boolean bl6 = fullChunkStatus2.isOrAfter(ChunkHolder.FullChunkStatus.TICKING);
-        if (!bl5 && bl6) {
-            this.tickingChunkFuture = chunkStorage.prepareTickingChunk(this);
-            this.scheduleFullChunkPromotion(chunkStorage, this.tickingChunkFuture, executor, ChunkHolder.FullChunkStatus.TICKING);
-            this.updateChunkToSave(this.tickingChunkFuture, "ticking");
-        }
-
-        if (bl5 && !bl6) {
-            this.tickingChunkFuture.complete(UNLOADED_LEVEL_CHUNK);
-            this.tickingChunkFuture = UNLOADED_LEVEL_CHUNK_FUTURE;
-        }
-
-        boolean bl7 = fullChunkStatus.isOrAfter(ChunkHolder.FullChunkStatus.ENTITY_TICKING);
-        boolean bl8 = fullChunkStatus2.isOrAfter(ChunkHolder.FullChunkStatus.ENTITY_TICKING);
-        if (!bl7 && bl8) {
-            if (this.entityTickingChunkFuture != UNLOADED_LEVEL_CHUNK_FUTURE) {
-                throw (IllegalStateException)Util.pauseInIde(new IllegalStateException());
-            }
-
-            this.entityTickingChunkFuture = chunkStorage.prepareEntityTickingChunk(this.pos);
-            this.scheduleFullChunkPromotion(chunkStorage, this.entityTickingChunkFuture, executor, ChunkHolder.FullChunkStatus.ENTITY_TICKING);
-            this.updateChunkToSave(this.entityTickingChunkFuture, "entity ticking");
-        }
-
-        if (bl7 && !bl8) {
-            this.entityTickingChunkFuture.complete(UNLOADED_LEVEL_CHUNK);
-            this.entityTickingChunkFuture = UNLOADED_LEVEL_CHUNK_FUTURE;
-        }
-
-        if (!fullChunkStatus2.isOrAfter(fullChunkStatus)) {
-            this.demoteFullChunk(chunkStorage, fullChunkStatus2);
-        }
-
-        this.onLevelChange.onLevelChange(this.pos, this::getQueueLevel, this.ticketLevel, this::setQueueLevel);
-        this.oldTicketLevel = this.ticketLevel;
-    }
+    // Paper - rewrite chunk system
 
     public static ChunkStatus getStatus(int level) {
         return level < 33 ? ChunkStatus.FULL : ChunkStatus.getStatusAroundFullChunk(level - 33);
     }
 
     public static ChunkHolder.FullChunkStatus getFullChunkStatus(int distance) {
-        return FULL_CHUNK_STATUSES[Mth.clamp(33 - distance + 1, 0, FULL_CHUNK_STATUSES.length - 1)];
-    }
-
-    public boolean wasAccessibleSinceLastSave() {
-        return this.wasAccessibleSinceLastSave;
+        return ChunkHolder.FULL_CHUNK_STATUSES[Mth.clamp(33 - distance + 1, (int) 0, ChunkHolder.FULL_CHUNK_STATUSES.length - 1)];
     }
 
-    public void refreshAccessibility() {
-        this.wasAccessibleSinceLastSave = getFullChunkStatus(this.ticketLevel).isOrAfter(ChunkHolder.FullChunkStatus.BORDER);
-    }
+    // Paper - rewrite chunk system
 
     public void replaceProtoChunk(ImposterProtoChunk chunk) {
-        for(int i = 0; i < this.futures.length(); ++i) {
-            CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> completableFuture = this.futures.get(i);
-            if (completableFuture != null) {
-                Optional<ChunkAccess> optional = completableFuture.getNow(UNLOADED_CHUNK).left();
-                if (!optional.isEmpty() && optional.get() instanceof ProtoChunk) {
-                    this.futures.set(i, CompletableFuture.completedFuture(Either.left(chunk)));
-                }
-            }
-        }
-
-        this.updateChunkToSave(CompletableFuture.completedFuture(Either.left(chunk.getWrapped())), "replaceProto");
+        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
     }
 
     public List<Pair<ChunkStatus, CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>>>> getAllFutures() {
-        List<Pair<ChunkStatus, CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>>>> list = new ArrayList<>();
+        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
+    }
 
-        for(int i = 0; i < CHUNK_STATUSES.size(); ++i) {
-            list.add(Pair.of(CHUNK_STATUSES.get(i), this.futures.get(i)));
-        }
+    @FunctionalInterface
+    public interface LevelChangeListener {
 
-        return list;
+        void onLevelChange(ChunkPos pos, IntSupplier levelGetter, int targetLevel, IntConsumer levelSetter);
     }
 
-    public interface ChunkLoadingFailure {
-        ChunkHolder.ChunkLoadingFailure UNLOADED = new ChunkHolder.ChunkLoadingFailure() {
-            @Override
-            public String toString() {
-                return "UNLOADED";
-            }
-        };
+    public interface PlayerProvider {
+
+        List<ServerPlayer> getPlayers(ChunkPos chunkPos, boolean onlyOnWatchDistanceEdge);
     }
 
-    static final class ChunkSaveDebug {
+    private static final class ChunkSaveDebug {
+
         private final Thread thread;
         private final CompletableFuture<?> future;
         private final String source;
@@ -483,22 +435,36 @@
     }
 
     public static enum FullChunkStatus {
-        INACCESSIBLE,
-        BORDER,
-        TICKING,
-        ENTITY_TICKING;
+
+        INACCESSIBLE, BORDER, TICKING, ENTITY_TICKING;
+
+        private FullChunkStatus() {}
 
         public boolean isOrAfter(ChunkHolder.FullChunkStatus levelType) {
             return this.ordinal() >= levelType.ordinal();
         }
     }
 
-    @FunctionalInterface
-    public interface LevelChangeListener {
-        void onLevelChange(ChunkPos pos, IntSupplier levelGetter, int targetLevel, IntConsumer levelSetter);
+    public interface ChunkLoadingFailure {
+
+        ChunkHolder.ChunkLoadingFailure UNLOADED = new ChunkHolder.ChunkLoadingFailure() {
+            public String toString() {
+                return "UNLOADED";
+            }
+        };
     }
 
-    public interface PlayerProvider {
-        List<ServerPlayer> getPlayers(ChunkPos chunkPos, boolean onlyOnWatchDistanceEdge);
+    // Paper start
+    public final boolean isEntityTickingReady() {
+        return this.newChunkHolder.isEntityTickingReady(); // Paper - rewrite chunk system
     }
+
+    public final boolean isTickingReady() {
+        return this.newChunkHolder.isTickingReady(); // Paper - rewrite chunk system
+    }
+
+    public final boolean isFullChunkReady() {
+        return this.newChunkHolder.isFullChunkReady(); // Paper - rewrite chunk system
+    }
+    // Paper end
 }
