--- a/net/minecraft/server/level/ThreadedLevelLightEngine.java
+++ b/net/minecraft/server/level/ThreadedLevelLightEngine.java
@@ -23,15 +23,29 @@
 import net.minecraft.world.level.lighting.LevelLightEngine;
 import org.slf4j.Logger;
 
+// Paper start
+import ca.spottedleaf.starlight.common.light.StarLightEngine;
+import io.papermc.paper.util.CoordinateUtils;
+import java.util.function.Supplier;
+import net.minecraft.world.level.lighting.LayerLightEventListener;
+import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
+import it.unimi.dsi.fastutil.longs.LongArrayList;
+import it.unimi.dsi.fastutil.longs.LongIterator;
+import net.minecraft.world.level.chunk.status.ChunkStatus;
+// Paper end
+
 public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCloseable {
     public static final int DEFAULT_BATCH_SIZE = 1000;
     private static final Logger LOGGER = LogUtils.getLogger();
-    private final ProcessorMailbox<Runnable> taskMailbox;
-    private final ObjectList<Pair<ThreadedLevelLightEngine.TaskType, Runnable>> lightTasks = new ObjectArrayList<>();
+    // Paper - rewrite chunk system
     private final ChunkMap chunkMap;
-    private final ProcessorHandle<ChunkTaskPriorityQueueSorter.Message<Runnable>> sorterMailbox;
-    private final int taskPerBatch = 1000;
-    private final AtomicBoolean scheduled = new AtomicBoolean();
+    // Paper - rewrite chunk system
+
+    // Paper start - replace light engine impl
+    public final ca.spottedleaf.starlight.common.light.StarLightInterface theLightEngine;
+    public final boolean hasBlockLight;
+    public final boolean hasSkyLight;
+    // Paper end - replace light engine impl
 
     public ThreadedLevelLightEngine(
         LightChunkGetter chunkProvider,
@@ -40,11 +54,152 @@
         ProcessorMailbox<Runnable> processor,
         ProcessorHandle<ChunkTaskPriorityQueueSorter.Message<Runnable>> executor
     ) {
-        super(chunkProvider, true, hasBlockLight);
+        super(chunkProvider, false, false); // Paper - destroy vanilla light engine state
         this.chunkMap = chunkStorage;
-        this.sorterMailbox = executor;
-        this.taskMailbox = processor;
+        // Paper - rewrite chunk system
+        // Paper start - replace light engine impl
+        this.hasBlockLight = true;
+        this.hasSkyLight = hasBlockLight; // Nice variable name.
+        this.theLightEngine = new ca.spottedleaf.starlight.common.light.StarLightInterface(chunkProvider, this.hasSkyLight, this.hasBlockLight, this);
+        // Paper end - replace light engine impl
+    }
+
+    // Paper start - replace light engine impl
+    protected final ChunkAccess getChunk(final int chunkX, final int chunkZ) {
+        return ((ServerLevel)this.theLightEngine.getWorld()).getChunkSource().getChunkAtImmediately(chunkX, chunkZ);
+    }
+
+    protected long relightCounter;
+
+    public int relight(java.util.Set<ChunkPos> chunks_param,
+                        java.util.function.Consumer<ChunkPos> chunkLightCallback,
+                        java.util.function.IntConsumer onComplete) {
+        if (!org.bukkit.Bukkit.isPrimaryThread()) {
+            throw new IllegalStateException("Must only be called on the main thread");
+        }
+
+        java.util.Set<ChunkPos> chunks = new java.util.LinkedHashSet<>(chunks_param);
+        // add tickets
+        java.util.Map<ChunkPos, Long> ticketIds = new java.util.HashMap<>();
+        int totalChunks = 0;
+        for (java.util.Iterator<ChunkPos> iterator = chunks.iterator(); iterator.hasNext();) {
+            final ChunkPos chunkPos = iterator.next();
+
+            final ChunkAccess chunk = (ChunkAccess)((ServerLevel)this.theLightEngine.getWorld()).getChunkSource().getChunkForLighting(chunkPos.x, chunkPos.z);
+            if (chunk == null || !chunk.isLightCorrect() || !chunk.getStatus().isOrAfter(ChunkStatus.LIGHT)) {
+                // cannot relight this chunk
+                iterator.remove();
+                continue;
+            }
+
+            final Long id = Long.valueOf(this.relightCounter++);
+
+            ((ServerLevel)this.theLightEngine.getWorld()).getChunkSource().addTicketAtLevel(TicketType.CHUNK_RELIGHT, chunkPos, io.papermc.paper.util.MCUtil.getTicketLevelFor(ChunkStatus.LIGHT), id);
+            ticketIds.put(chunkPos, id);
+
+            ++totalChunks;
+        }
+
+        this.chunkMap.level.chunkTaskScheduler.radiusAwareScheduler.queueInfiniteRadiusTask(() -> { // Paper - rewrite chunk system
+            this.theLightEngine.relightChunks(chunks, (ChunkPos chunkPos) -> {
+                chunkLightCallback.accept(chunkPos);
+                ((java.util.concurrent.Executor)((ServerLevel)this.theLightEngine.getWorld()).getChunkSource().mainThreadProcessor).execute(() -> {
+                    ((ServerLevel)this.theLightEngine.getWorld()).getChunkSource().chunkMap.getUpdatingChunkIfPresent(chunkPos.toLong()).broadcast(new net.minecraft.network.protocol.game.ClientboundLightUpdatePacket(chunkPos, ThreadedLevelLightEngine.this, null, null), false);
+                    ((ServerLevel)this.theLightEngine.getWorld()).getChunkSource().removeTicketAtLevel(TicketType.CHUNK_RELIGHT, chunkPos, io.papermc.paper.util.MCUtil.getTicketLevelFor(ChunkStatus.LIGHT), ticketIds.get(chunkPos));
+                });
+            }, onComplete);
+        });
+        this.tryScheduleUpdate();
+
+        return totalChunks;
+    }
+
+    private final Long2IntOpenHashMap chunksBeingWorkedOn = new Long2IntOpenHashMap();
+
+    private void queueTaskForSection(final int chunkX, final int chunkY, final int chunkZ,
+                                     final Supplier<io.papermc.paper.chunk.system.light.LightQueue.ChunkTasks> runnable) { // Paper - rewrite chunk system
+        final ServerLevel world = (ServerLevel)this.theLightEngine.getWorld();
+
+        final ChunkAccess center = this.theLightEngine.getAnyChunkNow(chunkX, chunkZ);
+        if (center == null || !center.getStatus().isOrAfter(ChunkStatus.LIGHT)) {
+            // do not accept updates in unlit chunks, unless we might be generating a chunk. thanks to the amazing
+            // chunk scheduling, we could be lighting and generating a chunk at the same time
+            return;
+        }
+
+        if (center.getStatus() != ChunkStatus.FULL) {
+            // do not keep chunk loaded, we are probably in a gen thread
+            // if we proceed to add a ticket the chunk will be loaded, which is not what we want (avoid cascading gen)
+            runnable.get();
+            return;
+        }
+
+        if (!world.getChunkSource().chunkMap.mainThreadExecutor.isSameThread()) {
+            // ticket logic is not safe to run off-main, re-schedule
+            world.getChunkSource().chunkMap.mainThreadExecutor.execute(() -> {
+                this.queueTaskForSection(chunkX, chunkY, chunkZ, runnable);
+            });
+            return;
+        }
+
+        final long key = CoordinateUtils.getChunkKey(chunkX, chunkZ);
+
+        final io.papermc.paper.chunk.system.light.LightQueue.ChunkTasks updateFuture = runnable.get(); // Paper - rewrite chunk system
+
+        if (updateFuture == null) {
+            // not scheduled
+            return;
+        }
+
+        if (updateFuture.isTicketAdded) {
+            // ticket already added
+            return;
+        }
+        updateFuture.isTicketAdded = true;
+
+        final int references = this.chunksBeingWorkedOn.addTo(key, 1);
+        if (references == 0) {
+            final ChunkPos pos = new ChunkPos(chunkX, chunkZ);
+            world.getChunkSource().addRegionTicket(ca.spottedleaf.starlight.common.light.StarLightInterface.CHUNK_WORK_TICKET, pos, 0, pos);
+        }
+
+        updateFuture.onComplete.thenAcceptAsync((final Void ignore) -> {
+            final int newReferences = this.chunksBeingWorkedOn.get(key);
+            if (newReferences == 1) {
+                this.chunksBeingWorkedOn.remove(key);
+                final ChunkPos pos = new ChunkPos(chunkX, chunkZ);
+                world.getChunkSource().removeRegionTicket(ca.spottedleaf.starlight.common.light.StarLightInterface.CHUNK_WORK_TICKET, pos, 0, pos);
+            } else {
+                this.chunksBeingWorkedOn.put(key, newReferences - 1);
+            }
+        }, world.getChunkSource().chunkMap.mainThreadExecutor).whenComplete((final Void ignore, final Throwable thr) -> {
+            if (thr != null) {
+                LOGGER.error("Failed to remove ticket level for post chunk task " + new ChunkPos(chunkX, chunkZ), thr);
+            }
+        });
+    }
+
+    @Override
+    public boolean hasLightWork() {
+        // route to new light engine
+        return this.theLightEngine.hasUpdates();
+    }
+
+    @Override
+    public LayerLightEventListener getLayerListener(final LightLayer lightType) {
+        return lightType == LightLayer.BLOCK ? this.theLightEngine.getBlockReader() : this.theLightEngine.getSkyReader();
+    }
+
+    @Override
+    public int getRawBrightness(final BlockPos pos, final int ambientDarkness) {
+        // need to use new light hooks for this
+        final int sky = this.theLightEngine.getSkyReader().getLightValue(pos) - ambientDarkness;
+        // Don't fetch the block light level if the skylight level is 15, since the value will never be higher.
+        if (sky == 15) return 15;
+        final int block = this.theLightEngine.getBlockReader().getLightValue(pos);
+        return Math.max(sky, block);
     }
+    // Paper end - replace light engine imp
 
     @Override
     public void close() {
@@ -57,16 +212,16 @@
 
     @Override
     public void checkBlock(BlockPos pos) {
-        BlockPos blockPos = pos.immutable();
-        this.addTask(
-            SectionPos.blockToSectionCoord(pos.getX()),
-            SectionPos.blockToSectionCoord(pos.getZ()),
-            ThreadedLevelLightEngine.TaskType.PRE_UPDATE,
-            Util.name(() -> super.checkBlock(blockPos), () -> "checkBlock " + blockPos)
-        );
+        // Paper start - replace light engine impl
+        final BlockPos posCopy = pos.immutable();
+        this.queueTaskForSection(posCopy.getX() >> 4, posCopy.getY() >> 4, posCopy.getZ() >> 4, () -> {
+            return this.theLightEngine.blockChange(posCopy);
+        });
+        // Paper end - replace light engine impl
     }
 
     protected void updateChunkStatus(ChunkPos pos) {
+        if (true) return; // Paper - replace light engine impl
         this.addTask(pos.x, pos.z, () -> 0, ThreadedLevelLightEngine.TaskType.PRE_UPDATE, Util.name(() -> {
             super.retainData(pos, false);
             super.setLightEnabled(pos, false);
@@ -84,17 +239,16 @@
 
     @Override
     public void updateSectionStatus(SectionPos pos, boolean notReady) {
-        this.addTask(
-            pos.x(),
-            pos.z(),
-            () -> 0,
-            ThreadedLevelLightEngine.TaskType.PRE_UPDATE,
-            Util.name(() -> super.updateSectionStatus(pos, notReady), () -> "updateSectionStatus " + pos + " " + notReady)
-        );
+        // Paper start - replace light engine impl
+        this.queueTaskForSection(pos.getX(), pos.getY(), pos.getZ(), () -> {
+            return this.theLightEngine.sectionChange(pos, notReady);
+        });
+        // Paper end - replace light engine impl
     }
 
     @Override
     public void propagateLightSources(ChunkPos chunkPos) {
+        if (true) return; // Paper - replace light engine impl
         this.addTask(
             chunkPos.x,
             chunkPos.z,
@@ -105,6 +259,7 @@
 
     @Override
     public void setLightEnabled(ChunkPos pos, boolean retainData) {
+        if (true) return; // Paper - replace light engine impl
         this.addTask(
             pos.x,
             pos.z,
@@ -115,6 +270,7 @@
 
     @Override
     public void queueSectionData(LightLayer lightType, SectionPos pos, @Nullable DataLayer nibbles) {
+        if (true) return; // Paper - replace light engine impl
         this.addTask(
             pos.x(),
             pos.z(),
@@ -125,26 +281,23 @@
     }
 
     private void addTask(int x, int z, ThreadedLevelLightEngine.TaskType stage, Runnable task) {
-        this.addTask(x, z, this.chunkMap.getChunkQueueLevel(ChunkPos.asLong(x, z)), stage, task);
+        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
     }
 
     private void addTask(int x, int z, IntSupplier completedLevelSupplier, ThreadedLevelLightEngine.TaskType stage, Runnable task) {
-        this.sorterMailbox.tell(ChunkTaskPriorityQueueSorter.message(() -> {
-            this.lightTasks.add(Pair.of(stage, task));
-            if (this.lightTasks.size() >= 1000) {
-                this.runUpdate();
-            }
-        }, ChunkPos.asLong(x, z), completedLevelSupplier));
+        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
     }
 
     @Override
     public void retainData(ChunkPos pos, boolean retainData) {
+        if (true) return; // Paper - replace light engine impl
         this.addTask(
             pos.x, pos.z, () -> 0, ThreadedLevelLightEngine.TaskType.PRE_UPDATE, Util.name(() -> super.retainData(pos, retainData), () -> "retainData " + pos)
         );
     }
 
     public CompletableFuture<ChunkAccess> initializeLight(ChunkAccess chunk, boolean bl) {
+        if (true) return CompletableFuture.completedFuture(chunk); // Paper - replace light engine impl
         ChunkPos chunkPos = chunk.getPos();
         this.addTask(chunkPos.x, chunkPos.z, ThreadedLevelLightEngine.TaskType.PRE_UPDATE, Util.name(() -> {
             LevelChunkSection[] levelChunkSections = chunk.getSections();
@@ -165,52 +318,15 @@
     }
 
     public CompletableFuture<ChunkAccess> lightChunk(ChunkAccess chunk, boolean excludeBlocks) {
-        ChunkPos chunkPos = chunk.getPos();
-        chunk.setLightCorrect(false);
-        this.addTask(chunkPos.x, chunkPos.z, ThreadedLevelLightEngine.TaskType.PRE_UPDATE, Util.name(() -> {
-            if (!excludeBlocks) {
-                super.propagateLightSources(chunkPos);
-            }
-        }, () -> "lightChunk " + chunkPos + " " + excludeBlocks));
-        return CompletableFuture.supplyAsync(() -> {
-            chunk.setLightCorrect(true);
-            this.chunkMap.releaseLightTicket(chunkPos);
-            return chunk;
-        }, task -> this.addTask(chunkPos.x, chunkPos.z, ThreadedLevelLightEngine.TaskType.POST_UPDATE, task));
+        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
     }
 
     public void tryScheduleUpdate() {
-        if ((!this.lightTasks.isEmpty() || super.hasLightWork()) && this.scheduled.compareAndSet(false, true)) {
-            this.taskMailbox.tell(() -> {
-                this.runUpdate();
-                this.scheduled.set(false);
-            });
-        }
+        // Paper - rewrite chunk system
     }
 
     private void runUpdate() {
-        int i = Math.min(this.lightTasks.size(), 1000);
-        ObjectListIterator<Pair<ThreadedLevelLightEngine.TaskType, Runnable>> objectListIterator = this.lightTasks.iterator();
-
-        int j;
-        for (j = 0; objectListIterator.hasNext() && j < i; j++) {
-            Pair<ThreadedLevelLightEngine.TaskType, Runnable> pair = objectListIterator.next();
-            if (pair.getFirst() == ThreadedLevelLightEngine.TaskType.PRE_UPDATE) {
-                pair.getSecond().run();
-            }
-        }
-
-        objectListIterator.back(j);
-        super.runLightUpdates();
-
-        for (int var5 = 0; objectListIterator.hasNext() && var5 < i; var5++) {
-            Pair<ThreadedLevelLightEngine.TaskType, Runnable> pair2 = objectListIterator.next();
-            if (pair2.getFirst() == ThreadedLevelLightEngine.TaskType.POST_UPDATE) {
-                pair2.getSecond().run();
-            }
-
-            objectListIterator.remove();
-        }
+        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
     }
 
     public CompletableFuture<?> waitForPendingTasks(int x, int z) {
