--- a/net/minecraft/server/level/ServerChunkCache.java
+++ b/net/minecraft/server/level/ServerChunkCache.java
@@ -6,9 +6,10 @@
 import java.io.File;
 import java.io.IOException;
 import java.util.Arrays;
+import java.util.Iterator;
 import java.util.List;
+import java.util.Objects;
 import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.CompletionStage;
 import java.util.concurrent.Executor;
 import java.util.function.BooleanSupplier;
 import java.util.function.Consumer;
@@ -45,7 +46,9 @@
 import net.minecraft.world.level.storage.DimensionDataStorage;
 import net.minecraft.world.level.storage.LevelStorageSource;
 
-public class ServerChunkCache extends ChunkSource {
+public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moonrise.patches.chunk_system.world.ChunkSystemServerChunkCache { // Paper - rewrite chunk system
+
+    public static final org.slf4j.Logger LOGGER = com.mojang.logging.LogUtils.getLogger(); // Paper
     private static final List<ChunkStatus> CHUNK_STATUSES = ChunkStatus.getStatusList();
     private final DistanceManager distanceManager;
     final ServerLevel level;
@@ -64,48 +67,135 @@
     @Nullable
     @VisibleForDebug
     private NaturalSpawner.SpawnState lastSpawnState;
+    // Paper start
+    public final io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet<LevelChunk> tickingChunks = new io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet<>(4096, 0.75f, 4096, 0.15, true);
+    public final io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet<LevelChunk> entityTickingChunks = new io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet<>(4096, 0.75f, 4096, 0.15, true);
+    private final ca.spottedleaf.concurrentutil.map.ConcurrentLong2ReferenceChainedHashTable<net.minecraft.world.level.chunk.LevelChunk> fullChunks = new ca.spottedleaf.concurrentutil.map.ConcurrentLong2ReferenceChainedHashTable<>();
+    long chunkFutureAwaitCounter;
+    // Paper end
+    // Paper start - rewrite chunk system
 
-    public ServerChunkCache(
-        ServerLevel world,
-        LevelStorageSource.LevelStorageAccess session,
-        DataFixer dataFixer,
-        StructureTemplateManager structureTemplateManager,
-        Executor workerExecutor,
-        ChunkGenerator chunkGenerator,
-        int viewDistance,
-        int simulationDistance,
-        boolean dsync,
-        ChunkProgressListener worldGenerationProgressListener,
-        ChunkStatusUpdateListener chunkStatusChangeListener,
-        Supplier<DimensionDataStorage> persistentStateManagerFactory
-    ) {
+    @Override
+    public final void moonrise$setFullChunk(final int chunkX, final int chunkZ, final LevelChunk chunk) {
+        final long key = ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkKey(chunkX, chunkZ);
+        if (chunk == null) {
+            this.fullChunks.remove(key);
+        } else {
+            this.fullChunks.put(key, chunk);
+        }
+    }
+
+    @Override
+    public final LevelChunk moonrise$getFullChunkIfLoaded(final int chunkX, final int chunkZ) {
+        return this.fullChunks.get(ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkKey(chunkX, chunkZ));
+    }
+
+    private ChunkAccess syncLoad(final int chunkX, final int chunkZ, final ChunkStatus toStatus) {
+        final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler chunkTaskScheduler = ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)this.level).moonrise$getChunkTaskScheduler();
+        final CompletableFuture<ChunkAccess> completable = new CompletableFuture<>();
+        chunkTaskScheduler.scheduleChunkLoad(
+            chunkX, chunkZ, toStatus, true, ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority.BLOCKING,
+            completable::complete
+        );
+
+        if (io.papermc.paper.util.TickThread.isTickThreadFor(this.level, chunkX, chunkZ)) {
+            ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler.pushChunkWait(this.level, chunkX, chunkZ);
+            this.mainThreadProcessor.managedBlock(completable::isDone);
+            ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler.popChunkWait();
+        }
+
+        final ChunkAccess ret = completable.join();
+        if (ret == null) {
+            throw new IllegalStateException("Chunk not loaded when requested");
+        }
+
+        return ret;
+    }
+
+    private ChunkAccess getChunkFallback(final int chunkX, final int chunkZ, final ChunkStatus toStatus,
+                                         final boolean load) {
+        final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler chunkTaskScheduler = ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)this.level).moonrise$getChunkTaskScheduler();
+        final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkHolderManager chunkHolderManager = chunkTaskScheduler.chunkHolderManager;
+
+        final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder currentChunk = chunkHolderManager.getChunkHolder(ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkKey(chunkX, chunkZ));
+
+        final ChunkAccess ifPresent = currentChunk == null ? null : currentChunk.getChunkIfPresent(toStatus);
+
+        if (ifPresent != null && (toStatus != ChunkStatus.FULL || currentChunk.isFullChunkReady())) {
+            return ifPresent;
+        }
+
+        return load ? this.syncLoad(chunkX, chunkZ, toStatus) : null;
+    }
+    // Paper end - rewrite chunk system
+
+    public ServerChunkCache(ServerLevel world, LevelStorageSource.LevelStorageAccess session, DataFixer dataFixer, StructureTemplateManager structureTemplateManager, Executor workerExecutor, ChunkGenerator chunkGenerator, int viewDistance, int simulationDistance, boolean dsync, ChunkProgressListener worldGenerationProgressListener, ChunkStatusUpdateListener chunkStatusChangeListener, Supplier<DimensionDataStorage> persistentStateManagerFactory) {
         this.level = world;
         this.mainThreadProcessor = new ServerChunkCache.MainThreadExecutor(world);
         this.mainThread = Thread.currentThread();
         File file = session.getDimensionPath(world.dimension()).resolve("data").toFile();
+
         file.mkdirs();
         this.dataStorage = new DimensionDataStorage(file, dataFixer, world.registryAccess());
-        this.chunkMap = new ChunkMap(
-            world,
-            session,
-            dataFixer,
-            structureTemplateManager,
-            workerExecutor,
-            this.mainThreadProcessor,
-            this,
-            chunkGenerator,
-            worldGenerationProgressListener,
-            chunkStatusChangeListener,
-            persistentStateManagerFactory,
-            viewDistance,
-            dsync
-        );
+        this.chunkMap = new ChunkMap(world, session, dataFixer, structureTemplateManager, workerExecutor, this.mainThreadProcessor, this, chunkGenerator, worldGenerationProgressListener, chunkStatusChangeListener, persistentStateManagerFactory, viewDistance, dsync);
         this.lightEngine = this.chunkMap.getLightEngine();
         this.distanceManager = this.chunkMap.getDistanceManager();
         this.distanceManager.updateSimulationDistance(simulationDistance);
         this.clearCache();
     }
 
+    // CraftBukkit start - properly implement isChunkLoaded
+    public boolean isChunkLoaded(int chunkX, int chunkZ) {
+        ChunkHolder chunk = this.chunkMap.getUpdatingChunkIfPresent(ChunkPos.asLong(chunkX, chunkZ));
+        if (chunk == null) {
+            return false;
+        }
+        return chunk.getFullChunkNow() != null;
+    }
+    // CraftBukkit end
+    // Paper start
+    // Paper - rewrite chunk system
+
+    @Nullable
+    public ChunkAccess getChunkAtImmediately(int x, int z) {
+        ChunkHolder holder = this.chunkMap.getVisibleChunkIfPresent(ChunkPos.asLong(x, z));
+        if (holder == null) {
+            return null;
+        }
+
+        return holder.getLatestChunk();
+    }
+
+    public <T> void addTicketAtLevel(TicketType<T> ticketType, ChunkPos chunkPos, int ticketLevel, T identifier) {
+        this.distanceManager.addTicket(ticketType, chunkPos, ticketLevel, identifier);
+    }
+
+    public <T> void removeTicketAtLevel(TicketType<T> ticketType, ChunkPos chunkPos, int ticketLevel, T identifier) {
+        this.distanceManager.removeTicket(ticketType, chunkPos, ticketLevel, identifier);
+    }
+
+    // "real" get chunk if loaded
+    // Note: Partially copied from the getChunkAt method below
+    @Nullable
+    public LevelChunk getChunkAtIfCachedImmediately(int x, int z) {
+        long k = ChunkPos.asLong(x, z);
+
+        // Note: Bypass cache since we need to check ticket level, and to make this MT-Safe
+
+        ChunkHolder playerChunk = this.getVisibleChunkIfPresent(k);
+        if (playerChunk == null) {
+            return null;
+        }
+
+        return playerChunk.getFullChunkNowUnchecked();
+    }
+
+    @Nullable
+    public LevelChunk getChunkAtIfLoadedImmediately(int x, int z) {
+        return this.fullChunks.get(ChunkPos.asLong(x, z));
+    }
+    // Paper end
+
     @Override
     public ThreadedLevelLightEngine getLightEngine() {
         return this.lightEngine;
@@ -121,10 +211,10 @@
     }
 
     private void storeInCache(long pos, @Nullable ChunkAccess chunk, ChunkStatus status) {
-        for (int i = 3; i > 0; i--) {
-            this.lastChunkPos[i] = this.lastChunkPos[i - 1];
-            this.lastChunkStatus[i] = this.lastChunkStatus[i - 1];
-            this.lastChunk[i] = this.lastChunk[i - 1];
+        for (int j = 3; j > 0; --j) {
+            this.lastChunkPos[j] = this.lastChunkPos[j - 1];
+            this.lastChunkStatus[j] = this.lastChunkStatus[j - 1];
+            this.lastChunk[j] = this.lastChunk[j - 1];
         }
 
         this.lastChunkPos[0] = pos;
@@ -135,132 +225,108 @@
     @Nullable
     @Override
     public ChunkAccess getChunk(int x, int z, ChunkStatus leastStatus, boolean create) {
-        if (Thread.currentThread() != this.mainThread) {
-            return CompletableFuture.<ChunkAccess>supplyAsync(() -> this.getChunk(x, z, leastStatus, create), this.mainThreadProcessor).join();
-        } else {
-            ProfilerFiller profilerFiller = this.level.getProfiler();
-            profilerFiller.incrementCounter("getChunk");
-            long l = ChunkPos.asLong(x, z);
+        // Paper start - rewrite chunk system
+        if (leastStatus == ChunkStatus.FULL) {
+            final LevelChunk ret = this.fullChunks.get(ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkKey(x, z));
 
-            for (int i = 0; i < 4; i++) {
-                if (l == this.lastChunkPos[i] && leastStatus == this.lastChunkStatus[i]) {
-                    ChunkAccess chunkAccess = this.lastChunk[i];
-                    if (chunkAccess != null || !create) {
-                        return chunkAccess;
-                    }
-                }
+            if (ret != null) {
+                return ret;
             }
 
-            profilerFiller.incrementCounter("getChunkCacheMiss");
-            CompletableFuture<ChunkResult<ChunkAccess>> completableFuture = this.getChunkFutureMainThread(x, z, leastStatus, create);
-            this.mainThreadProcessor.managedBlock(completableFuture::isDone);
-            ChunkResult<ChunkAccess> chunkResult = completableFuture.join();
-            ChunkAccess chunkAccess2 = chunkResult.orElse(null);
-            if (chunkAccess2 == null && create) {
-                throw (IllegalStateException)Util.pauseInIde(new IllegalStateException("Chunk not there when requested: " + chunkResult.getError()));
-            } else {
-                this.storeInCache(l, chunkAccess2, leastStatus);
-                return chunkAccess2;
-            }
+            return create ? this.getChunkFallback(x, z, leastStatus, create) : null;
         }
+
+        return this.getChunkFallback(x, z, leastStatus, create);
+        // Paper end - rewrite chunk system
     }
 
     @Nullable
     @Override
     public LevelChunk getChunkNow(int chunkX, int chunkZ) {
-        if (Thread.currentThread() != this.mainThread) {
-            return null;
-        } else {
-            this.level.getProfiler().incrementCounter("getChunkNow");
-            long l = ChunkPos.asLong(chunkX, chunkZ);
-
-            for (int i = 0; i < 4; i++) {
-                if (l == this.lastChunkPos[i] && this.lastChunkStatus[i] == ChunkStatus.FULL) {
-                    ChunkAccess chunkAccess = this.lastChunk[i];
-                    return chunkAccess instanceof LevelChunk ? (LevelChunk)chunkAccess : null;
-                }
-            }
-
-            ChunkHolder chunkHolder = this.getVisibleChunkIfPresent(l);
-            if (chunkHolder == null) {
-                return null;
-            } else {
-                ChunkAccess chunkAccess2 = chunkHolder.getChunkIfPresent(ChunkStatus.FULL);
-                if (chunkAccess2 != null) {
-                    this.storeInCache(l, chunkAccess2, ChunkStatus.FULL);
-                    if (chunkAccess2 instanceof LevelChunk) {
-                        return (LevelChunk)chunkAccess2;
-                    }
-                }
-
-                return null;
-            }
-        }
+        return ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)this.level).moonrise$getFullChunkIfLoaded(chunkX, chunkZ); // Paper - rewrite chunk system
     }
 
     private void clearCache() {
         Arrays.fill(this.lastChunkPos, ChunkPos.INVALID_CHUNK_POS);
-        Arrays.fill(this.lastChunkStatus, null);
-        Arrays.fill(this.lastChunk, null);
+        Arrays.fill(this.lastChunkStatus, (Object) null);
+        Arrays.fill(this.lastChunk, (Object) null);
     }
 
     public CompletableFuture<ChunkResult<ChunkAccess>> getChunkFuture(int chunkX, int chunkZ, ChunkStatus leastStatus, boolean create) {
-        boolean bl = Thread.currentThread() == this.mainThread;
-        CompletableFuture<ChunkResult<ChunkAccess>> completableFuture;
-        if (bl) {
-            completableFuture = this.getChunkFutureMainThread(chunkX, chunkZ, leastStatus, create);
-            this.mainThreadProcessor.managedBlock(completableFuture::isDone);
+        boolean flag1 = Thread.currentThread() == this.mainThread;
+        CompletableFuture completablefuture;
+
+        if (flag1) {
+            completablefuture = this.getChunkFutureMainThread(chunkX, chunkZ, leastStatus, create);
+            ServerChunkCache.MainThreadExecutor chunkproviderserver_b = this.mainThreadProcessor;
+
+            Objects.requireNonNull(completablefuture);
+            chunkproviderserver_b.managedBlock(completablefuture::isDone);
         } else {
-            completableFuture = CompletableFuture.<CompletableFuture<ChunkResult<ChunkAccess>>>supplyAsync(
-                    () -> this.getChunkFutureMainThread(chunkX, chunkZ, leastStatus, create), this.mainThreadProcessor
-                )
-                .thenCompose(future -> (CompletionStage<ChunkResult<ChunkAccess>>)future);
+            completablefuture = CompletableFuture.supplyAsync(() -> {
+                return this.getChunkFutureMainThread(chunkX, chunkZ, leastStatus, create);
+            }, this.mainThreadProcessor).thenCompose((completablefuture1) -> {
+                return completablefuture1;
+            });
         }
 
-        return completableFuture;
+        return completablefuture;
     }
 
     private CompletableFuture<ChunkResult<ChunkAccess>> getChunkFutureMainThread(int chunkX, int chunkZ, ChunkStatus leastStatus, boolean create) {
-        ChunkPos chunkPos = new ChunkPos(chunkX, chunkZ);
-        long l = chunkPos.toLong();
-        int i = ChunkLevel.byStatus(leastStatus);
-        ChunkHolder chunkHolder = this.getVisibleChunkIfPresent(l);
-        if (create) {
-            this.distanceManager.addTicket(TicketType.UNKNOWN, chunkPos, i, chunkPos);
-            if (this.chunkAbsent(chunkHolder, i)) {
-                ProfilerFiller profilerFiller = this.level.getProfiler();
-                profilerFiller.push("chunkLoad");
-                this.runDistanceManagerUpdates();
-                chunkHolder = this.getVisibleChunkIfPresent(l);
-                profilerFiller.pop();
-                if (this.chunkAbsent(chunkHolder, i)) {
-                    throw (IllegalStateException)Util.pauseInIde(new IllegalStateException("No chunk holder after ticket has been added"));
-                }
-            }
+        // Paper start - rewrite chunk system
+        io.papermc.paper.util.TickThread.ensureTickThread(this.level, chunkX, chunkZ, "Scheduling chunk load off-main");
+
+        final int minLevel = ChunkLevel.byStatus(leastStatus);
+        final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder chunkHolder = ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)this.level).moonrise$getChunkTaskScheduler().chunkHolderManager.getChunkHolder(chunkX, chunkZ);
+
+        final boolean needsFullScheduling = leastStatus == ChunkStatus.FULL && (chunkHolder == null || !chunkHolder.getChunkStatus().isOrAfter(FullChunkStatus.FULL));
+
+        if ((chunkHolder == null || chunkHolder.getTicketLevel() > minLevel || needsFullScheduling) && !create) {
+            return ChunkHolder.UNLOADED_CHUNK_FUTURE;
         }
 
-        return this.chunkAbsent(chunkHolder, i)
-            ? GenerationChunkHolder.UNLOADED_CHUNK_FUTURE
-            : chunkHolder.scheduleChunkGenerationTask(leastStatus, this.chunkMap);
-    }
+        final ChunkAccess ifPresent = chunkHolder == null ? null : chunkHolder.getChunkIfPresent(leastStatus);
+        if (needsFullScheduling || ifPresent == null) {
+            // schedule
+            final CompletableFuture<ChunkResult<ChunkAccess>> ret = new CompletableFuture<>();
+            final Consumer<ChunkAccess> complete = (ChunkAccess chunk) -> {
+                if (chunk == null) {
+                    ret.complete(ChunkHolder.UNLOADED_CHUNK);
+                } else {
+                    ret.complete(ChunkResult.of(chunk));
+                }
+            };
 
-    private boolean chunkAbsent(@Nullable ChunkHolder holder, int maxLevel) {
-        return holder == null || holder.getTicketLevel() > maxLevel;
+            ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)this.level).moonrise$getChunkTaskScheduler().scheduleChunkLoad(
+                chunkX, chunkZ, leastStatus, true,
+                ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority.HIGHER,
+                complete
+            );
+
+            return ret;
+        } else {
+            // can return now
+            return CompletableFuture.completedFuture(ChunkResult.of(ifPresent));
+        }
+        // Paper end - rewrite chunk system
     }
 
     @Override
     public boolean hasChunk(int x, int z) {
-        ChunkHolder chunkHolder = this.getVisibleChunkIfPresent(new ChunkPos(x, z).toLong());
-        int i = ChunkLevel.byStatus(ChunkStatus.FULL);
-        return !this.chunkAbsent(chunkHolder, i);
+        return this.getChunkNow(x, z) != null; // Paper - rewrite chunk system
     }
 
     @Nullable
     @Override
     public LightChunk getChunkForLighting(int chunkX, int chunkZ) {
-        long l = ChunkPos.asLong(chunkX, chunkZ);
-        ChunkHolder chunkHolder = this.getVisibleChunkIfPresent(l);
-        return chunkHolder == null ? null : chunkHolder.getChunkIfPresentUnchecked(ChunkStatus.INITIALIZE_LIGHT.getParent());
+        // Paper start - rewrite chunk system
+        final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder newChunkHolder = ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)this.level).moonrise$getChunkTaskScheduler().chunkHolderManager.getChunkHolder(chunkX, chunkZ);
+        if (newChunkHolder == null) {
+            return null;
+        }
+        return newChunkHolder.getChunkIfPresentUnchecked(ChunkStatus.INITIALIZE_LIGHT.getParent());
+        // Paper end - rewrite chunk system
     }
 
     @Override
@@ -272,120 +338,207 @@
         return this.mainThreadProcessor.pollTask();
     }
 
-    boolean runDistanceManagerUpdates() {
-        boolean bl = this.distanceManager.runAllUpdates(this.chunkMap);
-        boolean bl2 = this.chunkMap.promoteChunkMap();
-        this.chunkMap.runGenerationTasks();
-        if (!bl && !bl2) {
-            return false;
-        } else {
-            this.clearCache();
-            return true;
-        }
+    public boolean runDistanceManagerUpdates() { // Paper - public
+        return ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)this.level).moonrise$getChunkTaskScheduler().chunkHolderManager.processTicketUpdates(); // Paper - rewrite chunk system
     }
 
+    // Paper start
+    public boolean isPositionTicking(Entity entity) {
+        return this.isPositionTicking(ChunkPos.asLong(net.minecraft.util.Mth.floor(entity.getX()) >> 4, net.minecraft.util.Mth.floor(entity.getZ()) >> 4));
+    }
+    // Paper end
+
     public boolean isPositionTicking(long pos) {
-        ChunkHolder chunkHolder = this.getVisibleChunkIfPresent(pos);
-        return chunkHolder != null
-            && this.level.shouldTickBlocksAt(pos)
-            && chunkHolder.getTickingChunkFuture().getNow(ChunkHolder.UNLOADED_LEVEL_CHUNK).isSuccess();
+        // Paper start - rewrite chunk system
+        final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder newChunkHolder = ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)this.level).moonrise$getChunkTaskScheduler().chunkHolderManager.getChunkHolder(pos);
+        return newChunkHolder != null && newChunkHolder.isTickingReady();
+        // Paper end - rewrite chunk system
     }
 
     public void save(boolean flush) {
-        this.runDistanceManagerUpdates();
+        // Paper - rewrite chunk system
+        try (co.aikar.timings.Timing timed = level.timings.chunkSaveData.startTiming()) { // Paper - Timings
         this.chunkMap.saveAllChunks(flush);
+        } // Paper - Timings
     }
 
     @Override
     public void close() throws IOException {
-        this.save(true);
-        this.lightEngine.close();
-        this.chunkMap.close();
+        // CraftBukkit start
+        this.close(true);
     }
 
+    public void close(boolean save) throws IOException {
+        ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)this.level).moonrise$getChunkTaskScheduler().chunkHolderManager.close(save, true); // Paper - rewrite chunk system
+        // Paper start - Write SavedData IO async
+        try {
+            this.dataStorage.close();
+        } catch (final IOException e) {
+            LOGGER.error("Failed to close persistent world data", e);
+        }
+        // Paper end - Write SavedData IO async
+    }
+
+    // CraftBukkit start - modelled on below
+    public void purgeUnload() {
+        this.level.getProfiler().push("purge");
+        this.distanceManager.purgeStaleTickets();
+        this.runDistanceManagerUpdates();
+        this.level.getProfiler().popPush("unload");
+        this.chunkMap.tick(() -> true);
+        this.level.getProfiler().pop();
+        this.clearCache();
+    }
+    // CraftBukkit end
+
     @Override
     public void tick(BooleanSupplier shouldKeepTicking, boolean tickChunks) {
         this.level.getProfiler().push("purge");
-        if (this.level.tickRateManager().runsNormally() || !tickChunks) {
+        this.level.timings.doChunkMap.startTiming(); // Spigot
+        if (this.level.tickRateManager().runsNormally() || !tickChunks || this.level.spigotConfig.unloadFrozenChunks) { // Spigot
             this.distanceManager.purgeStaleTickets();
         }
 
         this.runDistanceManagerUpdates();
+        this.level.timings.doChunkMap.stopTiming(); // Spigot
         this.level.getProfiler().popPush("chunks");
         if (tickChunks) {
+            this.level.timings.chunks.startTiming(); // Paper - timings
+            ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)this.level).moonrise$getPlayerChunkLoader().tick(); // Paper - rewrite chunk system
             this.tickChunks();
+            this.level.timings.chunks.stopTiming(); // Paper - timings
             this.chunkMap.tick();
         }
 
+        this.level.timings.doChunkUnload.startTiming(); // Spigot
         this.level.getProfiler().popPush("unload");
         this.chunkMap.tick(shouldKeepTicking);
+        this.level.timings.doChunkUnload.stopTiming(); // Spigot
         this.level.getProfiler().pop();
         this.clearCache();
     }
 
     private void tickChunks() {
-        long l = this.level.getGameTime();
-        long m = l - this.lastInhabitedUpdate;
-        this.lastInhabitedUpdate = l;
+        long chunksTicked = 0; // Paper - rewrite chunk system
+        long i = this.level.getGameTime();
+        long j = i - this.lastInhabitedUpdate;
+
+        this.lastInhabitedUpdate = i;
         if (!this.level.isDebug()) {
-            ProfilerFiller profilerFiller = this.level.getProfiler();
-            profilerFiller.push("pollingChunks");
-            profilerFiller.push("filteringLoadedChunks");
+            ProfilerFiller gameprofilerfiller = this.level.getProfiler();
+
+            gameprofilerfiller.push("pollingChunks");
+            gameprofilerfiller.push("filteringLoadedChunks");
             List<ServerChunkCache.ChunkAndHolder> list = Lists.newArrayListWithCapacity(this.chunkMap.size());
+            Iterator iterator = this.chunkMap.getChunks().iterator();
+            if (this.level.getServer().tickRateManager().runsNormally()) this.level.timings.chunkTicks.startTiming(); // Paper
 
-            for (ChunkHolder chunkHolder : this.chunkMap.getChunks()) {
-                LevelChunk levelChunk = chunkHolder.getTickingChunk();
-                if (levelChunk != null) {
-                    list.add(new ServerChunkCache.ChunkAndHolder(levelChunk, chunkHolder));
+            while (iterator.hasNext()) {
+                ChunkHolder playerchunk = (ChunkHolder) iterator.next();
+                LevelChunk chunk = playerchunk.getTickingChunk();
+
+                if (chunk != null) {
+                    list.add(new ServerChunkCache.ChunkAndHolder(chunk, playerchunk));
                 }
             }
 
             if (this.level.tickRateManager().runsNormally()) {
-                profilerFiller.popPush("naturalSpawnCount");
-                int i = this.distanceManager.getNaturalSpawnChunkCount();
-                NaturalSpawner.SpawnState spawnState = NaturalSpawner.createState(
-                    i, this.level.getAllEntities(), this::getFullChunk, new LocalMobCapCalculator(this.chunkMap)
-                );
-                this.lastSpawnState = spawnState;
-                profilerFiller.popPush("spawnAndTick");
-                boolean bl = this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING);
+                gameprofilerfiller.popPush("naturalSpawnCount");
+                this.level.timings.countNaturalMobs.startTiming(); // Paper - timings
+                int k = this.distanceManager.getNaturalSpawnChunkCount();
+                // Paper start - Optional per player mob spawns
+                int naturalSpawnChunkCount = k;
+                NaturalSpawner.SpawnState spawnercreature_d; // moved down
+                if ((this.spawnFriendlies || this.spawnEnemies) && this.level.paperConfig().entities.spawning.perPlayerMobSpawns) { // don't count mobs when animals and monsters are disabled
+                    // re-set mob counts
+                    for (ServerPlayer player : this.level.players) {
+                        // Paper start - per player mob spawning backoff
+                        for (int ii = 0; ii < ServerPlayer.MOBCATEGORY_TOTAL_ENUMS; ii++) {
+                            player.mobCounts[ii] = 0;
+
+                            int newBackoff = player.mobBackoffCounts[ii] - 1; // TODO make configurable bleed // TODO use nonlinear algorithm?
+                            if (newBackoff < 0) {
+                                newBackoff = 0;
+                            }
+                            player.mobBackoffCounts[ii] = newBackoff;
+                        }
+                        // Paper end - per player mob spawning backoff
+                    }
+                    spawnercreature_d = NaturalSpawner.createState(naturalSpawnChunkCount, this.level.getAllEntities(), this::getFullChunk, null, true);
+                } else {
+                    spawnercreature_d = NaturalSpawner.createState(naturalSpawnChunkCount, this.level.getAllEntities(), this::getFullChunk, !this.level.paperConfig().entities.spawning.perPlayerMobSpawns ? new LocalMobCapCalculator(this.chunkMap) : null, false);
+                }
+                // Paper end - Optional per player mob spawns
+                this.level.timings.countNaturalMobs.stopTiming(); // Paper - timings
+
+                this.lastSpawnState = spawnercreature_d;
+                gameprofilerfiller.popPush("spawnAndTick");
+                boolean flag = this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && !this.level.players().isEmpty(); // CraftBukkit
+
                 Util.shuffle(list, this.level.random);
-                int j = this.level.getGameRules().getInt(GameRules.RULE_RANDOMTICKING);
-                boolean bl2 = this.level.getLevelData().getGameTime() % 400L == 0L;
+                // Paper start - PlayerNaturallySpawnCreaturesEvent
+                int chunkRange = level.spigotConfig.mobSpawnRange;
+                chunkRange = (chunkRange > level.spigotConfig.viewDistance) ? (byte) level.spigotConfig.viewDistance : chunkRange;
+                chunkRange = Math.min(chunkRange, 8);
+                for (ServerPlayer entityPlayer : this.level.players()) {
+                    entityPlayer.playerNaturallySpawnedEvent = new com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent(entityPlayer.getBukkitEntity(), (byte) chunkRange);
+                    entityPlayer.playerNaturallySpawnedEvent.callEvent();
+                }
+                // Paper end - PlayerNaturallySpawnCreaturesEvent
+                int l = this.level.getGameRules().getInt(GameRules.RULE_RANDOMTICKING);
+                boolean flag1 = this.level.ticksPerSpawnCategory.getLong(org.bukkit.entity.SpawnCategory.ANIMAL) != 0L && this.level.getLevelData().getGameTime() % this.level.ticksPerSpawnCategory.getLong(org.bukkit.entity.SpawnCategory.ANIMAL) == 0L; // CraftBukkit
+                Iterator iterator1 = list.iterator();
 
-                for (ServerChunkCache.ChunkAndHolder chunkAndHolder : list) {
-                    LevelChunk levelChunk2 = chunkAndHolder.chunk;
-                    ChunkPos chunkPos = levelChunk2.getPos();
-                    if (this.level.isNaturalSpawningAllowed(chunkPos) && this.chunkMap.anyPlayerCloseEnoughForSpawning(chunkPos)) {
-                        levelChunk2.incrementInhabitedTime(m);
-                        if (bl && (this.spawnEnemies || this.spawnFriendlies) && this.level.getWorldBorder().isWithinBounds(chunkPos)) {
-                            NaturalSpawner.spawnForChunk(this.level, levelChunk2, spawnState, this.spawnFriendlies, this.spawnEnemies, bl2);
+                while (iterator1.hasNext()) {
+                    ServerChunkCache.ChunkAndHolder chunkproviderserver_a = (ServerChunkCache.ChunkAndHolder) iterator1.next();
+                    LevelChunk chunk1 = chunkproviderserver_a.chunk;
+                    ChunkPos chunkcoordintpair = chunk1.getPos();
+
+                    if (this.level.isNaturalSpawningAllowed(chunkcoordintpair) && this.chunkMap.anyPlayerCloseEnoughForSpawning(chunkcoordintpair)) {
+                        chunk1.incrementInhabitedTime(j);
+                        if (flag && (this.spawnEnemies || this.spawnFriendlies) && this.level.getWorldBorder().isWithinBounds(chunkcoordintpair) && this.chunkMap.anyPlayerCloseEnoughForSpawning(chunkcoordintpair, true)) { // Spigot
+                            NaturalSpawner.spawnForChunk(this.level, chunk1, spawnercreature_d, this.spawnFriendlies, this.spawnEnemies, flag1);
                         }
 
-                        if (this.level.shouldTickBlocksAt(chunkPos.toLong())) {
-                            this.level.tickChunk(levelChunk2, j);
+                        if (this.level.shouldTickBlocksAt(chunkcoordintpair.toLong())) {
+                            this.level.tickChunk(chunk1, l);
+                            // Paper start - rewrite chunk system
+                            if ((++chunksTicked & 7L) == 0L) {
+                                ((ca.spottedleaf.moonrise.patches.chunk_system.server.ChunkSystemMinecraftServer)this.level.getServer()).moonrise$executeMidTickTasks();
+                            }
+                            // Paper end - rewrite chunk system
                         }
                     }
                 }
+                this.level.timings.chunkTicks.stopTiming(); // Paper
 
-                profilerFiller.popPush("customSpawners");
-                if (bl) {
+                gameprofilerfiller.popPush("customSpawners");
+                if (flag) {
+                    try (co.aikar.timings.Timing ignored = this.level.timings.miscMobSpawning.startTiming()) { // Paper - timings
                     this.level.tickCustomSpawners(this.spawnEnemies, this.spawnFriendlies);
+                    } // Paper - timings
                 }
             }
 
-            profilerFiller.popPush("broadcast");
-            list.forEach(chunk -> chunk.holder.broadcastChanges(chunk.chunk));
-            profilerFiller.pop();
-            profilerFiller.pop();
+            gameprofilerfiller.popPush("broadcast");
+            list.forEach((chunkproviderserver_a1) -> {
+                this.level.timings.broadcastChunkUpdates.startTiming(); // Paper - timing
+                chunkproviderserver_a1.holder.broadcastChanges(chunkproviderserver_a1.chunk);
+                this.level.timings.broadcastChunkUpdates.stopTiming(); // Paper - timing
+            });
+            gameprofilerfiller.pop();
+            gameprofilerfiller.pop();
         }
     }
 
     private void getFullChunk(long pos, Consumer<LevelChunk> chunkConsumer) {
-        ChunkHolder chunkHolder = this.getVisibleChunkIfPresent(pos);
-        if (chunkHolder != null) {
-            chunkHolder.getFullChunkFuture().getNow(ChunkHolder.UNLOADED_LEVEL_CHUNK).ifSuccess(chunkConsumer);
+        // Paper start - rewrite chunk system
+        final LevelChunk fullChunk = this.getChunkNow(ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkX(pos), ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkZ(pos));
+        if (fullChunk != null) {
+            chunkConsumer.accept(fullChunk);
         }
+        // Paper end - rewrite chunk system
+
     }
 
     @Override
@@ -418,19 +571,23 @@
     public void blockChanged(BlockPos pos) {
         int i = SectionPos.blockToSectionCoord(pos.getX());
         int j = SectionPos.blockToSectionCoord(pos.getZ());
-        ChunkHolder chunkHolder = this.getVisibleChunkIfPresent(ChunkPos.asLong(i, j));
-        if (chunkHolder != null) {
-            chunkHolder.blockChanged(pos);
+        ChunkHolder playerchunk = this.getVisibleChunkIfPresent(ChunkPos.asLong(i, j));
+
+        if (playerchunk != null) {
+            playerchunk.blockChanged(pos);
         }
+
     }
 
     @Override
     public void onLightUpdate(LightLayer type, SectionPos pos) {
         this.mainThreadProcessor.execute(() -> {
-            ChunkHolder chunkHolder = this.getVisibleChunkIfPresent(pos.chunk().toLong());
-            if (chunkHolder != null) {
-                chunkHolder.sectionLightChanged(type, pos.y());
+            ChunkHolder playerchunk = this.getVisibleChunkIfPresent(pos.chunk().toLong());
+
+            if (playerchunk != null) {
+                playerchunk.sectionLightChanged(type, pos.y());
             }
+
         });
     }
 
@@ -451,6 +608,7 @@
         if (!player.isRemoved()) {
             this.chunkMap.move(player);
         }
+
     }
 
     public void removeEntity(Entity entity) {
@@ -473,6 +631,12 @@
         this.chunkMap.setServerViewDistance(watchDistance);
     }
 
+    // Paper start - rewrite chunk system
+    public void setSendViewDistance(int viewDistance) {
+        ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)this.level).moonrise$getPlayerChunkLoader().setSendDistance(viewDistance);
+    }
+    // Paper end - rewrite chunk system
+
     public void setSimulationDistance(int simulationDistance) {
         this.distanceManager.updateSimulationDistance(simulationDistance);
     }
@@ -509,17 +673,17 @@
         this.distanceManager.removeTicketsOnClosing();
     }
 
-    static record ChunkAndHolder(LevelChunk chunk, ChunkHolder holder) {
-    }
-
     public final class MainThreadExecutor extends BlockableEventLoop<Runnable> {
+
         MainThreadExecutor(final Level world) {
-            super("Chunk source main thread executor for " + world.dimension().location());
+            super("Chunk source main thread executor for " + String.valueOf(world.dimension().location()));
         }
 
         @Override
         public void managedBlock(BooleanSupplier stopCondition) {
-            super.managedBlock(() -> MinecraftServer.throwIfFatalException() && stopCondition.getAsBoolean());
+            super.managedBlock(() -> {
+                return MinecraftServer.throwIfFatalException() && stopCondition.getAsBoolean();
+            });
         }
 
         @Override
@@ -549,13 +713,21 @@
         }
 
         @Override
-        protected boolean pollTask() {
-            if (ServerChunkCache.this.runDistanceManagerUpdates()) {
+        // CraftBukkit start - process pending Chunk loadCallback() and unloadCallback() after each run task
+        public boolean pollTask() {
+            // Paper start - rewrite chunk system
+            final ServerChunkCache serverChunkCache = ServerChunkCache.this;
+            if (serverChunkCache.runDistanceManagerUpdates()) {
                 return true;
             } else {
-                ServerChunkCache.this.lightEngine.tryScheduleUpdate();
-                return super.pollTask();
+                return super.pollTask() | ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)serverChunkCache.level).moonrise$getChunkTaskScheduler().executeMainThreadTask();
             }
+            // Paper end - rewrite chunk system
+        // CraftBukkit end
         }
     }
+
+    private static record ChunkAndHolder(LevelChunk chunk, ChunkHolder holder) {
+
+    }
 }
