--- a/net/minecraft/world/level/chunk/storage/ChunkSerializer.java
+++ b/net/minecraft/world/level/chunk/storage/ChunkSerializer.java
@@ -3,14 +3,19 @@
 import com.google.common.collect.Maps;
 import com.mojang.logging.LogUtils;
 import com.mojang.serialization.Codec;
+import com.mojang.serialization.DataResult;
 import com.mojang.serialization.Dynamic;
 import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
 import it.unimi.dsi.fastutil.longs.LongSet;
 import it.unimi.dsi.fastutil.shorts.ShortList;
+import it.unimi.dsi.fastutil.shorts.ShortListIterator;
 import java.util.Arrays;
 import java.util.EnumSet;
+import java.util.Iterator;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Objects;
+import java.util.Optional;
 import javax.annotation.Nullable;
 import net.minecraft.core.BlockPos;
 import net.minecraft.core.Holder;
@@ -25,8 +30,11 @@
 import net.minecraft.nbt.NbtOps;
 import net.minecraft.nbt.NbtUtils;
 import net.minecraft.nbt.ShortTag;
+import net.minecraft.nbt.Tag;
 import net.minecraft.resources.ResourceLocation;
+import net.minecraft.server.level.ServerChunkCache;
 import net.minecraft.server.level.ServerLevel;
+import net.minecraft.server.level.ThreadedLevelLightEngine;
 import net.minecraft.world.entity.EntityType;
 import net.minecraft.world.entity.ai.village.poi.PoiManager;
 import net.minecraft.world.level.ChunkPos;
@@ -39,7 +47,6 @@
 import net.minecraft.world.level.block.state.BlockState;
 import net.minecraft.world.level.chunk.CarvingMask;
 import net.minecraft.world.level.chunk.ChunkAccess;
-import net.minecraft.world.level.chunk.ChunkSource;
 import net.minecraft.world.level.chunk.ChunkStatus;
 import net.minecraft.world.level.chunk.DataLayer;
 import net.minecraft.world.level.chunk.ImposterProtoChunk;
@@ -63,7 +70,20 @@
 import org.slf4j.Logger;
 
 public class ChunkSerializer {
-    public static final Codec<PalettedContainer<BlockState>> BLOCK_STATE_CODEC = PalettedContainer.codecRW(Block.BLOCK_STATE_REGISTRY, BlockState.CODEC, PalettedContainer.Strategy.SECTION_STATES, Blocks.AIR.defaultBlockState());
+    // Paper start
+    // TODO: Check on update
+    public static long getLastWorldSaveTime(CompoundTag chunkData) {
+        final int dataVersion = ChunkStorage.getVersion(chunkData);
+        if (dataVersion < 2842) { // Level tag is removed after this version
+            final CompoundTag levelData = chunkData.getCompound("Level");
+            return levelData.getLong("LastUpdate");
+        } else {
+            return chunkData.getLong("LastUpdate");
+        }
+    }
+    // Paper end
+
+    public static final Codec<PalettedContainer<BlockState>> BLOCK_STATE_CODEC = PalettedContainer.codecRW(Block.BLOCK_STATE_REGISTRY, BlockState.CODEC, PalettedContainer.Strategy.SECTION_STATES, Blocks.AIR.defaultBlockState(), null); // Paper - Anti-Xray - Add preset block states
     private static final Logger LOGGER = LogUtils.getLogger();
     private static final String TAG_UPGRADE_DATA = "UpgradeData";
     private static final String BLOCK_TICKS_TAG = "block_ticks";
@@ -76,321 +96,613 @@
     public static final String BLOCK_LIGHT_TAG = "BlockLight";
     public static final String SKY_LIGHT_TAG = "SkyLight";
 
+    // Paper start - replace light engine impl
+    private static final int STARLIGHT_LIGHT_VERSION = 9;
+
+    private static final String BLOCKLIGHT_STATE_TAG = "starlight.blocklight_state";
+    private static final String SKYLIGHT_STATE_TAG = "starlight.skylight_state";
+    private static final String STARLIGHT_VERSION_TAG = "starlight.light_version";
+    // Paper end - replace light engine impl
+
+    public ChunkSerializer() {}
+
+    // Paper start - guard against serializing mismatching coordinates
+    // TODO Note: This needs to be re-checked each update
+    public static ChunkPos getChunkCoordinate(CompoundTag chunkData) {
+        final int dataVersion = ChunkStorage.getVersion(chunkData);
+        if (dataVersion < 2842) { // Level tag is removed after this version
+            final CompoundTag levelData = chunkData.getCompound("Level");
+            return new ChunkPos(levelData.getInt("xPos"), levelData.getInt("zPos"));
+        } else {
+            return new ChunkPos(chunkData.getInt("xPos"), chunkData.getInt("zPos"));
+        }
+    }
+    // Paper end
+    // Paper start
+    public static final class InProgressChunkHolder {
+
+        public final ProtoChunk protoChunk;
+
+        public CompoundTag poiData;
+
+        public InProgressChunkHolder(final ProtoChunk protoChunk) {
+            this.protoChunk = protoChunk;
+        }
+    }
+    // Paper end
+
     public static ProtoChunk read(ServerLevel world, PoiManager poiStorage, ChunkPos chunkPos, CompoundTag nbt) {
-        ChunkPos chunkPos2 = new ChunkPos(nbt.getInt("xPos"), nbt.getInt("zPos"));
-        if (!Objects.equals(chunkPos, chunkPos2)) {
-            LOGGER.error("Chunk file at {} is in the wrong location; relocating. (Expected {}, got {})", chunkPos, chunkPos, chunkPos2);
+        // Paper start - add variant for async calls
+        InProgressChunkHolder holder = loadChunk(world, poiStorage, chunkPos, nbt, true);
+        return holder.protoChunk;
+    }
+    // Paper start
+    private static final int CURRENT_DATA_VERSION = net.minecraft.SharedConstants.getCurrentVersion().getDataVersion().getVersion();
+    private static final boolean JUST_CORRUPT_IT = Boolean.getBoolean("Paper.ignoreWorldDataVersion");
+    // Paper end
+
+    public static InProgressChunkHolder loadChunk(ServerLevel world, PoiManager poiStorage, ChunkPos chunkPos, CompoundTag nbt, boolean distinguish) {
+        // Paper start - Do NOT attempt to load chunks saved with newer versions
+        if (nbt.contains("DataVersion", 99)) {
+            int dataVersion = nbt.getInt("DataVersion");
+            if (!JUST_CORRUPT_IT && dataVersion > CURRENT_DATA_VERSION) {
+                new RuntimeException("Server attempted to load chunk saved with newer version of minecraft! " + dataVersion + " > " + CURRENT_DATA_VERSION).printStackTrace();
+                System.exit(1);
+            }
         }
+        // Paper end
+        ChunkPos chunkcoordintpair1 = new ChunkPos(nbt.getInt("xPos"), nbt.getInt("zPos")); // Paper - diff on change, see ChunkSerializer#getChunkCoordinate
 
-        UpgradeData upgradeData = nbt.contains("UpgradeData", 10) ? new UpgradeData(nbt.getCompound("UpgradeData"), world) : UpgradeData.EMPTY;
-        boolean bl = nbt.getBoolean("isLightOn");
-        ListTag listTag = nbt.getList("sections", 10);
+        if (!Objects.equals(chunkPos, chunkcoordintpair1)) {
+            ChunkSerializer.LOGGER.error("Chunk file at {} is in the wrong location; relocating. (Expected {}, got {})", new Object[]{chunkPos, chunkPos, chunkcoordintpair1});
+        }
+
+        UpgradeData chunkconverter = nbt.contains("UpgradeData", 10) ? new UpgradeData(nbt.getCompound("UpgradeData"), world) : UpgradeData.EMPTY;
+        boolean flag = getStatus(nbt) != null && getStatus(nbt).isOrAfter(ChunkStatus.LIGHT) && nbt.get("isLightOn") != null && nbt.getInt(STARLIGHT_VERSION_TAG) == STARLIGHT_LIGHT_VERSION; // Paper
+        ListTag nbttaglist = nbt.getList("sections", 10);
         int i = world.getSectionsCount();
-        LevelChunkSection[] levelChunkSections = new LevelChunkSection[i];
-        boolean bl2 = world.dimensionType().hasSkyLight();
-        ChunkSource chunkSource = world.getChunkSource();
-        LevelLightEngine levelLightEngine = chunkSource.getLightEngine();
-        Registry<Biome> registry = world.registryAccess().registryOrThrow(Registries.BIOME);
-        Codec<PalettedContainerRO<Holder<Biome>>> codec = makeBiomeCodec(registry);
-        boolean bl3 = false;
+        LevelChunkSection[] achunksection = new LevelChunkSection[i];
+        boolean flag1 = world.dimensionType().hasSkyLight();
+        ServerChunkCache chunkproviderserver = world.getChunkSource();
+        LevelLightEngine levellightengine = chunkproviderserver.getLightEngine();
+        // Paper start
+        ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] blockNibbles = ca.spottedleaf.starlight.common.light.StarLightEngine.getFilledEmptyLight(world);
+        ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] skyNibbles = ca.spottedleaf.starlight.common.light.StarLightEngine.getFilledEmptyLight(world);
+        final int minSection = io.papermc.paper.util.WorldUtil.getMinLightSection(world);
+        final int maxSection = io.papermc.paper.util.WorldUtil.getMaxLightSection(world);
+        boolean canReadSky = world.dimensionType().hasSkyLight();
+        // Paper end
+        Registry<Biome> iregistry = world.registryAccess().registryOrThrow(Registries.BIOME);
+        Codec<PalettedContainer<Holder<Biome>>> codec = ChunkSerializer.makeBiomeCodecRW(iregistry); // CraftBukkit - read/write
+        boolean flag2 = false;
 
-        for(int j = 0; j < listTag.size(); ++j) {
-            CompoundTag compoundTag = listTag.getCompound(j);
-            int k = compoundTag.getByte("Y");
-            int l = world.getSectionIndexFromSectionY(k);
-            if (l >= 0 && l < levelChunkSections.length) {
-                PalettedContainer<BlockState> palettedContainer;
-                if (compoundTag.contains("block_states", 10)) {
-                    palettedContainer = BLOCK_STATE_CODEC.parse(NbtOps.INSTANCE, compoundTag.getCompound("block_states")).promotePartial((errorMessage) -> {
-                        logErrors(chunkPos, k, errorMessage);
-                    }).getOrThrow(false, LOGGER::error);
+        DataResult dataresult;
+
+        for (int j = 0; j < nbttaglist.size(); ++j) {
+            CompoundTag nbttagcompound1 = nbttaglist.getCompound(j); CompoundTag sectionData = nbttagcompound1; // Paper
+            byte b0 = nbttagcompound1.getByte("Y");
+            int k = world.getSectionIndexFromSectionY(b0);
+
+            if (k >= 0 && k < achunksection.length) {
+                Logger logger;
+                PalettedContainer datapaletteblock;
+                // Paper start - Anti-Xray - Add preset block states
+                BlockState[] presetBlockStates = world.chunkPacketBlockController.getPresetBlockStates(world, chunkPos, b0);
+
+                if (nbttagcompound1.contains("block_states", 10)) {
+                    Codec<PalettedContainer<BlockState>> blockStateCodec = presetBlockStates == null ? ChunkSerializer.BLOCK_STATE_CODEC : PalettedContainer.codecRW(Block.BLOCK_STATE_REGISTRY, BlockState.CODEC, PalettedContainer.Strategy.SECTION_STATES, Blocks.AIR.defaultBlockState(), presetBlockStates);
+                    dataresult = blockStateCodec.parse(NbtOps.INSTANCE, nbttagcompound1.getCompound("block_states")).promotePartial((s) -> {
+                        ChunkSerializer.logErrors(chunkPos, b0, s);
+                    });
+                    logger = ChunkSerializer.LOGGER;
+                    Objects.requireNonNull(logger);
+                    datapaletteblock = (PalettedContainer) ((DataResult<PalettedContainer<BlockState>>) dataresult).getOrThrow(false, logger::error); // CraftBukkit - decompile error
                 } else {
-                    palettedContainer = new PalettedContainer<>(Block.BLOCK_STATE_REGISTRY, Blocks.AIR.defaultBlockState(), PalettedContainer.Strategy.SECTION_STATES);
+                    datapaletteblock = new PalettedContainer<>(Block.BLOCK_STATE_REGISTRY, Blocks.AIR.defaultBlockState(), PalettedContainer.Strategy.SECTION_STATES, presetBlockStates);
+                    // Paper end
                 }
 
-                PalettedContainerRO<Holder<Biome>> palettedContainerRO;
-                if (compoundTag.contains("biomes", 10)) {
-                    palettedContainerRO = codec.parse(NbtOps.INSTANCE, compoundTag.getCompound("biomes")).promotePartial((errorMessage) -> {
-                        logErrors(chunkPos, k, errorMessage);
-                    }).getOrThrow(false, LOGGER::error);
+                PalettedContainer object; // CraftBukkit - read/write
+
+                if (nbttagcompound1.contains("biomes", 10)) {
+                    dataresult = codec.parse(NbtOps.INSTANCE, nbttagcompound1.getCompound("biomes")).promotePartial((s) -> {
+                        ChunkSerializer.logErrors(chunkPos, b0, s);
+                    });
+                    logger = ChunkSerializer.LOGGER;
+                    Objects.requireNonNull(logger);
+                    object = ((DataResult<PalettedContainer<Holder<Biome>>>) dataresult).getOrThrow(false, logger::error); // CraftBukkit - decompile error
                 } else {
-                    palettedContainerRO = new PalettedContainer<>(registry.asHolderIdMap(), registry.getHolderOrThrow(Biomes.PLAINS), PalettedContainer.Strategy.SECTION_BIOMES);
+                    object = new PalettedContainer<>(iregistry.asHolderIdMap(), iregistry.getHolderOrThrow(Biomes.PLAINS), PalettedContainer.Strategy.SECTION_BIOMES, null); // Paper - Anti-Xray - Add preset biomes
                 }
 
-                LevelChunkSection levelChunkSection = new LevelChunkSection(palettedContainer, palettedContainerRO);
-                levelChunkSections[l] = levelChunkSection;
-                SectionPos sectionPos = SectionPos.of(chunkPos, k);
-                poiStorage.checkConsistencyWithBlocks(sectionPos, levelChunkSection);
+                LevelChunkSection chunksection = new LevelChunkSection(datapaletteblock, (PalettedContainer) object); // CraftBukkit - read/write
+
+                achunksection[k] = chunksection;
+                SectionPos sectionposition = SectionPos.of(chunkPos, b0);
+
+                // Paper - rewrite chunk system - moved to final load stage
             }
 
-            boolean bl4 = compoundTag.contains("BlockLight", 7);
-            boolean bl5 = bl2 && compoundTag.contains("SkyLight", 7);
-            if (bl4 || bl5) {
-                if (!bl3) {
-                    levelLightEngine.retainData(chunkPos, true);
-                    bl3 = true;
+            boolean flag3 = nbttagcompound1.contains("BlockLight", 7);
+            boolean flag4 = flag1 && nbttagcompound1.contains("SkyLight", 7);
+
+            // Paper start - rewrite the light engine
+            if (flag) {
+                try {
+                int y = sectionData.getByte("Y");
+                // Paper end - rewrite the light engine
+                if (flag3) {
+                    // Paper start - rewrite the light engine
+                    // this is where our diff is
+                    blockNibbles[y - minSection] = new ca.spottedleaf.starlight.common.light.SWMRNibbleArray(sectionData.getByteArray("BlockLight").clone(), sectionData.getInt(BLOCKLIGHT_STATE_TAG)); // clone for data safety
+                } else {
+                    blockNibbles[y - minSection] = new ca.spottedleaf.starlight.common.light.SWMRNibbleArray(null, sectionData.getInt(BLOCKLIGHT_STATE_TAG));
+                    // Paper end - rewrite the light engine
                 }
 
-                if (bl4) {
-                    levelLightEngine.queueSectionData(LightLayer.BLOCK, SectionPos.of(chunkPos, k), new DataLayer(compoundTag.getByteArray("BlockLight")));
+                if (flag4) {
+                    // Paper start - rewrite the light engine
+                    // we store under the same key so mod programs editing nbt
+                    // can still read the data, hopefully.
+                    // however, for compatibility we store chunks as unlit so vanilla
+                    // is forced to re-light them if it encounters our data. It's too much of a burden
+                    // to try and maintain compatibility with a broken and inferior skylight management system.
+                    skyNibbles[y - minSection] = new ca.spottedleaf.starlight.common.light.SWMRNibbleArray(sectionData.getByteArray("SkyLight").clone(), sectionData.getInt(SKYLIGHT_STATE_TAG)); // clone for data safety
+                } else if (flag1) {
+                    skyNibbles[y - minSection] = new ca.spottedleaf.starlight.common.light.SWMRNibbleArray(null, sectionData.getInt(SKYLIGHT_STATE_TAG));
+                    // Paper end - rewrite the light engine
                 }
 
-                if (bl5) {
-                    levelLightEngine.queueSectionData(LightLayer.SKY, SectionPos.of(chunkPos, k), new DataLayer(compoundTag.getByteArray("SkyLight")));
+                // Paper start - rewrite the light engine
+                } catch (Exception ex) {
+                    LOGGER.warn("Failed to load light data for chunk " + chunkPos + " in world '" + world.getWorld().getName() + "', light will be regenerated", ex);
+                    flag = false;
                 }
+                // Paper end - rewrite light engine
             }
         }
 
-        long m = nbt.getLong("InhabitedTime");
-        ChunkStatus.ChunkType chunkType = getChunkTypeFromTag(nbt);
-        BlendingData blendingData;
+        long l = nbt.getLong("InhabitedTime");
+        ChunkStatus.ChunkType chunkstatus_type = ChunkSerializer.getChunkTypeFromTag(nbt);
+        Logger logger1;
+        BlendingData blendingdata;
+
         if (nbt.contains("blending_data", 10)) {
-            blendingData = BlendingData.CODEC.parse(new Dynamic<>(NbtOps.INSTANCE, nbt.getCompound("blending_data"))).resultOrPartial(LOGGER::error).orElse((BlendingData)null);
+            dataresult = BlendingData.CODEC.parse(new Dynamic(NbtOps.INSTANCE, nbt.getCompound("blending_data")));
+            logger1 = ChunkSerializer.LOGGER;
+            Objects.requireNonNull(logger1);
+            blendingdata = (BlendingData) ((DataResult<BlendingData>) dataresult).resultOrPartial(logger1::error).orElse(null); // CraftBukkit - decompile error
         } else {
-            blendingData = null;
+            blendingdata = null;
         }
 
-        ChunkAccess chunkAccess;
-        if (chunkType == ChunkStatus.ChunkType.LEVELCHUNK) {
-            LevelChunkTicks<Block> levelChunkTicks = LevelChunkTicks.load(nbt.getList("block_ticks", 10), (id) -> {
-                return BuiltInRegistries.BLOCK.getOptional(ResourceLocation.tryParse(id));
+        Object object1;
+
+        if (chunkstatus_type == ChunkStatus.ChunkType.LEVELCHUNK) {
+            LevelChunkTicks<Block> levelchunkticks = LevelChunkTicks.load(nbt.getList("block_ticks", 10), (s) -> {
+                return BuiltInRegistries.BLOCK.getOptional(ResourceLocation.tryParse(s));
             }, chunkPos);
-            LevelChunkTicks<Fluid> levelChunkTicks2 = LevelChunkTicks.load(nbt.getList("fluid_ticks", 10), (id) -> {
-                return BuiltInRegistries.FLUID.getOptional(ResourceLocation.tryParse(id));
+            LevelChunkTicks<Fluid> levelchunkticks1 = LevelChunkTicks.load(nbt.getList("fluid_ticks", 10), (s) -> {
+                return BuiltInRegistries.FLUID.getOptional(ResourceLocation.tryParse(s));
             }, chunkPos);
-            chunkAccess = new LevelChunk(world.getLevel(), chunkPos, upgradeData, levelChunkTicks, levelChunkTicks2, m, levelChunkSections, postLoadChunk(world, nbt), blendingData);
+
+            object1 = new LevelChunk(world.getLevel(), chunkPos, chunkconverter, levelchunkticks, levelchunkticks1, l, achunksection, ChunkSerializer.postLoadChunk(world, nbt), blendingdata);
+            ((LevelChunk)object1).setBlockNibbles(blockNibbles); // Paper - replace light impl
+            ((LevelChunk)object1).setSkyNibbles(skyNibbles); // Paper - replace light impl
         } else {
-            ProtoChunkTicks<Block> protoChunkTicks = ProtoChunkTicks.load(nbt.getList("block_ticks", 10), (id) -> {
-                return BuiltInRegistries.BLOCK.getOptional(ResourceLocation.tryParse(id));
+            ProtoChunkTicks<Block> protochunkticklist = ProtoChunkTicks.load(nbt.getList("block_ticks", 10), (s) -> {
+                return BuiltInRegistries.BLOCK.getOptional(ResourceLocation.tryParse(s));
             }, chunkPos);
-            ProtoChunkTicks<Fluid> protoChunkTicks2 = ProtoChunkTicks.load(nbt.getList("fluid_ticks", 10), (id) -> {
-                return BuiltInRegistries.FLUID.getOptional(ResourceLocation.tryParse(id));
+            ProtoChunkTicks<Fluid> protochunkticklist1 = ProtoChunkTicks.load(nbt.getList("fluid_ticks", 10), (s) -> {
+                return BuiltInRegistries.FLUID.getOptional(ResourceLocation.tryParse(s));
             }, chunkPos);
-            ProtoChunk protoChunk = new ProtoChunk(chunkPos, upgradeData, levelChunkSections, protoChunkTicks, protoChunkTicks2, world, registry, blendingData);
-            chunkAccess = protoChunk;
-            protoChunk.setInhabitedTime(m);
+            ProtoChunk protochunk = new ProtoChunk(chunkPos, chunkconverter, achunksection, protochunkticklist, protochunkticklist1, world, iregistry, blendingdata);
+            protochunk.setBlockNibbles(blockNibbles); // Paper - replace light impl
+            protochunk.setSkyNibbles(skyNibbles); // Paper - replace light impl
+
+            object1 = protochunk;
+            protochunk.setInhabitedTime(l);
             if (nbt.contains("below_zero_retrogen", 10)) {
-                BelowZeroRetrogen.CODEC.parse(new Dynamic<>(NbtOps.INSTANCE, nbt.getCompound("below_zero_retrogen"))).resultOrPartial(LOGGER::error).ifPresent(protoChunk::setBelowZeroRetrogen);
+                dataresult = BelowZeroRetrogen.CODEC.parse(new Dynamic(NbtOps.INSTANCE, nbt.getCompound("below_zero_retrogen")));
+                logger1 = ChunkSerializer.LOGGER;
+                Objects.requireNonNull(logger1);
+                Optional<BelowZeroRetrogen> optional = ((DataResult<BelowZeroRetrogen>) dataresult).resultOrPartial(logger1::error); // CraftBukkit - decompile error
+
+                Objects.requireNonNull(protochunk);
+                optional.ifPresent(protochunk::setBelowZeroRetrogen);
             }
 
-            ChunkStatus chunkStatus = ChunkStatus.byName(nbt.getString("Status"));
-            protoChunk.setStatus(chunkStatus);
-            if (chunkStatus.isOrAfter(ChunkStatus.INITIALIZE_LIGHT)) {
-                protoChunk.setLightEngine(levelLightEngine);
+            ChunkStatus chunkstatus = ChunkStatus.byName(nbt.getString("Status"));
+
+            protochunk.setStatus(chunkstatus);
+            if (chunkstatus.isOrAfter(ChunkStatus.INITIALIZE_LIGHT)) {
+                protochunk.setLightEngine(levellightengine);
             }
         }
 
-        chunkAccess.setLightCorrect(bl);
-        CompoundTag compoundTag2 = nbt.getCompound("Heightmaps");
-        EnumSet<Heightmap.Types> enumSet = EnumSet.noneOf(Heightmap.Types.class);
+        // CraftBukkit start - load chunk persistent data from nbt - SPIGOT-6814: Already load PDC here to account for 1.17 to 1.18 chunk upgrading.
+        net.minecraft.nbt.Tag persistentBase = nbt.get("ChunkBukkitValues");
+        if (persistentBase instanceof CompoundTag) {
+            ((ChunkAccess) object1).persistentDataContainer.putAll((CompoundTag) persistentBase);
+        }
+        // CraftBukkit end
 
-        for(Heightmap.Types types : chunkAccess.getStatus().heightmapsAfter()) {
-            String string = types.getSerializationKey();
-            if (compoundTag2.contains(string, 12)) {
-                chunkAccess.setHeightmap(types, compoundTag2.getLongArray(string));
+        ((ChunkAccess) object1).setLightCorrect(flag);
+        CompoundTag nbttagcompound2 = nbt.getCompound("Heightmaps");
+        EnumSet<Heightmap.Types> enumset = EnumSet.noneOf(Heightmap.Types.class);
+        Iterator iterator = ((ChunkAccess) object1).getStatus().heightmapsAfter().iterator();
+
+        while (iterator.hasNext()) {
+            Heightmap.Types heightmap_type = (Heightmap.Types) iterator.next();
+            String s = heightmap_type.getSerializationKey();
+
+            if (nbttagcompound2.contains(s, 12)) {
+                ((ChunkAccess) object1).setHeightmap(heightmap_type, nbttagcompound2.getLongArray(s));
             } else {
-                enumSet.add(types);
+                enumset.add(heightmap_type);
             }
         }
 
-        Heightmap.primeHeightmaps(chunkAccess, enumSet);
-        CompoundTag compoundTag3 = nbt.getCompound("structures");
-        chunkAccess.setAllStarts(unpackStructureStart(StructurePieceSerializationContext.fromLevel(world), compoundTag3, world.getSeed()));
-        chunkAccess.setAllReferences(unpackStructureReferences(world.registryAccess(), chunkPos, compoundTag3));
+        Heightmap.primeHeightmaps((ChunkAccess) object1, enumset);
+        CompoundTag nbttagcompound3 = nbt.getCompound("structures");
+
+        ((ChunkAccess) object1).setAllStarts(ChunkSerializer.unpackStructureStart(StructurePieceSerializationContext.fromLevel(world), nbttagcompound3, world.getSeed()));
+        ((ChunkAccess) object1).setAllReferences(ChunkSerializer.unpackStructureReferences(world.registryAccess(), chunkPos, nbttagcompound3));
         if (nbt.getBoolean("shouldSave")) {
-            chunkAccess.setUnsaved(true);
+            ((ChunkAccess) object1).setUnsaved(true);
         }
 
-        ListTag listTag2 = nbt.getList("PostProcessing", 9);
+        ListTag nbttaglist1 = nbt.getList("PostProcessing", 9);
 
-        for(int n = 0; n < listTag2.size(); ++n) {
-            ListTag listTag3 = listTag2.getList(n);
+        ListTag nbttaglist2;
+        int i1;
 
-            for(int o = 0; o < listTag3.size(); ++o) {
-                chunkAccess.addPackedPostProcess(listTag3.getShort(o), n);
+        for (int j1 = 0; j1 < nbttaglist1.size(); ++j1) {
+            nbttaglist2 = nbttaglist1.getList(j1);
+
+            for (i1 = 0; i1 < nbttaglist2.size(); ++i1) {
+                ((ChunkAccess) object1).addPackedPostProcess(nbttaglist2.getShort(i1), j1);
             }
         }
 
-        if (chunkType == ChunkStatus.ChunkType.LEVELCHUNK) {
-            return new ImposterProtoChunk((LevelChunk)chunkAccess, false);
+        if (chunkstatus_type == ChunkStatus.ChunkType.LEVELCHUNK) {
+            return new InProgressChunkHolder(new ImposterProtoChunk((LevelChunk) object1, false)); // Paper - Async chunk loading
         } else {
-            ProtoChunk protoChunk2 = (ProtoChunk)chunkAccess;
-            ListTag listTag4 = nbt.getList("entities", 10);
+            ProtoChunk protochunk1 = (ProtoChunk) object1;
 
-            for(int p = 0; p < listTag4.size(); ++p) {
-                protoChunk2.addEntity(listTag4.getCompound(p));
+            nbttaglist2 = nbt.getList("entities", 10);
+
+            for (i1 = 0; i1 < nbttaglist2.size(); ++i1) {
+                protochunk1.addEntity(nbttaglist2.getCompound(i1));
             }
 
-            ListTag listTag5 = nbt.getList("block_entities", 10);
+            ListTag nbttaglist3 = nbt.getList("block_entities", 10);
 
-            for(int q = 0; q < listTag5.size(); ++q) {
-                CompoundTag compoundTag4 = listTag5.getCompound(q);
-                chunkAccess.setBlockEntityNbt(compoundTag4);
+            for (int k1 = 0; k1 < nbttaglist3.size(); ++k1) {
+                CompoundTag nbttagcompound4 = nbttaglist3.getCompound(k1);
+
+                // Paper start - do not read tile entities positioned outside the chunk
+                BlockPos blockposition = BlockEntity.getPosFromTag(nbttagcompound4);
+                if ((blockposition.getX() >> 4) != chunkPos.x || (blockposition.getZ() >> 4) != chunkPos.z) {
+                    LOGGER.warn("Tile entity serialized in chunk " + chunkPos + " in world '" + world.getWorld().getName() + "' positioned at " + blockposition + " is located outside of the chunk");
+                    continue;
+                }
+                // Paper end - do not read tile entities positioned outside the chunk
+                ((ChunkAccess) object1).setBlockEntityNbt(nbttagcompound4);
             }
 
-            CompoundTag compoundTag5 = nbt.getCompound("CarvingMasks");
+            CompoundTag nbttagcompound5 = nbt.getCompound("CarvingMasks");
+            Iterator iterator1 = nbttagcompound5.getAllKeys().iterator();
 
-            for(String string2 : compoundTag5.getAllKeys()) {
-                GenerationStep.Carving carving = GenerationStep.Carving.valueOf(string2);
-                protoChunk2.setCarvingMask(carving, new CarvingMask(compoundTag5.getLongArray(string2), chunkAccess.getMinBuildHeight()));
+            while (iterator1.hasNext()) {
+                String s1 = (String) iterator1.next();
+                GenerationStep.Carving worldgenstage_features = GenerationStep.Carving.valueOf(s1);
+
+                protochunk1.setCarvingMask(worldgenstage_features, new CarvingMask(nbttagcompound5.getLongArray(s1), ((ChunkAccess) object1).getMinBuildHeight()));
             }
 
-            return protoChunk2;
+            return new InProgressChunkHolder(protochunk1); // Paper - Async chunk loading
+        }
+    }
+
+    // Paper start - async chunk save for unload
+    public record AsyncSaveData(
+        Tag blockTickList, // non-null if we had to go to the server's tick list
+        Tag fluidTickList, // non-null if we had to go to the server's tick list
+        ListTag blockEntities,
+        long worldTime
+    ) {}
+
+    // must be called sync
+    public static AsyncSaveData getAsyncSaveData(ServerLevel world, ChunkAccess chunk) {
+        org.spigotmc.AsyncCatcher.catchOp("preparation of chunk data for async save");
+
+        final CompoundTag tickLists = new CompoundTag();
+        ChunkSerializer.saveTicks(world, tickLists, chunk.getTicksForSerialization());
+
+        ListTag blockEntitiesSerialized = new ListTag();
+        for (final BlockPos blockPos : chunk.getBlockEntitiesPos()) {
+            final CompoundTag blockEntityNbt = chunk.getBlockEntityNbtForSaving(blockPos);
+            if (blockEntityNbt != null) {
+                blockEntitiesSerialized.add(blockEntityNbt);
+            }
         }
+
+        return new AsyncSaveData(
+            tickLists.get(BLOCK_TICKS_TAG),
+            tickLists.get(FLUID_TICKS_TAG),
+            blockEntitiesSerialized,
+            world.getGameTime()
+        );
     }
+    // Paper end
 
     private static void logErrors(ChunkPos chunkPos, int y, String message) {
-        LOGGER.error("Recoverable errors when loading section [" + chunkPos.x + ", " + y + ", " + chunkPos.z + "]: " + message);
+        ChunkSerializer.LOGGER.error("Recoverable errors when loading section [" + chunkPos.x + ", " + y + ", " + chunkPos.z + "]: " + message);
     }
 
     private static Codec<PalettedContainerRO<Holder<Biome>>> makeBiomeCodec(Registry<Biome> biomeRegistry) {
         return PalettedContainer.codecRO(biomeRegistry.asHolderIdMap(), biomeRegistry.holderByNameCodec(), PalettedContainer.Strategy.SECTION_BIOMES, biomeRegistry.getHolderOrThrow(Biomes.PLAINS));
     }
 
+    // CraftBukkit start - read/write
+    private static Codec<PalettedContainer<Holder<Biome>>> makeBiomeCodecRW(Registry<Biome> iregistry) {
+        return PalettedContainer.codecRW(iregistry.asHolderIdMap(), iregistry.holderByNameCodec(), PalettedContainer.Strategy.SECTION_BIOMES, iregistry.getHolderOrThrow(Biomes.PLAINS), null); // Paper - Anti-Xray - Add preset biomes
+    }
+    // CraftBukkit end
+
     public static CompoundTag write(ServerLevel world, ChunkAccess chunk) {
-        ChunkPos chunkPos = chunk.getPos();
-        CompoundTag compoundTag = NbtUtils.addCurrentDataVersion(new CompoundTag());
-        compoundTag.putInt("xPos", chunkPos.x);
-        compoundTag.putInt("yPos", chunk.getMinSection());
-        compoundTag.putInt("zPos", chunkPos.z);
-        compoundTag.putLong("LastUpdate", world.getGameTime());
-        compoundTag.putLong("InhabitedTime", chunk.getInhabitedTime());
-        compoundTag.putString("Status", BuiltInRegistries.CHUNK_STATUS.getKey(chunk.getStatus()).toString());
-        BlendingData blendingData = chunk.getBlendingData();
-        if (blendingData != null) {
-            BlendingData.CODEC.encodeStart(NbtOps.INSTANCE, blendingData).resultOrPartial(LOGGER::error).ifPresent((tag) -> {
-                compoundTag.put("blending_data", tag);
+        // Paper start
+        return saveChunk(world, chunk, null);
+    }
+    public static CompoundTag saveChunk(ServerLevel world, ChunkAccess chunk, @org.checkerframework.checker.nullness.qual.Nullable AsyncSaveData asyncsavedata) {
+        // Paper end
+        // Paper start - rewrite light impl
+        final int minSection = io.papermc.paper.util.WorldUtil.getMinLightSection(world);
+        final int maxSection = io.papermc.paper.util.WorldUtil.getMaxLightSection(world);
+        ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] blockNibbles = chunk.getBlockNibbles();
+        ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] skyNibbles = chunk.getSkyNibbles();
+        // Paper end - rewrite light impl
+        ChunkPos chunkcoordintpair = chunk.getPos();
+        CompoundTag nbttagcompound = NbtUtils.addCurrentDataVersion(new CompoundTag());
+
+        nbttagcompound.putInt("xPos", chunkcoordintpair.x);
+        nbttagcompound.putInt("yPos", chunk.getMinSection());
+        nbttagcompound.putInt("zPos", chunkcoordintpair.z);
+        nbttagcompound.putLong("LastUpdate", asyncsavedata != null ? asyncsavedata.worldTime : world.getGameTime()); // Paper - async chunk unloading // Paper - diff on change
+        nbttagcompound.putLong("InhabitedTime", chunk.getInhabitedTime());
+        nbttagcompound.putString("Status", BuiltInRegistries.CHUNK_STATUS.getKey(chunk.getStatus()).toString());
+        BlendingData blendingdata = chunk.getBlendingData();
+        DataResult<Tag> dataresult; // CraftBukkit - decompile error
+        Logger logger;
+
+        if (blendingdata != null) {
+            dataresult = BlendingData.CODEC.encodeStart(NbtOps.INSTANCE, blendingdata);
+            logger = ChunkSerializer.LOGGER;
+            Objects.requireNonNull(logger);
+            dataresult.resultOrPartial(logger::error).ifPresent((nbtbase) -> {
+                nbttagcompound.put("blending_data", nbtbase);
             });
         }
 
-        BelowZeroRetrogen belowZeroRetrogen = chunk.getBelowZeroRetrogen();
-        if (belowZeroRetrogen != null) {
-            BelowZeroRetrogen.CODEC.encodeStart(NbtOps.INSTANCE, belowZeroRetrogen).resultOrPartial(LOGGER::error).ifPresent((tag) -> {
-                compoundTag.put("below_zero_retrogen", tag);
+        BelowZeroRetrogen belowzeroretrogen = chunk.getBelowZeroRetrogen();
+
+        if (belowzeroretrogen != null) {
+            dataresult = BelowZeroRetrogen.CODEC.encodeStart(NbtOps.INSTANCE, belowzeroretrogen);
+            logger = ChunkSerializer.LOGGER;
+            Objects.requireNonNull(logger);
+            dataresult.resultOrPartial(logger::error).ifPresent((nbtbase) -> {
+                nbttagcompound.put("below_zero_retrogen", nbtbase);
             });
         }
 
-        UpgradeData upgradeData = chunk.getUpgradeData();
-        if (!upgradeData.isEmpty()) {
-            compoundTag.put("UpgradeData", upgradeData.write());
+        UpgradeData chunkconverter = chunk.getUpgradeData();
+
+        if (!chunkconverter.isEmpty()) {
+            nbttagcompound.put("UpgradeData", chunkconverter.write());
         }
 
-        LevelChunkSection[] levelChunkSections = chunk.getSections();
-        ListTag listTag = new ListTag();
-        LevelLightEngine levelLightEngine = world.getChunkSource().getLightEngine();
-        Registry<Biome> registry = world.registryAccess().registryOrThrow(Registries.BIOME);
-        Codec<PalettedContainerRO<Holder<Biome>>> codec = makeBiomeCodec(registry);
-        boolean bl = chunk.isLightCorrect();
+        LevelChunkSection[] achunksection = chunk.getSections();
+        ListTag nbttaglist = new ListTag();
+        ThreadedLevelLightEngine lightenginethreaded = world.getChunkSource().getLightEngine();
+        Registry<Biome> iregistry = world.registryAccess().registryOrThrow(Registries.BIOME);
+        Codec<PalettedContainerRO<Holder<Biome>>> codec = ChunkSerializer.makeBiomeCodec(iregistry);
+        boolean flag = chunk.isLightCorrect();
 
-        for(int i = levelLightEngine.getMinLightSection(); i < levelLightEngine.getMaxLightSection(); ++i) {
+        for (int i = lightenginethreaded.getMinLightSection(); i < lightenginethreaded.getMaxLightSection(); ++i) {
             int j = chunk.getSectionIndexFromSectionY(i);
-            boolean bl2 = j >= 0 && j < levelChunkSections.length;
-            DataLayer dataLayer = levelLightEngine.getLayerListener(LightLayer.BLOCK).getDataLayerData(SectionPos.of(chunkPos, i));
-            DataLayer dataLayer2 = levelLightEngine.getLayerListener(LightLayer.SKY).getDataLayerData(SectionPos.of(chunkPos, i));
-            if (bl2 || dataLayer != null || dataLayer2 != null) {
-                CompoundTag compoundTag2 = new CompoundTag();
-                if (bl2) {
-                    LevelChunkSection levelChunkSection = levelChunkSections[j];
-                    compoundTag2.put("block_states", BLOCK_STATE_CODEC.encodeStart(NbtOps.INSTANCE, levelChunkSection.getStates()).getOrThrow(false, LOGGER::error));
-                    compoundTag2.put("biomes", codec.encodeStart(NbtOps.INSTANCE, levelChunkSection.getBiomes()).getOrThrow(false, LOGGER::error));
+            boolean flag1 = j >= 0 && j < achunksection.length;
+            // Paper - replace light engine
+
+            // Paper start - replace light engine
+            ca.spottedleaf.starlight.common.light.SWMRNibbleArray.SaveState blockNibble = blockNibbles[i - minSection].getSaveState();
+            ca.spottedleaf.starlight.common.light.SWMRNibbleArray.SaveState skyNibble = skyNibbles[i - minSection].getSaveState();
+            if (flag1 || blockNibble != null || skyNibble != null) {
+                // Paper end - replace light engine
+                CompoundTag nbttagcompound1 = new CompoundTag(); CompoundTag section = nbttagcompound1; // Paper
+
+                if (flag1) {
+                    LevelChunkSection chunksection = achunksection[j];
+                    DataResult<Tag> dataresult1 = ChunkSerializer.BLOCK_STATE_CODEC.encodeStart(NbtOps.INSTANCE, chunksection.getStates()); // CraftBukkit - decompile error
+                    Logger logger1 = ChunkSerializer.LOGGER;
+
+                    Objects.requireNonNull(logger1);
+                    nbttagcompound1.put("block_states", (Tag) dataresult1.getOrThrow(false, logger1::error));
+                    dataresult1 = codec.encodeStart(NbtOps.INSTANCE, chunksection.getBiomes());
+                    logger1 = ChunkSerializer.LOGGER;
+                    Objects.requireNonNull(logger1);
+                    nbttagcompound1.put("biomes", (Tag) dataresult1.getOrThrow(false, logger1::error));
                 }
 
-                if (dataLayer != null && !dataLayer.isEmpty()) {
-                    compoundTag2.putByteArray("BlockLight", dataLayer.getData());
+                // Paper start
+                // we store under the same key so mod programs editing nbt
+                // can still read the data, hopefully.
+                // however, for compatibility we store chunks as unlit so vanilla
+                // is forced to re-light them if it encounters our data. It's too much of a burden
+                // to try and maintain compatibility with a broken and inferior skylight management system.
+
+                if (blockNibble != null) {
+                    if (blockNibble.data != null) {
+                        section.putByteArray("BlockLight", blockNibble.data);
+                    }
+                    section.putInt(BLOCKLIGHT_STATE_TAG, blockNibble.state);
                 }
 
-                if (dataLayer2 != null && !dataLayer2.isEmpty()) {
-                    compoundTag2.putByteArray("SkyLight", dataLayer2.getData());
+                if (skyNibble != null) {
+                    if (skyNibble.data != null) {
+                        section.putByteArray("SkyLight", skyNibble.data);
+                    }
+                    section.putInt(SKYLIGHT_STATE_TAG, skyNibble.state);
                 }
+                // Paper end
 
-                if (!compoundTag2.isEmpty()) {
-                    compoundTag2.putByte("Y", (byte)i);
-                    listTag.add(compoundTag2);
+                if (!nbttagcompound1.isEmpty()) {
+                    nbttagcompound1.putByte("Y", (byte) i);
+                    nbttaglist.add(nbttagcompound1);
                 }
             }
         }
 
-        compoundTag.put("sections", listTag);
-        if (bl) {
-            compoundTag.putBoolean("isLightOn", true);
+        nbttagcompound.put("sections", nbttaglist);
+        if (flag) {
+            nbttagcompound.putInt(STARLIGHT_VERSION_TAG, STARLIGHT_LIGHT_VERSION); // Paper
+            nbttagcompound.putBoolean("isLightOn", false); // Paper - set to false but still store, this allows us to detect --eraseCache (as eraseCache _removes_)
         }
 
-        ListTag listTag2 = new ListTag();
+        // Paper start
+        ListTag nbttaglist1;
+        Iterator<BlockPos> iterator;
+        if (asyncsavedata != null) {
+            nbttaglist1 = asyncsavedata.blockEntities;
+            iterator = java.util.Collections.emptyIterator();
+        } else {
+            nbttaglist1 = new ListTag();
+            iterator = chunk.getBlockEntitiesPos().iterator();
+        }
+        // Paper end
 
-        for(BlockPos blockPos : chunk.getBlockEntitiesPos()) {
-            CompoundTag compoundTag3 = chunk.getBlockEntityNbtForSaving(blockPos);
-            if (compoundTag3 != null) {
-                listTag2.add(compoundTag3);
+        CompoundTag nbttagcompound2;
+
+        while (iterator.hasNext()) {
+            BlockPos blockposition = (BlockPos) iterator.next();
+
+            nbttagcompound2 = chunk.getBlockEntityNbtForSaving(blockposition);
+            if (nbttagcompound2 != null) {
+                nbttaglist1.add(nbttagcompound2);
             }
         }
 
-        compoundTag.put("block_entities", listTag2);
+        nbttagcompound.put("block_entities", nbttaglist1);
         if (chunk.getStatus().getChunkType() == ChunkStatus.ChunkType.PROTOCHUNK) {
-            ProtoChunk protoChunk = (ProtoChunk)chunk;
-            ListTag listTag3 = new ListTag();
-            listTag3.addAll(protoChunk.getEntities());
-            compoundTag.put("entities", listTag3);
-            CompoundTag compoundTag4 = new CompoundTag();
+            ProtoChunk protochunk = (ProtoChunk) chunk;
+            ListTag nbttaglist2 = new ListTag();
 
-            for(GenerationStep.Carving carving : GenerationStep.Carving.values()) {
-                CarvingMask carvingMask = protoChunk.getCarvingMask(carving);
-                if (carvingMask != null) {
-                    compoundTag4.putLongArray(carving.toString(), carvingMask.toArray());
+            nbttaglist2.addAll(protochunk.getEntities());
+            nbttagcompound.put("entities", nbttaglist2);
+            nbttagcompound2 = new CompoundTag();
+            GenerationStep.Carving[] aworldgenstage_features = GenerationStep.Carving.values();
+            int k = aworldgenstage_features.length;
+
+            for (int l = 0; l < k; ++l) {
+                GenerationStep.Carving worldgenstage_features = aworldgenstage_features[l];
+                CarvingMask carvingmask = protochunk.getCarvingMask(worldgenstage_features);
+
+                if (carvingmask != null) {
+                    nbttagcompound2.putLongArray(worldgenstage_features.toString(), carvingmask.toArray());
                 }
             }
 
-            compoundTag.put("CarvingMasks", compoundTag4);
+            nbttagcompound.put("CarvingMasks", nbttagcompound2);
         }
 
-        saveTicks(world, compoundTag, chunk.getTicksForSerialization());
-        compoundTag.put("PostProcessing", packOffsets(chunk.getPostProcessing()));
-        CompoundTag compoundTag5 = new CompoundTag();
+        // Paper start
+        if (asyncsavedata != null) {
+            nbttagcompound.put(BLOCK_TICKS_TAG, asyncsavedata.blockTickList);
+            nbttagcompound.put(FLUID_TICKS_TAG, asyncsavedata.fluidTickList);
+        } else {
+        ChunkSerializer.saveTicks(world, nbttagcompound, chunk.getTicksForSerialization());
+        }
+        // Paper end
+        nbttagcompound.put("PostProcessing", ChunkSerializer.packOffsets(chunk.getPostProcessing()));
+        CompoundTag nbttagcompound3 = new CompoundTag();
+        Iterator iterator1 = chunk.getHeightmaps().iterator();
+
+        while (iterator1.hasNext()) {
+            Entry<Heightmap.Types, Heightmap> entry = (Entry) iterator1.next();
 
-        for(Map.Entry<Heightmap.Types, Heightmap> entry : chunk.getHeightmaps()) {
             if (chunk.getStatus().heightmapsAfter().contains(entry.getKey())) {
-                compoundTag5.put(entry.getKey().getSerializationKey(), new LongArrayTag(entry.getValue().getRawData()));
+                nbttagcompound3.put(((Heightmap.Types) entry.getKey()).getSerializationKey(), new LongArrayTag(((Heightmap) entry.getValue()).getRawData()));
             }
         }
 
-        compoundTag.put("Heightmaps", compoundTag5);
-        compoundTag.put("structures", packStructureData(StructurePieceSerializationContext.fromLevel(world), chunkPos, chunk.getAllStarts(), chunk.getAllReferences()));
-        return compoundTag;
+        nbttagcompound.put("Heightmaps", nbttagcompound3);
+        nbttagcompound.put("structures", ChunkSerializer.packStructureData(StructurePieceSerializationContext.fromLevel(world), chunkcoordintpair, chunk.getAllStarts(), chunk.getAllReferences()));
+        // CraftBukkit start - store chunk persistent data in nbt
+        if (!chunk.persistentDataContainer.isEmpty()) { // SPIGOT-6814: Always save PDC to account for 1.17 to 1.18 chunk upgrading.
+            nbttagcompound.put("ChunkBukkitValues", chunk.persistentDataContainer.toTagCompound());
+        }
+        // CraftBukkit end
+        return nbttagcompound;
     }
 
     private static void saveTicks(ServerLevel world, CompoundTag nbt, ChunkAccess.TicksToSave tickSchedulers) {
-        long l = world.getLevelData().getGameTime();
-        nbt.put("block_ticks", tickSchedulers.blocks().save(l, (block) -> {
+        long i = world.getLevelData().getGameTime();
+
+        nbt.put("block_ticks", tickSchedulers.blocks().save(i, (block) -> {
             return BuiltInRegistries.BLOCK.getKey(block).toString();
         }));
-        nbt.put("fluid_ticks", tickSchedulers.fluids().save(l, (fluid) -> {
-            return BuiltInRegistries.FLUID.getKey(fluid).toString();
+        nbt.put("fluid_ticks", tickSchedulers.fluids().save(i, (fluidtype) -> {
+            return BuiltInRegistries.FLUID.getKey(fluidtype).toString();
         }));
     }
 
+    // Paper start
+    public static @Nullable ChunkStatus getStatus(@Nullable CompoundTag compound) {
+        if (compound == null) {
+            return null;
+        }
+
+        // Note: Copied from below
+        return ChunkStatus.getStatus(compound.getString("Status"));
+    }
+    // Paper end
+
     public static ChunkStatus.ChunkType getChunkTypeFromTag(@Nullable CompoundTag nbt) {
         return nbt != null ? ChunkStatus.byName(nbt.getString("Status")).getChunkType() : ChunkStatus.ChunkType.PROTOCHUNK;
     }
 
     @Nullable
     private static LevelChunk.PostLoadProcessor postLoadChunk(ServerLevel world, CompoundTag nbt) {
-        ListTag listTag = getListOfCompoundsOrNull(nbt, "entities");
-        ListTag listTag2 = getListOfCompoundsOrNull(nbt, "block_entities");
-        return listTag == null && listTag2 == null ? null : (chunk) -> {
-            if (listTag != null) {
-                world.addLegacyChunkEntities(EntityType.loadEntitiesRecursive(listTag, world));
+        ListTag nbttaglist = ChunkSerializer.getListOfCompoundsOrNull(nbt, "entities");
+        ListTag nbttaglist1 = ChunkSerializer.getListOfCompoundsOrNull(nbt, "block_entities");
+
+        return nbttaglist == null && nbttaglist1 == null ? null : (chunk) -> {
+            if (nbttaglist != null) {
+                world.addLegacyChunkEntities(EntityType.loadEntitiesRecursive(nbttaglist, world), chunk.getPos()); // Paper - rewrite chunk system
             }
 
-            if (listTag2 != null) {
-                for(int i = 0; i < listTag2.size(); ++i) {
-                    CompoundTag compoundTag = listTag2.getCompound(i);
-                    boolean bl = compoundTag.getBoolean("keepPacked");
-                    if (bl) {
-                        chunk.setBlockEntityNbt(compoundTag);
+            if (nbttaglist1 != null) {
+                for (int i = 0; i < nbttaglist1.size(); ++i) {
+                    CompoundTag nbttagcompound1 = nbttaglist1.getCompound(i);
+                    boolean flag = nbttagcompound1.getBoolean("keepPacked");
+
+                    // Paper start - do not read tile entities positioned outside the chunk
+                    BlockPos blockposition = BlockEntity.getPosFromTag(nbttagcompound1); //  moved up
+                    ChunkPos chunkPos = chunk.getPos();
+                    if ((blockposition.getX() >> 4) != chunkPos.x || (blockposition.getZ() >> 4) != chunkPos.z) {
+                        LOGGER.warn("Tile entity serialized in chunk " + chunkPos + " in world '" + world.getWorld().getName() + "' positioned at " + blockposition + " is located outside of the chunk");
+                        continue;
+                    }
+                    // Paper end - do not read tile entities positioned outside the chunk
+
+                    if (flag) {
+                        chunk.setBlockEntityNbt(nbttagcompound1);
                     } else {
-                        BlockPos blockPos = BlockEntity.getPosFromTag(compoundTag);
-                        BlockEntity blockEntity = BlockEntity.loadStatic(blockPos, chunk.getBlockState(blockPos), compoundTag);
-                        if (blockEntity != null) {
-                            chunk.setBlockEntity(blockEntity);
+                        // Paper - do not read tile entities positioned outside the chunk - move up
+                        BlockEntity tileentity = BlockEntity.loadStatic(blockposition, chunk.getBlockState(blockposition), nbttagcompound1);
+
+                        if (tileentity != null) {
+                            chunk.setBlockEntity(tileentity);
                         }
                     }
                 }
@@ -401,48 +713,60 @@
 
     @Nullable
     private static ListTag getListOfCompoundsOrNull(CompoundTag nbt, String key) {
-        ListTag listTag = nbt.getList(key, 10);
-        return listTag.isEmpty() ? null : listTag;
+        ListTag nbttaglist = nbt.getList(key, 10);
+
+        return nbttaglist.isEmpty() ? null : nbttaglist;
     }
 
     private static CompoundTag packStructureData(StructurePieceSerializationContext context, ChunkPos pos, Map<Structure, StructureStart> starts, Map<Structure, LongSet> references) {
-        CompoundTag compoundTag = new CompoundTag();
-        CompoundTag compoundTag2 = new CompoundTag();
-        Registry<Structure> registry = context.registryAccess().registryOrThrow(Registries.STRUCTURE);
+        CompoundTag nbttagcompound = new CompoundTag();
+        CompoundTag nbttagcompound1 = new CompoundTag();
+        Registry<Structure> iregistry = context.registryAccess().registryOrThrow(Registries.STRUCTURE);
+        Iterator iterator = starts.entrySet().iterator();
 
-        for(Map.Entry<Structure, StructureStart> entry : starts.entrySet()) {
-            ResourceLocation resourceLocation = registry.getKey(entry.getKey());
-            compoundTag2.put(resourceLocation.toString(), entry.getValue().createTag(context, pos));
+        while (iterator.hasNext()) {
+            Entry<Structure, StructureStart> entry = (Entry) iterator.next();
+            ResourceLocation minecraftkey = iregistry.getKey((Structure) entry.getKey());
+
+            nbttagcompound1.put(minecraftkey.toString(), ((StructureStart) entry.getValue()).createTag(context, pos));
         }
 
-        compoundTag.put("starts", compoundTag2);
-        CompoundTag compoundTag3 = new CompoundTag();
+        nbttagcompound.put("starts", nbttagcompound1);
+        CompoundTag nbttagcompound2 = new CompoundTag();
+        Iterator iterator1 = references.entrySet().iterator();
 
-        for(Map.Entry<Structure, LongSet> entry2 : references.entrySet()) {
-            if (!entry2.getValue().isEmpty()) {
-                ResourceLocation resourceLocation2 = registry.getKey(entry2.getKey());
-                compoundTag3.put(resourceLocation2.toString(), new LongArrayTag(entry2.getValue()));
+        while (iterator1.hasNext()) {
+            Entry<Structure, LongSet> entry1 = (Entry) iterator1.next();
+
+            if (!((LongSet) entry1.getValue()).isEmpty()) {
+                ResourceLocation minecraftkey1 = iregistry.getKey((Structure) entry1.getKey());
+
+                nbttagcompound2.put(minecraftkey1.toString(), new LongArrayTag((LongSet) entry1.getValue()));
             }
         }
 
-        compoundTag.put("References", compoundTag3);
-        return compoundTag;
+        nbttagcompound.put("References", nbttagcompound2);
+        return nbttagcompound;
     }
 
     private static Map<Structure, StructureStart> unpackStructureStart(StructurePieceSerializationContext context, CompoundTag nbt, long worldSeed) {
         Map<Structure, StructureStart> map = Maps.newHashMap();
-        Registry<Structure> registry = context.registryAccess().registryOrThrow(Registries.STRUCTURE);
-        CompoundTag compoundTag = nbt.getCompound("starts");
+        Registry<Structure> iregistry = context.registryAccess().registryOrThrow(Registries.STRUCTURE);
+        CompoundTag nbttagcompound1 = nbt.getCompound("starts");
+        Iterator iterator = nbttagcompound1.getAllKeys().iterator();
+
+        while (iterator.hasNext()) {
+            String s = (String) iterator.next();
+            ResourceLocation minecraftkey = ResourceLocation.tryParse(s);
+            Structure structure = (Structure) iregistry.get(minecraftkey);
 
-        for(String string : compoundTag.getAllKeys()) {
-            ResourceLocation resourceLocation = ResourceLocation.tryParse(string);
-            Structure structure = registry.get(resourceLocation);
             if (structure == null) {
-                LOGGER.error("Unknown structure start: {}", (Object)resourceLocation);
+                ChunkSerializer.LOGGER.error("Unknown structure start: {}", minecraftkey);
             } else {
-                StructureStart structureStart = StructureStart.loadStaticStart(context, compoundTag.getCompound(string), worldSeed);
-                if (structureStart != null) {
-                    map.put(structure, structureStart);
+                StructureStart structurestart = StructureStart.loadStaticStart(context, nbttagcompound1.getCompound(s), worldSeed);
+
+                if (structurestart != null) {
+                    map.put(structure, structurestart);
                 }
             }
         }
@@ -452,21 +776,26 @@
 
     private static Map<Structure, LongSet> unpackStructureReferences(RegistryAccess registryManager, ChunkPos pos, CompoundTag nbt) {
         Map<Structure, LongSet> map = Maps.newHashMap();
-        Registry<Structure> registry = registryManager.registryOrThrow(Registries.STRUCTURE);
-        CompoundTag compoundTag = nbt.getCompound("References");
+        Registry<Structure> iregistry = registryManager.registryOrThrow(Registries.STRUCTURE);
+        CompoundTag nbttagcompound1 = nbt.getCompound("References");
+        Iterator iterator = nbttagcompound1.getAllKeys().iterator();
+
+        while (iterator.hasNext()) {
+            String s = (String) iterator.next();
+            ResourceLocation minecraftkey = ResourceLocation.tryParse(s);
+            Structure structure = (Structure) iregistry.get(minecraftkey);
 
-        for(String string : compoundTag.getAllKeys()) {
-            ResourceLocation resourceLocation = ResourceLocation.tryParse(string);
-            Structure structure = registry.get(resourceLocation);
             if (structure == null) {
-                LOGGER.warn("Found reference to unknown structure '{}' in chunk {}, discarding", resourceLocation, pos);
+                ChunkSerializer.LOGGER.warn("Found reference to unknown structure '{}' in chunk {}, discarding", minecraftkey, pos);
             } else {
-                long[] ls = compoundTag.getLongArray(string);
-                if (ls.length != 0) {
-                    map.put(structure, new LongOpenHashSet(Arrays.stream(ls).filter((packedPos) -> {
-                        ChunkPos chunkPos2 = new ChunkPos(packedPos);
-                        if (chunkPos2.getChessboardDistance(pos) > 8) {
-                            LOGGER.warn("Found invalid structure reference [ {} @ {} ] for chunk {}.", resourceLocation, chunkPos2, pos);
+                long[] along = nbttagcompound1.getLongArray(s);
+
+                if (along.length != 0) {
+                    map.put(structure, new LongOpenHashSet(Arrays.stream(along).filter((i) -> {
+                        ChunkPos chunkcoordintpair1 = new ChunkPos(i);
+
+                        if (chunkcoordintpair1.getChessboardDistance(pos) > 8) {
+                            ChunkSerializer.LOGGER.warn("Found invalid structure reference [ {} @ {} ] for chunk {}.", new Object[]{minecraftkey, chunkcoordintpair1, pos});
                             return false;
                         } else {
                             return true;
@@ -480,19 +809,27 @@
     }
 
     public static ListTag packOffsets(ShortList[] lists) {
-        ListTag listTag = new ListTag();
+        ListTag nbttaglist = new ListTag();
+        ShortList[] ashortlist1 = lists;
+        int i = lists.length;
 
-        for(ShortList shortList : lists) {
-            ListTag listTag2 = new ListTag();
-            if (shortList != null) {
-                for(Short short_ : shortList) {
-                    listTag2.add(ShortTag.valueOf(short_));
+        for (int j = 0; j < i; ++j) {
+            ShortList shortlist = ashortlist1[j];
+            ListTag nbttaglist1 = new ListTag();
+
+            if (shortlist != null) {
+                ShortListIterator shortlistiterator = shortlist.iterator();
+
+                while (shortlistiterator.hasNext()) {
+                    Short oshort = (Short) shortlistiterator.next();
+
+                    nbttaglist1.add(ShortTag.valueOf(oshort));
                 }
             }
 
-            listTag.add(listTag2);
+            nbttaglist.add(nbttaglist1);
         }
 
-        return listTag;
+        return nbttaglist;
     }
 }
