--- a/net/minecraft/world/ticks/LevelChunkTicks.java
+++ b/net/minecraft/world/ticks/LevelChunkTicks.java
@@ -18,7 +18,7 @@
 import net.minecraft.nbt.ListTag;
 import net.minecraft.world.level.ChunkPos;
 
-public class LevelChunkTicks<T> implements SerializableTickContainer<T>, TickContainerAccess<T> {
+public class LevelChunkTicks<T> implements SerializableTickContainer<T>, TickContainerAccess<T>, ca.spottedleaf.moonrise.patches.chunk_system.ticks.ChunkSystemLevelChunkTicks { // Paper - rewrite chunk system
     private final Queue<ScheduledTick<T>> tickQueue = new PriorityQueue<>(ScheduledTick.DRAIN_ORDER);
     @Nullable
     private List<SavedTick<T>> pendingTicks;
@@ -26,6 +26,30 @@
     @Nullable
     private BiConsumer<LevelChunkTicks<T>, ScheduledTick<T>> onTickAdded;
 
+    // Paper start - rewrite chunk system
+    /*
+     * Since ticks are saved using relative delays, we need to consider the entire tick list dirty when there are scheduled ticks
+     * and the last saved tick is not equal to the current tick
+     */
+    /*
+     * In general, it would be nice to be able to "re-pack" ticks once the chunk becomes non-ticking again, but that is a
+     * bit out of scope for the chunk system
+     */
+
+    private boolean dirty;
+    private long lastSaved = Long.MIN_VALUE;
+
+    @Override
+    public final boolean moonrise$isDirty(final long tick) {
+        return this.dirty || (!this.tickQueue.isEmpty() && tick != this.lastSaved);
+    }
+
+    @Override
+    public final void moonrise$clearDirty() {
+        this.dirty = false;
+    }
+    // Paper end - rewrite chunk system
+
     public LevelChunkTicks() {
     }
 
@@ -50,7 +74,7 @@
     public ScheduledTick<T> poll() {
         ScheduledTick<T> scheduledTick = this.tickQueue.poll();
         if (scheduledTick != null) {
-            this.ticksPerPosition.remove(scheduledTick);
+            this.ticksPerPosition.remove(scheduledTick); this.dirty = true; // Paper - rewrite chunk system
         }
 
         return scheduledTick;
@@ -59,7 +83,7 @@
     @Override
     public void schedule(ScheduledTick<T> orderedTick) {
         if (this.ticksPerPosition.add(orderedTick)) {
-            this.scheduleUnchecked(orderedTick);
+            this.scheduleUnchecked(orderedTick); this.dirty = true; // Paper - rewrite chunk system
         }
     }
 
@@ -81,7 +105,7 @@
         while (iterator.hasNext()) {
             ScheduledTick<T> scheduledTick = iterator.next();
             if (predicate.test(scheduledTick)) {
-                iterator.remove();
+                iterator.remove(); this.dirty = true; // Paper - rewrite chunk system
                 this.ticksPerPosition.remove(scheduledTick);
             }
         }
@@ -98,6 +122,7 @@
 
     @Override
     public ListTag save(long l, Function<T, String> function) {
+        this.lastSaved = l; // Paper - rewrite chunk system
         ListTag listTag = new ListTag();
         if (this.pendingTicks != null) {
             for (SavedTick<T> savedTick : this.pendingTicks) {
@@ -114,6 +139,7 @@
 
     public void unpack(long time) {
         if (this.pendingTicks != null) {
+            this.lastSaved = time; // Paper - rewrite chunk system
             int i = -this.pendingTicks.size();
 
             for (SavedTick<T> savedTick : this.pendingTicks) {
