--- a/net/minecraft/server/level/DistanceManager.java
+++ b/net/minecraft/server/level/DistanceManager.java
@@ -34,55 +_,55 @@
 import net.minecraft.world.level.chunk.LevelChunk;
 import org.slf4j.Logger;
 
-public abstract class DistanceManager {
+public abstract class DistanceManager implements ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemDistanceManager, ca.spottedleaf.moonrise.patches.chunk_tick_iteration.ChunkTickDistanceManager { // Paper - rewrite chunk system // Paper - chunk tick iteration optimisation
     static final Logger LOGGER = LogUtils.getLogger();
     static final int PLAYER_TICKET_LEVEL = ChunkLevel.byStatus(FullChunkStatus.ENTITY_TICKING);
     private static final int INITIAL_TICKET_LIST_CAPACITY = 4;
     final Long2ObjectMap<ObjectSet<ServerPlayer>> playersPerChunk = new Long2ObjectOpenHashMap<>();
-    final Long2ObjectOpenHashMap<SortedArraySet<Ticket<?>>> tickets = new Long2ObjectOpenHashMap<>();
-    private final DistanceManager.ChunkTicketTracker ticketTracker = new DistanceManager.ChunkTicketTracker();
-    private final DistanceManager.FixedPlayerDistanceChunkTracker naturalSpawnChunkCounter = new DistanceManager.FixedPlayerDistanceChunkTracker(8);
-    private final TickingTracker tickingTicketsTracker = new TickingTracker();
-    private final DistanceManager.PlayerTicketTracker playerTicketManager = new DistanceManager.PlayerTicketTracker(32);
-    final Set<ChunkHolder> chunksToUpdateFutures = new ReferenceOpenHashSet<>();
-    final ThrottlingChunkTaskDispatcher ticketDispatcher;
-    final LongSet ticketsToRelease = new LongOpenHashSet();
-    final Executor mainThreadExecutor;
+    // Paper - rewrite chunk system
+    // Paper - chunk tick iteration optimisation
+    // Paper - rewrite chunk system
     private long ticketTickCounter;
-    private int simulationDistance = 10;
+    // Paper - rewrite chunk system
 
     protected DistanceManager(Executor dispatcher, Executor mainThreadExecutor) {
         TaskScheduler<Runnable> taskScheduler = TaskScheduler.wrapExecutor("player ticket throttler", mainThreadExecutor);
-        this.ticketDispatcher = new ThrottlingChunkTaskDispatcher(taskScheduler, dispatcher, 4);
-        this.mainThreadExecutor = mainThreadExecutor;
-    }
+        // Paper - rewrite chunk system
+    }
+
+    // Paper start - rewrite chunk system
+    @Override
+    public final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkHolderManager moonrise$getChunkHolderManager() {
+        return ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)this.moonrise$getChunkMap().level).moonrise$getChunkTaskScheduler().chunkHolderManager;
+    }
+    // Paper end - rewrite chunk system
+    // Paper start - chunk tick iteration optimisation
+    private final ca.spottedleaf.moonrise.common.misc.PositionCountingAreaMap<ServerPlayer> spawnChunkTracker = new ca.spottedleaf.moonrise.common.misc.PositionCountingAreaMap<>();
+
+    @Override
+    public final void moonrise$addPlayer(final ServerPlayer player, final SectionPos pos) {
+        this.spawnChunkTracker.add(player, pos.x(), pos.z(), ca.spottedleaf.moonrise.patches.chunk_tick_iteration.ChunkTickConstants.PLAYER_SPAWN_TRACK_RANGE);
+    }
+
+    @Override
+    public final void moonrise$removePlayer(final ServerPlayer player, final SectionPos pos) {
+        this.spawnChunkTracker.remove(player);
+    }
+
+    @Override
+    public final void moonrise$updatePlayer(final ServerPlayer player,
+                                            final SectionPos oldPos, final SectionPos newPos,
+                                            final boolean oldIgnore, final boolean newIgnore) {
+        if (newIgnore) {
+            this.spawnChunkTracker.remove(player);
+        } else {
+            this.spawnChunkTracker.addOrUpdate(player, newPos.x(), newPos.z(), ca.spottedleaf.moonrise.patches.chunk_tick_iteration.ChunkTickConstants.PLAYER_SPAWN_TRACK_RANGE);
+        }
+    }
+    // Paper end - chunk tick iteration optimisation
 
     protected void purgeStaleTickets() {
-        this.ticketTickCounter++;
-        ObjectIterator<Entry<SortedArraySet<Ticket<?>>>> objectIterator = this.tickets.long2ObjectEntrySet().fastIterator();
-
-        while (objectIterator.hasNext()) {
-            Entry<SortedArraySet<Ticket<?>>> entry = objectIterator.next();
-            Iterator<Ticket<?>> iterator = entry.getValue().iterator();
-            boolean flag = false;
-
-            while (iterator.hasNext()) {
-                Ticket<?> ticket = iterator.next();
-                if (ticket.timedOut(this.ticketTickCounter)) {
-                    iterator.remove();
-                    flag = true;
-                    this.tickingTicketsTracker.removeTicket(entry.getLongKey(), ticket);
-                }
-            }
-
-            if (flag) {
-                this.ticketTracker.update(entry.getLongKey(), getTicketLevelAt(entry.getValue()), false);
-            }
-
-            if (entry.getValue().isEmpty()) {
-                objectIterator.remove();
-            }
-        }
+        this.moonrise$getChunkHolderManager().tick(); // Paper - rewrite chunk system
     }
 
     private static int getTicketLevelAt(SortedArraySet<Ticket<?>> tickets) {
@@ -98,71 +_,15 @@
     protected abstract ChunkHolder updateChunkScheduling(long chunkPos, int i, @Nullable ChunkHolder newLevel, int holder);
 
     public boolean runAllUpdates(ChunkMap chunkMap) {
-        this.naturalSpawnChunkCounter.runAllUpdates();
-        this.tickingTicketsTracker.runAllUpdates();
-        this.playerTicketManager.runAllUpdates();
-        int i = Integer.MAX_VALUE - this.ticketTracker.runDistanceUpdates(Integer.MAX_VALUE);
-        boolean flag = i != 0;
-        if (flag) {
-        }
-
-        if (!this.chunksToUpdateFutures.isEmpty()) {
-            for (ChunkHolder chunkHolder : this.chunksToUpdateFutures) {
-                chunkHolder.updateHighestAllowedStatus(chunkMap);
-            }
-
-            for (ChunkHolder chunkHolder : this.chunksToUpdateFutures) {
-                chunkHolder.updateFutures(chunkMap, this.mainThreadExecutor);
-            }
-
-            this.chunksToUpdateFutures.clear();
-            return true;
-        } else {
-            if (!this.ticketsToRelease.isEmpty()) {
-                LongIterator longIterator = this.ticketsToRelease.iterator();
-
-                while (longIterator.hasNext()) {
-                    long l = longIterator.nextLong();
-                    if (this.getTickets(l).stream().anyMatch(ticket -> ticket.getType() == TicketType.PLAYER)) {
-                        ChunkHolder updatingChunkIfPresent = chunkMap.getUpdatingChunkIfPresent(l);
-                        if (updatingChunkIfPresent == null) {
-                            throw new IllegalStateException();
-                        }
-
-                        CompletableFuture<ChunkResult<LevelChunk>> entityTickingChunkFuture = updatingChunkIfPresent.getEntityTickingChunkFuture();
-                        entityTickingChunkFuture.thenAccept(
-                            chunkResult -> this.mainThreadExecutor.execute(() -> this.ticketDispatcher.release(l, () -> {}, false))
-                        );
-                    }
-                }
-
-                this.ticketsToRelease.clear();
-            }
-
-            return flag;
-        }
+        return this.moonrise$getChunkHolderManager().processTicketUpdates(); // Paper - rewrite chunk system
     }
 
     void addTicket(long chunkPos, Ticket<?> ticket) {
-        SortedArraySet<Ticket<?>> tickets = this.getTickets(chunkPos);
-        int ticketLevelAt = getTicketLevelAt(tickets);
-        Ticket<?> ticket1 = tickets.addOrGet(ticket);
-        ticket1.setCreatedTick(this.ticketTickCounter);
-        if (ticket.getTicketLevel() < ticketLevelAt) {
-            this.ticketTracker.update(chunkPos, ticket.getTicketLevel(), true);
-        }
+        this.moonrise$getChunkHolderManager().addTicketAtLevel((TicketType)ticket.getType(), chunkPos, ticket.getTicketLevel(), ticket.key); // Paper - rewrite chunk system
     }
 
     void removeTicket(long chunkPos, Ticket<?> ticket) {
-        SortedArraySet<Ticket<?>> tickets = this.getTickets(chunkPos);
-        if (tickets.remove(ticket)) {
-        }
-
-        if (tickets.isEmpty()) {
-            this.tickets.remove(chunkPos);
-        }
-
-        this.ticketTracker.update(chunkPos, getTicketLevelAt(tickets), false);
+        this.moonrise$getChunkHolderManager().removeTicketAtLevel((TicketType)ticket.getType(), chunkPos, ticket.getTicketLevel(), ticket.key); // Paper - rewrite chunk system
     }
 
     public <T> void addTicket(TicketType<T> type, ChunkPos pos, int level, T value) {
@@ -175,161 +_,138 @@
     }
 
     public <T> void addRegionTicket(TicketType<T> type, ChunkPos pos, int distance, T value) {
-        Ticket<T> ticket = new Ticket<>(type, ChunkLevel.byStatus(FullChunkStatus.FULL) - distance, value);
-        long packedChunkPos = pos.toLong();
-        this.addTicket(packedChunkPos, ticket);
-        this.tickingTicketsTracker.addTicket(packedChunkPos, ticket);
+        this.moonrise$getChunkHolderManager().addTicketAtLevel(type, pos, ChunkLevel.byStatus(FullChunkStatus.FULL) - distance, value); // Paper - rewrite chunk system
     }
 
     public <T> void removeRegionTicket(TicketType<T> type, ChunkPos pos, int distance, T value) {
-        Ticket<T> ticket = new Ticket<>(type, ChunkLevel.byStatus(FullChunkStatus.FULL) - distance, value);
-        long packedChunkPos = pos.toLong();
-        this.removeTicket(packedChunkPos, ticket);
-        this.tickingTicketsTracker.removeTicket(packedChunkPos, ticket);
-    }
+        this.moonrise$getChunkHolderManager().removeTicketAtLevel(type, pos, ChunkLevel.byStatus(FullChunkStatus.FULL) - distance, value); // Paper - rewrite chunk system
+    }
+
+    // Paper start
+    public boolean addPluginRegionTicket(final ChunkPos pos, final org.bukkit.plugin.Plugin value) {
+        return this.moonrise$getChunkHolderManager().addTicketAtLevel(TicketType.PLUGIN_TICKET, pos, ChunkLevel.byStatus(FullChunkStatus.FULL) - 2, value); // Paper - rewrite chunk system
+    }
+
+    public boolean removePluginRegionTicket(final ChunkPos pos, final org.bukkit.plugin.Plugin value) {
+        return this.moonrise$getChunkHolderManager().removeTicketAtLevel(TicketType.PLUGIN_TICKET, pos, ChunkLevel.byStatus(FullChunkStatus.FULL) - 2, value); // Paper - rewrite chunk system
+    }
+    // Paper end
 
     private SortedArraySet<Ticket<?>> getTickets(long chunkPos) {
-        return this.tickets.computeIfAbsent(chunkPos, l -> SortedArraySet.create(4));
+        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
     }
 
     protected void updateChunkForced(ChunkPos pos, boolean add) {
-        Ticket<ChunkPos> ticket = new Ticket<>(TicketType.FORCED, ChunkMap.FORCED_TICKET_LEVEL, pos);
-        long packedChunkPos = pos.toLong();
+        // Paper start - rewrite chunk system
         if (add) {
-            this.addTicket(packedChunkPos, ticket);
-            this.tickingTicketsTracker.addTicket(packedChunkPos, ticket);
+            this.moonrise$getChunkHolderManager().addTicketAtLevel(TicketType.FORCED, pos, ChunkMap.FORCED_TICKET_LEVEL, pos);
         } else {
-            this.removeTicket(packedChunkPos, ticket);
-            this.tickingTicketsTracker.removeTicket(packedChunkPos, ticket);
+            this.moonrise$getChunkHolderManager().removeTicketAtLevel(TicketType.FORCED, pos, ChunkMap.FORCED_TICKET_LEVEL, pos);
         }
+        // Paper end - rewrite chunk system
     }
 
     public void addPlayer(SectionPos sectionPos, ServerPlayer player) {
         ChunkPos chunkPos = sectionPos.chunk();
         long packedChunkPos = chunkPos.toLong();
         this.playersPerChunk.computeIfAbsent(packedChunkPos, l -> new ObjectOpenHashSet<>()).add(player);
-        this.naturalSpawnChunkCounter.update(packedChunkPos, 0, true);
-        this.playerTicketManager.update(packedChunkPos, 0, true);
-        this.tickingTicketsTracker.addTicket(TicketType.PLAYER, chunkPos, this.getPlayerTicketLevel(), chunkPos);
+        // Paper - chunk tick iteration optimisation
+        // Paper - rewrite chunk system
     }
 
     public void removePlayer(SectionPos sectionPos, ServerPlayer player) {
         ChunkPos chunkPos = sectionPos.chunk();
         long packedChunkPos = chunkPos.toLong();
         ObjectSet<ServerPlayer> set = this.playersPerChunk.get(packedChunkPos);
-        set.remove(player);
-        if (set.isEmpty()) {
+        // Paper start - some state corruption happens here, don't crash, clean up gracefully
+        if (set != null) set.remove(player);
+        if (set == null || set.isEmpty()) {
+        // Paper end - some state corruption happens here, don't crash, clean up gracefully
             this.playersPerChunk.remove(packedChunkPos);
-            this.naturalSpawnChunkCounter.update(packedChunkPos, Integer.MAX_VALUE, false);
-            this.playerTicketManager.update(packedChunkPos, Integer.MAX_VALUE, false);
-            this.tickingTicketsTracker.removeTicket(TicketType.PLAYER, chunkPos, this.getPlayerTicketLevel(), chunkPos);
+            // Paper - chunk tick iteration optimisation
+            // Paper - rewrite chunk system
         }
     }
 
     private int getPlayerTicketLevel() {
-        return Math.max(0, ChunkLevel.byStatus(FullChunkStatus.ENTITY_TICKING) - this.simulationDistance);
+        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
     }
 
     public boolean inEntityTickingRange(long chunkPos) {
-        return ChunkLevel.isEntityTicking(this.tickingTicketsTracker.getLevel(chunkPos));
+        // Paper start - rewrite chunk system
+        final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder chunkHolder = this.moonrise$getChunkHolderManager().getChunkHolder(chunkPos);
+        return chunkHolder != null && chunkHolder.isEntityTickingReady();
+        // Paper end - rewrite chunk system
     }
 
     public boolean inBlockTickingRange(long chunkPos) {
-        return ChunkLevel.isBlockTicking(this.tickingTicketsTracker.getLevel(chunkPos));
+        // Paper start - rewrite chunk system
+        final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder chunkHolder = this.moonrise$getChunkHolderManager().getChunkHolder(chunkPos);
+        return chunkHolder != null && chunkHolder.isTickingReady();
+        // Paper end - rewrite chunk system
     }
 
     protected String getTicketDebugString(long chunkPos) {
-        SortedArraySet<Ticket<?>> set = this.tickets.get(chunkPos);
-        return set != null && !set.isEmpty() ? set.first().toString() : "no_ticket";
+        return this.moonrise$getChunkHolderManager().getTicketDebugString(chunkPos); // Paper - rewrite chunk system
     }
 
     protected void updatePlayerTickets(int viewDistance) {
-        this.playerTicketManager.updateViewDistance(viewDistance);
+        this.moonrise$getChunkMap().setServerViewDistance(viewDistance); // Paper - rewrite chunk system
     }
 
     public void updateSimulationDistance(int simulationDistance) {
-        if (simulationDistance != this.simulationDistance) {
-            this.simulationDistance = simulationDistance;
-            this.tickingTicketsTracker.replacePlayerTicketsLevel(this.getPlayerTicketLevel());
-        }
+        // Paper start - rewrite chunk system
+        // note: vanilla does not clamp to 0, but we do simply because we need a min of 0
+        final int clamped = net.minecraft.util.Mth.clamp(simulationDistance, 0, ca.spottedleaf.moonrise.common.util.MoonriseConstants.MAX_VIEW_DISTANCE);
+
+        ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)this.moonrise$getChunkMap().level).moonrise$getPlayerChunkLoader().setTickDistance(clamped);
+        // Paper end - rewrite chunk system
     }
 
     public int getNaturalSpawnChunkCount() {
-        this.naturalSpawnChunkCounter.runAllUpdates();
-        return this.naturalSpawnChunkCounter.chunks.size();
+        return this.spawnChunkTracker.getTotalPositions(); // Paper - chunk tick iteration optimisation
     }
 
     public boolean hasPlayersNearby(long chunkPos) {
-        this.naturalSpawnChunkCounter.runAllUpdates();
-        return this.naturalSpawnChunkCounter.chunks.containsKey(chunkPos);
+        return this.spawnChunkTracker.hasObjectsNear(ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkX(chunkPos), ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkZ(chunkPos)); // Paper - chunk tick iteration optimisation
     }
 
     public LongIterator getSpawnCandidateChunks() {
-        this.naturalSpawnChunkCounter.runAllUpdates();
-        return this.naturalSpawnChunkCounter.chunks.keySet().iterator();
+        return this.spawnChunkTracker.getPositions().iterator(); // Paper - chunk tick iteration optimisation
     }
 
     public String getDebugStatus() {
-        return this.ticketDispatcher.getDebugStatus();
+        return "No DistanceManager stats available"; // Paper - rewrite chunk system
     }
 
     private void dumpTickets(String filename) {
-        try (FileOutputStream fileOutputStream = new FileOutputStream(new File(filename))) {
-            for (Entry<SortedArraySet<Ticket<?>>> entry : this.tickets.long2ObjectEntrySet()) {
-                ChunkPos chunkPos = new ChunkPos(entry.getLongKey());
-
-                for (Ticket<?> ticket : entry.getValue()) {
-                    fileOutputStream.write(
-                        (chunkPos.x + "\t" + chunkPos.z + "\t" + ticket.getType() + "\t" + ticket.getTicketLevel() + "\t\n").getBytes(StandardCharsets.UTF_8)
-                    );
-                }
-            }
-        } catch (IOException var10) {
-            LOGGER.error("Failed to dump tickets to {}", filename, var10);
-        }
+        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
     }
 
     @VisibleForTesting
     TickingTracker tickingTracker() {
-        return this.tickingTicketsTracker;
+        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
     }
 
     public LongSet getTickingChunks() {
-        return this.tickingTicketsTracker.getTickingChunks();
+        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
     }
 
     public void removeTicketsOnClosing() {
-        ImmutableSet<TicketType<?>> set = ImmutableSet.of(TicketType.UNKNOWN);
-        ObjectIterator<Entry<SortedArraySet<Ticket<?>>>> objectIterator = this.tickets.long2ObjectEntrySet().fastIterator();
-
-        while (objectIterator.hasNext()) {
-            Entry<SortedArraySet<Ticket<?>>> entry = objectIterator.next();
-            Iterator<Ticket<?>> iterator = entry.getValue().iterator();
-            boolean flag = false;
-
-            while (iterator.hasNext()) {
-                Ticket<?> ticket = iterator.next();
-                if (!set.contains(ticket.getType())) {
-                    iterator.remove();
-                    flag = true;
-                    this.tickingTicketsTracker.removeTicket(entry.getLongKey(), ticket);
-                }
-            }
-
-            if (flag) {
-                this.ticketTracker.update(entry.getLongKey(), getTicketLevelAt(entry.getValue()), false);
-            }
-
-            if (entry.getValue().isEmpty()) {
-                objectIterator.remove();
-            }
-        }
+        // Paper - rewrite chunk system
     }
 
     public boolean hasTickets() {
-        return !this.tickets.isEmpty();
-    }
-
+        throw new UnsupportedOperationException();  // Paper - rewrite chunk system
+    }
+
+    // CraftBukkit start
+    public <T> void removeAllTicketsFor(TicketType<T> ticketType, int ticketLevel, T ticketIdentifier) {
+        this.moonrise$getChunkHolderManager().removeAllTicketsFor(ticketType, ticketLevel, ticketIdentifier); // Paper - rewrite chunk system
+    }
+    // CraftBukkit end
+
+/*  // Paper - rewrite chunk system
     class ChunkTicketTracker extends ChunkTracker {
         private static final int MAX_LEVEL = ChunkLevel.MAX_LEVEL + 1;
 
@@ -374,7 +_,7 @@
         public int runDistanceUpdates(int toUpdateCount) {
             return this.runUpdates(toUpdateCount);
         }
-    }
+    }*/  // Paper - rewrite chunk system
 
     class FixedPlayerDistanceChunkTracker extends ChunkTracker {
         protected final Long2ByteMap chunks = new Long2ByteOpenHashMap();
@@ -433,6 +_,7 @@
         }
     }
 
+/*  // Paper - rewrite chunk system
     class PlayerTicketTracker extends DistanceManager.FixedPlayerDistanceChunkTracker {
         private int viewDistance;
         private final Long2IntMap queueLevels = Long2IntMaps.synchronize(new Long2IntOpenHashMap());
@@ -509,5 +_,5 @@
         private boolean haveTicketFor(int level) {
             return level <= this.viewDistance;
         }
-    }
+    }*/  // Paper - rewrite chunk system
 }
