--- a/net/minecraft/world/level/TicketStorage.java
+++ b/net/minecraft/world/level/TicketStorage.java
@@ -29,7 +_,7 @@
 import net.minecraft.world.level.saveddata.SavedDataType;
 import org.slf4j.Logger;
 
-public class TicketStorage extends SavedData {
+public class TicketStorage extends SavedData implements ca.spottedleaf.moonrise.patches.chunk_system.ticket.ChunkSystemTicketStorage { // Paper - rewrite chunk system
     private static final int INITIAL_TICKET_LIST_CAPACITY = 4;
     private static final Logger LOGGER = LogUtils.getLogger();
     private static final Codec<Pair<ChunkPos, Ticket>> TICKET_ENTRY = Codec.mapPair(ChunkPos.CODEC.fieldOf("chunk_pos"), Ticket.CODEC).codec();
@@ -38,18 +_,32 @@
             .apply(instance, TicketStorage::fromPacked)
     );
     public static final SavedDataType<TicketStorage> TYPE = new SavedDataType<>("chunks", TicketStorage::new, CODEC, DataFixTypes.SAVED_DATA_FORCED_CHUNKS);
-    private final Long2ObjectOpenHashMap<List<Ticket>> tickets;
+    // Paper - rewrite chunk system
     private final Long2ObjectOpenHashMap<List<Ticket>> deactivatedTickets;
-    private LongSet chunksWithForcedTickets = new LongOpenHashSet();
+    // Paper - rewrite chunk system
     @Nullable
     private TicketStorage.ChunkUpdated loadingChunkUpdatedListener;
     @Nullable
     private TicketStorage.ChunkUpdated simulationChunkUpdatedListener;
 
+    // Paper start - rewrite chunk system
+    private ChunkMap chunkMap;
+
+    @Override
+    public final ChunkMap moonrise$getChunkMap() {
+        return this.chunkMap;
+    }
+
+    @Override
+    public final void moonrise$setChunkMap(final ChunkMap chunkMap) {
+        this.chunkMap = chunkMap;
+    }
+    // Paper end - rewrite chunk system
+
     private TicketStorage(Long2ObjectOpenHashMap<List<Ticket>> tickets, Long2ObjectOpenHashMap<List<Ticket>> deactivatedTickets) {
-        this.tickets = tickets;
+        // Paper - rewrite chunk system
         this.deactivatedTickets = deactivatedTickets;
-        this.updateForcedChunks();
+        // Paper - rewrite chunk system
     }
 
     public TicketStorage() {
@@ -78,8 +_,32 @@
         return list;
     }
 
+    // Paper start - rewrite chunk system
+    private void redirectRegularTickets(final BiConsumer<ChunkPos, Ticket> consumer, final Long2ObjectOpenHashMap<List<Ticket>> ticketsParam) {
+        if (ticketsParam != null) {
+            throw new IllegalStateException("Bad injection point");
+        }
+
+        final Long2ObjectOpenHashMap<java.util.Collection<net.minecraft.server.level.Ticket>> tickets = ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)this.chunkMap.level)
+            .moonrise$getChunkTaskScheduler().chunkHolderManager.getTicketsCopy();
+
+        for (final Iterator<it.unimi.dsi.fastutil.longs.Long2ObjectMap.Entry<java.util.Collection<net.minecraft.server.level.Ticket>>> iterator = tickets.long2ObjectEntrySet().fastIterator(); iterator.hasNext();) {
+            final it.unimi.dsi.fastutil.longs.Long2ObjectMap.Entry<java.util.Collection<net.minecraft.server.level.Ticket>> entry = iterator.next();
+
+            final long pos = entry.getLongKey();
+            final java.util.Collection<net.minecraft.server.level.Ticket> chunkTickets = entry.getValue();
+
+            final ChunkPos chunkPos = new ChunkPos(pos);
+
+            for (final Ticket ticket : chunkTickets) {
+                consumer.accept(chunkPos, ticket);
+            }
+        }
+    }
+    // Paper end - rewrite chunk system
+
     private void forEachTicket(BiConsumer<ChunkPos, Ticket> action) {
-        forEachTicket(action, this.tickets);
+        this.redirectRegularTickets(action, null); // Paper - rewrite chunk system
         forEachTicket(action, this.deactivatedTickets);
     }
 
@@ -104,35 +_,34 @@
     }
 
     public void setLoadingChunkUpdatedListener(@Nullable TicketStorage.ChunkUpdated loadingChunkUpdatedListener) {
-        this.loadingChunkUpdatedListener = loadingChunkUpdatedListener;
+        // Paper - rewrite chunk system
     }
 
     public void setSimulationChunkUpdatedListener(@Nullable TicketStorage.ChunkUpdated simulationChunkUpdatedListener) {
-        this.simulationChunkUpdatedListener = simulationChunkUpdatedListener;
+        // Paper - rewrite chunk system
     }
 
     public boolean hasTickets() {
-        return !this.tickets.isEmpty();
+        return ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)this.chunkMap.level).moonrise$getChunkTaskScheduler().chunkHolderManager.hasTickets(); // Paper - rewrite chunk system
     }
 
     public boolean shouldKeepDimensionActive() {
-        for (List<Ticket> list : this.tickets.values()) {
-            for (Ticket ticket : list) {
-                if (ticket.getType().shouldKeepDimensionActive()) {
-                    return true;
-                }
-            }
-        }
-
-        return false;
+        // Paper start - rewrite chunk system
+        final ca.spottedleaf.concurrentutil.map.ConcurrentLong2LongChainedHashTable ticketCounters = ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)this.chunkMap.level).moonrise$getChunkTaskScheduler().chunkHolderManager
+            .getTicketCounters(ca.spottedleaf.moonrise.patches.chunk_system.ticket.ChunkSystemTicketType.COUNTER_TYPE_KEEP_DIMENSION_ACTIVE);
+        return ticketCounters != null && !ticketCounters.isEmpty();
+        // Paper end - rewrite chunk system
     }
 
     public List<Ticket> getTickets(long chunkPos) {
-        return this.tickets.getOrDefault(chunkPos, List.of());
+        // Paper start - rewrite chunk system
+        return ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)this.chunkMap.level).moonrise$getChunkTaskScheduler().chunkHolderManager
+            .getTicketsAt(ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkX(chunkPos), ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkZ(chunkPos));
+        // Paper end - rewrite chunk system
     }
 
     private List<Ticket> getOrCreateTickets(long chunkPos) {
-        return this.tickets.computeIfAbsent(chunkPos, l -> new ObjectArrayList<>(4));
+        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
     }
 
     public void addTicketWithRadius(TicketType ticketType, ChunkPos chunkPos, int radius) {
@@ -145,41 +_,18 @@
     }
 
     public boolean addTicket(long chunkPos, Ticket ticket) {
-        List<Ticket> tickets = this.getOrCreateTickets(chunkPos);
-
-        for (Ticket ticket1 : tickets) {
-            if (isTicketSameTypeAndLevel(ticket, ticket1)) {
-                ticket1.resetTicksLeft();
-                this.setDirty();
-                return false;
-            }
-        }
-
-        int ticketLevelAt = getTicketLevelAt(tickets, true);
-        int ticketLevelAt1 = getTicketLevelAt(tickets, false);
-        tickets.add(ticket);
-        if (SharedConstants.DEBUG_VERBOSE_SERVER_EVENTS) {
-            LOGGER.debug("ATI {} {}", new ChunkPos(chunkPos), ticket);
-        }
-
-        if (ticket.getType().doesSimulate() && ticket.getTicketLevel() < ticketLevelAt && this.simulationChunkUpdatedListener != null) {
-            this.simulationChunkUpdatedListener.update(chunkPos, ticket.getTicketLevel(), true);
-        }
-
-        if (ticket.getType().doesLoad() && ticket.getTicketLevel() < ticketLevelAt1 && this.loadingChunkUpdatedListener != null) {
-            this.loadingChunkUpdatedListener.update(chunkPos, ticket.getTicketLevel(), true);
-        }
-
-        if (ticket.getType().equals(TicketType.FORCED)) {
-            this.chunksWithForcedTickets.add(chunkPos);
-        }
+        // Paper start - rewrite chunk system
+        final boolean ret = ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)this.chunkMap.level).moonrise$getChunkTaskScheduler().chunkHolderManager
+            .addTicketAtLevel(ticket.getType(), chunkPos, ticket.getTicketLevel(), ((ca.spottedleaf.moonrise.patches.chunk_system.ticket.ChunkSystemTicket<?>)ticket).moonrise$getIdentifier());
 
         this.setDirty();
-        return true;
+
+        return ret;
+        // Paper end - rewrite chunk system
     }
 
     private static boolean isTicketSameTypeAndLevel(Ticket first, Ticket second) {
-        return second.getType() == first.getType() && second.getTicketLevel() == first.getTicketLevel();
+        return second.getType() == first.getType() && second.getTicketLevel() == first.getTicketLevel() && java.util.Objects.equals(second.getIdentifier(), first.getIdentifier()); // Paper - add identifier
     }
 
     public int getTicketLevelAt(long chunkPos, boolean requireSimulation) {
@@ -222,53 +_,20 @@
     }
 
     public boolean removeTicket(long chunkPos, Ticket ticket) {
-        List<Ticket> list = this.tickets.get(chunkPos);
-        if (list == null) {
-            return false;
-        } else {
-            boolean flag = false;
-            Iterator<Ticket> iterator = list.iterator();
-
-            while (iterator.hasNext()) {
-                Ticket ticket1 = iterator.next();
-                if (isTicketSameTypeAndLevel(ticket, ticket1)) {
-                    iterator.remove();
-                    if (SharedConstants.DEBUG_VERBOSE_SERVER_EVENTS) {
-                        LOGGER.debug("RTI {} {}", new ChunkPos(chunkPos), ticket1);
-                    }
-
-                    flag = true;
-                    break;
-                }
-            }
-
-            if (!flag) {
-                return false;
-            } else {
-                if (list.isEmpty()) {
-                    this.tickets.remove(chunkPos);
-                }
-
-                if (ticket.getType().doesSimulate() && this.simulationChunkUpdatedListener != null) {
-                    this.simulationChunkUpdatedListener.update(chunkPos, getTicketLevelAt(list, true), false);
-                }
-
-                if (ticket.getType().doesLoad() && this.loadingChunkUpdatedListener != null) {
-                    this.loadingChunkUpdatedListener.update(chunkPos, getTicketLevelAt(list, false), false);
-                }
-
-                if (ticket.getType().equals(TicketType.FORCED)) {
-                    this.updateForcedChunks();
-                }
-
-                this.setDirty();
-                return true;
-            }
+        // Paper start - rewrite chunk system
+        final boolean ret = ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)this.chunkMap.level).moonrise$getChunkTaskScheduler().chunkHolderManager
+            .removeTicketAtLevel(ticket.getType(), chunkPos, ticket.getTicketLevel(), ((ca.spottedleaf.moonrise.patches.chunk_system.ticket.ChunkSystemTicket<?>)ticket).moonrise$getIdentifier());
+
+        if (ret) {
+            this.setDirty();
         }
+
+        return ret;
+        // Paper end - rewrite chunk system
     }
 
     private void updateForcedChunks() {
-        this.chunksWithForcedTickets = this.getAllChunksWithTicketThat(ticket -> ticket.getType().equals(TicketType.FORCED));
+        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
     }
 
     public String getTicketDebugString(long chunkPos, boolean requireSimulation) {
@@ -278,14 +_,7 @@
     }
 
     public void purgeStaleTickets(ChunkMap map) {
-        this.removeTicketIf((ticket, chunkPos) -> {
-            if (this.canTicketExpire(map, ticket, chunkPos)) {
-                ticket.decreaseTicksLeft();
-                return ticket.isTimedOut();
-            } else {
-                return false;
-            }
-        }, null);
+        ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)chunkMap.level).moonrise$getChunkTaskScheduler().chunkHolderManager.tick(); // Paper - rewrite chunk system
         this.setDirty();
     }
 
@@ -301,82 +_,15 @@
     }
 
     public void deactivateTicketsOnClosing() {
-        this.removeTicketIf((ticket, chunkPos) -> ticket.getType() != TicketType.UNKNOWN, this.deactivatedTickets);
+        // Paper - rewrite chunk system
     }
 
     public void removeTicketIf(TicketStorage.TicketPredicate predicate, @Nullable Long2ObjectOpenHashMap<List<Ticket>> tickets) {
-        ObjectIterator<Entry<List<Ticket>>> objectIterator = this.tickets.long2ObjectEntrySet().fastIterator();
-        boolean flag = false;
-
-        while (objectIterator.hasNext()) {
-            Entry<List<Ticket>> entry = objectIterator.next();
-            Iterator<Ticket> iterator = entry.getValue().iterator();
-            long longKey = entry.getLongKey();
-            boolean flag1 = false;
-            boolean flag2 = false;
-
-            while (iterator.hasNext()) {
-                Ticket ticket = iterator.next();
-                if (predicate.test(ticket, longKey)) {
-                    if (tickets != null) {
-                        List<Ticket> list = tickets.computeIfAbsent(longKey, chunkPos -> new ObjectArrayList<>(entry.getValue().size()));
-                        list.add(ticket);
-                    }
-
-                    iterator.remove();
-                    if (ticket.getType().doesLoad()) {
-                        flag2 = true;
-                    }
-
-                    if (ticket.getType().doesSimulate()) {
-                        flag1 = true;
-                    }
-
-                    if (ticket.getType().equals(TicketType.FORCED)) {
-                        flag = true;
-                    }
-                }
-            }
-
-            if (flag2 || flag1) {
-                if (flag2 && this.loadingChunkUpdatedListener != null) {
-                    this.loadingChunkUpdatedListener.update(longKey, getTicketLevelAt(entry.getValue(), false), false);
-                }
-
-                if (flag1 && this.simulationChunkUpdatedListener != null) {
-                    this.simulationChunkUpdatedListener.update(longKey, getTicketLevelAt(entry.getValue(), true), false);
-                }
-
-                this.setDirty();
-                if (entry.getValue().isEmpty()) {
-                    objectIterator.remove();
-                }
-            }
-        }
-
-        if (flag) {
-            this.updateForcedChunks();
-        }
+        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
     }
 
     public void replaceTicketLevelOfType(int level, TicketType type) {
-        List<Pair<Ticket, Long>> list = new ArrayList<>();
-
-        for (Entry<List<Ticket>> entry : this.tickets.long2ObjectEntrySet()) {
-            for (Ticket ticket : entry.getValue()) {
-                if (ticket.getType() == type) {
-                    list.add(Pair.of(ticket, entry.getLongKey()));
-                }
-            }
-        }
-
-        for (Pair<Ticket, Long> pair : list) {
-            Long _long = pair.getSecond();
-            Ticket ticketx = pair.getFirst();
-            this.removeTicket(_long, ticketx);
-            TicketType type1 = ticketx.getType();
-            this.addTicket(_long, new Ticket(type1, level));
-        }
+        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
     }
 
     public boolean updateChunkForced(ChunkPos chunkPos, boolean add) {
@@ -385,22 +_,29 @@
     }
 
     public LongSet getForceLoadedChunks() {
-        return this.chunksWithForcedTickets;
+        // Paper start - rewrite chunk system
+        final ca.spottedleaf.concurrentutil.map.ConcurrentLong2LongChainedHashTable forced = ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)this.chunkMap.level).moonrise$getChunkTaskScheduler()
+            .chunkHolderManager.getTicketCounters(ca.spottedleaf.moonrise.patches.chunk_system.ticket.ChunkSystemTicketType.COUNTER_TYPE_FORCED);
+
+        if (forced == null) {
+            return new it.unimi.dsi.fastutil.longs.LongLinkedOpenHashSet();
+        }
+
+       // note: important to presize correctly using size/loadfactor to avoid awful write performance
+       //       think: iteration over our map has the same hash strategy, and if ret is not sized
+       //       correctly then every (ret.table.length) may collide. During resize, open hashed tables
+       //       (like LongLinkedOpenHashSet) must reinsert - leading to O(n^2) to copy IF we do not initially
+       //       size correctly
+       final it.unimi.dsi.fastutil.longs.LongLinkedOpenHashSet ret = new it.unimi.dsi.fastutil.longs.LongLinkedOpenHashSet(forced.size(), forced.getLoadFactor());
+       for (final java.util.PrimitiveIterator.OfLong iterator = forced.keyIterator(); iterator.hasNext();) {
+           ret.add(iterator.nextLong());
+       }
+       return ret;
+        // Paper end - rewrite chunk system
     }
 
     private LongSet getAllChunksWithTicketThat(Predicate<Ticket> predicate) {
-        LongOpenHashSet set = new LongOpenHashSet();
-
-        for (Entry<List<Ticket>> entry : Long2ObjectMaps.fastIterable(this.tickets)) {
-            for (Ticket ticket : entry.getValue()) {
-                if (predicate.test(ticket)) {
-                    set.add(entry.getLongKey());
-                    break;
-                }
-            }
-        }
-
-        return set;
+        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
     }
 
     @FunctionalInterface
@@ -411,4 +_,20 @@
     public interface TicketPredicate {
         boolean test(Ticket ticket, long chunkPos);
     }
+
+    // Paper start
+    public boolean addPluginRegionTicket(final ChunkPos pos, final org.bukkit.plugin.Plugin value) {
+        // Keep inline with force loading
+        return addTicket(pos.toLong(), new Ticket(TicketType.PLUGIN_TICKET, ChunkMap.FORCED_TICKET_LEVEL, value));
+    }
+
+    public boolean removePluginRegionTicket(final ChunkPos pos, final org.bukkit.plugin.Plugin value) {
+        // Keep inline with force loading
+        return removeTicket(pos.toLong(), new Ticket(TicketType.PLUGIN_TICKET, ChunkMap.FORCED_TICKET_LEVEL, value));
+    }
+
+    public void removeAllPluginRegionTickets(TicketType ticketType, int ticketLevel, org.bukkit.plugin.Plugin ticketIdentifier) {
+        this.chunkMap.level.moonrise$getChunkTaskScheduler().chunkHolderManager.removeAllTicketsFor(ticketType, ticketLevel, ticketIdentifier); // Paper - rewrite chunk system
+    }
+    // Paper end
 }
