diff --git a/game/client/src/main/java/net/minecraft/client/Minecraft.java b/game/client/src/main/java/net/minecraft/client/Minecraft.java
index 51ef4cb84..e0e7d0029 100644
--- a/game/client/src/main/java/net/minecraft/client/Minecraft.java
+++ b/game/client/src/main/java/net/minecraft/client/Minecraft.java
@@ -2568,7 +2568,7 @@ public class Minecraft
             chunkcoordinates.x = (int)thePlayer.x;
             chunkcoordinates.z = (int)thePlayer.z;
         }
-            ichunkprovider.setCurrentChunkOver(Math.floorDiv(chunkcoordinates.x, Chunk.CHUNK_SIZE_X), Math.floorDiv(chunkcoordinates.z, Chunk.CHUNK_SIZE_Z));
+        ichunkprovider.setCurrentChunkOver(Math.floorDiv(chunkcoordinates.x, Chunk.CHUNK_SIZE_X), Math.floorDiv(chunkcoordinates.z, Chunk.CHUNK_SIZE_Z));
         for(int k = -c; k <= c; k += Chunk.CHUNK_SIZE_X)
         {
             for(int l = -c; l <= c; l += Chunk.CHUNK_SIZE_Z)
diff --git a/game/client/src/main/java/net/minecraft/client/gui/ScreenConnecting.java b/game/client/src/main/java/net/minecraft/client/gui/ScreenConnecting.java
index 9bc0a89d1..bb9cadba5 100644
--- a/game/client/src/main/java/net/minecraft/client/gui/ScreenConnecting.java
+++ b/game/client/src/main/java/net/minecraft/client/gui/ScreenConnecting.java
@@ -105,7 +105,7 @@ public class ScreenConnecting extends Screen
     {
         renderBackground();
         I18n stringtranslate = I18n.getInstance();
-        if(clientHandler == null)
+        if(clientHandler == null || clientHandler.message == null)
         {
             drawStringCenteredShadow(fontRenderer, stringtranslate.translateKey("gui.connecting.label.connecting"), width / 2, height / 2 - 50, 0xffffff);
             drawStringCenteredShadow(fontRenderer, "", width / 2, height / 2 - 10, 0xffffff);
diff --git a/game/client/src/main/java/net/minecraft/client/gui/modelviewer/ScreenModelViewer.java b/game/client/src/main/java/net/minecraft/client/gui/modelviewer/ScreenModelViewer.java
index fc583f7aa..9c57be8e3 100644
--- a/game/client/src/main/java/net/minecraft/client/gui/modelviewer/ScreenModelViewer.java
+++ b/game/client/src/main/java/net/minecraft/client/gui/modelviewer/ScreenModelViewer.java
@@ -85,8 +85,8 @@ public class ScreenModelViewer extends ScreenPhotoMode implements ListenerTextFi
     public ScreenModelViewer(){
         DisplayChunk.displayBlockId = 0;
         DisplayChunk.displayBlockMeta = 0;
-        mc.currentWorld.loadedEntityList.clear();
-        mc.currentWorld.loadedTileEntityList.clear();
+        mc.currentWorld.entities.clear();
+        mc.currentWorld.tileEntityList.clear();
         mc.thePlayer.absMoveTo(0.5, 64.5, 0.5, 180, 0);
         mc.thePlayer.noPhysics = true;
         mc.textureManager.refreshTexturesAndDisplayErrors();
@@ -331,8 +331,8 @@ public class ScreenModelViewer extends ScreenPhotoMode implements ListenerTextFi
         }
         if (this.currentCategory == blockCategory && !blockCategoryBulkRenders.isEmpty()) {
             lastInfo = blockCategoryBulkRenders.remove(0);
-            mc.currentWorld.loadedEntityList.clear();
-            mc.currentWorld.loadedTileEntityList.clear();
+            mc.currentWorld.entities.clear();
+            mc.currentWorld.tileEntityList.clear();
             blockCategory.setupRender(lastInfo);
             iRotation = iRotationGoal = lastInfo.renderRotation();
             iZoom = iZoomGoal = lastInfo.renderZoom();
diff --git a/game/client/src/main/java/net/minecraft/client/gui/modelviewer/categories/ViewerCategoryBlock.java b/game/client/src/main/java/net/minecraft/client/gui/modelviewer/categories/ViewerCategoryBlock.java
index 0be3ae05f..f2dabc63c 100644
--- a/game/client/src/main/java/net/minecraft/client/gui/modelviewer/categories/ViewerCategoryBlock.java
+++ b/game/client/src/main/java/net/minecraft/client/gui/modelviewer/categories/ViewerCategoryBlock.java
@@ -1,6 +1,5 @@
 package net.minecraft.client.gui.modelviewer.categories;
 
-import net.minecraft.client.Minecraft;
 import net.minecraft.client.gui.ButtonElement;
 import net.minecraft.client.gui.modelviewer.ScreenModelViewer;
 import net.minecraft.client.gui.modelviewer.categories.entries.block.BlockEntryBasic;
@@ -190,7 +189,7 @@ public class ViewerCategoryBlock extends ModelViewerCategory<ViewerCategoryBlock
             entry.onTick(mc.currentWorld, DisplayChunk.displayBlockMeta);
         }
         // Delete any items blocks create when added/removed
-        mc.currentWorld.loadedEntityList.removeIf(entity -> entity instanceof EntityItem);
+        mc.currentWorld.entities.removeIf(entity -> entity instanceof EntityItem);
     }
 
     @Override
diff --git a/game/client/src/main/java/net/minecraft/client/gui/modelviewer/categories/ViewerCategoryEntity.java b/game/client/src/main/java/net/minecraft/client/gui/modelviewer/categories/ViewerCategoryEntity.java
index 865e141e0..ad43af76d 100644
--- a/game/client/src/main/java/net/minecraft/client/gui/modelviewer/categories/ViewerCategoryEntity.java
+++ b/game/client/src/main/java/net/minecraft/client/gui/modelviewer/categories/ViewerCategoryEntity.java
@@ -210,7 +210,7 @@ public class ViewerCategoryEntity extends ModelViewerCategory<ModelViewerCategor
     public void removeDisplayEntity(){
         if (displayEntity != null){
             displayEntity.remove();
-            mc.currentWorld.loadedEntityList.remove(displayEntity);
+            mc.currentWorld.entities.remove(displayEntity);
         }
         mc.particleEngine.setWorld(mc.currentWorld);
     }
diff --git a/game/client/src/main/java/net/minecraft/client/render/block/model/BlockModelFence.java b/game/client/src/main/java/net/minecraft/client/render/block/model/BlockModelFence.java
index 1e62ca44f..9c84b7a54 100644
--- a/game/client/src/main/java/net/minecraft/client/render/block/model/BlockModelFence.java
+++ b/game/client/src/main/java/net/minecraft/client/render/block/model/BlockModelFence.java
@@ -28,24 +28,24 @@ public class BlockModelFence<T  extends BlockLogicFence> extends BlockModelStand
         final float f1 = 0.625F;
         AABB bounds = AABB.fromPool(f, 0.0F, f, f1, 1.0F, f1);
         renderBlocks.renderStandardBlock(tessellator, worldSource, this, bounds, x, y, z);
-        boolean flag1 = false;
-        boolean flag2 = false;
+        boolean connectedWestEast = false;
+        boolean connectedNorthSouth = false;
         BlockLogicFence fence = block.getLogic();
-        if(fence.canConnectTo(worldSource, x - 1, y, z) || fence.canConnectTo(worldSource, x + 1, y, z))
+        if(fence.canConnectTo(worldSource, x, y, z, Side.WEST) || fence.canConnectTo(worldSource, x, y, z, Side.EAST))
         {
-            flag1 = true;
+            connectedWestEast = true;
         }
-        if(fence.canConnectTo(worldSource, x, y, z - 1) || fence.canConnectTo(worldSource, x, y, z + 1))
+        if(fence.canConnectTo(worldSource, x, y, z, Side.NORTH) || fence.canConnectTo(worldSource, x, y, z, Side.SOUTH))
         {
-            flag2 = true;
+            connectedNorthSouth = true;
         }
-        boolean flag3 = fence.canConnectTo(worldSource, x - 1, y, z);
-        boolean flag4 = fence.canConnectTo(worldSource, x + 1, y, z);
-        boolean flag5 = fence.canConnectTo(worldSource, x, y, z - 1);
-        boolean flag6 = fence.canConnectTo(worldSource, x, y, z + 1);
-        if(!flag1 && !flag2)
+        boolean flag3 = fence.canConnectTo(worldSource, x, y, z, Side.WEST);
+        boolean flag4 = fence.canConnectTo(worldSource, x, y, z, Side.EAST);
+        boolean flag5 = fence.canConnectTo(worldSource, x, y, z, Side.NORTH);
+        boolean flag6 = fence.canConnectTo(worldSource, x, y, z, Side.SOUTH);
+        if(!connectedWestEast && !connectedNorthSouth)
         {
-            flag1 = true;
+            connectedWestEast = true;
         }
         final float f8 = 0.4375F;
         final float f9 = 0.5625F;
@@ -55,24 +55,24 @@ public class BlockModelFence<T  extends BlockLogicFence> extends BlockModelStand
         final float f5 = flag4 ? 1.0F : f9;
         final float f6 = flag5 ? 0.0F : f8;
         final float f7 = flag6 ? 1.0F : f9;
-        if(flag1)
+        if(connectedWestEast)
         {
             bounds.set(f4, f2, f8, f5, f3, f9);
             renderBlocks.renderStandardBlock(tessellator, worldSource, this, bounds, x, y, z);
         }
-        if(flag2)
+        if(connectedNorthSouth)
         {
             bounds.set(f8, f2, f6, f9, f3, f7);
             renderBlocks.renderStandardBlock(tessellator, worldSource, this, bounds, x, y, z);
         }
         final float f10 = 0.375F;
         final float f11 = 0.5625F;
-        if(flag1)
+        if(connectedWestEast)
         {
             bounds.set(f4, f10, f8, f5, f11, f9);
             renderBlocks.renderStandardBlock(tessellator, worldSource, this, bounds, x, y, z);
         }
-        if(flag2)
+        if(connectedNorthSouth)
         {
             bounds.set(f8, f10, f6, f9, f11, f7);
             renderBlocks.renderStandardBlock(tessellator, worldSource, this, bounds, x, y, z);
diff --git a/game/client/src/main/java/net/minecraft/client/render/block/model/BlockModelFenceThin.java b/game/client/src/main/java/net/minecraft/client/render/block/model/BlockModelFenceThin.java
index 282fc5263..1633f070d 100644
--- a/game/client/src/main/java/net/minecraft/client/render/block/model/BlockModelFenceThin.java
+++ b/game/client/src/main/java/net/minecraft/client/render/block/model/BlockModelFenceThin.java
@@ -7,6 +7,7 @@ import net.minecraft.client.render.tessellator.Tessellator;
 import net.minecraft.core.block.Block;
 import net.minecraft.core.block.BlockLogicFenceThin;
 import net.minecraft.core.util.helper.Direction;
+import net.minecraft.core.util.helper.Side;
 import net.minecraft.core.util.phys.AABB;
 import net.minecraft.core.world.WorldSource;
 import org.jetbrains.annotations.NotNull;
@@ -51,10 +52,10 @@ public class BlockModelFenceThin<T extends BlockLogicFenceThin> extends BlockMod
 			cEastWest = 0.6f;
 		}
         BlockLogicFenceThin thin = block.getLogic();
-        boolean connectNorth = thin.canConnectTo(worldSource, x + Direction.NORTH.getOffsetX(), y + Direction.NORTH.getOffsetY(), z + Direction.NORTH.getOffsetZ());
-        boolean connectSouth = thin.canConnectTo(worldSource, x + Direction.SOUTH.getOffsetX(), y + Direction.SOUTH.getOffsetY(), z + Direction.SOUTH.getOffsetZ());
-        boolean connectEast = thin.canConnectTo(worldSource, x + Direction.EAST.getOffsetX(), y + Direction.EAST.getOffsetY(), z + Direction.EAST.getOffsetZ());
-        boolean connectWest = thin.canConnectTo(worldSource, x + Direction.WEST.getOffsetX(), y + Direction.WEST.getOffsetY(), z + Direction.WEST.getOffsetZ());
+        boolean connectNorth = thin.canConnectTo(worldSource, x, y, z, Side.NORTH);
+        boolean connectSouth = thin.canConnectTo(worldSource, x, y, z, Side.SOUTH);
+        boolean connectEast = thin.canConnectTo(worldSource, x, y, z , Side.EAST);
+        boolean connectWest = thin.canConnectTo(worldSource, x, y, z , Side.WEST);
 
         boolean lineNorthSouth = connectNorth && connectSouth;
         boolean lineEastWest = connectEast && connectWest;
@@ -91,16 +92,16 @@ public class BlockModelFenceThin<T extends BlockLogicFenceThin> extends BlockMod
         double fenceBottommostMaxV = fenceBottommostTex.getSubIconV(onePix * 16);
 
         Block<?> blockAbove = worldSource.getBlock(x, y + 1, z);
-        boolean connectUpNorth = blockAbove == block && thin.canConnectTo(worldSource, x + Direction.NORTH.getOffsetX(), y + Direction.NORTH.getOffsetY() + 1, z + Direction.NORTH.getOffsetZ());
-        boolean connectUpSouth = blockAbove == block && thin.canConnectTo(worldSource, x + Direction.SOUTH.getOffsetX(), y + Direction.SOUTH.getOffsetY() + 1, z + Direction.SOUTH.getOffsetZ());
-        boolean connectUpEast = blockAbove == block && thin.canConnectTo(worldSource, x + Direction.EAST.getOffsetX(), y + Direction.EAST.getOffsetY() + 1, z + Direction.EAST.getOffsetZ());
-        boolean connectUpWest = blockAbove == block && thin.canConnectTo(worldSource, x + Direction.WEST.getOffsetX(), y + Direction.WEST.getOffsetY() + 1, z + Direction.WEST.getOffsetZ());
+        boolean connectUpNorth = blockAbove == block && thin.canConnectTo(worldSource, x, y + 1, z, Side.NORTH);
+        boolean connectUpSouth = blockAbove == block && thin.canConnectTo(worldSource, x, y + 1, z, Side.SOUTH);
+        boolean connectUpEast = blockAbove == block && thin.canConnectTo(worldSource, x, y + 1, z, Side.EAST);
+        boolean connectUpWest = blockAbove == block && thin.canConnectTo(worldSource, x, y + 1, z, Side.WEST);
 
         Block<?> blockBelow = worldSource.getBlock(x, y - 1, z);
-        boolean connectDownNorth = blockBelow == block && thin.canConnectTo(worldSource, x + Direction.NORTH.getOffsetX(), y + Direction.NORTH.getOffsetY() - 1, z + Direction.NORTH.getOffsetZ());
-        boolean connectDownSouth = blockBelow == block && thin.canConnectTo(worldSource, x + Direction.SOUTH.getOffsetX(), y + Direction.SOUTH.getOffsetY() - 1, z + Direction.SOUTH.getOffsetZ());
-        boolean connectDownEast = blockBelow == block && thin.canConnectTo(worldSource, x + Direction.EAST.getOffsetX(), y + Direction.EAST.getOffsetY() - 1, z + Direction.EAST.getOffsetZ());
-        boolean connectDownWest = blockBelow == block && thin.canConnectTo(worldSource, x + Direction.WEST.getOffsetX(), y + Direction.WEST.getOffsetY() - 1, z + Direction.WEST.getOffsetZ());
+        boolean connectDownNorth = blockBelow == block && thin.canConnectTo(worldSource, x, y - 1, z, Side.NORTH);
+        boolean connectDownSouth = blockBelow == block && thin.canConnectTo(worldSource, x, y - 1, z, Side.SOUTH);
+        boolean connectDownEast = blockBelow == block && thin.canConnectTo(worldSource, x, y - 1, z, Side.EAST);
+        boolean connectDownWest = blockBelow == block && thin.canConnectTo(worldSource, x, y - 1, z, Side.WEST);
 
         if (thin.shouldDrawColumn(worldSource, x, y, z))
         {
diff --git a/game/client/src/main/java/net/minecraft/client/render/block/model/BlockModelRail.java b/game/client/src/main/java/net/minecraft/client/render/block/model/BlockModelRail.java
index 1f1c2e9b0..a0cca0587 100644
--- a/game/client/src/main/java/net/minecraft/client/render/block/model/BlockModelRail.java
+++ b/game/client/src/main/java/net/minecraft/client/render/block/model/BlockModelRail.java
@@ -7,6 +7,7 @@ import net.minecraft.client.render.tessellator.Tessellator;
 import net.minecraft.core.block.Block;
 import net.minecraft.core.block.BlockLogicRail;
 import net.minecraft.core.block.Blocks;
+import net.minecraft.core.block.logic.RailDirection;
 import net.minecraft.core.enums.LightLayer;
 import net.minecraft.core.util.helper.Side;
 import net.minecraft.core.world.WorldSource;
@@ -30,16 +31,13 @@ public class BlockModelRail<T extends BlockLogicRail> extends BlockModelStandard
 
     @Override
     public boolean render(@NotNull Tessellator tessellator, @NotNull WorldSource worldSource, int x, int y, int z) {
-        int l = worldSource.getBlockMetadata(x, y, z);
-        IconCoordinate i1 = getBlockTextureFromSideAndMetadata(Side.BOTTOM, l);
+        int meta = worldSource.getBlockMetadata(x, y, z);
+        IconCoordinate icon = getBlockTextureFromSideAndMetadata(Side.BOTTOM, meta);
         if(renderBlocks.overrideBlockTexture != null)
         {
-            i1 = renderBlocks.overrideBlockTexture;
-        }
-        if(block.getLogic().getIsPowered())
-        {
-            l &= 7;
+            icon = renderBlocks.overrideBlockTexture;
         }
+        RailDirection railDirection = block.getLogic().getRailDirection(worldSource, x, y, z);
 		float brightness = 1.0f;
 		if(LightmapHelper.isLightmapEnabled()) {
 			if(renderBlocks.overbright) {
@@ -53,68 +51,72 @@ public class BlockModelRail<T extends BlockLogicRail> extends BlockModelStandard
 			}
 		}
 		if(renderBlocks.overbright) {
-			i1 = getBlockOverbrightTexture(worldSource, x, y, z, Side.BOTTOM);
-			if(i1 == null) {
-				return false;
-			}
+			icon = getBlockOverbrightTexture(worldSource, x, y, z, Side.BOTTOM);
 		}
+        if(icon == null) { return false; }
+
         tessellator.setColorOpaque_F(brightness, brightness, brightness);
-        double d = i1.getIconUMin();
-        double d1 = i1.getIconUMax();
-        double d2 = i1.getIconVMin();
-        double d3 = i1.getIconVMax();
-        float f1 = 0.0625F;
-        float f2 = x + 1;
-        float f3 = x + 1;
-        float f4 = x;
-        float f5 = x;
-        float f6 = z;
-        float f7 = z + 1;
-        float f8 = z + 1;
-        float f9 = z;
-        float f10 = (float)y + f1;
-        float f11 = (float)y + f1;
-        float f12 = (float)y + f1;
-        float f13 = (float)y + f1;
-        if(l == 1 || l == 2 || l == 3 || l == 7)
-        {
-            f2 = f5 = x + 1;
-            f3 = f4 = x;
-            f6 = f7 = z + 1;
-            f8 = f9 = z;
-        } else
-        if(l == 8)
-        {
-            f2 = f3 = x;
-            f4 = f5 = x + 1;
-            f6 = f9 = z + 1;
-            f7 = f8 = z;
-        } else
-        if(l == 9)
-        {
-            f2 = f5 = x;
-            f3 = f4 = x + 1;
-            f6 = f7 = z;
-            f8 = f9 = z + 1;
+        final double minU = icon.getIconUMin();
+        final double maxU = icon.getIconUMax();
+        final double minV = icon.getIconVMin();
+        final double maxV = icon.getIconVMax();
+        final float yOffset = 1 / 16f; // Offset from the ground
+        float vX1 = x + 1;
+        float vX2 = x + 1;
+        float vX3 = x;
+        float vX4 = x;
+        float vZ1 = z;
+        float vZ2 = z + 1;
+        float vZ3 = z + 1;
+        float vZ4 = z;
+        float vY1 = (float)y + yOffset;
+        float vY2 = (float)y + yOffset;
+        float vY3 = (float)y + yOffset;
+        float vY4 = (float)y + yOffset;
+        switch (railDirection) {
+            case STRAIGHT_EW:
+            case SLOPE_E:
+            case SLOPE_W:
+            case TURN_WS:
+                vX1 = vX4 = x + 1;
+                vX2 = vX3 = x;
+                vZ1 = vZ2 = z + 1;
+                vZ3 = vZ4 = z;
+                break;
+            case TURN_WN:
+                vX1 = vX2 = x;
+                vX3 = vX4 = x + 1;
+                vZ1 = vZ4 = z + 1;
+                vZ2 = vZ3 = z;
+                break;
+            case TURN_EN:
+                vX1 = vX4 = x;
+                vX2 = vX3 = x + 1;
+                vZ1 = vZ2 = z;
+                vZ3 = vZ4 = z + 1;
+                break;
         }
-        if(l == 2 || l == 4)
-        {
-            f10++;
-            f13++;
-        } else
-        if(l == 3 || l == 5)
-        {
-            f11++;
-            f12++;
+        switch (railDirection) {
+            case SLOPE_E:
+            case SLOPE_N:
+                vY1 += 1;
+                vY4 += 1;
+                break;
+            case SLOPE_W:
+            case SLOPE_S:
+                vY2 += 1;
+                vY3 += 1;
+                break;
         }
-        tessellator.addVertexWithUV(f2, f10, f6, d1, d2);
-        tessellator.addVertexWithUV(f3, f11, f7, d1, d3);
-        tessellator.addVertexWithUV(f4, f12, f8, d, d3);
-        tessellator.addVertexWithUV(f5, f13, f9, d, d2);
-        tessellator.addVertexWithUV(f5, f13, f9, d, d2);
-        tessellator.addVertexWithUV(f4, f12, f8, d, d3);
-        tessellator.addVertexWithUV(f3, f11, f7, d1, d3);
-        tessellator.addVertexWithUV(f2, f10, f6, d1, d2);
+        tessellator.addVertexWithUV(vX1, vY1, vZ1, maxU, minV);
+        tessellator.addVertexWithUV(vX2, vY2, vZ2, maxU, maxV);
+        tessellator.addVertexWithUV(vX3, vY3, vZ3, minU, maxV);
+        tessellator.addVertexWithUV(vX4, vY4, vZ4, minU, minV);
+
+        tessellator.addVertexWithUV(vX4, vY4, vZ4, minU, minV);
+        tessellator.addVertexWithUV(vX3, vY3, vZ3, minU, maxV);
+        tessellator.addVertexWithUV(vX2, vY2, vZ2, maxU, maxV);
+        tessellator.addVertexWithUV(vX1, vY1, vZ1, maxU, minV);
         return true;
     }
     @Override
@@ -124,7 +126,7 @@ public class BlockModelRail<T extends BlockLogicRail> extends BlockModelStandard
     @Override
     public @Nullable IconCoordinate getBlockOverbrightTexture(WorldSource blockAccess, int x, int y, int z, @NotNull Side side)
     {
-        if (!block.getLogic().isPowered || (blockAccess.getBlockMetadata(x, y, z) & 8) == 0) return null;
+        if (!block.getLogic().isPowered || (blockAccess.getBlockMetadata(x, y, z) & BlockLogicRail.MASK_POWERED) == 0) return null;
 
         return block == Blocks.RAIL_POWERED ? powerActiveOverlay : detectorActiveOverlay;
     }
@@ -132,7 +134,7 @@ public class BlockModelRail<T extends BlockLogicRail> extends BlockModelStandard
     @Override
     public @Nullable IconCoordinate getBlockTextureFromSideAndMetadata(Side side, int data)
     {
-        if(block == Blocks.RAIL_POWERED && (data & 8) != 0)
+        if(block == Blocks.RAIL_POWERED && (data & BlockLogicRail.MASK_POWERED) != 0)
         {
             return powerActive;
         } else if(!block.getLogic().isPowered && data >= 6)
diff --git a/game/client/src/main/java/net/minecraft/client/render/block/model/BlockModelRope.java b/game/client/src/main/java/net/minecraft/client/render/block/model/BlockModelRope.java
index 0b8234b41..83f489b63 100644
--- a/game/client/src/main/java/net/minecraft/client/render/block/model/BlockModelRope.java
+++ b/game/client/src/main/java/net/minecraft/client/render/block/model/BlockModelRope.java
@@ -2,6 +2,7 @@ package net.minecraft.client.render.block.model;
 
 import net.minecraft.client.render.tessellator.Tessellator;
 import net.minecraft.core.block.Block;
+import net.minecraft.core.block.BlockLogicJarButterfly;
 import net.minecraft.core.block.BlockLogicLanternFirefly;
 import net.minecraft.core.block.BlockLogicRope;
 import net.minecraft.core.block.tag.BlockTags;
@@ -89,9 +90,11 @@ extends BlockModelStandard<T> {
         int blockY = y + side.getOffsetY();
         int blockZ = z + side.getOffsetZ();
         Block<?> _block = worldSource.getBlock(blockX, blockY, blockZ);
-        if (side == Side.BOTTOM){
+        if (side == Side.BOTTOM){ // TODO replace with a proper system for querying what block another block is "attached" to
             if (Block.hasLogicClass(_block, BlockLogicLanternFirefly.class) && (worldSource.getBlockMetadata(blockX, blockY, blockZ) & 0b1) != 0){
                 return true;
+            } else if (Block.hasLogicClass(_block, BlockLogicJarButterfly.class) && (worldSource.getBlockMetadata(blockX, blockY, blockZ) & 0b1) != 0) {
+                return true;
             }
         } else if (side == Side.TOP) {
             if (worldSource.isBlockOpaqueCube(blockX, blockY, blockZ) || BlockTags.CAN_HANG_OFF.appliesTo(_block)) return true;
diff --git a/game/client/src/main/java/net/minecraft/client/world/WorldClientMP.java b/game/client/src/main/java/net/minecraft/client/world/WorldClientMP.java
index c0f4a482f..982700b31 100644
--- a/game/client/src/main/java/net/minecraft/client/world/WorldClientMP.java
+++ b/game/client/src/main/java/net/minecraft/client/world/WorldClientMP.java
@@ -1,6 +1,5 @@
 package net.minecraft.client.world;
 
-import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap;
 import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
 import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
 import net.minecraft.client.net.handler.PacketHandlerClient;
@@ -47,7 +46,7 @@ public class WorldClientMP extends WorldClient {
         for(int k = 0; k < 10 && !reEntries.isEmpty(); k++)
         {
             Entity entity = reEntries.iterator().next();
-            if(!loadedEntityList.contains(entity))
+            if(!entities.contains(entity))
             {
                 entityJoinedWorld(entity);
             }
@@ -107,12 +106,12 @@ public class WorldClientMP extends WorldClient {
     }
 
     @Override
-	public void scheduleBlockUpdate(int x, int y, int z, int id, int delay)
+	public void scheduleBlockUpdate(int x, int y, int z, int id, long delay)
     {
     }
 
     @Override
-	public boolean tickUpdates(boolean all)
+	public boolean tickPendingTicks(boolean all)
     {
         return false;
     }
diff --git a/game/core/src/main/java/net/minecraft/core/Global.java b/game/core/src/main/java/net/minecraft/core/Global.java
index 7223bf108..0c8c58c74 100644
--- a/game/core/src/main/java/net/minecraft/core/Global.java
+++ b/game/core/src/main/java/net/minecraft/core/Global.java
@@ -8,7 +8,7 @@ public class Global
 	public static final long BUILD_TIME = Version.BUILD_TIME;
 	public static final BuildChannel BUILD_CHANNEL = Version.BUILD_CHANNEL;
 	public static final int CURRENT_SAVE_VERSION = 19134;
-	public static final int CURRENT_CHUNK_VERSION = 2;
+	public static final int CURRENT_CHUNK_VERSION = 3;
 	public static boolean isServer = false;
 	public static final boolean DISABLE_OLD_WORLDS = false;
 	public static final int ASSET_PACK_FORMAT = Version.ASSET_PACK_FORMAT;
diff --git a/game/core/src/main/java/net/minecraft/core/NextTickListEntry.java b/game/core/src/main/java/net/minecraft/core/NextTickListEntry.java
index 58a8b6c2b..8ff26b7d8 100644
--- a/game/core/src/main/java/net/minecraft/core/NextTickListEntry.java
+++ b/game/core/src/main/java/net/minecraft/core/NextTickListEntry.java
@@ -1,66 +1,131 @@
 package net.minecraft.core;
 
 
+import com.mojang.nbt.tags.CompoundTag;
+import net.minecraft.core.world.chunk.Chunk;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
 public class NextTickListEntry
-    implements Comparable<NextTickListEntry>
-{
+    implements Comparable<NextTickListEntry> {
+
+    public static @NotNull CompoundTag serialize(final @NotNull NextTickListEntry entry) {
+        final @NotNull CompoundTag tag = new CompoundTag();
+
+        tag.putInt("pos", compressChunkCoord((short) entry.x, (short) entry.y, (short) entry.z));
+        tag.putShort("blockId", (short) entry.blockId);
+        tag.putLong("creationTime", entry.creationTime);
+        if (entry.delay != 0) {
+            tag.putLong("delay", entry.delay);
+        }
+
+        return tag;
+    }
+
+    public static @Nullable NextTickListEntry deserialize(final @NotNull CompoundTag tag) {
+        if (!tag.containsKey("pos")) {
+            return null;
+        }
+        final int pos = tag.getInteger("pos");
+
+        final short @NotNull [] xyz = new short[3];
+        decompressChunkCoord(pos, xyz);
+
+        if (!tag.containsKey("blockId")) {
+            return null;
+        }
+        final short blockId = tag.getShort("blockId");
+
+        if (!tag.containsKey("creationTime")) {
+            return null;
+        }
+        final long creationTime = tag.getLong("creationTime");
+
+        final @NotNull NextTickListEntry entry = new NextTickListEntry(xyz[0], xyz[1], xyz[2], blockId, creationTime);
+
+        if (tag.containsKey("delay")) {
+            entry.setDelay(tag.getLong("delay"));
+        }
+
+        return entry;
+    }
+
+    public static int compressChunkCoord(final short x, final short y, final short z) {
+        return ((y & 0xFFFF) << 16) | ((z & 0xFF) << 8) | ((x & 0xFF) << 0);
+    }
+
+    public static void decompressChunkCoord(final int compressed, final short @NotNull [] xyz) {
+        final int y = (compressed & 0xFFFF0000) >> 16;
+        final int z = (compressed & 0x0000FF00) >> 8;
+        final int x = (compressed & 0x000000FF) >> 0;
+
+        xyz[0] = (short) x;
+        xyz[1] = (short) y;
+        xyz[2] = (short) z;
+    }
+
     private static long counter = 0L;
+
     public int x;
     public int y;
     public int z;
     public int blockId;
+    public long creationTime;
     public long delay;
+    public boolean loaded;
     private final long entryId;
 
-    public NextTickListEntry(int x, int y, int z, int blockId)
-    {
-        entryId = counter++;
+    public NextTickListEntry(final int x, final int y, final int z, final int blockId, final long creationTime) {
+        this.entryId = counter++;
         this.x = x;
         this.y = y;
         this.z = z;
         this.blockId = blockId;
+        this.creationTime = creationTime;
     }
 
     @Override
-	public boolean equals(Object obj)
-    {
-        if(obj instanceof NextTickListEntry)
-        {
-            NextTickListEntry nextticklistentry = (NextTickListEntry)obj;
-            return x == nextticklistentry.x && y == nextticklistentry.y && z == nextticklistentry.z && blockId == nextticklistentry.blockId;
-        }
-        else
-        {
+    public boolean equals(final @Nullable Object obj) {
+        if (obj instanceof NextTickListEntry) {
+            final @NotNull NextTickListEntry entry = (NextTickListEntry) obj;
+            return this.x == entry.x && this.y == entry.y && this.z == entry.z && this.blockId == entry.blockId;
+        } else {
             return false;
         }
     }
 
     @Override
-	public int hashCode()
-    {
-        return (x * 128 * 1024 + z * 128 + y) * 256 + blockId;
+    public int hashCode() {
+        return (this.x * 128 * 1024 + this.z * 128 + this.y) * 256 + this.blockId;
     }
 
-    public NextTickListEntry setDelay(long delay)
-    {
+    public @NotNull NextTickListEntry setDelay(final long delay) {
         this.delay = delay;
         return this;
     }
 
+    public long getEndTime() {
+        return this.creationTime + this.delay;
+    }
+
     @Override
-	public int compareTo(NextTickListEntry other) {
-        if(delay < other.delay)
-        {
+    public int compareTo(final @NotNull NextTickListEntry other) {
+        if (this.getEndTime() < other.getEndTime()) {
             return -1;
         }
-        if(delay > other.delay)
-        {
+        if (this.getEndTime() > other.getEndTime()) {
             return 1;
         }
-        if(entryId < other.entryId)
-        {
+        if (this.entryId < other.entryId) {
             return -1;
         }
-        return entryId <= other.entryId ? 0 : 1;
+        return this.entryId <= other.entryId ? 0 : 1;
+    }
+
+    public @NotNull NextTickListEntry clone() {
+        final @NotNull NextTickListEntry cloned = new NextTickListEntry(this.x, this.y, this.z, this.blockId, this.creationTime);
+        cloned.delay = this.delay;
+
+        return cloned;
     }
 }
diff --git a/game/core/src/main/java/net/minecraft/core/block/Block.java b/game/core/src/main/java/net/minecraft/core/block/Block.java
index e11b96a93..5275da452 100644
--- a/game/core/src/main/java/net/minecraft/core/block/Block.java
+++ b/game/core/src/main/java/net/minecraft/core/block/Block.java
@@ -619,6 +619,11 @@ public final class Block<T extends BlockLogic>
         return logic.isEquivalent(world, thisX, thisY, thisZ, otherX, otherY, otherZ);
     }
 
+    @Override
+    public @NotNull Side getAttachedSide(@NotNull WorldSource world, int x, int y, int z) {
+        return logic.getAttachedSide(world, x, y, z);
+    }
+
     public static boolean hasLogicClass(@Nullable Block<?> block, Class<?> logicClass) {
         return block != null && logicClass.isAssignableFrom(block.getLogic().getClass());
     }
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockInterface.java b/game/core/src/main/java/net/minecraft/core/block/BlockInterface.java
index 671421c04..75965220c 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockInterface.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockInterface.java
@@ -109,4 +109,5 @@ interface BlockInterface {
     int getPlacedBlockMetadata(@Nullable Player player, ItemStack stack, World world, int x, int y, int z, Side side, double xPlaced, double yPlaced);
     boolean isClimbable(World world, int x, int y, int z);
     boolean isEquivalent(@NotNull World world, int thisX, int thisY, int thisZ, int otherX, int otherY, int otherZ);
+    @NotNull Side getAttachedSide(@NotNull WorldSource world, int x, int y, int z);
 }
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogic.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogic.java
index 282862f05..b31ca89f4 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogic.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogic.java
@@ -500,6 +500,11 @@ public class BlockLogic implements BlockInterface, IItemConvertible {
         return this.block == bBlock && thisMeta == otherMeta;
     }
 
+    @Override
+    public @NotNull Side getAttachedSide(@NotNull WorldSource world, int x, int y, int z) {
+        return Side.NONE;
+    }
+
     @Override
     public Item asItem() {
         return block.asItem();
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicFence.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicFence.java
index 953c0934a..46a8f0494 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicFence.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicFence.java
@@ -83,24 +83,24 @@ public class BlockLogicFence extends BlockLogic implements IPaintable
 
     @Override
     public void getCollidingBoundingBoxes(World world, int x, int y, int z, AABB aabb, ArrayList<AABB> aabbList) {
-        boolean connectXPos = canConnectTo(world, x + 1, y, z);
-        boolean connectXNeg = canConnectTo(world, x - 1, y, z);
-        boolean connectZPos = canConnectTo(world, x, y, z + 1);
-        boolean connectZNeg = canConnectTo(world, x, y, z - 1);
+        boolean connectEast = canConnectTo(world, x, y, z, Side.EAST);
+        boolean connectWest = canConnectTo(world, x, y, z, Side.WEST);
+        boolean connectSouth = canConnectTo(world, x, y, z, Side.SOUTH);
+        boolean connectNorth = canConnectTo(world, x, y, z, Side.NORTH);
         final double thickness = 0.5 - 0.375;
-        if (connectXPos) {
+        if (connectEast) {
             addIntersectingBoundingBox(aabb, AABB.fromPool(0.5 - thickness, 0, 0.5 - thickness, 1, 1.5, 0.5 + thickness).move(x, y, z), aabbList);
         }
-        if (connectXNeg) {
+        if (connectWest) {
             addIntersectingBoundingBox(aabb, AABB.fromPool(0, 0, 0.5 - thickness, 0.5 + thickness, 1.5, 0.5 + thickness).move(x, y, z), aabbList);
         }
-        if (connectZPos) {
+        if (connectSouth) {
             addIntersectingBoundingBox(aabb, AABB.fromPool(0.5 - thickness, 0, 0.5 - thickness, 0.5 + thickness, 1.5, 1).move(x, y, z), aabbList);
         }
-        if (connectZNeg) {
+        if (connectNorth) {
             addIntersectingBoundingBox(aabb, AABB.fromPool(0.5 - thickness, 0, 0, 0.5 + thickness, 1.5, 0.5 + thickness).move(x, y, z), aabbList);
         }
-        if (!connectXPos && !connectXNeg && !connectZPos && !connectZNeg) {
+        if (!connectEast && !connectWest && !connectSouth && !connectNorth) {
             addIntersectingBoundingBox(aabb, AABB.fromPool(0.5 - thickness, 0, 0.5 - thickness, 0.5 + thickness, 1.5, 0.5 + thickness).move(x, y, z), aabbList);
         }
     }
@@ -126,10 +126,10 @@ public class BlockLogicFence extends BlockLogic implements IPaintable
     @Override
     public AABB getBlockBoundsFromState(WorldSource world, int x, int y, int z)
     {
-        boolean connectXPos = canConnectTo(world, x + 1, y, z);
-        boolean connectXNeg = canConnectTo(world, x - 1, y, z);
-        boolean connectZPos = canConnectTo(world, x, y, z + 1);
-        boolean connectZNeg = canConnectTo(world, x, y, z - 1);
+        boolean connectXPos = canConnectTo(world, x, y, z, Side.EAST);
+        boolean connectXNeg = canConnectTo(world, x, y, z, Side.WEST);
+        boolean connectZPos = canConnectTo(world, x, y, z, Side.SOUTH);
+        boolean connectZNeg = canConnectTo(world, x, y, z, Side.NORTH);
         return AABB.fromPool((connectXNeg ? 0.0f : 0.375f), 0, (connectZNeg ? 0.0f : 0.375f), 1 - (connectXPos ? 0.0f : 0.375f), 1, 1 - (connectZPos ? 0.0f : 0.375f));
     }
 
@@ -145,10 +145,10 @@ public class BlockLogicFence extends BlockLogic implements IPaintable
         return false;
     }
 
-    public boolean canConnectTo(WorldSource worldSource, int x, int y, int z)
+    public boolean canConnectTo(@NotNull WorldSource worldSource, int x, int y, int z, @NotNull Side side)
     {
-        int l = worldSource.getBlockId(x, y, z);
-        return Blocks.hasTag(l, BlockTags.FENCES_CONNECT);
+        Block<?> b = worldSource.getBlock(x + side.getOffsetX(), y + side.getOffsetY(), z + side.getOffsetZ());
+        return Blocks.hasTag(b, BlockTags.FENCES_CONNECT) || (b != null && b.getAttachedSide(worldSource, x + side.getOffsetX(), y + side.getOffsetY(), z + side.getOffsetZ()) == side.getOpposite());
     }
 
     @Override
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicFenceChainlink.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicFenceChainlink.java
index 49f58ab89..4f25ce74e 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicFenceChainlink.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicFenceChainlink.java
@@ -2,8 +2,10 @@ package net.minecraft.core.block;
 
 import net.minecraft.core.block.material.Material;
 import net.minecraft.core.block.tag.BlockTags;
+import net.minecraft.core.util.helper.Side;
 import net.minecraft.core.world.World;
 import net.minecraft.core.world.WorldSource;
+import org.jetbrains.annotations.NotNull;
 
 public class BlockLogicFenceChainlink extends BlockLogicFenceThin
 {
@@ -13,10 +15,10 @@ public class BlockLogicFenceChainlink extends BlockLogicFenceThin
     }
 
     @Override
-    public boolean canConnectTo(WorldSource world, int x, int y, int z)
+    public boolean canConnectTo(@NotNull WorldSource world, int x, int y, int z, @NotNull Side side)
     {
-        Block<?> b = world.getBlock(x, y, z);
-        return BlockTags.CHAINLINK_FENCES_CONNECT.appliesTo(b) || (b != null && (b.getMaterial().isStone() || b.getMaterial().isMetal()));
+        Block<?> b = world.getBlock(x + side.getOffsetX(), y + side.getOffsetY(), z + side.getOffsetZ());
+        return Blocks.hasTag(b, BlockTags.CHAINLINK_FENCES_CONNECT) || (b != null && b.getAttachedSide(world, x + side.getOffsetX(), y + side.getOffsetY(), z + side.getOffsetZ()) == side.getOpposite());
     }
     
     @Override
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicFenceSteel.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicFenceSteel.java
index 1b3f4f2fd..a501c8d98 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicFenceSteel.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicFenceSteel.java
@@ -2,7 +2,9 @@ package net.minecraft.core.block;
 
 import net.minecraft.core.block.material.Material;
 import net.minecraft.core.block.tag.BlockTags;
+import net.minecraft.core.util.helper.Side;
 import net.minecraft.core.world.WorldSource;
+import org.jetbrains.annotations.NotNull;
 
 public class BlockLogicFenceSteel extends BlockLogicFenceThin {
     public BlockLogicFenceSteel(Block<?> block, Material material) {
@@ -10,8 +12,8 @@ public class BlockLogicFenceSteel extends BlockLogicFenceThin {
     }
 
     @Override
-    public boolean canConnectTo(WorldSource world, int x, int y, int z) {
-        Block<?> block = world.getBlock(x, y, z);
-        return BlockTags.CHAINLINK_FENCES_CONNECT.appliesTo(block) || (block != null && (block.getMaterial().isStone() || block.getMaterial().isMetal()));
+    public boolean canConnectTo(@NotNull WorldSource world, int x, int y, int z, @NotNull Side side) {
+        Block<?> b = world.getBlock(x + side.getOffsetX(), y + side.getOffsetY(), z + side.getOffsetZ());
+        return Blocks.hasTag(b, BlockTags.CHAINLINK_FENCES_CONNECT) || (b != null && b.getAttachedSide(world, x + side.getOffsetX(), y + side.getOffsetY(), z + side.getOffsetZ()) == side.getOpposite());
     }
 }
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicFenceThin.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicFenceThin.java
index 3d8797bfb..b73b6b50e 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicFenceThin.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicFenceThin.java
@@ -3,13 +3,14 @@ package net.minecraft.core.block;
 import net.minecraft.core.block.material.Material;
 import net.minecraft.core.block.support.ISupport;
 import net.minecraft.core.block.support.PartialSupport;
-import net.minecraft.core.util.helper.Direction;
 import net.minecraft.core.util.helper.Side;
 import net.minecraft.core.util.phys.AABB;
 import net.minecraft.core.world.World;
 import net.minecraft.core.world.WorldSource;
 import org.jetbrains.annotations.NotNull;
 
+import java.util.ArrayList;
+
 public abstract class BlockLogicFenceThin extends BlockLogic
 {
     public BlockLogicFenceThin(Block<?> block, Material material)
@@ -31,11 +32,35 @@ public abstract class BlockLogicFenceThin extends BlockLogic
     @Override
     public AABB getBlockBoundsFromState(WorldSource world, int x, int y, int z)
     {
-        boolean connectXPos = canConnectTo(world, x + 1, y, z);
-        boolean connectXNeg = canConnectTo(world, x - 1, y, z);
-        boolean connectZPos = canConnectTo(world, x, y, z + 1);
-        boolean connectZNeg = canConnectTo(world, x, y, z - 1);
-        return AABB.fromPool((connectXNeg ? 0.0f : 0.375f), 0, (connectZNeg ? 0.0f : 0.375f), 1 - (connectXPos ? 0.0f : 0.375f), 1, 1 - (connectZPos ? 0.0f : 0.375f));
+        boolean connectEast = canConnectTo(world, x, y, z, Side.EAST);
+        boolean connectWest = canConnectTo(world, x, y, z, Side.WEST);
+        boolean connectSouth = canConnectTo(world, x, y, z, Side.SOUTH);
+        boolean connectNorth = canConnectTo(world, x, y, z, Side.NORTH);
+        return AABB.fromPool((connectWest ? 0.0f : 0.375f), 0, (connectNorth ? 0.0f : 0.375f), 1 - (connectEast ? 0.0f : 0.375f), 1, 1 - (connectSouth ? 0.0f : 0.375f));
+    }
+
+    @Override
+    public void getCollidingBoundingBoxes(World world, int x, int y, int z, AABB aabb, ArrayList<AABB> aabbList) {
+        boolean connectEast = canConnectTo(world, x, y, z, Side.EAST);
+        boolean connectWest = canConnectTo(world, x, y, z, Side.WEST);
+        boolean connectSouth = canConnectTo(world, x, y, z, Side.SOUTH);
+        boolean connectNorth = canConnectTo(world, x, y, z, Side.NORTH);
+        final double thickness = 0.5 - 0.375;
+        if (connectEast) {
+            addIntersectingBoundingBox(aabb, AABB.fromPool(0.5 - thickness, 0, 0.5 - thickness, 1, 1, 0.5 + thickness).move(x, y, z), aabbList);
+        }
+        if (connectWest) {
+            addIntersectingBoundingBox(aabb, AABB.fromPool(0, 0, 0.5 - thickness, 0.5 + thickness, 1, 0.5 + thickness).move(x, y, z), aabbList);
+        }
+        if (connectSouth) {
+            addIntersectingBoundingBox(aabb, AABB.fromPool(0.5 - thickness, 0, 0.5 - thickness, 0.5 + thickness, 1, 1).move(x, y, z), aabbList);
+        }
+        if (connectNorth) {
+            addIntersectingBoundingBox(aabb, AABB.fromPool(0.5 - thickness, 0, 0, 0.5 + thickness, 1, 0.5 + thickness).move(x, y, z), aabbList);
+        }
+        if (!connectEast && !connectWest && !connectSouth && !connectNorth) {
+            addIntersectingBoundingBox(aabb, AABB.fromPool(0.5 - thickness, 0, 0.5 - thickness, 0.5 + thickness, 1, 0.5 + thickness).move(x, y, z), aabbList);
+        }
     }
 
     @Override
@@ -50,7 +75,7 @@ public abstract class BlockLogicFenceThin extends BlockLogic
         return false;
     }
 
-    public abstract boolean canConnectTo(WorldSource world, int x, int y, int z);
+    public abstract boolean canConnectTo(@NotNull WorldSource world, int x, int y, int z, @NotNull Side side);
 
     public boolean shouldDrawColumn(WorldSource world, int x, int y, int z) {
         boolean drawColumn = shouldDrawColumn_do(world, x, y, z);
@@ -79,10 +104,10 @@ public abstract class BlockLogicFenceThin extends BlockLogic
 
     private boolean shouldDrawColumn_do(WorldSource world, int x, int y, int z)
     {
-        boolean connectNorth = canConnectTo(world, x + Direction.NORTH.getOffsetX(), y + Direction.NORTH.getOffsetY(), z + Direction.NORTH.getOffsetZ());
-        boolean connectSouth = canConnectTo(world, x + Direction.SOUTH.getOffsetX(), y + Direction.SOUTH.getOffsetY(), z + Direction.SOUTH.getOffsetZ());
-        boolean connectEast = canConnectTo(world, x + Direction.EAST.getOffsetX(), y + Direction.EAST.getOffsetY(), z + Direction.EAST.getOffsetZ());
-        boolean connectWest = canConnectTo(world, x + Direction.WEST.getOffsetX(), y + Direction.WEST.getOffsetY(), z + Direction.WEST.getOffsetZ());
+        boolean connectNorth = canConnectTo(world, x, y, z, Side.NORTH);
+        boolean connectSouth = canConnectTo(world, x, y, z, Side.SOUTH);
+        boolean connectEast = canConnectTo(world, x, y, z, Side.EAST);
+        boolean connectWest = canConnectTo(world, x, y, z, Side.WEST);
 
         boolean lineNorthSouth = connectNorth && connectSouth;
         boolean lineEastWest = connectEast && connectWest;
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicFenceWallPaper.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicFenceWallPaper.java
index a8fa39226..2e0df70af 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicFenceWallPaper.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicFenceWallPaper.java
@@ -2,7 +2,9 @@ package net.minecraft.core.block;
 
 import net.minecraft.core.block.material.Material;
 import net.minecraft.core.block.tag.BlockTags;
+import net.minecraft.core.util.helper.Side;
 import net.minecraft.core.world.WorldSource;
+import org.jetbrains.annotations.NotNull;
 
 public class BlockLogicFenceWallPaper extends BlockLogicFenceThin
 {
@@ -12,9 +14,9 @@ public class BlockLogicFenceWallPaper extends BlockLogicFenceThin
     }
 
     @Override
-    public boolean canConnectTo(WorldSource world, int x, int y, int z)
+    public boolean canConnectTo(@NotNull WorldSource world, int x, int y, int z, @NotNull Side side)
     {
-        int l = world.getBlockId(x, y, z);
-        return Blocks.hasTag(l, BlockTags.FENCES_CONNECT);
+        Block<?> b = world.getBlock(x + side.getOffsetX(), y + side.getOffsetY(), z + side.getOffsetZ());
+        return Blocks.hasTag(b, BlockTags.FENCES_CONNECT) || (b != null && b.getAttachedSide(world, x + side.getOffsetX(), y + side.getOffsetY(), z + side.getOffsetZ()) == side.getOpposite());
     }
 }
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicRail.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicRail.java
index 4ffe4a40e..7e0e35403 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicRail.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicRail.java
@@ -99,10 +99,25 @@ public class BlockLogicRail extends BlockLogic implements ISupportable {
         boolean isPoweredFlagSet = (meta & MASK_POWERED) != 0;
         RailDirection railDirection = getRailDirection(world, x, y, z);
 
-        if(!isValidState(world, x, y, z)) {
+        if(!isSupported(world, x, y, z, Side.BOTTOM)) {
             dropBlockWithCause(world, EnumDropCause.WORLD, x, y, z, world.getBlockMetadata(x, y, z), null, null);
             world.setBlockWithNotify(x, y, z, 0);
             return;
+        } else if (!isRailValid(world, x, y, z)) {
+            switch (railDirection) {
+                case SLOPE_W:
+                case SLOPE_E: {
+                    railDirection = RailDirection.STRAIGHT_EW;
+                    world.setBlockMetadataWithNotify(x, y, z, railDirection.meta);
+                    break;
+                }
+                case SLOPE_N:
+                case SLOPE_S: {
+                    railDirection = RailDirection.STRAIGHT_NS;
+                    world.setBlockMetadataWithNotify(x, y, z, railDirection.meta);
+                    break;
+                }
+            }
         }
 
         Block<?> block = Blocks.getBlock(blockId);
@@ -137,20 +152,15 @@ public class BlockLogicRail extends BlockLogic implements ISupportable {
     }
     // Checks that the rail is placed on top of a block and doesn't slope into the air
 
-    private boolean isValidState(World world, int x, int y, int z){
-        if (!isSupported(world, x, y, z, Side.BOTTOM)) return false;
-
+    private boolean isRailValid(World world, int x, int y, int z){
         RailDirection railDirection = getRailDirection(world, x, y, z);
-        if(railDirection == RailDirection.SLOPE_E && !isSupported(world, x + 1, y + 1, z, Side.BOTTOM)) {
-            return false;
-        }
-        if(railDirection == RailDirection.SLOPE_W && !isSupported(world, x - 1, y + 1, z, Side.BOTTOM)) {
-            return false;
-        }
-        if(railDirection == RailDirection.SLOPE_N && !isSupported(world, x, y + 1, z - 1, Side.BOTTOM)) {
-            return false;
+        switch (railDirection) {
+            case SLOPE_E: return isSupported(world, x + 1, y + 1, z, Side.BOTTOM);
+            case SLOPE_W: return isSupported(world, x - 1, y + 1, z, Side.BOTTOM);
+            case SLOPE_N: return isSupported(world, x, y + 1, z - 1, Side.BOTTOM);
+            case SLOPE_S: return isSupported(world, x, y + 1, z + 1, Side.BOTTOM);
         }
-        return railDirection != RailDirection.SLOPE_S || isSupported(world, x, y + 1, z + 1, Side.BOTTOM);
+        return true;
     }
     // Turn logic what a regular rail is powered in an intersection
 
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicSign.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicSign.java
index aa9431773..b07628b83 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicSign.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicSign.java
@@ -10,6 +10,7 @@ import net.minecraft.core.entity.player.Player;
 import net.minecraft.core.enums.EnumDropCause;
 import net.minecraft.core.item.ItemStack;
 import net.minecraft.core.item.Items;
+import net.minecraft.core.sound.SoundCategory;
 import net.minecraft.core.util.helper.DyeColor;
 import net.minecraft.core.util.helper.Side;
 import net.minecraft.core.util.phys.AABB;
@@ -83,6 +84,22 @@ public class BlockLogicSign extends BlockLogic implements IPaintable
         return PartialSupport.INSTANCE;
     }
 
+    @Override
+    public @NotNull Side getAttachedSide(@NotNull WorldSource world, int x, int y, int z) {
+        switch (world.getBlockMetadata(x, y, z) & MASK_SIDE) {
+            case SIDE_NORTH:
+                return Side.SOUTH;
+            case SIDE_SOUTH:
+                return Side.NORTH;
+            case SIDE_WEST:
+                return Side.EAST;
+            case SIDE_EAST:
+                return Side.WEST;
+            default:
+                return Side.NONE;
+        }
+    }
+
     @Override
 	public void onNeighborBlockChange(World world, int x, int y, int z, int blockId)
     {
@@ -127,19 +144,26 @@ public class BlockLogicSign extends BlockLogic implements IPaintable
     @Override
     public boolean onBlockRightClicked(World world, int x, int y, int z, Player player, Side side, double xPlaced, double yPlaced) {
         TileEntitySign signEntity = (TileEntitySign)world.getTileEntity(x, y, z);
-        if (signEntity != null && player != null) {
-            if (player.getHeldItem() != null && player.getHeldItem().itemID == Items.DUST_GLOWSTONE.id && !signEntity.isGlowing()) {
-                signEntity.setGlowing(true);
-                if (player.getGamemode().consumeBlocks()) {
-                    player.getHeldItem().stackSize--;
+        if (signEntity != null) {
+            if (signEntity.isLocked()) return false;
+            if (player != null) {
+                ItemStack heldItem = player.getHeldItem();
+                if (heldItem != null && heldItem.itemID == Items.DUST_GLOWSTONE.id && !signEntity.isGlowing() && heldItem.consumeItem(player)) {
+                    signEntity.setGlowing(true);
+                    if (player.getGamemode().consumeBlocks()) {
+                        heldItem.stackSize--;
+                    }
+                    player.addStat(Achievements.LIGHT_SIGN, 1);
+                    return true;
+                } else if (heldItem != null && heldItem.itemID == Items.SLIMEBALL.id && heldItem.consumeItem(player)) {
+                    signEntity.setLocked(true);
+                    return true;
+                } else if (heldItem != null && (heldItem.itemID == Items.DYE.id || heldItem.itemID == Items.PAINTBRUSH.id)) {
+                    return false;
+                } else if (signEntity.isEditableBy(player)) {
+                    player.displaySignEditorScreen(signEntity);
+                    return true;
                 }
-                player.addStat(Achievements.LIGHT_SIGN, 1);
-                return true;
-            } else if (player.getHeldItem() != null && (player.getHeldItem().itemID == Items.DYE.id || player.getHeldItem().itemID == Items.PAINTBRUSH.id)) {
-                return false;
-            } else if (signEntity.isEditableBy(player)) {
-                player.displaySignEditorScreen(signEntity);
-                return true;
             }
         }
         return false;
diff --git a/game/core/src/main/java/net/minecraft/core/block/entity/TileEntitySign.java b/game/core/src/main/java/net/minecraft/core/block/entity/TileEntitySign.java
index ad833a99a..88d956cb6 100644
--- a/game/core/src/main/java/net/minecraft/core/block/entity/TileEntitySign.java
+++ b/game/core/src/main/java/net/minecraft/core/block/entity/TileEntitySign.java
@@ -21,6 +21,7 @@ public class TileEntitySign extends TileEntity
     private int selectedColor = 15;
     private @Nullable UUID owner;
     private boolean glowing = false;
+    private boolean locked = false;
 
     public TileEntitySign()
     {
@@ -37,6 +38,7 @@ public class TileEntitySign extends TileEntity
         compoundTag.putInt("Picture", selectedPicture);
         compoundTag.putInt("Color", selectedColor);
         compoundTag.putBoolean("Glowing", glowing);
+        compoundTag.putBoolean("Locked", locked);
         UUIDHelper.writeToTag(compoundTag, owner, "OwnerUUID");
     }
 
@@ -54,6 +56,7 @@ public class TileEntitySign extends TileEntity
         selectedPicture = compoundTag.getIntegerOrDefault("Picture", 0);
         selectedColor = compoundTag.getIntegerOrDefault("Color", 15);
         glowing = compoundTag.getBooleanOrDefault("Glowing", false);
+        locked = compoundTag.getBooleanOrDefault("Locked", false);
 
         UUID ownerUUID = UUIDHelper.readFromTag(compoundTag, "OwnerUUID");
         if (ownerUUID == null) {
@@ -70,7 +73,7 @@ public class TileEntitySign extends TileEntity
     
     public boolean isEditableBy(Player player)
     {
-        return player.uuid.equals(owner);
+        return player.uuid.equals(owner) && !locked;
     }
 
     public void setOwner(Player player){
@@ -112,6 +115,14 @@ public class TileEntitySign extends TileEntity
     public void setGlowing(boolean glowing) {
         this.glowing = glowing;
     }
+
+    public boolean isLocked() {
+        return locked;
+    }
+
+    public void setLocked(boolean locked) {
+        this.locked = locked;
+    }
     
     @Override
 	public Packet getDescriptionPacket()
diff --git a/game/core/src/main/java/net/minecraft/core/entity/EntityDispatcher.java b/game/core/src/main/java/net/minecraft/core/entity/EntityDispatcher.java
index 247764890..ea20d7f3c 100644
--- a/game/core/src/main/java/net/minecraft/core/entity/EntityDispatcher.java
+++ b/game/core/src/main/java/net/minecraft/core/entity/EntityDispatcher.java
@@ -158,6 +158,7 @@ public final class EntityDispatcher
         stringIdToEntryMap.put(entry.stringID, entry);
         classToEntryMap.put(entry.entityClass, entry);
         numericIdToEntryMap.put(entry.numericID, entry);
+        numericIdToStringIdMap.put(entry.numericID, entry.stringID);
 
         return factory;
     }
diff --git a/game/core/src/main/java/net/minecraft/core/entity/EntityLightning.java b/game/core/src/main/java/net/minecraft/core/entity/EntityLightning.java
index 26aada370..49b91bea7 100644
--- a/game/core/src/main/java/net/minecraft/core/entity/EntityLightning.java
+++ b/game/core/src/main/java/net/minecraft/core/entity/EntityLightning.java
@@ -40,8 +40,8 @@ public class EntityLightning extends Entity
         {
             world.playSoundEffect(null, SoundCategory.WEATHER_SOUNDS, x, y, z, "ambient.weather.thunder", 10000F, 0.8F + random.nextFloat() * 0.2F);
             world.playSoundEffect(null, SoundCategory.WEATHER_SOUNDS, x, y, z, "random.explode", 2.0F, 0.5F + random.nextFloat() * 0.2F);
-            for (int i = 0; i < world.loadedEntityList.size(); i++) {
-                Entity e = world.loadedEntityList.get(i);
+            for (int i = 0; i < world.entities.size(); i++) {
+                Entity e = world.entities.get(i);
                 if (e instanceof MobWolf) {
                     ((MobWolf) e).whine();
                 }
diff --git a/game/core/src/main/java/net/minecraft/core/entity/monster/MobZombiePig.java b/game/core/src/main/java/net/minecraft/core/entity/monster/MobZombiePig.java
index ab6066da0..daae51138 100644
--- a/game/core/src/main/java/net/minecraft/core/entity/monster/MobZombiePig.java
+++ b/game/core/src/main/java/net/minecraft/core/entity/monster/MobZombiePig.java
@@ -9,7 +9,6 @@ import net.minecraft.core.entity.player.Player;
 import net.minecraft.core.item.Items;
 import net.minecraft.core.item.ItemStack;
 import net.minecraft.core.enums.LightLayer;
-import net.minecraft.core.util.collection.NamespaceID;
 import net.minecraft.core.util.helper.MathHelper;
 import com.mojang.nbt.tags.CompoundTag;
 import net.minecraft.core.world.World;
@@ -55,7 +54,7 @@ public class MobZombiePig extends MobZombie
     {
         if (tickCount % 200 == 0 && target instanceof Player && ((Player) target).getStat(Achievements.MOST_WANTED) <= 0) {
             int agroNumber = 1;
-            for (Entity e : world.loadedEntityList) {
+            for (Entity e : world.entities) {
                 if (e instanceof MobZombiePig && ((MobZombiePig) e).target == this.target) {
                     agroNumber++;
                 }
diff --git a/game/core/src/main/java/net/minecraft/core/item/ItemDoor.java b/game/core/src/main/java/net/minecraft/core/item/ItemDoor.java
index 13757c6e0..61dab17d4 100644
--- a/game/core/src/main/java/net/minecraft/core/item/ItemDoor.java
+++ b/game/core/src/main/java/net/minecraft/core/item/ItemDoor.java
@@ -9,6 +9,7 @@ import net.minecraft.core.block.support.PartialSupport;
 import net.minecraft.core.entity.player.Player;
 import net.minecraft.core.enums.EnumBlockSoundEffectType;
 import net.minecraft.core.util.helper.Direction;
+import net.minecraft.core.util.helper.DyeColor;
 import net.minecraft.core.util.helper.Side;
 import net.minecraft.core.world.World;
 import org.jetbrains.annotations.NotNull;
@@ -57,6 +58,8 @@ public class ItemDoor extends Item {
 			meta |= 0b100;
 			meta |= 0b1000;
 		}
+		meta |= (DyeColor.colorFromItemMeta(itemstack.getMetadata()).blockMeta) << 4 & 0xF0;
+
 		if(!doorBlockBottom.getLogic().canPlaceBlockAt(world, blockX, blockY, blockZ, meta)) {
 			return false;
 		}
@@ -107,6 +110,7 @@ public class ItemDoor extends Item {
 			meta |= 0b100;
 			meta |= 0b1000;
 		}
+		meta |= (DyeColor.colorFromItemMeta(itemStack.getMetadata()).blockMeta) << 4 & 0xF0;
 		if(!doorBlockBottom.getLogic().canPlaceBlockAt(world, blockX, blockY, blockZ, meta)) {
 			return;
 		}
diff --git a/game/core/src/main/java/net/minecraft/core/item/ItemDoorPainted.java b/game/core/src/main/java/net/minecraft/core/item/ItemDoorPainted.java
index 6eed035e1..d3998c104 100644
--- a/game/core/src/main/java/net/minecraft/core/item/ItemDoorPainted.java
+++ b/game/core/src/main/java/net/minecraft/core/item/ItemDoorPainted.java
@@ -2,67 +2,13 @@ package net.minecraft.core.item;
 
 import net.minecraft.core.block.Block;
 import net.minecraft.core.block.BlockLogicDoor;
-import net.minecraft.core.entity.player.Player;
-import net.minecraft.core.enums.EnumBlockSoundEffectType;
-import net.minecraft.core.util.helper.Direction;
 import net.minecraft.core.util.helper.DyeColor;
-import net.minecraft.core.util.helper.Side;
-import net.minecraft.core.world.World;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
 
 public class ItemDoorPainted extends ItemDoor {
     public ItemDoorPainted(String name, String namespaceId, int id, Block<? extends BlockLogicDoor> doorBottom, Block<? extends BlockLogicDoor> doorTopId) {
         super(name, namespaceId, id, doorBottom, doorTopId);
     }
     @Override
-    public boolean onUseItemOnBlock(@NotNull ItemStack itemstack, @Nullable Player player, @NotNull World world, int blockX, int blockY, int blockZ, @NotNull Side side, double xPlaced, double yPlaced) {
-        if(!world.canPlaceInsideBlock(blockX, blockY, blockZ)) {
-            blockX += side.getOffsetX();
-            blockY += side.getOffsetY();
-            blockZ += side.getOffsetZ();
-        }
-        if(!doorBlockBottom.canPlaceBlockAt(world, blockX, blockY, blockZ)) {
-            return false;
-        }
-
-        Direction dir = player.getHorizontalPlacementDirection(side).rotate(3);
-        int meta = dir.getHorizontalIndex();
-
-        int xOffset = -dir.getOffsetX();
-        int zOffset = -dir.getOffsetZ();
-
-        int isSolidBlockLeft = (world.isBlockNormalCube(blockX - xOffset, blockY, blockZ - zOffset) ? 1 : 0) + (world.isBlockNormalCube(blockX - xOffset, blockY + 1, blockZ - zOffset) ? 1 : 0);
-        int isSolidBlockRight = (world.isBlockNormalCube(blockX + xOffset, blockY, blockZ + zOffset) ? 1 : 0) + (world.isBlockNormalCube(blockX + xOffset, blockY + 1, blockZ + zOffset) ? 1 : 0);
-        boolean isDoorLeft = world.getBlockId(blockX - xOffset, blockY, blockZ - zOffset) == doorBlockBottom.id() || world.getBlockId(blockX - xOffset, blockY + 1, blockZ - zOffset) == doorBlockTop.id();
-        boolean isDoorRight = world.getBlockId(blockX + xOffset, blockY, blockZ + zOffset) == doorBlockBottom.id() || world.getBlockId(blockX + xOffset, blockY + 1, blockZ + zOffset) == doorBlockTop.id();
-        boolean isMirrored = false;
-
-        if(isDoorLeft && !isDoorRight) {
-            isMirrored = true;
-        } else if(isSolidBlockRight > isSolidBlockLeft) {
-            isMirrored = true;
-        }
-        if(isMirrored) {
-            meta = meta - 1 & 3;
-            meta += 4;
-            meta |= 0x8;
-        }
-        meta |= (15 - itemstack.getMetadata()) << 4 &0xF0;
-
-        world.noNeighborUpdate = true;
-        world.setBlockAndMetadataWithNotify(blockX, blockY, blockZ, doorBlockBottom.id(), meta);
-        world.setBlockAndMetadataWithNotify(blockX, blockY + 1, blockZ, doorBlockTop.id(), meta);
-        world.noNeighborUpdate = false;
-        world.notifyBlocksOfNeighborChange(blockX, blockY, blockZ, doorBlockBottom.id());
-        world.notifyBlocksOfNeighborChange(blockX, blockY + 1, blockZ, doorBlockTop.id());
-        world.playBlockSoundEffect(player,blockX + 0.5F, blockY + 0.5F, blockZ + 0.5F, doorBlockBottom, EnumBlockSoundEffectType.PLACE);
-        doorBlockBottom.onBlockPlacedByMob(world, blockX, blockY, blockZ, side, player, xPlaced, yPlaced);
-        doorBlockTop.onBlockPlacedByMob(world, blockX, blockY + 1, blockZ, side, player, xPlaced, yPlaced);
-        itemstack.consumeItem(player);
-        return true;
-    }
-    @Override
     public String getLanguageKey(ItemStack itemstack)
     {
         return super.getKey() + "." + DyeColor.colorFromItemMeta(itemstack.getMetadata()).colorID;
diff --git a/game/core/src/main/java/net/minecraft/core/net/command/helpers/EntitySelector.java b/game/core/src/main/java/net/minecraft/core/net/command/helpers/EntitySelector.java
index dfb149799..7d3a2a559 100644
--- a/game/core/src/main/java/net/minecraft/core/net/command/helpers/EntitySelector.java
+++ b/game/core/src/main/java/net/minecraft/core/net/command/helpers/EntitySelector.java
@@ -51,7 +51,7 @@ public class EntitySelector {
         // Entity ID/player
         if (this.entityId != null) {
             List<Entity> entities = new ArrayList<>();
-            for (Entity entity : source.getWorld().loadedEntityList) {
+            for (Entity entity : source.getWorld().entities) {
                 if ((ENTITY_PREFIX + entity.hashCode()).equals(this.entityId)) {
                     entities.add(entity);
                 }
@@ -79,7 +79,7 @@ public class EntitySelector {
                 entities = Collections.singletonList(source.getSender());
             }
         } else if (this.includesEntities) {
-            entities = source.getWorld().loadedEntityList;
+            entities = source.getWorld().entities;
         } else {
             entities = source.getWorld().players;
         }
diff --git a/game/core/src/main/java/net/minecraft/core/world/PortalHandler.java b/game/core/src/main/java/net/minecraft/core/world/PortalHandler.java
index 7cf290f5a..8a35b76fe 100644
--- a/game/core/src/main/java/net/minecraft/core/world/PortalHandler.java
+++ b/game/core/src/main/java/net/minecraft/core/world/PortalHandler.java
@@ -153,7 +153,7 @@ public class PortalHandler
             {
                 newEntityZ += 0.5D;
             }
-            entity.moveTo(newEntityX, newEntityY - 0.5f, newEntityZ, entity.yRot, 0.0F);
+            entity.moveTo(newEntityX, newEntityY, newEntityZ, entity.yRot, 0.0F);
             entity.xd = entity.yd = entity.zd = 0.0D;
             return true;
         }
diff --git a/game/core/src/main/java/net/minecraft/core/world/World.java b/game/core/src/main/java/net/minecraft/core/world/World.java
index 781685bde..57433468e 100644
--- a/game/core/src/main/java/net/minecraft/core/world/World.java
+++ b/game/core/src/main/java/net/minecraft/core/world/World.java
@@ -2,20 +2,12 @@ package net.minecraft.core.world;
 
 import static net.minecraft.core.Global.*;
 
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Random;
-import java.util.Set;
-import java.util.TreeSet;
-import java.util.UUID;
+import java.util.*;
 
 import com.mojang.logging.LogUtils;
 
 import net.minecraft.core.block.entity.TileEntity;
 import net.minecraft.core.block.support.ISupport;
-import net.minecraft.core.block.support.ISupportable;
 import net.minecraft.core.block.support.PartialSupport;
 import net.minecraft.core.current.wire.WireHandler;
 import net.minecraft.core.entity.EntityItem;
@@ -90,19 +82,18 @@ public class World implements WorldSource {
     public static final int MAX_SCHEDULED_TICKS_IN_TICK = 1000;
     public static final int ANIMATION_TICKS_PER_TICK = 1000;
 
-    public List<Entity> loadedEntityList = new ArrayList<>();
-    private final List<Entity> unloadedEntityList = new ArrayList<>();
-    private final TreeSet<NextTickListEntry> scheduledTickTreeSet = new TreeSet<>();
-    private final Set<NextTickListEntry> scheduledTickSet = new HashSet<>();
-    public List<TileEntity> loadedTileEntityList = new ArrayList<>();
-    public List<Player> players = new ArrayList<>();
-    public List<Entity> weatherEffects = new ArrayList<>();
-    public final ArrayList<AABB> collidingBoundingBoxes = new ArrayList<>();
-    public List<LevelListener> listeners = new ArrayList<>();
-    private final Set<ChunkCoordinate> positionsToUpdate = new HashSet<>();
-    private final List<Entity> entityBuffer = new ArrayList<Entity>();
-    private final Set<NextTickListEntry> immediatelyUpdatedPositions = new HashSet<>();
-    
+    public final @NotNull List<@NotNull Entity> entities = new ArrayList<>();
+    private final @NotNull List<@NotNull Entity> entitiesToRemove = new ArrayList<>();
+    private final @NotNull TreeSet<@NotNull NextTickListEntry> tickNextTickList = new TreeSet<>();
+    public final @NotNull List<@NotNull TileEntity> tileEntityList = new ArrayList<>();
+    public final @NotNull List<@NotNull Player> players = new ArrayList<>();
+    public final @NotNull List<@NotNull Entity> weatherEffects = new ArrayList<>();
+    public final @NotNull ArrayList<@NotNull AABB> collidingBoundingBoxes = new ArrayList<>();
+    public final @NotNull List<@NotNull LevelListener> listeners = new ArrayList<>();
+    private final @NotNull Set<@NotNull ChunkCoordinate> positionsToUpdate = new HashSet<>();
+    private final @NotNull List<@NotNull Entity> entityBuffer = new ArrayList<>();
+    private final @NotNull Set<@NotNull NextTickListEntry> immediatelyUpdatedPositions = new HashSet<>();
+
     public boolean scheduledUpdatesAreImmediate = false;
     public int skyDarken = 0;
     protected int updateLCG = new Random().nextInt();
@@ -112,8 +103,7 @@ public class World implements WorldSource {
     public boolean dayCanHaveRainbow = true;
     public boolean noNeighborUpdate = false;
     private long lockTimestamp = System.currentTimeMillis();
-    //protected int autosavePeriod = 40;
-    public Random rand = new Random();
+    public final @NotNull Random rand = new Random();
     public boolean isNewWorld = false;
     public Dimension dimension;
     public WorldType worldType;
@@ -127,7 +117,7 @@ public class World implements WorldSource {
     protected final LightingEngine lightingEngine = new LightingEngineLegacy(this);
     private boolean updatingBlockEntities;
 
-    private int caveSoundCounter = rand.nextInt(12000);
+    private int caveSoundCounter = this.rand.nextInt(12000);
     public boolean isClientSide = false;
     public int sleepPercent = 100;
     private long runtime = 0;
@@ -1401,7 +1391,7 @@ public class World implements WorldSource {
             }
 
             getChunkFromChunkCoords(i, j).addEntity(entity);
-            loadedEntityList.add(entity);
+            entities.add(entity);
             obtainEntitySkin(entity);
             return true;
         } else
@@ -1460,7 +1450,7 @@ public class World implements WorldSource {
         {
             getChunkFromChunkCoords(i, j).removeEntity(entity);
         }
-        loadedEntityList.remove(entity);
+        entities.remove(entity);
         releaseEntitySkin(entity);
     }
 
@@ -1655,12 +1645,17 @@ public class World implements WorldSource {
         return f2 * f2 * 0.5F;
     }
 
-    public void scheduleBlockUpdate(int x, int y, int z, int id, int delay) {
-        NextTickListEntry entry = new NextTickListEntry(x, y, z, id);
+    public void scheduleBlockUpdate(int x, int y, int z, int id, long delay) {
+        this.scheduleBlockUpdate(x, y, z, id, delay, false);
+    }
+
+    public void scheduleBlockUpdate(int x, int y, int z, int id, long delay, boolean loaded) {
+        NextTickListEntry entry = new NextTickListEntry(x, y, z, id, getWorldTime());
+        entry.loaded = loaded;
         byte radius = 8;
 
         if(scheduledUpdatesAreImmediate && !immediatelyUpdatedPositions.contains(entry)) {
-            if(areBlocksLoaded(entry.x - radius, entry.y - radius, entry.z - radius, entry.x + radius, entry.y + radius, entry.z + radius)) {
+            if(entry.loaded || areBlocksLoaded(entry.x - radius, entry.y - radius, entry.z - radius, entry.x + radius, entry.y + radius, entry.z + radius)) {
                 int id1 = getBlockId(entry.x, entry.y, entry.z);
                 if(id1 == entry.blockId && id1 > 0) {
                     immediatelyUpdatedPositions.add(entry);
@@ -1669,13 +1664,14 @@ public class World implements WorldSource {
             }
             return;
         }
-        if(areBlocksLoaded(x - radius, y - radius, z - radius, x + radius, y + radius, z + radius)) {
+        if(entry.loaded || areBlocksLoaded(x - radius, y - radius, z - radius, x + radius, y + radius, z + radius)) {
             if(id > 0) {
-                entry.setDelay((long) delay + runtime);
+                entry.setDelay(delay);
             }
-            if(!scheduledTickSet.contains(entry)) {
-                scheduledTickSet.add(entry);
-                scheduledTickTreeSet.add(entry);
+            if(!tickNextTickList.contains(entry)) {
+                tickNextTickList.add(entry);
+                final @NotNull Chunk chunk = Objects.requireNonNull(this.getChunkFromBlockCoords(x, z));
+                chunk.addTick(entry);
             }
         }
     }
@@ -1691,10 +1687,10 @@ public class World implements WorldSource {
             }
         }
 
-        loadedEntityList.removeAll(unloadedEntityList);
-        for(int j = 0; j < unloadedEntityList.size(); j++)
+        entities.removeAll(entitiesToRemove);
+        for(int j = 0; j < entitiesToRemove.size(); j++)
         {
-            Entity entity1 = unloadedEntityList.get(j);
+            Entity entity1 = entitiesToRemove.get(j);
             int i1 = entity1.chunkCoordX;
             int k1 = entity1.chunkCoordZ;
             if(entity1.addedToChunk && isChunkLoaded(i1, k1))
@@ -1703,15 +1699,15 @@ public class World implements WorldSource {
             }
         }
 
-        for(int k = 0; k < unloadedEntityList.size(); k++)
+        for(int k = 0; k < entitiesToRemove.size(); k++)
         {
-            releaseEntitySkin(unloadedEntityList.get(k));
+            releaseEntitySkin(entitiesToRemove.get(k));
         }
 
-        unloadedEntityList.clear();
-        for(int l = 0; l < loadedEntityList.size(); l++)
+        entitiesToRemove.clear();
+        for(int l = 0; l < entities.size(); l++)
         {
-            Entity entity2 = loadedEntityList.get(l);
+            Entity entity2 = entities.get(l);
             if(entity2.vehicle != null)
             {
                 if(!entity2.vehicle.isRemoved() && entity2.vehicle.getPassenger() == entity2)
@@ -1735,18 +1731,18 @@ public class World implements WorldSource {
             {
                 getChunkFromChunkCoords(j1, l1).removeEntity(entity2);
             }
-            loadedEntityList.remove(l--);
+            entities.remove(l--);
             releaseEntitySkin(entity2);
         }
 
         updatingBlockEntities = true;
-        for (int i = loadedTileEntityList.size() - 1; i >= 0; i--) {
-            TileEntity tileEntity = loadedTileEntityList.get(i);
+        for (int i = tileEntityList.size() - 1; i >= 0; i--) {
+            TileEntity tileEntity = tileEntityList.get(i);
             if (!tileEntity.isInvalid() && isBlockLoaded(tileEntity.x, tileEntity.y, tileEntity.z)) {
                 tileEntity.tick();
             }
             if (tileEntity.isInvalid()) {
-                loadedTileEntityList.remove(i);
+                tileEntityList.remove(i);
                 Chunk chunk = getChunkFromChunkCoords(Math.floorDiv(tileEntity.x, Chunk.CHUNK_SIZE_X), Math.floorDiv(tileEntity.z, Chunk.CHUNK_SIZE_Z));
                 if (chunk != null) {
                     chunk.removeTileEntity(tileEntity);
@@ -1758,7 +1754,7 @@ public class World implements WorldSource {
 
     public void addAllBlockEntities(Collection<TileEntity> blockEntities)
     {
-        loadedTileEntityList.addAll(blockEntities);
+        tileEntityList.addAll(blockEntities);
     }
 
     public void updateEntity(Entity entity)
@@ -2132,9 +2128,9 @@ public class World implements WorldSource {
 
     public @Nullable Entity getEntityByID(int id)
     {
-        for(int i = 0; i < loadedEntityList.size(); i++)
+        for(int i = 0; i < entities.size(); i++)
         {
-            Entity entity = loadedEntityList.get(i);
+            Entity entity = entities.get(i);
             if(entity.id == id)
             {
                 return entity;
@@ -2146,7 +2142,7 @@ public class World implements WorldSource {
 
     public String getNumLoadedEntitiesString()
     {
-        return "All: " + loadedEntityList.size();
+        return "All: " + entities.size();
     }
 
     public String getChunkProviderInfoString()
@@ -2174,8 +2170,8 @@ public class World implements WorldSource {
             Chunk chunk = getChunkFromChunkCoords(Math.floorDiv(x, Chunk.CHUNK_SIZE_X), Math.floorDiv(z, Chunk.CHUNK_SIZE_Z));
             if(chunk != null)
             {
-                if (chunk.setTileEntity(x & (Chunk.CHUNK_SIZE_X - 1), y, z & (Chunk.CHUNK_SIZE_Z - 1), tileEntity) && !loadedTileEntityList.contains(tileEntity)) {
-                    loadedTileEntityList.add(tileEntity);
+                if (chunk.setTileEntity(x & (Chunk.CHUNK_SIZE_X - 1), y, z & (Chunk.CHUNK_SIZE_Z - 1), tileEntity) && !tileEntityList.contains(tileEntity)) {
+                    tileEntityList.add(tileEntity);
                 }
             }
         }
@@ -2192,7 +2188,7 @@ public class World implements WorldSource {
         {
             if(tileEntity != null)
             {
-                loadedTileEntityList.remove(tileEntity);
+                tileEntityList.remove(tileEntity);
             }
             Chunk chunk = getChunkFromChunkCoords(Math.floorDiv(x, Chunk.CHUNK_SIZE_X), Math.floorDiv(z, Chunk.CHUNK_SIZE_Z));
             if(chunk != null)
@@ -2267,8 +2263,8 @@ public class World implements WorldSource {
             LOGGER.error("Unhandled exception while unloading all entities!", t);
             throw t;
         } finally {
-            loadedEntityList.clear();
-            unloadedEntityList.clear();
+            entities.clear();
+            entitiesToRemove.clear();
         }
     }
 
@@ -2350,7 +2346,7 @@ public class World implements WorldSource {
 
         runtime++;
 
-        tickUpdates(false);
+        tickPendingTicks(false);
         Debug.change("blocks&cavesSounds");
         updateBlocksAndPlayCaveSounds();
         Debug.change("season&light");
@@ -2462,39 +2458,43 @@ public class World implements WorldSource {
 
     }
 
-    public boolean tickUpdates(boolean all)
-    {
-        int count = scheduledTickTreeSet.size();
-        if(count != scheduledTickSet.size())
-        {
-            throw new IllegalStateException("TickNextTick list out of sync");
-        }
+    public boolean tickPendingTicks(final boolean all) {
+        int count = this.tickNextTickList.size();
         if(count > MAX_SCHEDULED_TICKS_IN_TICK)
         {
             count = MAX_SCHEDULED_TICKS_IN_TICK;
         }
-        for(int j = 0; j < count; j++)
+
+        for (int i = 0; i < count; i++)
         {
-            NextTickListEntry entry = scheduledTickTreeSet.first();
-            if(!all && entry.delay > runtime)
+            final @NotNull NextTickListEntry entry = this.tickNextTickList.first();
+            if(!all && entry.getEndTime() > this.getWorldTime())
             {
                 break;
             }
-            scheduledTickTreeSet.remove(entry);
-            scheduledTickSet.remove(entry);
+            if (!entry.loaded) {
+                this.tickNextTickList.remove(entry);
+            }
             final byte radius = 8;
-            if(!areBlocksLoaded(entry.x - radius, entry.y - radius, entry.z - radius, entry.x + radius, entry.y + radius, entry.z + radius))
-            {
+            if(!this.areBlocksLoaded(entry.x - radius, entry.y - radius, entry.z - radius, entry.x + radius, entry.y + radius, entry.z + radius)) {
+                if (entry.loaded) { // Re-insert loaded tick with appropriate delay
+                    entry.creationTime = this.getWorldTime();
+                }
                 continue;
             }
-            int blockId = getBlockId(entry.x, entry.y, entry.z);
-            if(blockId == entry.blockId && blockId > 0)
+            if (entry.loaded) {
+                this.tickNextTickList.remove(entry);
+            }
+            final @NotNull Chunk chunk = Objects.requireNonNull(this.getChunkFromBlockCoords(entry.x, entry.z));
+            chunk.dropTick(entry);
+            final @Nullable Block<?> block = this.getBlock(entry.x, entry.y, entry.z);
+            if(block != null && block.id() == entry.blockId)
             {
-                Blocks.blocksList[blockId].updateTick(this, entry.x, entry.y, entry.z, rand, false);
+                block.updateTick(this, entry.x, entry.y, entry.z, this.rand, false);
             }
         }
 
-        return !scheduledTickTreeSet.isEmpty();
+        return !tickNextTickList.isEmpty();
     }
 
     public void randomDisplayUpdates(int x, int y, int z)
@@ -2559,12 +2559,12 @@ public class World implements WorldSource {
 
     public List<Entity> getLoadedEntityList()
     {
-        return loadedEntityList;
+        return entities;
     }
 
     public List<TileEntity> getLoadedTileEntityList()
     {
-        return loadedTileEntityList;
+        return tileEntityList;
     }
 
     public void updateTileEntityChunkAndSendToPlayer(int x, int y, int z, TileEntity tileEntity)
@@ -2582,7 +2582,7 @@ public class World implements WorldSource {
     public int countEntities(Class<?> clazz)
     {
         int i = 0;
-        for (Entity entity : loadedEntityList) {
+        for (Entity entity : entities) {
             if (clazz.isAssignableFrom(entity.getClass())) {
                 i++;
             }
@@ -2593,7 +2593,7 @@ public class World implements WorldSource {
 
     public void addLoadedEntities(List<Entity> list)
     {
-        loadedEntityList.addAll(list);
+        entities.addAll(list);
         for(int i = 0; i < list.size(); i++)
         {
             obtainEntitySkin(list.get(i));
@@ -2603,7 +2603,7 @@ public class World implements WorldSource {
 
     public void unloadEntities(List<Entity> list)
     {
-        unloadedEntityList.addAll(list);
+        entitiesToRemove.addAll(list);
     }
 
     public void dropOldChunks()
@@ -2901,14 +2901,17 @@ public class World implements WorldSource {
         levelData.setWorldTime(l);
     }
     
-    public void setWorldTimeUpdateTicks(long l)
-    {
-        long l1 = l - levelData.getWorldTime();
-        for (NextTickListEntry nextticklistentry : scheduledTickSet) {
-            nextticklistentry.delay += l1;
+    public void setWorldTimeUpdateTicks(final long ticks) {
+        final long currentTime = this.levelData.getWorldTime();
+        final long newTime = ticks;
+
+        for (final @NotNull NextTickListEntry entry : this.tickNextTickList) {
+            final long ticksRemaining = entry.getEndTime() - currentTime;
+            entry.creationTime = newTime;
+            entry.delay = ticksRemaining;
         }
 
-        setWorldTime(l);
+        this.setWorldTime(ticks);
     }
 
     public long getRandomSeed()
@@ -2956,9 +2959,9 @@ public class World implements WorldSource {
 
         }
 
-        if(!loadedEntityList.contains(entity))
+        if(!entities.contains(entity))
         {
-            loadedEntityList.add(entity);
+            entities.add(entity);
         }
     }
 
@@ -2981,10 +2984,10 @@ public class World implements WorldSource {
 
     public void updateEntityList()
     {
-        loadedEntityList.removeAll(unloadedEntityList);
-        for(int i = 0; i < unloadedEntityList.size(); i++)
+        entities.removeAll(entitiesToRemove);
+        for(int i = 0; i < entitiesToRemove.size(); i++)
         {
-            Entity entity = unloadedEntityList.get(i);
+            Entity entity = entitiesToRemove.get(i);
             int cX = entity.chunkCoordX;
             int cZ = entity.chunkCoordZ;
             if(entity.addedToChunk && isChunkLoaded(cX, cZ))
@@ -2993,15 +2996,15 @@ public class World implements WorldSource {
             }
         }
 
-        for(int j = 0; j < unloadedEntityList.size(); j++)
+        for(int j = 0; j < entitiesToRemove.size(); j++)
         {
-            releaseEntitySkin(unloadedEntityList.get(j));
+            releaseEntitySkin(entitiesToRemove.get(j));
         }
 
-        unloadedEntityList.clear();
-        for(int i = 0; i < loadedEntityList.size(); i++)
+        entitiesToRemove.clear();
+        for(int i = 0; i < entities.size(); i++)
         {
-            Entity entity = loadedEntityList.get(i);
+            Entity entity = entities.get(i);
             if(entity.vehicle != null)
             {
                 if(!entity.vehicle.isRemoved() && entity.vehicle.getPassenger() == entity)
@@ -3021,7 +3024,7 @@ public class World implements WorldSource {
             {
                 getChunkFromChunkCoords(cX, cZ).removeEntity(entity);
             }
-            loadedEntityList.remove(i--);
+            entities.remove(i--);
             releaseEntitySkin(entity);
         }
 
diff --git a/game/core/src/main/java/net/minecraft/core/world/chunk/Chunk.java b/game/core/src/main/java/net/minecraft/core/world/chunk/Chunk.java
index f0397e2bc..16c7d2869 100644
--- a/game/core/src/main/java/net/minecraft/core/world/chunk/Chunk.java
+++ b/game/core/src/main/java/net/minecraft/core/world/chunk/Chunk.java
@@ -1,6 +1,9 @@
 package net.minecraft.core.world.chunk;
 
 import com.mojang.logging.LogUtils;
+import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap;
+import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
+import net.minecraft.core.NextTickListEntry;
 import net.minecraft.core.block.Block;
 import net.minecraft.core.block.Blocks;
 import net.minecraft.core.block.entity.TileEntity;
@@ -23,17 +26,18 @@ public class Chunk
     public static final int CHUNK_SIZE_Z = 16;
     public static final int CHUNK_SECTIONS = 16;
     public static boolean isLit;
-
     public boolean isLoaded;
     public World world;
     public short[] heightMap;
     public int lowestY;
     public final int xPosition;
     public final int zPosition;
+    public long ticksOnUnload = -1;
     public Map<ChunkPosition, TileEntity> tileEntityMap;
     public double[] temperature;
     public double[] humidity;
     public double[] variety;
+    public @NotNull Int2ObjectMap<NextTickListEntry> tickList;
     public int averageBlockHeight;
     public boolean isTerrainPopulated;
     public boolean isModified;
@@ -56,6 +60,7 @@ public class Chunk
         temperature = new double[CHUNK_SIZE_X * CHUNK_SIZE_Z];
         humidity = new double[CHUNK_SIZE_X * CHUNK_SIZE_Z];
         variety = new double[CHUNK_SIZE_X * CHUNK_SIZE_Z];
+        tickList = new Int2ObjectArrayMap<>();
 
         for (int i = 0; i < sections.length; i++) {
             sections[i] = new ChunkSection(this, i);
@@ -92,6 +97,19 @@ public class Chunk
         heightMap[z * CHUNK_SIZE_X + x] = (short) y;
     }
 
+    public void addTick(final @NotNull NextTickListEntry entry) {
+        final @NotNull NextTickListEntry cloned = entry.clone();
+        cloned.x = cloned.x & (CHUNK_SIZE_X - 1);
+        cloned.z = cloned.z & (CHUNK_SIZE_Z - 1);
+        final int pos = NextTickListEntry.compressChunkCoord((short) entry.x, (short) entry.y, (short) entry.z);
+        this.tickList.put(pos, cloned);
+    }
+
+    public void dropTick(final @NotNull NextTickListEntry entry) {
+        final int pos = NextTickListEntry.compressChunkCoord((short) entry.x, (short) entry.y, (short) entry.z);
+        this.tickList.remove(pos);
+    }
+
     public void recalcHeightmapOnly()
     {
         int acc = 0;
@@ -522,7 +540,7 @@ public class Chunk
         setTileEntity(x, y, z, tileEntity);
         if(isLoaded)
         {
-            world.loadedTileEntityList.add(tileEntity);
+            world.tileEntityList.add(tileEntity);
         }
     }
 
@@ -582,6 +600,34 @@ public class Chunk
         for (ChunkSection section : sections) {
             section.onLoad(world);
         }
+
+        // Perform time correction on delayed ticks
+        if (this.ticksOnUnload >= 0) {
+            final long oldTime = this.ticksOnUnload;
+            final long newTime = this.world.getWorldTime();
+            for (final @NotNull NextTickListEntry entry : this.tickList.values()) {
+                final long ticksRemaining = entry.getEndTime() - oldTime;
+                entry.creationTime = newTime;
+                entry.delay = ticksRemaining;
+            }
+        }
+
+        if (this.xPosition == 0 && this.zPosition == 0) {
+            System.out.println("Loaded 0, 0");
+        }
+
+        this.scheduleLoadedUpdates();
+    }
+
+    private void scheduleLoadedUpdates() {
+        final @NotNull Int2ObjectMap<NextTickListEntry> oldTickList = this.tickList;
+        this.tickList = new Int2ObjectArrayMap<>();
+
+        final int worldX = this.xPosition * CHUNK_SIZE_X;
+        final int worldZ = this.zPosition * CHUNK_SIZE_Z;
+        for (final @NotNull NextTickListEntry entry : oldTickList.values()) {
+            this.world.scheduleBlockUpdate(worldX + entry.x, entry.y, worldZ + entry.z, entry.blockId, entry.delay, true);
+        }
     }
 
     public void onUnload()
@@ -596,6 +642,10 @@ public class Chunk
         for (ChunkSection section : sections) {
             section.onUnload(world);
         }
+
+        if (this.xPosition == 0 && this.zPosition == 0) {
+            System.out.println("Unloaded 0, 0");
+        }
     }
 
     public void setChunkModified()
diff --git a/game/core/src/main/java/net/minecraft/core/world/chunk/ChunkLoaderLegacy.java b/game/core/src/main/java/net/minecraft/core/world/chunk/ChunkLoaderLegacy.java
index 4b0154c8b..1914a817b 100644
--- a/game/core/src/main/java/net/minecraft/core/world/chunk/ChunkLoaderLegacy.java
+++ b/game/core/src/main/java/net/minecraft/core/world/chunk/ChunkLoaderLegacy.java
@@ -1,6 +1,7 @@
 package net.minecraft.core.world.chunk;
 
 import com.mojang.logging.LogUtils;
+import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap;
 import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
 import net.minecraft.core.Global;
 import net.minecraft.core.block.entity.TileEntityDispatcher;
@@ -11,10 +12,7 @@ import com.mojang.nbt.tags.Tag;
 import com.mojang.nbt.tags.CompoundTag;
 import com.mojang.nbt.tags.ListTag;
 import com.mojang.nbt.NbtIo;
-import net.minecraft.core.world.chunk.reader.ChunkReader;
-import net.minecraft.core.world.chunk.reader.ChunkReaderLegacy;
-import net.minecraft.core.world.chunk.reader.ChunkReaderVersion1;
-import net.minecraft.core.world.chunk.reader.ChunkReaderVersion2;
+import net.minecraft.core.world.chunk.reader.*;
 import net.minecraft.core.world.chunk.writer.ChunkWriter;
 import net.minecraft.core.world.data.ChunkNibbleArray;
 import net.minecraft.core.world.data.ChunkUnsignedByteArray;
@@ -172,12 +170,13 @@ public class ChunkLoaderLegacy
         // Write tags
         writer.putXPos(chunk.xPosition);
         writer.putZPos(chunk.zPosition);
-        writer.putLastUpdate(world.getWorldTime());
+        writer.putTicksOnUnload(world.getWorldTime());
         writer.putHeightMap(chunk.heightMap);
         writer.putAverageBlockHeight(chunk.averageBlockHeight);
         writer.putTerrainPopulated(chunk.isTerrainPopulated);
         writer.putTemperatureMap(chunk.temperature);
         writer.putHumidityMap(chunk.humidity);
+        writer.putTickList(chunk.tickList);
 
         // Write sections
         for (int i = 0; i < Chunk.CHUNK_SECTIONS; i++) {
@@ -246,11 +245,13 @@ public class ChunkLoaderLegacy
         int x = reader.getX();
         int z = reader.getZ();
         Chunk chunk = new Chunk(world, x, z);
+        chunk.ticksOnUnload = reader.getTicksOnUnload();
         chunk.heightMap = reader.getHeightMap();
         chunk.averageBlockHeight = reader.getAverageBlockHeight();
         chunk.isTerrainPopulated = reader.getIsTerrainPopulated();
         chunk.temperature = reader.getTemperatureMap();
         chunk.humidity = reader.getHumidityMap();
+        chunk.tickList = reader.getTickList();
 
         // Read registries
         Int2ObjectMap<String> biomeRegistry = reader.getBiomeRegistry();
@@ -276,6 +277,9 @@ public class ChunkLoaderLegacy
             chunk.humidity = new double[256];
             Arrays.fill(chunk.humidity, Double.NEGATIVE_INFINITY);
         }
+        if (chunk.tickList == null) {
+            chunk.tickList = new Int2ObjectArrayMap<>();
+        }
 
         // Read entities
         ListTag entityListTag = tag.getList("Entities");
@@ -347,6 +351,8 @@ public class ChunkLoaderLegacy
                 return new ChunkReaderVersion1(world, tag);
             case 2:
                 return new ChunkReaderVersion2(world, tag);
+            case 3:
+                return new ChunkReaderVersion3(world, tag);
             default:
                 return new ChunkReaderLegacy(world, tag);
         }
diff --git a/game/core/src/main/java/net/minecraft/core/world/chunk/reader/ChunkReader.java b/game/core/src/main/java/net/minecraft/core/world/chunk/reader/ChunkReader.java
index 17fb38176..5c236d4a2 100644
--- a/game/core/src/main/java/net/minecraft/core/world/chunk/reader/ChunkReader.java
+++ b/game/core/src/main/java/net/minecraft/core/world/chunk/reader/ChunkReader.java
@@ -2,33 +2,49 @@ package net.minecraft.core.world.chunk.reader;
 
 import com.mojang.nbt.tags.CompoundTag;
 import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
+import net.minecraft.core.NextTickListEntry;
 import net.minecraft.core.world.World;
 import net.minecraft.core.world.data.ChunkNibbleArray;
 import net.minecraft.core.world.data.ChunkUnsignedByteArray;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 
-import java.util.Map;
+public abstract class ChunkReader {
+    protected final @NotNull CompoundTag tag;
+    protected final @Nullable World world;
 
-public abstract class ChunkReader
-{
-    protected final CompoundTag tag;
-    protected final World world;
-
-    public ChunkReader(World world, CompoundTag chunkTag)
-    {
+    public ChunkReader(final @Nullable World world, final @NotNull CompoundTag chunkTag) {
         this.world = world;
         this.tag = chunkTag;
     }
+
     public abstract int getX();
+
     public abstract int getZ();
-    public abstract short[] getBlocks(int ySection);
-    public abstract ChunkUnsignedByteArray getData(int ySection);
-    public abstract ChunkNibbleArray getSkyLight(int ySection);
-    public abstract ChunkNibbleArray getBlockLight(int ySection);
-    public abstract short[] getHeightMap();
+
+    public abstract long getTicksOnUnload();
+
+    public abstract short @Nullable [] getBlocks(int ySection);
+
+    public abstract @Nullable ChunkUnsignedByteArray getData(int ySection);
+
+    public abstract @Nullable ChunkNibbleArray getSkyLight(int ySection);
+
+    public abstract @Nullable ChunkNibbleArray getBlockLight(int ySection);
+
+    public abstract short @Nullable [] getHeightMap();
+
     public abstract int getAverageBlockHeight();
+
     public abstract boolean getIsTerrainPopulated();
-    public abstract double[] getTemperatureMap();
-    public abstract double[] getHumidityMap();
-    public abstract byte[] getBiomeMap(int ySection, Int2ObjectMap<String> biomeRegistry);
-    public abstract Int2ObjectMap<String> getBiomeRegistry();
+
+    public abstract double @Nullable [] getTemperatureMap();
+
+    public abstract double @Nullable [] getHumidityMap();
+
+    public abstract byte @Nullable [] getBiomeMap(int ySection, @NotNull Int2ObjectMap<String> biomeRegistry);
+
+    public abstract @Nullable Int2ObjectMap<String> getBiomeRegistry();
+
+    public abstract @Nullable Int2ObjectMap<NextTickListEntry> getTickList();
 }
diff --git a/game/core/src/main/java/net/minecraft/core/world/chunk/reader/ChunkReaderLegacy.java b/game/core/src/main/java/net/minecraft/core/world/chunk/reader/ChunkReaderLegacy.java
index 092a9b4fe..ba7343ad4 100644
--- a/game/core/src/main/java/net/minecraft/core/world/chunk/reader/ChunkReaderLegacy.java
+++ b/game/core/src/main/java/net/minecraft/core/world/chunk/reader/ChunkReaderLegacy.java
@@ -2,11 +2,15 @@ package net.minecraft.core.world.chunk.reader;
 
 import com.mojang.nbt.tags.CompoundTag;
 import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
+import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
+import net.minecraft.core.NextTickListEntry;
 import net.minecraft.core.world.World;
 import net.minecraft.core.world.chunk.Chunk;
 import net.minecraft.core.world.chunk.ChunkSection;
 import net.minecraft.core.world.data.ChunkNibbleArray;
 import net.minecraft.core.world.data.ChunkUnsignedByteArray;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 
 import java.util.Map;
 
@@ -51,7 +55,13 @@ public class ChunkReaderLegacy
     }
 
     @Override
-    public short[] getBlocks(int ySection)
+    public long getTicksOnUnload() {
+        // Not supported
+        return -1;
+    }
+
+    @Override
+    public short @NotNull [] getBlocks(int ySection)
     {
         short[] newBlocks = new short[Chunk.CHUNK_SIZE_X * ChunkSection.SECTION_SIZE_Y * Chunk.CHUNK_SIZE_Z];
 
@@ -71,7 +81,7 @@ public class ChunkReaderLegacy
     }
 
     @Override
-    public ChunkUnsignedByteArray getData(int ySection)
+    public @NotNull ChunkUnsignedByteArray getData(int ySection)
     {
         byte[] newData = new byte[Chunk.CHUNK_SIZE_X * ChunkSection.SECTION_SIZE_Y * Chunk.CHUNK_SIZE_Z];
         ChunkUnsignedByteArray array = new ChunkUnsignedByteArray(Chunk.CHUNK_SIZE_X, ChunkSection.SECTION_SIZE_Y, Chunk.CHUNK_SIZE_Z, newData);
@@ -92,7 +102,7 @@ public class ChunkReaderLegacy
     }
 
     @Override
-    public ChunkNibbleArray getSkyLight(int ySection)
+    public @NotNull ChunkNibbleArray getSkyLight(int ySection)
     {
         byte[] newSkyLight = new byte[(Chunk.CHUNK_SIZE_X * ChunkSection.SECTION_SIZE_Y * Chunk.CHUNK_SIZE_Z) >> 1];
         ChunkNibbleArray array = new ChunkNibbleArray(Chunk.CHUNK_SIZE_X, ChunkSection.SECTION_SIZE_Y, Chunk.CHUNK_SIZE_Z, newSkyLight);
@@ -121,7 +131,7 @@ public class ChunkReaderLegacy
     }
 
     @Override
-    public ChunkNibbleArray getBlockLight(int ySection)
+    public @NotNull ChunkNibbleArray getBlockLight(int ySection)
     {
         byte[] newBlockLight = new byte[(Chunk.CHUNK_SIZE_X * ChunkSection.SECTION_SIZE_Y * Chunk.CHUNK_SIZE_Z) >> 1];
         ChunkNibbleArray array = new ChunkNibbleArray(Chunk.CHUNK_SIZE_X, ChunkSection.SECTION_SIZE_Y, Chunk.CHUNK_SIZE_Z, newBlockLight);
@@ -150,7 +160,7 @@ public class ChunkReaderLegacy
     }
 
     @Override
-    public short[] getHeightMap()
+    public short @NotNull [] getHeightMap()
     {
         return tag.getShortArrayOrDefault("HeightMap", null);
     }
@@ -183,7 +193,7 @@ public class ChunkReaderLegacy
     }
 
     @Override
-    public byte[] getBiomeMap(int ySection, Int2ObjectMap<String> biomeRegistry)
+    public byte[] getBiomeMap(int ySection, @NotNull Int2ObjectMap<String> biomeRegistry)
     {
         // Not supported
         return null;
@@ -194,4 +204,10 @@ public class ChunkReaderLegacy
         // Not supported
         return null;
     }
+
+    @Override
+    public @Nullable Int2ObjectMap<NextTickListEntry> getTickList() {
+        // Not supported
+        return null;
+    }
 }
diff --git a/game/core/src/main/java/net/minecraft/core/world/chunk/reader/ChunkReaderVersion1.java b/game/core/src/main/java/net/minecraft/core/world/chunk/reader/ChunkReaderVersion1.java
index a2cc78d2a..814e18d21 100644
--- a/game/core/src/main/java/net/minecraft/core/world/chunk/reader/ChunkReaderVersion1.java
+++ b/game/core/src/main/java/net/minecraft/core/world/chunk/reader/ChunkReaderVersion1.java
@@ -2,6 +2,8 @@ package net.minecraft.core.world.chunk.reader;
 
 import com.mojang.nbt.tags.CompoundTag;
 import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
+import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
+import net.minecraft.core.NextTickListEntry;
 import net.minecraft.core.data.registry.Registries;
 import net.minecraft.core.data.registry.Registry;
 import net.minecraft.core.world.World;
@@ -9,64 +11,67 @@ import net.minecraft.core.world.chunk.Chunk;
 import net.minecraft.core.world.chunk.ChunkSection;
 import net.minecraft.core.world.data.ChunkNibbleArray;
 import net.minecraft.core.world.data.ChunkUnsignedByteArray;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 
 import java.util.Map;
 
 public class ChunkReaderVersion1
-    extends ChunkReader
-{
+    extends ChunkReader {
     private final short[] blocks;
     private final byte[] data;
     private final byte[] skylight;
     private final byte[] blocklight;
     private final byte[] biome;
 
-    private static int makeBlockIndex(int x, int y, int z) {
+    private static int makeBlockIndex(final int x, final int y, final int z) {
         return (x * 16 * 256) + (z * 256) + y;
     }
 
-    public static int makeBiomeIndex(int x, int y, int z) {
+    public static int makeBiomeIndex(final int x, final int y, final int z) {
         return ((y >> 3) * 16 * 16) + (x * 16) + z;
     }
 
-    private static int makeNibbleIndex(int x, int y, int z) {
+    private static int makeNibbleIndex(final int x, final int y, final int z) {
         return (x * 16 * 256) + (z * 256) + y;
     }
 
-    public ChunkReaderVersion1(World world, CompoundTag tag)
-    {
+    public ChunkReaderVersion1(final @Nullable World world, final @NotNull CompoundTag tag) {
         super(world, tag);
 
-        blocks = tag.getShortArrayOrDefault("Blocks", null);
-        data = tag.getByteArrayOrDefault("Data", null);
-        skylight = tag.getByteArrayOrDefault("SkyLight", null);
-        blocklight = tag.getByteArrayOrDefault("BlockLight", null);
-        biome = tag.getByteArrayOrDefault("BiomeMap", null);
+        this.blocks = tag.getShortArrayOrDefault("Blocks", null);
+        this.data = tag.getByteArrayOrDefault("Data", null);
+        this.skylight = tag.getByteArrayOrDefault("SkyLight", null);
+        this.blocklight = tag.getByteArrayOrDefault("BlockLight", null);
+        this.biome = tag.getByteArrayOrDefault("BiomeMap", null);
     }
 
     @Override
-    public int getX()
-    {
-        return tag.getInteger("xPos");
+    public int getX() {
+        return this.tag.getInteger("xPos");
     }
 
     @Override
-    public int getZ()
-    {
-        return tag.getInteger("zPos");
+    public int getZ() {
+        return this.tag.getInteger("zPos");
     }
 
     @Override
-    public short[] getBlocks(int ySection)
-    {
-        short[] newBlocks = new short[Chunk.CHUNK_SIZE_X * ChunkSection.SECTION_SIZE_Y * Chunk.CHUNK_SIZE_Z];
+    public long getTicksOnUnload() {
+        // Not supported
+        return -1;
+    }
+
+    @Override
+    public short @NotNull [] getBlocks(final int ySection) {
+        final short[] newBlocks = new short[Chunk.CHUNK_SIZE_X * ChunkSection.SECTION_SIZE_Y * Chunk.CHUNK_SIZE_Z];
 
-        if (blocks != null && makeBlockIndex(15, (ySection * ChunkSection.SECTION_SIZE_Y) + 15, 15) < blocks.length) {
+        if (this.blocks != null && makeBlockIndex(15, (ySection * ChunkSection.SECTION_SIZE_Y) + 15, 15) < this.blocks.length) {
             for (int x = 0; x < 16; x++) {
                 for (int z = 0; z < 16; z++) {
                     for (int y = 0; y < 16; y++) {
-                        int yOffset = ySection * ChunkSection.SECTION_SIZE_Y;
-                        short id = this.blocks[makeBlockIndex(x, yOffset + y, z)];
+                        final int yOffset = ySection * ChunkSection.SECTION_SIZE_Y;
+                        final short id = this.blocks[makeBlockIndex(x, yOffset + y, z)];
                         newBlocks[ChunkSection.makeBlockIndex(x, y, z)] = id;
                     }
                 }
@@ -77,17 +82,16 @@ public class ChunkReaderVersion1
     }
 
     @Override
-    public ChunkUnsignedByteArray getData(int ySection)
-    {
-        byte[] newData = new byte[Chunk.CHUNK_SIZE_X * ChunkSection.SECTION_SIZE_Y * Chunk.CHUNK_SIZE_Z];
-        ChunkUnsignedByteArray array = new ChunkUnsignedByteArray(Chunk.CHUNK_SIZE_X, ChunkSection.SECTION_SIZE_Y, Chunk.CHUNK_SIZE_Z, newData);
+    public @NotNull ChunkUnsignedByteArray getData(final int ySection) {
+        final byte[] newData = new byte[Chunk.CHUNK_SIZE_X * ChunkSection.SECTION_SIZE_Y * Chunk.CHUNK_SIZE_Z];
+        final ChunkUnsignedByteArray array = new ChunkUnsignedByteArray(Chunk.CHUNK_SIZE_X, ChunkSection.SECTION_SIZE_Y, Chunk.CHUNK_SIZE_Z, newData);
 
-        if (data != null && makeBlockIndex(15, (ySection * ChunkSection.SECTION_SIZE_Y) + 15, 15) < data.length) {
+        if (this.data != null && makeBlockIndex(15, (ySection * ChunkSection.SECTION_SIZE_Y) + 15, 15) < this.data.length) {
             for (int x = 0; x < 16; x++) {
                 for (int z = 0; z < 16; z++) {
                     for (int y = 0; y < 16; y++) {
-                        int yOffset = ySection * ChunkSection.SECTION_SIZE_Y;
-                        byte data = this.data[makeBlockIndex(x, yOffset + y, z)];
+                        final int yOffset = ySection * ChunkSection.SECTION_SIZE_Y;
+                        final byte data = this.data[makeBlockIndex(x, yOffset + y, z)];
                         array.set(x, y, z, Byte.toUnsignedInt(data));
                     }
                 }
@@ -98,24 +102,23 @@ public class ChunkReaderVersion1
     }
 
     @Override
-    public ChunkNibbleArray getSkyLight(int ySection)
-    {
-        byte[] newSkyLight = new byte[(Chunk.CHUNK_SIZE_X * ChunkSection.SECTION_SIZE_Y * Chunk.CHUNK_SIZE_Z) >> 1];
-        ChunkNibbleArray array = new ChunkNibbleArray(Chunk.CHUNK_SIZE_X, ChunkSection.SECTION_SIZE_Y, Chunk.CHUNK_SIZE_Z, newSkyLight);
+    public @NotNull ChunkNibbleArray getSkyLight(final int ySection) {
+        final byte[] newSkyLight = new byte[(Chunk.CHUNK_SIZE_X * ChunkSection.SECTION_SIZE_Y * Chunk.CHUNK_SIZE_Z) >> 1];
+        final ChunkNibbleArray array = new ChunkNibbleArray(Chunk.CHUNK_SIZE_X, ChunkSection.SECTION_SIZE_Y, Chunk.CHUNK_SIZE_Z, newSkyLight);
 
-        if (skylight != null && makeNibbleIndex(15, (ySection * ChunkSection.SECTION_SIZE_Y) + 15, 15) < skylight.length * 2) {
+        if (this.skylight != null && makeNibbleIndex(15, (ySection * ChunkSection.SECTION_SIZE_Y) + 15, 15) < this.skylight.length * 2) {
             for (int x = 0; x < 16; x++) {
                 for (int z = 0; z < 16; z++) {
                     for (int y = 0; y < 16; y++) {
-                        int yOffset = ySection * ChunkSection.SECTION_SIZE_Y;
-                        int index = makeNibbleIndex(x, yOffset + y, z);
-                        int nibbleIndex = index >> 1;
-                        int nibblePart = index & 0x01;
-                        int nibble;
+                        final int yOffset = ySection * ChunkSection.SECTION_SIZE_Y;
+                        final int index = makeNibbleIndex(x, yOffset + y, z);
+                        final int nibbleIndex = index >> 1;
+                        final int nibblePart = index & 0x01;
+                        final int nibble;
                         if (nibblePart == 0) {
-                            nibble = Byte.toUnsignedInt(skylight[nibbleIndex]) & 0x0F;
+                            nibble = Byte.toUnsignedInt(this.skylight[nibbleIndex]) & 0x0F;
                         } else {
-                            nibble = (Byte.toUnsignedInt(skylight[nibbleIndex]) >> 4) & 0x0F;
+                            nibble = (Byte.toUnsignedInt(this.skylight[nibbleIndex]) >> 4) & 0x0F;
                         }
                         array.set(x, y, z, nibble);
                     }
@@ -127,24 +130,23 @@ public class ChunkReaderVersion1
     }
 
     @Override
-    public ChunkNibbleArray getBlockLight(int ySection)
-    {
-        byte[] newBlockLight = new byte[(Chunk.CHUNK_SIZE_X * ChunkSection.SECTION_SIZE_Y * Chunk.CHUNK_SIZE_Z) >> 1];
-        ChunkNibbleArray array = new ChunkNibbleArray(Chunk.CHUNK_SIZE_X, ChunkSection.SECTION_SIZE_Y, Chunk.CHUNK_SIZE_Z, newBlockLight);
+    public @NotNull ChunkNibbleArray getBlockLight(final int ySection) {
+        final byte[] newBlockLight = new byte[(Chunk.CHUNK_SIZE_X * ChunkSection.SECTION_SIZE_Y * Chunk.CHUNK_SIZE_Z) >> 1];
+        final ChunkNibbleArray array = new ChunkNibbleArray(Chunk.CHUNK_SIZE_X, ChunkSection.SECTION_SIZE_Y, Chunk.CHUNK_SIZE_Z, newBlockLight);
 
-        if (blocklight != null && makeNibbleIndex(15, (ySection * ChunkSection.SECTION_SIZE_Y) + 15, 15) < blocklight.length * 2) {
+        if (this.blocklight != null && makeNibbleIndex(15, (ySection * ChunkSection.SECTION_SIZE_Y) + 15, 15) < this.blocklight.length * 2) {
             for (int x = 0; x < 16; x++) {
                 for (int z = 0; z < 16; z++) {
                     for (int y = 0; y < 16; y++) {
-                        int yOffset = ySection * ChunkSection.SECTION_SIZE_Y;
-                        int index = makeNibbleIndex(x, yOffset + y, z);
-                        int nibbleIndex = index >> 1;
-                        int nibblePart = index & 0x01;
-                        int nibble;
+                        final int yOffset = ySection * ChunkSection.SECTION_SIZE_Y;
+                        final int index = makeNibbleIndex(x, yOffset + y, z);
+                        final int nibbleIndex = index >> 1;
+                        final int nibblePart = index & 0x01;
+                        final int nibble;
                         if (nibblePart == 0) {
-                            nibble = Byte.toUnsignedInt(blocklight[nibbleIndex]) & 0x0F;
+                            nibble = Byte.toUnsignedInt(this.blocklight[nibbleIndex]) & 0x0F;
                         } else {
-                            nibble = (Byte.toUnsignedInt(blocklight[nibbleIndex]) >> 4) & 0x0F;
+                            nibble = (Byte.toUnsignedInt(this.blocklight[nibbleIndex]) >> 4) & 0x0F;
                         }
                         array.set(x, y, z, nibble);
                     }
@@ -156,53 +158,47 @@ public class ChunkReaderVersion1
     }
 
     @Override
-    public short[] getHeightMap()
-    {
-        return tag.getShortArrayOrDefault("HeightMap", null);
+    public short @NotNull [] getHeightMap() {
+        return this.tag.getShortArrayOrDefault("HeightMap", null);
     }
 
     @Override
-    public int getAverageBlockHeight()
-    {
-        return tag.getInteger("AverageBlockHeight");
+    public int getAverageBlockHeight() {
+        return this.tag.getInteger("AverageBlockHeight");
     }
 
     @Override
-    public boolean getIsTerrainPopulated()
-    {
-        return tag.getBoolean("TerrainPopulated");
+    public boolean getIsTerrainPopulated() {
+        return this.tag.getBoolean("TerrainPopulated");
     }
 
     @Override
-    public double[] getTemperatureMap()
-    {
-        return tag.getDoubleArray("TemperatureMap");
+    public double[] getTemperatureMap() {
+        return this.tag.getDoubleArray("TemperatureMap");
     }
 
     @Override
-    public double[] getHumidityMap()
-    {
-        return tag.getDoubleArray("HumidityMap");
+    public double[] getHumidityMap() {
+        return this.tag.getDoubleArray("HumidityMap");
     }
 
     @Override
-    public byte[] getBiomeMap(int ySection, Int2ObjectMap<String> biomeRegistry)
-    {
-        byte[] newBiomes = new byte[Chunk.CHUNK_SIZE_X * (ChunkSection.SECTION_SIZE_Y >> 3) * Chunk.CHUNK_SIZE_Z];
+    public byte[] getBiomeMap(final int ySection, @NotNull final Int2ObjectMap<String> biomeRegistry) {
+        final byte[] newBiomes = new byte[Chunk.CHUNK_SIZE_X * (ChunkSection.SECTION_SIZE_Y >> 3) * Chunk.CHUNK_SIZE_Z];
 
-        if (biome != null && makeBiomeIndex(15, (ySection * ChunkSection.SECTION_SIZE_Y) + 15, 15) < biome.length) {
+        if (this.biome != null && makeBiomeIndex(15, (ySection * ChunkSection.SECTION_SIZE_Y) + 15, 15) < this.biome.length) {
             for (int x = 0; x < 16; x++) {
                 for (int z = 0; z < 16; z++) {
                     for (int y = 0; y < 16; y++) {
-                        int yOffset = ySection * ChunkSection.SECTION_SIZE_Y;
-                        byte oldBiomeId = biome[makeBiomeIndex(x, yOffset + y, z)];
+                        final int yOffset = ySection * ChunkSection.SECTION_SIZE_Y;
+                        final byte oldBiomeId = this.biome[makeBiomeIndex(x, yOffset + y, z)];
                         if (oldBiomeId < 0) {
                             newBiomes[ChunkSection.makeBiomeIndex(x, y, z)] = oldBiomeId;
                         } else {
                             try {
-                                byte newBiomeId = (byte) Registries.BIOMES.getNumericIdOfItem(Registries.BIOMES.getItem(biomeRegistry.get((int) oldBiomeId)));
+                                final byte newBiomeId = (byte) Registries.BIOMES.getNumericIdOfItem(Registries.BIOMES.getItem(biomeRegistry.get(oldBiomeId)));
                                 newBiomes[ChunkSection.makeBiomeIndex(x, y, z)] = newBiomeId;
-                            } catch (NullPointerException n){
+                            } catch (final NullPointerException n) {
                                 newBiomes[ChunkSection.makeBiomeIndex(x, y, z)] = -1;
                             }
                         }
@@ -216,19 +212,23 @@ public class ChunkReaderVersion1
 
     @Override
     public Int2ObjectMap<String> getBiomeRegistry() {
-        CompoundTag registriesTag = tag.getCompoundOrDefault("Registries", null);
+        final CompoundTag registriesTag = this.tag.getCompoundOrDefault("Registries", null);
         CompoundTag biomesTag = null;
-        if (registriesTag != null)
-        {
+        if (registriesTag != null) {
             biomesTag = registriesTag.getCompoundOrDefault("Biomes", null);
         }
 
-        if (registriesTag == null || biomesTag == null)
-        {
+        if (registriesTag == null || biomesTag == null) {
             // Give up on conversion as we can't really interpret this data meaningfully
             return null;
         }
 
         return Registry.readIdMapFromTag(biomesTag);
     }
+
+    @Override
+    public @Nullable Int2ObjectMap<NextTickListEntry> getTickList() {
+        // Not supported
+        return null;
+    }
 }
diff --git a/game/core/src/main/java/net/minecraft/core/world/chunk/reader/ChunkReaderVersion2.java b/game/core/src/main/java/net/minecraft/core/world/chunk/reader/ChunkReaderVersion2.java
index 51b3725c3..4de091930 100644
--- a/game/core/src/main/java/net/minecraft/core/world/chunk/reader/ChunkReaderVersion2.java
+++ b/game/core/src/main/java/net/minecraft/core/world/chunk/reader/ChunkReaderVersion2.java
@@ -4,6 +4,8 @@ import com.mojang.nbt.tags.CompoundTag;
 import com.mojang.nbt.tags.ListTag;
 import com.mojang.nbt.tags.Tag;
 import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
+import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
+import net.minecraft.core.NextTickListEntry;
 import net.minecraft.core.data.registry.Registries;
 import net.minecraft.core.data.registry.Registry;
 import net.minecraft.core.world.World;
@@ -11,6 +13,8 @@ import net.minecraft.core.world.chunk.Chunk;
 import net.minecraft.core.world.chunk.ChunkSection;
 import net.minecraft.core.world.data.ChunkNibbleArray;
 import net.minecraft.core.world.data.ChunkUnsignedByteArray;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 
 import java.util.Arrays;
 import java.util.Map;
@@ -50,7 +54,13 @@ public class ChunkReaderVersion2
     }
 
     @Override
-    public short[] getBlocks(int ySection) {
+    public long getTicksOnUnload() {
+        // Not supported
+        return -1;
+    }
+
+    @Override
+    public short @NotNull [] getBlocks(int ySection) {
         short[] blocks = null;
         CompoundTag sectionTag = findSection(ySection);
         if (sectionTag != null) {
@@ -61,7 +71,7 @@ public class ChunkReaderVersion2
     }
 
     @Override
-    public ChunkUnsignedByteArray getData(int ySection) {
+    public @NotNull ChunkUnsignedByteArray getData(int ySection) {
         byte[] data = null;
         CompoundTag sectionTag = findSection(ySection);
         if (sectionTag != null) {
@@ -76,7 +86,7 @@ public class ChunkReaderVersion2
     }
 
     @Override
-    public ChunkNibbleArray getSkyLight(int ySection) {
+    public @NotNull ChunkNibbleArray getSkyLight(int ySection) {
         byte[] skylight = null;
         CompoundTag sectionTag = findSection(ySection);
         if (sectionTag != null) {
@@ -91,7 +101,7 @@ public class ChunkReaderVersion2
     }
 
     @Override
-    public ChunkNibbleArray getBlockLight(int ySection) {
+    public @NotNull ChunkNibbleArray getBlockLight(int ySection) {
         byte[] blocklight = null;
         CompoundTag sectionTag = findSection(ySection);
         if (sectionTag != null) {
@@ -106,7 +116,7 @@ public class ChunkReaderVersion2
     }
 
     @Override
-    public short[] getHeightMap() {
+    public short @NotNull [] getHeightMap() {
         return tag.getShortArrayOrDefault("HeightMap", null);
     }
 
@@ -131,7 +141,7 @@ public class ChunkReaderVersion2
     }
 
     @Override
-    public byte[] getBiomeMap(int ySection, Int2ObjectMap<String> biomeRegistry) {
+    public byte[] getBiomeMap(int ySection, @NotNull Int2ObjectMap<String> biomeRegistry) {
         byte[] biomeMap = null;
         CompoundTag sectionTag = findSection(ySection);
         if (sectionTag != null) {
@@ -174,4 +184,10 @@ public class ChunkReaderVersion2
 
         return Registry.readIdMapFromTag(biomesTag);
     }
+
+    @Override
+    public @Nullable Int2ObjectMap<NextTickListEntry> getTickList() {
+        // Not supported
+        return null;
+    }
 }
diff --git a/game/core/src/main/java/net/minecraft/core/world/chunk/reader/ChunkReaderVersion3.java b/game/core/src/main/java/net/minecraft/core/world/chunk/reader/ChunkReaderVersion3.java
new file mode 100644
index 000000000..ba5d71691
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/world/chunk/reader/ChunkReaderVersion3.java
@@ -0,0 +1,207 @@
+package net.minecraft.core.world.chunk.reader;
+
+import com.mojang.nbt.tags.CompoundTag;
+import com.mojang.nbt.tags.ListTag;
+import com.mojang.nbt.tags.Tag;
+import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap;
+import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
+import it.unimi.dsi.fastutil.longs.Long2ObjectArrayMap;
+import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
+import net.minecraft.core.NextTickListEntry;
+import net.minecraft.core.data.registry.Registries;
+import net.minecraft.core.data.registry.Registry;
+import net.minecraft.core.world.World;
+import net.minecraft.core.world.chunk.Chunk;
+import net.minecraft.core.world.chunk.ChunkSection;
+import net.minecraft.core.world.data.ChunkNibbleArray;
+import net.minecraft.core.world.data.ChunkUnsignedByteArray;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.Arrays;
+
+public class ChunkReaderVersion3
+    extends ChunkReader {
+    private final @NotNull ListTag sectionsTag;
+
+    public ChunkReaderVersion3(final @Nullable World world, final @NotNull CompoundTag chunkTag) {
+        super(world, chunkTag);
+
+        this.sectionsTag = chunkTag.getList("Sections");
+    }
+
+    private @Nullable CompoundTag findSection(final int y) {
+        for (final @NotNull Tag<?> tag : this.sectionsTag) {
+            if (tag instanceof CompoundTag) {
+                final @NotNull CompoundTag cTag = (CompoundTag) tag;
+                final int tagY = cTag.getInteger("yPos");
+                if (tagY == y) {
+                    return cTag;
+                }
+            }
+        }
+
+        return null;
+    }
+
+    @Override
+    public int getX() {
+        return this.tag.getInteger("xPos");
+    }
+
+    @Override
+    public int getZ() {
+        return this.tag.getInteger("zPos");
+    }
+
+    @Override
+    public long getTicksOnUnload() {
+        return this.tag.getLong("TicksOnUnload");
+    }
+
+    @Override
+    public short @Nullable [] getBlocks(final int ySection) {
+        short @Nullable [] blocks = null;
+        final @Nullable CompoundTag sectionTag = this.findSection(ySection);
+        if (sectionTag != null) {
+            blocks = sectionTag.getShortArrayOrDefault("Blocks", null);
+        }
+
+        return blocks;
+    }
+
+    @Override
+    public @Nullable ChunkUnsignedByteArray getData(final int ySection) {
+        byte[] data = null;
+        final @Nullable CompoundTag sectionTag = this.findSection(ySection);
+        if (sectionTag != null) {
+            data = sectionTag.getByteArrayOrDefault("Data", null);
+        }
+
+        if (data == null) {
+            return null;
+        }
+
+        return new ChunkUnsignedByteArray(Chunk.CHUNK_SIZE_X, ChunkSection.SECTION_SIZE_Y, Chunk.CHUNK_SIZE_Z, data);
+    }
+
+    @Override
+    public @Nullable ChunkNibbleArray getSkyLight(final int ySection) {
+        byte @Nullable [] skylight = null;
+        final CompoundTag sectionTag = this.findSection(ySection);
+        if (sectionTag != null) {
+            skylight = sectionTag.getByteArrayOrDefault("SkyLight", null);
+        }
+
+        if (skylight == null) {
+            return null;
+        }
+
+        return new ChunkNibbleArray(Chunk.CHUNK_SIZE_X, ChunkSection.SECTION_SIZE_Y, Chunk.CHUNK_SIZE_Z, skylight);
+    }
+
+    @Override
+    public @Nullable ChunkNibbleArray getBlockLight(final int ySection) {
+        byte @Nullable [] blocklight = null;
+        final CompoundTag sectionTag = this.findSection(ySection);
+        if (sectionTag != null) {
+            blocklight = sectionTag.getByteArrayOrDefault("BlockLight", null);
+        }
+
+        if (blocklight == null) {
+            return null;
+        }
+
+        return new ChunkNibbleArray(Chunk.CHUNK_SIZE_X, ChunkSection.SECTION_SIZE_Y, Chunk.CHUNK_SIZE_Z, blocklight);
+    }
+
+    @Override
+    public short @Nullable [] getHeightMap() {
+        return this.tag.getShortArrayOrDefault("HeightMap", null);
+    }
+
+    @Override
+    public int getAverageBlockHeight() {
+        return this.tag.getInteger("AverageBlockHeight");
+    }
+
+    @Override
+    public boolean getIsTerrainPopulated() {
+        return this.tag.getBoolean("TerrainPopulated");
+    }
+
+    @Override
+    public double @Nullable [] getTemperatureMap() {
+        return this.tag.getDoubleArray("TemperatureMap");
+    }
+
+    @Override
+    public double @Nullable [] getHumidityMap() {
+        return this.tag.getDoubleArray("HumidityMap");
+    }
+
+    @Override
+    public byte @Nullable [] getBiomeMap(final int ySection, @NotNull final Int2ObjectMap<String> biomeRegistry) {
+        byte[] biomeMap = null;
+        final CompoundTag sectionTag = this.findSection(ySection);
+        if (sectionTag != null) {
+            biomeMap = sectionTag.getByteArrayOrDefault("BiomeMap", null);
+        }
+
+        if (biomeMap == null) {
+            biomeMap = new byte[Chunk.CHUNK_SIZE_X * (ChunkSection.SECTION_SIZE_Y >> 3) * Chunk.CHUNK_SIZE_Z];
+            Arrays.fill(biomeMap, (byte) -1);
+        } else {
+            for (int i = 0; i < biomeMap.length; i++) {
+                final byte oldBiomeId = biomeMap[i];
+                if (oldBiomeId >= 0) {
+                    try {
+                        biomeMap[i] = (byte) Registries.BIOMES.getNumericIdOfItem(Registries.BIOMES.getItem(biomeRegistry.get(oldBiomeId)));
+                    } catch (final NullPointerException n) {
+                        biomeMap[i] = -1;
+                    }
+                }
+            }
+        }
+
+        return biomeMap;
+    }
+
+    @Override
+    public @Nullable Int2ObjectMap<String> getBiomeRegistry() {
+        final @Nullable CompoundTag registriesTag = this.tag.getCompoundOrDefault("Registries", null);
+        CompoundTag biomesTag = null;
+        if (registriesTag != null) {
+            biomesTag = registriesTag.getCompoundOrDefault("Biomes", null);
+        }
+
+        if (registriesTag == null || biomesTag == null) {
+            // Give up on conversion as we can't really interpret this data meaningfully
+            return null;
+        }
+
+        return Registry.readIdMapFromTag(biomesTag);
+    }
+
+    @Override
+    public @Nullable Int2ObjectMap<NextTickListEntry> getTickList() {
+        final @Nullable ListTag ticksTag = this.tag.getListOrDefault("TicksList", null);
+        if (ticksTag == null) {
+            return null;
+        }
+
+        final @NotNull Int2ObjectMap<NextTickListEntry> entries = new Int2ObjectArrayMap<>();
+        for (final @NotNull Tag<?> tag : ticksTag) {
+            if (tag instanceof CompoundTag) {
+                final @NotNull CompoundTag tickTag = (CompoundTag) tag;
+                final @Nullable NextTickListEntry entry = NextTickListEntry.deserialize(tickTag);
+                if (entry != null) {
+                    final int compressed = NextTickListEntry.compressChunkCoord((short) entry.x, (short) entry.y, (short) entry.z);
+                    entries.put(compressed, entry);
+                }
+            }
+        }
+
+        return entries;
+    }
+}
diff --git a/game/core/src/main/java/net/minecraft/core/world/chunk/writer/ChunkWriter.java b/game/core/src/main/java/net/minecraft/core/world/chunk/writer/ChunkWriter.java
index 7b577d987..d671296c8 100644
--- a/game/core/src/main/java/net/minecraft/core/world/chunk/writer/ChunkWriter.java
+++ b/game/core/src/main/java/net/minecraft/core/world/chunk/writer/ChunkWriter.java
@@ -3,11 +3,14 @@ package net.minecraft.core.world.chunk.writer;
 import com.mojang.nbt.tags.CompoundTag;
 import com.mojang.nbt.tags.ListTag;
 import com.mojang.nbt.tags.Tag;
+import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
+import net.minecraft.core.NextTickListEntry;
 import net.minecraft.core.data.registry.Registries;
 import net.minecraft.core.data.registry.Registry;
 import net.minecraft.core.world.World;
 import net.minecraft.core.world.data.ChunkNibbleArray;
 import net.minecraft.core.world.data.ChunkUnsignedByteArray;
+import org.jetbrains.annotations.NotNull;
 
 public class ChunkWriter
 {
@@ -56,9 +59,8 @@ public class ChunkWriter
         tag.putInt("zPos", zPos);
     }
 
-    public void putLastUpdate(long lastUpdate)
-    {
-        tag.putLong("LastUpdate", lastUpdate);
+    public void putTicksOnUnload(final long ticksAtUnload) {
+        this.tag.putLong("TicksOnUnload", ticksAtUnload);
     }
 
     public void putBlocks(int ySection, short[] blocks)
@@ -118,4 +120,13 @@ public class ChunkWriter
         registriesTag.putCompound("Biomes", biomesTag);
         tag.putCompound("Registries", registriesTag);
     }
+
+    public void putTickList(final @NotNull Int2ObjectMap<NextTickListEntry> entries) {
+        final @NotNull ListTag ticksTag = new ListTag();
+        for (final @NotNull NextTickListEntry entry : entries.values()) {
+            ticksTag.addTag(NextTickListEntry.serialize(entry));
+        }
+
+        this.tag.putList("TicksList", ticksTag);
+    }
 }
diff --git a/game/server/src/main/java/net/minecraft/server/entity/player/PlayerServer.java b/game/server/src/main/java/net/minecraft/server/entity/player/PlayerServer.java
index c0a1ff409..cc7e6e520 100644
--- a/game/server/src/main/java/net/minecraft/server/entity/player/PlayerServer.java
+++ b/game/server/src/main/java/net/minecraft/server/entity/player/PlayerServer.java
@@ -424,10 +424,6 @@ public class PlayerServer extends Player implements ContainerListener {
         }
     }
 
-    public void func_22068_s()
-    {
-    }
-
     @Override
 	public EnumSleepStatus sleepInBedAt(int x, int y, int z)
     {
@@ -741,7 +737,7 @@ public class PlayerServer extends Player implements ContainerListener {
     @Override
     public void fling(final double xd, final double yd, final double zd, final float pushTime) {
         super.fling(xd, yd, zd, pushTime);
-        this.playerNetServerHandler.sendPacket(new PacketMovePlayer.PosRot(this.x, this.y + 1.625, this.z, this.yRot, this.xRot, false));
+        this.playerNetServerHandler.sendPacket(new PacketMovePlayer.PosRot(this.x, this.y, this.z, this.yRot, this.xRot, false));
         this.playerNetServerHandler.sendPacket(new PacketEntityFling(this.id, this.xd, this.yd, this.zd, this.pushTime, this.pushesThisTick));
     }
 
diff --git a/game/server/src/main/java/net/minecraft/server/net/PlayerList.java b/game/server/src/main/java/net/minecraft/server/net/PlayerList.java
index d7689c58f..8495a5036 100644
--- a/game/server/src/main/java/net/minecraft/server/net/PlayerList.java
+++ b/game/server/src/main/java/net/minecraft/server/net/PlayerList.java
@@ -249,7 +249,6 @@ public class PlayerList
         worldserver.entityJoinedWorld(newPlayer);
         playerEntities.add(newPlayer);
         newPlayer.setupCraftingInventoryListener();
-        newPlayer.func_22068_s();
         newPlayer.playerNetServerHandler.sendPacket(new PacketGameRule(server.getDimensionWorld(0).getLevelData().getGameRules()));
         return newPlayer;
     }
diff --git a/game/server/src/main/java/net/minecraft/server/net/handler/PacketHandlerServer.java b/game/server/src/main/java/net/minecraft/server/net/handler/PacketHandlerServer.java
index 5db443aa2..1c2d2615f 100644
--- a/game/server/src/main/java/net/minecraft/server/net/handler/PacketHandlerServer.java
+++ b/game/server/src/main/java/net/minecraft/server/net/handler/PacketHandlerServer.java
@@ -127,14 +127,10 @@ public class PacketHandlerServer extends PacketHandler
 
         if (!hasMoved)
         {
-            double dx = packet.x - lastPosX;
-            double dy = packet.y - lastPosY;
-            double dz = packet.z - lastPosZ;
-            dx *= dx;
-            dy *= dy;
-            dz *= dz;
-            if (dx * dx < 0.01D && dy * dy < 0.01D && dz * dz < 0.01D)
-            {
+            double dx = packet.x - playerEntity.x;
+            double dy = packet.y - playerEntity.y;
+            double dz = packet.z - playerEntity.z;
+            if (dx * dx > 0.01D || dy * dy > 0.01D || dz * dz > 0.01D) {
                 hasMoved = true;
             }
         }
@@ -208,7 +204,7 @@ public class PacketHandlerServer extends PacketHandler
             {
                 //kickPlayer("Illegal position");
                 LOGGER.warn("{} tried to move to an illegal position", playerEntity.username);
-                teleportAndRotate(lastPosX, lastPosY, lastPosZ, yRot, xRot);
+                teleport(lastPosX, lastPosY, lastPosZ);
                 return;
             }
         }
@@ -229,13 +225,13 @@ public class PacketHandlerServer extends PacketHandler
         {
             // This doesn't mean they are necessarily hacking, it's easy to do by hitting the vanilla speed limit
             LOGGER.warn("{} moved too quickly!", playerEntity.username);
-            teleportAndRotate(lastPosX, lastPosY, lastPosZ, yRot, xRot);
+            teleport(lastPosX, lastPosY, lastPosZ);
             //kickPlayer("You moved too quickly :( (Hacking?)");
             return;
         }
         // Time to trying and move like they did
         float bb_expand = 0.0625F;
-        boolean insideBlockOld = worldserver.getCubes(playerEntity, playerEntity.bb.copy().getInsetBoundingBox(bb_expand, bb_expand, bb_expand)).isEmpty();
+        boolean insideBlockOld = worldserver.getCubes(playerEntity, playerEntity.bb.copyPooled().getInsetBoundingBox(bb_expand, bb_expand, bb_expand)).isEmpty();
         playerEntity.move(dx, dy, dz);
         // Get the difference in intended move and actual move
         dx = newPosX - playerEntity.x;
@@ -252,7 +248,7 @@ public class PacketHandlerServer extends PacketHandler
             LOGGER.warn("Expected {}, {}, {}", playerEntity.x, playerEntity.y, playerEntity.z);
         }
         playerEntity.absMoveTo(newPosX, newPosY, newPosZ, yRot, xRot);
-        boolean insideBlockNew = worldserver.getCubes(playerEntity, playerEntity.bb.copy().getInsetBoundingBox(bb_expand, bb_expand, bb_expand)).isEmpty();
+        boolean insideBlockNew = worldserver.getCubes(playerEntity, playerEntity.bb.copyPooled().getInsetBoundingBox(bb_expand, bb_expand, bb_expand)).isEmpty();
         if (playerEntity.pushTime < 0.1f && !playerEntity.getGamemode().canPlayerFly() && insideBlockOld && (movedWrong || !insideBlockNew) && !playerEntity.isPlayerSleeping())
         {
             // If the player:
@@ -260,12 +256,12 @@ public class PacketHandlerServer extends PacketHandler
             // - Moved wrongly
             // - And is not sleeping or flying
             // Bounce them back
-            teleportAndRotate(lastPosX, lastPosY, lastPosZ, yRot, xRot);
+            teleport(lastPosX, lastPosY, lastPosZ);
             return;
         }
 
         // Falling and ground collision
-        AABB aabb = playerEntity.bb.copy().grow(bb_expand, bb_expand, bb_expand).expand(0.0D, -0.55D, 0.0D);
+        AABB aabb = playerEntity.bb.copyPooled().grow(bb_expand, bb_expand, bb_expand).expand(0.0D, -0.55D, 0.0D);
         if (!playerEntity.getGamemode().canPlayerFly() && !mcServer.allowFlight && !worldserver.getIsAnySolidGround(aabb))
         {
             // Don't tick fly counter if they are falling
@@ -300,7 +296,7 @@ public class PacketHandlerServer extends PacketHandler
 		lastPosY = y;
 		lastPosZ = z;
 		playerEntity.absMoveTo(x, y, z, playerEntity.yRot, playerEntity.xRot);
-		playerEntity.playerNetServerHandler.sendPacket(new PacketMovePlayer.Pos(x, y + 1.625, z, false));
+		playerEntity.playerNetServerHandler.sendPacket(new PacketMovePlayer.Pos(x, y, z, false));
 	}
 
 	public void teleportAndRotate(double x, double y, double z, float yaw, float pitch) {
@@ -309,7 +305,7 @@ public class PacketHandlerServer extends PacketHandler
 		lastPosY = y;
 		lastPosZ = z;
 		playerEntity.absMoveTo(x, y, z, yaw, pitch);
-		playerEntity.playerNetServerHandler.sendPacket(new PacketMovePlayer.PosRot(x, y + 1.625, z, yaw, pitch, false));
+		playerEntity.playerNetServerHandler.sendPacket(new PacketMovePlayer.PosRot(x, y, z, yaw, pitch, false));
 	}
 
     @Override
diff --git a/game/server/src/main/java/net/minecraft/server/world/WorldServer.java b/game/server/src/main/java/net/minecraft/server/world/WorldServer.java
index dcea78b96..063497fa0 100644
--- a/game/server/src/main/java/net/minecraft/server/world/WorldServer.java
+++ b/game/server/src/main/java/net/minecraft/server/world/WorldServer.java
@@ -42,9 +42,9 @@ public class WorldServer extends World
     public List<TileEntity> getBlockEntitiesWithinBounds(int minX, int minY, int minZ, int maxX, int maxY, int maxZ)
     {
         ArrayList<TileEntity> arraylist = new ArrayList<>();
-        for(int i = 0; i < loadedTileEntityList.size(); i++)
+        for(int i = 0; i < tileEntityList.size(); i++)
         {
-            TileEntity tileEntity = loadedTileEntityList.get(i);
+            TileEntity tileEntity = tileEntityList.get(i);
             if(tileEntity.x >= minX && tileEntity.y >= minY && tileEntity.z >= minZ && tileEntity.x < maxX && tileEntity.y < maxY && tileEntity.z < maxZ)
             {
                 arraylist.add(tileEntity);
diff --git a/util/seed-viewer/src/main/java/org/useless/seedviewer/bta/worldloader/ChunkLoader.java b/util/seed-viewer/src/main/java/org/useless/seedviewer/bta/worldloader/ChunkLoader.java
index 4de727b4c..63a922170 100644
--- a/util/seed-viewer/src/main/java/org/useless/seedviewer/bta/worldloader/ChunkLoader.java
+++ b/util/seed-viewer/src/main/java/org/useless/seedviewer/bta/worldloader/ChunkLoader.java
@@ -3,6 +3,7 @@ package org.useless.seedviewer.bta.worldloader;
 import com.mojang.logging.LogUtils;
 import com.mojang.nbt.tags.CompoundTag;
 import com.mojang.nbt.NbtIo;
+import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap;
 import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
 import net.minecraft.core.world.ProgressListener;
 import net.minecraft.core.world.World;
@@ -10,10 +11,7 @@ import net.minecraft.core.world.chunk.Chunk;
 import net.minecraft.core.world.chunk.ChunkLoaderLegacy;
 import net.minecraft.core.world.chunk.EmptyChunk;
 import net.minecraft.core.world.chunk.provider.IChunkProvider;
-import net.minecraft.core.world.chunk.reader.ChunkReader;
-import net.minecraft.core.world.chunk.reader.ChunkReaderLegacy;
-import net.minecraft.core.world.chunk.reader.ChunkReaderVersion1;
-import net.minecraft.core.world.chunk.reader.ChunkReaderVersion2;
+import net.minecraft.core.world.chunk.reader.*;
 import net.minecraft.core.world.save.mcregion.RegionFileCache;
 import net.minecraft.core.world.type.WorldTypes;
 import org.useless.seedviewer.collections.ChunkLocation;
@@ -89,6 +87,7 @@ public class ChunkLoader implements IChunkProvider {
         chunk.isTerrainPopulated = reader.getIsTerrainPopulated();
         chunk.temperature = reader.getTemperatureMap();
         chunk.humidity = reader.getHumidityMap();
+        chunk.tickList = reader.getTickList();
         Int2ObjectMap<String> biomeRegistry = reader.getBiomeRegistry();
 
         for(int i = 0; i < Chunk.CHUNK_SECTIONS; ++i) {
@@ -110,6 +109,10 @@ public class ChunkLoader implements IChunkProvider {
             Arrays.fill(chunk.humidity, Double.NEGATIVE_INFINITY);
         }
 
+        if (chunk.tickList == null) {
+            chunk.tickList = new Int2ObjectArrayMap<>();
+        }
+
         return chunk;
     }
     private static ChunkReader getChunkReaderByVersion(CompoundTag tag, int version) {
@@ -118,6 +121,8 @@ public class ChunkLoader implements IChunkProvider {
                 return new ChunkReaderVersion1(null, tag);
             case 2:
                 return new ChunkReaderVersion2(null, tag);
+            case 3:
+                return new ChunkReaderVersion3(null, tag);
             default:
                 return new ChunkReaderLegacy(null, tag);
         }
