--- a/net/minecraft/world/level/chunk/LevelChunkSection.java
+++ b/net/minecraft/world/level/chunk/LevelChunkSection.java
@@ -15,40 +15,151 @@
 import net.minecraft.world.level.material.FluidState;
 
 public class LevelChunkSection {
+
     public static final int SECTION_WIDTH = 16;
     public static final int SECTION_HEIGHT = 16;
     public static final int SECTION_SIZE = 4096;
     public static final int BIOME_CONTAINER_BITS = 2;
     private final int bottomBlockY;
-    private short nonEmptyBlockCount;
+    short nonEmptyBlockCount; // Paper - package-private
     private short tickingBlockCount;
     private short tickingFluidCount;
     public final PalettedContainer<BlockState> states;
-    private PalettedContainerRO<Holder<Biome>> biomes;
+    // CraftBukkit start - read/write
+    private PalettedContainer<Holder<Biome>> biomes;
+    public final com.destroystokyo.paper.util.maplist.IBlockDataList tickingList = new com.destroystokyo.paper.util.maplist.IBlockDataList(); // Paper
 
-    public LevelChunkSection(int chunkPos, PalettedContainer<BlockState> blockStateContainer, PalettedContainerRO<Holder<Biome>> palettedContainerRO) {
-        this.bottomBlockY = getBottomBlockY(chunkPos);
-        this.states = blockStateContainer;
-        this.biomes = palettedContainerRO;
+    public LevelChunkSection(int i, PalettedContainer<BlockState> datapaletteblock, PalettedContainer<Holder<Biome>> palettedcontainerro) {
+        // CraftBukkit end
+        this.bottomBlockY = LevelChunkSection.getBottomBlockY(i);
+        this.states = datapaletteblock;
+        this.biomes = palettedcontainerro;
         this.recalcBlockCounts();
     }
 
-    public LevelChunkSection(int chunkPos, Registry<Biome> biomeRegistry) {
-        this.bottomBlockY = getBottomBlockY(chunkPos);
-        this.states = new PalettedContainer<>(Block.BLOCK_STATE_REGISTRY, Blocks.AIR.defaultBlockState(), PalettedContainer.Strategy.SECTION_STATES);
-        this.biomes = new PalettedContainer<>(biomeRegistry.asHolderIdMap(), biomeRegistry.getHolderOrThrow(Biomes.PLAINS), PalettedContainer.Strategy.SECTION_BIOMES);
+    // Paper start - Anti-Xray - Add parameters
+    @Deprecated @io.papermc.paper.annotation.DoNotUse public LevelChunkSection(int chunkPos, Registry<Biome> biomeRegistry) { this(chunkPos, biomeRegistry, null, null); }
+    public LevelChunkSection(int chunkPos, Registry<Biome> biomeRegistry, net.minecraft.world.level.ChunkPos pos, net.minecraft.world.level.Level level) {
+        // Paper end
+        this.bottomBlockY = LevelChunkSection.getBottomBlockY(chunkPos);
+        this.states = new PalettedContainer<>(Block.BLOCK_STATE_REGISTRY, Blocks.AIR.defaultBlockState(), PalettedContainer.Strategy.SECTION_STATES, level == null || level.chunkPacketBlockController == null ? null : level.chunkPacketBlockController.getPresetBlockStates(level, pos, this.bottomBlockY())); // Paper - Anti-Xray - Add preset block states
+        this.biomes = new PalettedContainer<>(biomeRegistry.asHolderIdMap(), biomeRegistry.getHolderOrThrow(Biomes.PLAINS), PalettedContainer.Strategy.SECTION_BIOMES, null); // Paper - Anti-Xray - Add preset biomes
+    }
+
+    // Paper start
+    protected int specialCollidingBlocks;
+    // blockIndex = x | (z << 4) | (y << 8)
+    private long[] knownBlockCollisionData;
+
+    private long[] initKnownDataField() {
+        return this.knownBlockCollisionData = new long[16 * 16 * 16 * 2 / Long.SIZE];
+    }
+
+    public final boolean hasSpecialCollidingBlocks() {
+        return this.specialCollidingBlocks != 0;
+    }
+
+    public static long getKnownBlockInfo(final int blockIndex, final long value) {
+        final int valueShift = (blockIndex & (Long.SIZE / 2 - 1));
+
+        return (value >>> (valueShift << 1)) & 0b11L;
+    }
+
+    public final long getKnownBlockInfo(final int blockIndex) {
+        if (this.knownBlockCollisionData == null) {
+            return 0L;
+        }
+
+        final int arrayIndex = (blockIndex >>> (6 - 1)); // blockIndex / (64/2)
+        final int valueShift = (blockIndex & (Long.SIZE / 2 - 1));
+
+        final long value = this.knownBlockCollisionData[arrayIndex];
+
+        return (value >>> (valueShift << 1)) & 0b11L;
+    }
+
+    // important detail: this returns 32 values, one for localZ = localZ & (~1) and one for localZ = localZ | 1
+    // the even localZ is the lower 32 bits, the odd is the upper 32 bits
+    public final long getKnownBlockInfoHorizontalRaw(final int localY, final int localZ) {
+        if (this.knownBlockCollisionData == null) {
+            return 0L;
+        }
+
+        final int horizontalIndex = (localZ << 4) | (localY << 8);
+        return this.knownBlockCollisionData[horizontalIndex >>> (6 - 1)];
+    }
+
+    private void initBlockCollisionData() {
+        this.specialCollidingBlocks = 0;
+        // In 1.18 all sections will be initialised, whether or not they have blocks (fucking stupid btw)
+        // This means we can't aggressively initialise the backing long[], or else memory usage will just skyrocket.
+        // So only init if we contain non-empty blocks.
+        if (this.nonEmptyBlockCount == 0) {
+            this.knownBlockCollisionData = null;
+            return;
+        }
+        this.initKnownDataField();
+        for (int index = 0; index < (16 * 16 * 16); ++index) {
+            final BlockState state = this.states.get(index);
+            this.setKnownBlockInfo(index, state);
+            if (io.papermc.paper.util.CollisionUtil.isSpecialCollidingBlock(state)) {
+                ++this.specialCollidingBlocks;
+            }
+        }
+    }
+
+    // only use for initBlockCollisionData
+    private void setKnownBlockInfo(final int blockIndex, final BlockState blockState) {
+        final int arrayIndex = (blockIndex >>> (6 - 1)); // blockIndex / (64/2)
+        final int valueShift = (blockIndex & (Long.SIZE / 2 - 1)) << 1;
+
+        long value = this.knownBlockCollisionData[arrayIndex];
+
+        value &= ~(0b11L << valueShift);
+        value |= blockState.getBlockCollisionBehavior() << valueShift;
+
+        this.knownBlockCollisionData[arrayIndex] = value;
+    }
+
+    public void updateKnownBlockInfo(final int blockIndex, final BlockState from, final BlockState to) {
+        if (io.papermc.paper.util.CollisionUtil.isSpecialCollidingBlock(from)) {
+            --this.specialCollidingBlocks;
+        }
+        if (io.papermc.paper.util.CollisionUtil.isSpecialCollidingBlock(to)) {
+            ++this.specialCollidingBlocks;
+        }
+
+        if (this.nonEmptyBlockCount == 0) {
+            this.knownBlockCollisionData = null;
+            return;
+        }
+
+        if (this.knownBlockCollisionData == null) {
+            this.initKnownDataField();
+        }
+
+        final int arrayIndex = (blockIndex >>> (6 - 1)); // blockIndex / (64/2)
+        final int valueShift = (blockIndex & (Long.SIZE / 2 - 1)) << 1;
+
+        long value = this.knownBlockCollisionData[arrayIndex];
+
+        value &= ~(0b11L << valueShift);
+        value |= to.getBlockCollisionBehavior() << valueShift;
+
+        this.knownBlockCollisionData[arrayIndex] = value;
     }
+    // Paper end
 
     public static int getBottomBlockY(int chunkPos) {
         return chunkPos << 4;
     }
 
     public BlockState getBlockState(int x, int y, int z) {
-        return this.states.get(x, y, z);
+        return (BlockState) this.states.get(x, y, z);
     }
 
     public FluidState getFluidState(int x, int y, int z) {
-        return this.states.get(x, y, z).getFluidState();
+        return this.states.get(x, y, z).getFluidState(); // Paper - diff on change - we expect this to be effectively just getType(x, y, z).getFluid(). If this changes we need to check other patches that use IBlockData#getFluid.
     }
 
     public void acquire() {
@@ -63,24 +174,29 @@
         return this.setBlockState(x, y, z, state, true);
     }
 
-    public BlockState setBlockState(int x, int y, int z, BlockState state, boolean lock) {
-        BlockState blockState;
+    public BlockState setBlockState(int x, int y, int z, BlockState state, boolean lock) {  // Paper - state -> new state
+        BlockState iblockdata1; // Paper - iblockdata1 -> oldState
+
         if (lock) {
-            blockState = this.states.getAndSet(x, y, z, state);
+            iblockdata1 = (BlockState) this.states.getAndSet(x, y, z, state);
         } else {
-            blockState = this.states.getAndSetUnchecked(x, y, z, state);
+            iblockdata1 = (BlockState) this.states.getAndSetUnchecked(x, y, z, state);
         }
 
-        FluidState fluidState = blockState.getFluidState();
-        FluidState fluidState2 = state.getFluidState();
-        if (!blockState.isAir()) {
+        FluidState fluid = iblockdata1.getFluidState();
+        FluidState fluid1 = state.getFluidState();
+
+        if (!iblockdata1.isAir()) {
             --this.nonEmptyBlockCount;
-            if (blockState.isRandomlyTicking()) {
+            if (iblockdata1.isRandomlyTicking()) {
                 --this.tickingBlockCount;
+                // Paper start
+                this.tickingList.remove(x, y, z);
+                // Paper end
             }
         }
 
-        if (!fluidState.isEmpty()) {
+        if (!fluid.isEmpty()) {
             --this.tickingFluidCount;
         }
 
@@ -88,14 +204,18 @@
             ++this.nonEmptyBlockCount;
             if (state.isRandomlyTicking()) {
                 ++this.tickingBlockCount;
+                // Paper start
+                this.tickingList.add(x, y, z, state);
+                // Paper end
             }
         }
 
-        if (!fluidState2.isEmpty()) {
+        if (!fluid1.isEmpty()) {
             ++this.tickingFluidCount;
         }
 
-        return blockState;
+        this.updateKnownBlockInfo(x | (z << 4) | (y << 8), iblockdata1, state); // Paper
+        return iblockdata1;
     }
 
     public boolean hasOnlyAir() {
@@ -119,36 +239,32 @@
     }
 
     public void recalcBlockCounts() {
-        class BlockCounter implements PalettedContainer.CountConsumer<BlockState> {
-            public int nonEmptyBlockCount;
-            public int tickingBlockCount;
-            public int tickingFluidCount;
+        // Paper start - unfuck this
+        this.tickingList.clear();
+        this.nonEmptyBlockCount = 0;
+        this.tickingBlockCount = 0;
+        this.tickingFluidCount = 0;
+        this.states.forEachLocation((BlockState iblockdata, int i) -> {
+            FluidState fluid = iblockdata.getFluidState();
 
-            @Override
-            public void accept(BlockState blockState, int i) {
-                FluidState fluidState = blockState.getFluidState();
-                if (!blockState.isAir()) {
-                    this.nonEmptyBlockCount += i;
-                    if (blockState.isRandomlyTicking()) {
-                        this.tickingBlockCount += i;
-                    }
+            if (!iblockdata.isAir()) {
+                this.nonEmptyBlockCount = (short) (this.nonEmptyBlockCount + 1);
+                if (iblockdata.isRandomlyTicking()) {
+                    this.tickingBlockCount = (short)(this.tickingBlockCount + 1);
+                    this.tickingList.add(i, iblockdata);
                 }
+            }
 
-                if (!fluidState.isEmpty()) {
-                    this.nonEmptyBlockCount += i;
-                    if (fluidState.isRandomlyTicking()) {
-                        this.tickingFluidCount += i;
-                    }
+            if (!fluid.isEmpty()) {
+                this.nonEmptyBlockCount = (short) (this.nonEmptyBlockCount + 1);
+                if (fluid.isRandomlyTicking()) {
+                    this.tickingFluidCount = (short) (this.tickingFluidCount + 1);
                 }
-
             }
-        }
 
-        BlockCounter lv = new BlockCounter();
-        this.states.count(lv);
-        this.nonEmptyBlockCount = (short)lv.nonEmptyBlockCount;
-        this.tickingBlockCount = (short)lv.tickingBlockCount;
-        this.tickingFluidCount = (short)lv.tickingFluidCount;
+        });
+        // Paper end
+        this.initBlockCollisionData(); // Paper
     }
 
     public PalettedContainer<BlockState> getStates() {
@@ -162,15 +278,19 @@
     public void read(FriendlyByteBuf buf) {
         this.nonEmptyBlockCount = buf.readShort();
         this.states.read(buf);
-        PalettedContainer<Holder<Biome>> palettedContainer = this.biomes.recreate();
-        palettedContainer.read(buf);
-        this.biomes = palettedContainer;
+        PalettedContainer<Holder<Biome>> datapaletteblock = this.biomes.recreate();
+
+        datapaletteblock.read(buf);
+        this.biomes = datapaletteblock;
     }
 
-    public void write(FriendlyByteBuf buf) {
+    // Paper start - Anti-Xray - Add chunk packet info
+    @Deprecated @io.papermc.paper.annotation.DoNotUse public void write(FriendlyByteBuf buf) { this.write(buf, null); }
+    public void write(FriendlyByteBuf buf, com.destroystokyo.paper.antixray.ChunkPacketInfo<BlockState> chunkPacketInfo) {
         buf.writeShort(this.nonEmptyBlockCount);
-        this.states.write(buf);
-        this.biomes.write(buf);
+        this.states.write(buf, chunkPacketInfo, this.bottomBlockY());
+        this.biomes.write(buf, null, this.bottomBlockY());
+        // Paper end
     }
 
     public int getSerializedSize() {
@@ -182,22 +302,28 @@
     }
 
     public Holder<Biome> getNoiseBiome(int x, int y, int z) {
-        return this.biomes.get(x, y, z);
+        return (Holder) this.biomes.get(x, y, z);
+    }
+
+    // CraftBukkit start
+    public void setBiome(int i, int j, int k, Holder<Biome> biome) {
+        this.biomes.set(i, j, k, biome);
     }
+    // CraftBukkit end
 
     public void fillBiomesFromNoise(BiomeResolver biomeSupplier, Climate.Sampler sampler, int x, int z) {
-        PalettedContainer<Holder<Biome>> palettedContainer = this.biomes.recreate();
-        int i = QuartPos.fromBlock(this.bottomBlockY());
-        int j = 4;
+        PalettedContainer<Holder<Biome>> datapaletteblock = this.biomes.recreate();
+        int k = QuartPos.fromBlock(this.bottomBlockY());
+        boolean flag = true;
 
-        for(int k = 0; k < 4; ++k) {
-            for(int l = 0; l < 4; ++l) {
-                for(int m = 0; m < 4; ++m) {
-                    palettedContainer.getAndSetUnchecked(k, l, m, biomeSupplier.getNoiseBiome(x + k, i + l, z + m, sampler));
+        for (int l = 0; l < 4; ++l) {
+            for (int i1 = 0; i1 < 4; ++i1) {
+                for (int j1 = 0; j1 < 4; ++j1) {
+                    datapaletteblock.getAndSetUnchecked(l, i1, j1, biomeSupplier.getNoiseBiome(x + l, k + i1, z + j1, sampler));
                 }
             }
         }
 
-        this.biomes = palettedContainer;
+        this.biomes = datapaletteblock;
     }
 }
