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 1cdba1545..66b997a24 100644
--- a/game/client/src/main/java/net/minecraft/client/Minecraft.java
+++ b/game/client/src/main/java/net/minecraft/client/Minecraft.java
@@ -499,6 +499,7 @@ public class Minecraft
 
         setRenderer(new ShadersRenderer(this));
 
+        this.sndManager.init();
         TextureRegistry.init();
         this.texturePackList.init();
         Shaders.init();
@@ -546,7 +547,6 @@ public class Minecraft
         GLRenderer.projectionM4f().identity();
         GLRenderer.viewM4f().identity();
         OpenGLHelper.checkError("startup");
-        this.sndManager.init();
 
         loadScreen();    // Update loading screen so it doesn't say no response
 
diff --git a/game/client/src/main/java/net/minecraft/client/gui/options/data/OptionsPages.java b/game/client/src/main/java/net/minecraft/client/gui/options/data/OptionsPages.java
index 638f06345..de82047a8 100644
--- a/game/client/src/main/java/net/minecraft/client/gui/options/data/OptionsPages.java
+++ b/game/client/src/main/java/net/minecraft/client/gui/options/data/OptionsPages.java
@@ -176,7 +176,8 @@ public abstract class OptionsPages {
                     .withComponent(new FloatOptionComponent(GameSettings.BLOCK_OUTLINE_WIDTH))
                 )
                 .withComponent(new OptionsCategory("gui.options.page.video.category.post_processing")
-                    .withComponent(new FloatOptionComponent(GameSettings.COLOR_CORRECTION))
+                    .withComponent(new FloatOptionComponent(GameSettings.COLOR_CORRECTION_OVERWORLD))
+                    .withComponent(new FloatOptionComponent(GameSettings.COLOR_CORRECTION_NETHER))
                     .withComponent(new BooleanOptionComponent(GameSettings.FXAA))
                     .withComponent(new ToggleableOptionComponent<>(GameSettings.BLOOM))
                     .withComponent(new FloatOptionComponent(GameSettings.HEAT_HAZE))
diff --git a/game/client/src/main/java/net/minecraft/client/option/GameSettings.java b/game/client/src/main/java/net/minecraft/client/option/GameSettings.java
index b21898223..9f8e0ff4f 100644
--- a/game/client/src/main/java/net/minecraft/client/option/GameSettings.java
+++ b/game/client/src/main/java/net/minecraft/client/option/GameSettings.java
@@ -298,7 +298,8 @@ public final class GameSettings {
         }));
     public static final @NotNull OptionInteger PLAYER_LIST_POSITION = register(new OptionInteger("playerListPosition", 2));
     public static final @NotNull OptionInteger LAST_CREATIVE_INVENTORY_PAGE = register(new OptionInteger("lastCreativeInventoryPage", 0));
-    public static final @NotNull OptionFloat COLOR_CORRECTION = register(new OptionFloat("colorCorrection", 0.5f));
+    public static final @NotNull OptionFloat COLOR_CORRECTION_OVERWORLD = register(new OptionFloat("colorCorrectionOverworld", 0.5f));
+    public static final @NotNull OptionFloat COLOR_CORRECTION_NETHER = register(new OptionFloat("colorCorrectionNether", 0.5f));
     public static final @NotNull OptionBoolean SNEAK_TOGGLE = register(new OptionBoolean("sneakToggle", false));
     public static final @NotNull OptionFloat AUTOSAVE_TIMER = register(new OptionFloat("autosaveTimer", 0.04166666666F)
         .addOnChangeCallback((mc, option) -> {
diff --git a/game/client/src/main/java/net/minecraft/client/render/PostProcessingManager.java b/game/client/src/main/java/net/minecraft/client/render/PostProcessingManager.java
index 125e5a0ff..9e56daab6 100644
--- a/game/client/src/main/java/net/minecraft/client/render/PostProcessingManager.java
+++ b/game/client/src/main/java/net/minecraft/client/render/PostProcessingManager.java
@@ -68,7 +68,9 @@ public class PostProcessingManager {
         if (this.mc.currentWorld == null || this.mc.thePlayer == null) return;
 
         this.reset();
-        float ccValue = GameSettings.COLOR_CORRECTION.value;
+        float ccValue = this.mc.currentWorld.getWorldType().hasTag(WorldTypeTags.NETHER)
+            ? GameSettings.COLOR_CORRECTION_NETHER.value
+            : GameSettings.COLOR_CORRECTION_OVERWORLD.value;
         boolean haze = GameSettings.HEAT_HAZE.value != 0;
 
         final float y = (float) this.mc.thePlayer.y;
diff --git a/game/client/src/main/java/net/minecraft/client/render/block/model/BlockModelDispatcher.java b/game/client/src/main/java/net/minecraft/client/render/block/model/BlockModelDispatcher.java
index 0a10aed30..3bb84b6bb 100644
--- a/game/client/src/main/java/net/minecraft/client/render/block/model/BlockModelDispatcher.java
+++ b/game/client/src/main/java/net/minecraft/client/render/block/model/BlockModelDispatcher.java
@@ -483,7 +483,7 @@ public final class BlockModelDispatcher
         addDispatch(new BlockModelGeneric<>(Blocks.MOBSPAWNER, loadDataModel("minecraft:block/mobspawner")));
         addDispatch(new BlockModelGeneric<>(Blocks.MOBSPAWNER_DEACTIVATED, loadDataModel("minecraft:block/mobspawner_deactivated")));
 
-        addDispatch(new BlockModelGeneric<>(Blocks.WORKBENCH, loadDataModel("minecraft:block/workbench")));
+        addDispatch(new BlockModelGenericWorkbench<>(Blocks.WORKBENCH, loadDataModel("minecraft:block/workbench")));
 
         addDispatch(new BlockModelGenericFurnace<>(Blocks.FURNACE_STONE_IDLE, "minecraft:block/furnace_stone/idle"));
         addDispatch(new BlockModelGenericFurnace<>(Blocks.FURNACE_STONE_ACTIVE, "minecraft:block/furnace_stone/active"));
diff --git a/game/client/src/main/java/net/minecraft/client/render/block/model/BlockModelFluid.java b/game/client/src/main/java/net/minecraft/client/render/block/model/BlockModelFluid.java
index 1dba8e7f7..b6dfdbfe1 100644
--- a/game/client/src/main/java/net/minecraft/client/render/block/model/BlockModelFluid.java
+++ b/game/client/src/main/java/net/minecraft/client/render/block/model/BlockModelFluid.java
@@ -128,13 +128,13 @@ public class BlockModelFluid<T extends BlockLogicFluid> extends BlockModelStanda
                 );
 
                 tessellator.setLightmapCoord1i(lightSE);
-                tessellator.addVertexWithUV(x + 1, (float) y + hes, z + 1,
+                tessellator.addVertexWithUV(x + 1, y + hes, z + 1,
                     tex.getSubIconU(((0.25) * cos - (0.25) * sin) + 0.5),
                     tex.getSubIconV(((0.25) * sin + (0.25) * cos) + 0.5)
                 );
 
                 tessellator.setLightmapCoord1i(lightNE);
-                tessellator.addVertexWithUV(x + 1, (float) y + he, z,
+                tessellator.addVertexWithUV(x + 1, y + he, z,
                     tex.getSubIconU(((0.25) * cos - (-0.25) * sin) + 0.5),
                     tex.getSubIconV(((0.25) * sin + (-0.25) * cos) + 0.5)
                 );
@@ -142,16 +142,16 @@ public class BlockModelFluid<T extends BlockLogicFluid> extends BlockModelStanda
                 tessellator.setNormal(0, 1, 0);
 
                 tessellator.setLightmapCoord1i(lightNW);
-                tessellator.addVertexWithUV(x, (float) y + h, z, tex.getIconUMin(), tex.getIconVMax());
+                tessellator.addVertexWithUV(x, y + h, z, tex.getIconUMin(), tex.getIconVMax());
 
                 tessellator.setLightmapCoord1i(lightSW);
-                tessellator.addVertexWithUV(x, (float) y + hs, z + 1, tex.getIconUMax(), tex.getIconVMax());
+                tessellator.addVertexWithUV(x, y + hs, z + 1, tex.getIconUMax(), tex.getIconVMax());
 
                 tessellator.setLightmapCoord1i(lightSE);
-                tessellator.addVertexWithUV(x + 1, (float) y + hes, z + 1, tex.getIconUMax(), tex.getIconVMin());
+                tessellator.addVertexWithUV(x + 1, y + hes, z + 1, tex.getIconUMax(), tex.getIconVMin());
 
                 tessellator.setLightmapCoord1i(lightNE);
-                tessellator.addVertexWithUV(x + 1, (float) y + he, z, tex.getIconUMin(), tex.getIconVMin());
+                tessellator.addVertexWithUV(x + 1, y + he, z, tex.getIconUMin(), tex.getIconVMin());
 
                 if (worldSource.getWorldType().hasTag(WorldTypeTags.NETHER) && this.block.getLogic().fluid instanceof FluidWater) {
                     if (GameSettings.BIOME_WATER.value) {
@@ -170,31 +170,31 @@ public class BlockModelFluid<T extends BlockLogicFluid> extends BlockModelStanda
 
                     switch (random.nextInt(4)) {
                         case 0: {
-                            tessellator.addVertexWithUV(x, (float) y + h, z, this.bubbles.getIconUMin(), this.bubbles.getIconVMax());
-                            tessellator.addVertexWithUV(x, (float) y + hs, z + 1, this.bubbles.getIconUMax(), this.bubbles.getIconVMax());
-                            tessellator.addVertexWithUV(x + 1, (float) y + hes, z + 1, this.bubbles.getIconUMax(), this.bubbles.getIconVMin());
-                            tessellator.addVertexWithUV(x + 1, (float) y + he, z, this.bubbles.getIconUMin(), this.bubbles.getIconVMin());
+                            tessellator.addVertexWithUV(x, y + h, z, this.bubbles.getIconUMin(), this.bubbles.getIconVMax());
+                            tessellator.addVertexWithUV(x, y + hs, z + 1, this.bubbles.getIconUMax(), this.bubbles.getIconVMax());
+                            tessellator.addVertexWithUV(x + 1, y + hes, z + 1, this.bubbles.getIconUMax(), this.bubbles.getIconVMin());
+                            tessellator.addVertexWithUV(x + 1, y + he, z, this.bubbles.getIconUMin(), this.bubbles.getIconVMin());
                             break;
                         }
                         case 1: {
-                            tessellator.addVertexWithUV(x, (float) y + h, z, this.bubbles.getIconUMin(), this.bubbles.getIconVMin());
-                            tessellator.addVertexWithUV(x, (float) y + hs, z + 1, this.bubbles.getIconUMin(), this.bubbles.getIconVMax());
-                            tessellator.addVertexWithUV(x + 1, (float) y + hes, z + 1, this.bubbles.getIconUMax(), this.bubbles.getIconVMax());
-                            tessellator.addVertexWithUV(x + 1, (float) y + he, z, this.bubbles.getIconUMax(), this.bubbles.getIconVMin());
+                            tessellator.addVertexWithUV(x, y + h, z, this.bubbles.getIconUMin(), this.bubbles.getIconVMin());
+                            tessellator.addVertexWithUV(x, y + hs, z + 1, this.bubbles.getIconUMin(), this.bubbles.getIconVMax());
+                            tessellator.addVertexWithUV(x + 1, y + hes, z + 1, this.bubbles.getIconUMax(), this.bubbles.getIconVMax());
+                            tessellator.addVertexWithUV(x + 1, y + he, z, this.bubbles.getIconUMax(), this.bubbles.getIconVMin());
                             break;
                         }
                         case 2: {
-                            tessellator.addVertexWithUV(x, (float) y + h, z, this.bubbles.getIconUMax(), this.bubbles.getIconVMin());
-                            tessellator.addVertexWithUV(x, (float) y + hs, z + 1, this.bubbles.getIconUMin(), this.bubbles.getIconVMin());
-                            tessellator.addVertexWithUV(x + 1, (float) y + hes, z + 1, this.bubbles.getIconUMin(), this.bubbles.getIconVMax());
-                            tessellator.addVertexWithUV(x + 1, (float) y + he, z, this.bubbles.getIconUMax(), this.bubbles.getIconVMax());
+                            tessellator.addVertexWithUV(x, y + h, z, this.bubbles.getIconUMax(), this.bubbles.getIconVMin());
+                            tessellator.addVertexWithUV(x, y + hs, z + 1, this.bubbles.getIconUMin(), this.bubbles.getIconVMin());
+                            tessellator.addVertexWithUV(x + 1, y + hes, z + 1, this.bubbles.getIconUMin(), this.bubbles.getIconVMax());
+                            tessellator.addVertexWithUV(x + 1, y + he, z, this.bubbles.getIconUMax(), this.bubbles.getIconVMax());
                             break;
                         }
                         case 3: {
-                            tessellator.addVertexWithUV(x, (float) y + h, z, this.bubbles.getIconUMax(), this.bubbles.getIconVMax());
-                            tessellator.addVertexWithUV(x, (float) y + hs, z + 1, this.bubbles.getIconUMax(), this.bubbles.getIconVMin());
-                            tessellator.addVertexWithUV(x + 1, (float) y + hes, z + 1, this.bubbles.getIconUMin(), this.bubbles.getIconVMin());
-                            tessellator.addVertexWithUV(x + 1, (float) y + he, z, this.bubbles.getIconUMin(), this.bubbles.getIconVMax());
+                            tessellator.addVertexWithUV(x, y + h, z, this.bubbles.getIconUMax(), this.bubbles.getIconVMax());
+                            tessellator.addVertexWithUV(x, y + hs, z + 1, this.bubbles.getIconUMax(), this.bubbles.getIconVMin());
+                            tessellator.addVertexWithUV(x + 1, y + hes, z + 1, this.bubbles.getIconUMin(), this.bubbles.getIconVMin());
+                            tessellator.addVertexWithUV(x + 1, y + he, z, this.bubbles.getIconUMin(), this.bubbles.getIconVMax());
                             break;
                         }
                     }
@@ -224,44 +224,45 @@ public class BlockModelFluid<T extends BlockLogicFluid> extends BlockModelStanda
             if (!renderBlocks.renderAllFaces && !sideRenderFlags[side]) {
                 continue;
             }
-            float f13;
-            float f15;
-            float x1;
-            float z1;
-            float x2;
-            float z2;
+            double f13;
+            double f15;
+            double x1;
+            double z1;
+            double x2;
+            double z2;
+            final double ep = 0.0001;
             switch (side) {
                 case 0 -> { // North
                     f13 = h;
                     f15 = he;
-                    x1 = x + 0.0001f;
-                    x2 = x + 1 - 0.0001f;
-                    z1 = z + 0.0001f;
-                    z2 = z + 0.0001f;
+                    x1 = x - ep;
+                    x2 = x + 1 + ep;
+                    z1 = z + ep;
+                    z2 = z + ep;
                 }
                 case 1 -> { // South
                     f13 = hes;
                     f15 = hs;
-                    x1 = x + 1 - 0.0001f;
-                    x2 = x + 0.0001f;
-                    z1 = z + 1 - 0.0001f;
-                    z2 = z + 1 - 0.001f;
+                    x1 = x + 1 + ep;
+                    x2 = x - ep;
+                    z1 = z + 1 - ep;
+                    z2 = z + 1 - ep;
                 }
                 case 2 -> { // West
                     f13 = hs;
                     f15 = h;
-                    x1 = x + 0.0001f;
-                    x2 = x + 0.0001f;
-                    z1 = z + 1 - 0.0001f;
-                    z2 = z + 0.0001f;
+                    x1 = x + ep;
+                    x2 = x + ep;
+                    z1 = z + 1 + ep;
+                    z2 = z - ep;
                 }
                 default -> { // East
                     f13 = he;
                     f15 = hes;
-                    x1 = x + 1 - 0.0001f;
-                    x2 = x + 1 - 0.0001f;
-                    z1 = z + 0.0001f;
-                    z2 = z + 1 - 0.0001f;
+                    x1 = x + 1 - ep;
+                    x2 = x + 1 - ep;
+                    z1 = z - ep;
+                    z2 = z + 1 + ep;
                 }
             }
             didRender = true;
@@ -277,8 +278,8 @@ public class BlockModelFluid<T extends BlockLogicFluid> extends BlockModelStanda
                 case 2 -> tessellator.setNormal(-1, 0, 0);
                 case 3 -> tessellator.setNormal(1, 0, 0);
             }
-            tessellator.addVertexWithUV(x1, (float) y + f13, z1, u1, v1);
-            tessellator.addVertexWithUV(x2, (float) y + f15, z2, u2, v2);
+            tessellator.addVertexWithUV(x1, y + f13, z1, u1, v1);
+            tessellator.addVertexWithUV(x2, y + f15, z2, u2, v2);
             tessellator.addVertexWithUV(x2, y, z2, u2, v3);
             tessellator.addVertexWithUV(x1, y, z1, u1, v3);
         }
diff --git a/game/client/src/main/java/net/minecraft/client/render/block/model/generic/BlockModelGenericGrass.java b/game/client/src/main/java/net/minecraft/client/render/block/model/generic/BlockModelGenericGrass.java
index 2426570f9..cb7ec4cfb 100644
--- a/game/client/src/main/java/net/minecraft/client/render/block/model/generic/BlockModelGenericGrass.java
+++ b/game/client/src/main/java/net/minecraft/client/render/block/model/generic/BlockModelGenericGrass.java
@@ -12,16 +12,23 @@ import org.useless.dragonfly.models.block.StaticBlockModel;
 
 public class BlockModelGenericGrass<T extends BlockLogic> extends BlockModelGeneric<T> {
     public final @NotNull StaticBlockModel snowy;
+    public final @NotNull StaticBlockModel ashy;
     public BlockModelGenericGrass(@NotNull Block<T> block) {
         super(block, BlockModelDispatcher.loadDataModel("minecraft:block/grass"));
         this.snowy = BlockModelDispatcher.loadDataModel("minecraft:block/grass_snowy").asModel();
+        this.ashy = BlockModelDispatcher.loadDataModel("minecraft:block/grass_ashy").asModel();
+
     }
 
     @Override
     public @NotNull StaticBlockModel getModel(@NotNull WorldSource source, @NotNull TilePosc tilePosc) {
-        if (source.getBlockType(tilePosc.up(new TilePos())) == Blocks.LAYER_SNOW) {
+        TilePos up = tilePosc.up(new TilePos());
+        if (source.getBlockType(up) == Blocks.LAYER_SNOW) {
             return this.snowy;
         }
+        if (source.getBlockType(up) == Blocks.LAYER_ASH) {
+            return this.ashy;
+        }
         return super.getModel(source, tilePosc);
     }
 }
diff --git a/game/client/src/main/java/net/minecraft/client/render/block/model/generic/BlockModelGenericLayer.java b/game/client/src/main/java/net/minecraft/client/render/block/model/generic/BlockModelGenericLayer.java
index ad9efb476..081b83515 100644
--- a/game/client/src/main/java/net/minecraft/client/render/block/model/generic/BlockModelGenericLayer.java
+++ b/game/client/src/main/java/net/minecraft/client/render/block/model/generic/BlockModelGenericLayer.java
@@ -24,7 +24,7 @@ public class BlockModelGenericLayer<T extends BlockLogic> extends BlockModelGene
 
     @Override
     public @NotNull StaticBlockModel getModelFromData(int data) {
-        return this.models[(data & BlockLogicLayerBase.MASK_HEIGHT) % this.models.length];
+        return this.models[Math.min(data & BlockLogicLayerBase.MASK_HEIGHT, this.models.length - 1)];
     }
 
     private final @NotNull TilePos queryPos = new TilePos();
diff --git a/game/client/src/main/java/net/minecraft/client/render/block/model/generic/BlockModelGenericWorkbench.java b/game/client/src/main/java/net/minecraft/client/render/block/model/generic/BlockModelGenericWorkbench.java
new file mode 100644
index 000000000..a771072af
--- /dev/null
+++ b/game/client/src/main/java/net/minecraft/client/render/block/model/generic/BlockModelGenericWorkbench.java
@@ -0,0 +1,34 @@
+package net.minecraft.client.render.block.model.generic;
+
+import net.minecraft.client.render.tessellator.TessellatorGeneral;
+import net.minecraft.client.render.texture.stitcher.IconCoordinate;
+import net.minecraft.core.block.Block;
+import net.minecraft.core.block.BlockLogic;
+import net.minecraft.core.block.BlockLogicRotatable;
+import net.minecraft.core.util.helper.Direction;
+import net.minecraft.core.world.WorldSource;
+import net.minecraft.core.world.pos.TilePosc;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.useless.dragonfly.data.block.BlockModelData;
+import org.useless.dragonfly.models.block.StaticBlockModel;
+
+public class BlockModelGenericWorkbench<T extends BlockLogic> extends BlockModelGeneric<T> {
+    public BlockModelGenericWorkbench(@NotNull Block<T> block, @NotNull StaticBlockModel staticModel) {
+        super(block, staticModel);
+    }
+
+    public BlockModelGenericWorkbench(@NotNull Block<T> block, @NotNull BlockModelData staticModel) {
+        super(block, staticModel);
+    }
+
+    @Override
+    public boolean renderAttached(@NotNull TessellatorGeneral tessellator, @NotNull WorldSource worldSource, @NotNull TilePosc tilePos, boolean cullFaces, @Nullable IconCoordinate overrideTexture) {
+        Direction direction = BlockLogicRotatable.getDirectionFromMeta(worldSource.getBlockData(tilePos));
+        int rotY = switch (direction) {
+            case EAST, WEST -> 1;
+            default -> 0;
+        };
+        return getModel(worldSource, tilePos).renderAttached(this, tessellator, worldSource, tilePos, 0, rotY, 0, 0, 0, 0, false, cullFaces, overrideTexture);
+    }
+}
diff --git a/game/client/src/main/java/net/minecraft/client/render/dynamictexture/DynamicTextureLavaFlowing.java b/game/client/src/main/java/net/minecraft/client/render/dynamictexture/DynamicTextureLavaFlowing.java
index 8118f9267..dc5515286 100644
--- a/game/client/src/main/java/net/minecraft/client/render/dynamictexture/DynamicTextureLavaFlowing.java
+++ b/game/client/src/main/java/net/minecraft/client/render/dynamictexture/DynamicTextureLavaFlowing.java
@@ -12,6 +12,8 @@ public class DynamicTextureLavaFlowing extends DynamicTexture {
 	private float[] arr2;
 	private float[] arr3;
 	private float[] arr4;
+	private int width;
+	private int height;
 	private int ticks = 0;
 
 	public DynamicTextureLavaFlowing(@NotNull IconCoordinate targetTexture) {
@@ -22,56 +24,50 @@ public class DynamicTextureLavaFlowing extends DynamicTexture {
 	public void postInit() {
 		initTexture();
 
-        this.arr1 = new float[this.targetTexture.getArea()];
-        this.arr2 = new float[this.targetTexture.getArea()];
-        this.arr3 = new float[this.targetTexture.getArea()];
-        this.arr4 = new float[this.targetTexture.getArea()];
+		this.width = this.targetTexture.width/2;
+		this.height = this.targetTexture.height/2;
+        this.arr1 = new float[this.width * this.height];
+        this.arr2 = new float[this.width * this.height];
+        this.arr3 = new float[this.width * this.height];
+        this.arr4 = new float[this.width * this.height];
 	}
 
 	@Override
 	public void update() {
 		++this.ticks;
 
-		int y;
-		float var3;
-		int var5;
-		int _x;
-		int _y;
-		int var8;
-		int var9;
-
         byte[] imageData = getImageData(this.layerColor);
-		for (int x = 0; x < this.targetTexture.width; ++x) {
-			for (y = 0; y < this.targetTexture.height; ++y) {
-				var3 = 0.0F;
-				int var4 = (int) (MathHelper.sin((float) y * MathHelper.PI * 2.0F / this.targetTexture.height) * 1.2F);
-				var5 = (int) (MathHelper.sin((float) x * MathHelper.PI * 2.0F / this.targetTexture.width) * 1.2F);
-
-				for (_x = x - 1; _x <= x + 1; ++_x) {
-					for (_y = y - 1; _y <= y + 1; ++_y) {
-						var8 = floorMod(_x + var4, this.targetTexture.width);
-						var9 = floorMod(_y + var5, this.targetTexture.height);
-						var3 += this.arr1[var8 + var9 * this.targetTexture.width];
+        for (int x = 0; x < this.width; ++x) {
+			for (int y = 0; y < this.height; ++y) {
+				float var3 = 0.0F;
+				int var4 = (int) (MathHelper.sin((float) y * MathHelper.PI * 2.0F / this.height) * 1.2F);
+				int var5 = (int) (MathHelper.sin((float) x * MathHelper.PI * 2.0F / this.width) * 1.2F);
+
+				for (int _x = x - 1; _x <= x + 1; ++_x) {
+					for (int _y = y - 1; _y <= y + 1; ++_y) {
+                        int var8 = Math.floorMod(_x + var4, this.width);
+                        int var9 = Math.floorMod(_y + var5, this.height);
+                        var3 += this.arr1[var8 + var9 * this.width];
 					}
 				}
 
-				this.arr2[x + y * this.targetTexture.width] = var3 / 10.0F
-						+ (this.arr3[(floorMod(x, this.targetTexture.width))
-						+ (floorMod(y, this.targetTexture.height)) * this.targetTexture.width]
-						+ this.arr3[(floorMod(x + 1, this.targetTexture.width))
-						+ (floorMod(y, this.targetTexture.height)) * this.targetTexture.width]
-						+ this.arr3[(floorMod(x + 1, this.targetTexture.width))
-						+ (floorMod(y + 1, this.targetTexture.height)) * this.targetTexture.width]
-						+ this.arr3[(floorMod(x, this.targetTexture.width))
-						+ (floorMod(y + 1, this.targetTexture.height)) * this.targetTexture.width]) / 4.0F * 0.8F;
-				this.arr3[x + y * this.targetTexture.width] += this.arr4[x + y * this.targetTexture.width] * 0.01F;
-				if (this.arr3[x + y * this.targetTexture.width] < 0.0F) {
-					this.arr3[x + y * this.targetTexture.width] = 0.0F;
+				this.arr2[x + y * this.width] = var3 / 10.0F
+						+ (this.arr3[(floorMod(x, this.width))
+						+ (floorMod(y, this.height)) * this.width]
+						+ this.arr3[(floorMod(x + 1, this.width))
+						+ (floorMod(y, this.height)) * this.width]
+						+ this.arr3[(floorMod(x + 1, this.width))
+						+ (floorMod(y + 1, this.height)) * this.width]
+						+ this.arr3[(floorMod(x, this.width))
+						+ (floorMod(y + 1, this.height)) * this.width]) / 4.0F * 0.8F;
+				this.arr3[x + y * this.width] += this.arr4[x + y * this.width] * 0.01F;
+				if (this.arr3[x + y * this.width] < 0.0F) {
+					this.arr3[x + y * this.width] = 0.0F;
 				}
 
-				this.arr4[x + y * this.targetTexture.width] -= 0.06F;
+				this.arr4[x + y * this.width] -= 0.06F;
 				if (java.lang.Math.random() < 0.005D) {
-					this.arr4[x + y * this.targetTexture.width] = 1.5F;
+					this.arr4[x + y * this.width] = 1.5F;
 				}
 			}
 		}
@@ -80,24 +76,28 @@ public class DynamicTextureLavaFlowing extends DynamicTexture {
 		this.arr2 = this.arr1;
 		this.arr1 = var11;
 
-		for (int i = 0; i < this.targetTexture.getArea(); ++i) {
-			var3 = this.arr1[floorMod(i - this.ticks / 3 * this.targetTexture.width, this.targetTexture.getArea())] * 2.0F;
-			if (var3 > 1.0F) {
-				var3 = 1.0F;
-			}
+		for (int x = 0; x < this.targetTexture.width; x++) {
+			for (int y = 0; y < this.targetTexture.height; y++) {
+				int i = y * this.targetTexture.width + x;
+				int i2 = (y % this.height) * this.width + (x % this.width);
+				float var3 = this.arr1[floorMod(i2 - this.ticks / 3 * this.width, this.arr1.length)] * 2.0F;
+				if (var3 > 1.0F) {
+					var3 = 1.0F;
+				}
 
-			if (var3 < 0.0F) {
-				var3 = 0.0F;
-			}
+				if (var3 < 0.0F) {
+					var3 = 0.0F;
+				}
 
-			var5 = (int) (var3 * 100.0F + 155.0F);
-			_x = (int) (var3 * var3 * 255.0F);
-			_y = (int) (var3 * var3 * var3 * var3 * 128.0F);
+				int var5 = (int) (var3 * 100.0F + 155.0F);
+				int _x = (int) (var3 * var3 * 255.0F);
+				int _y = (int) (var3 * var3 * var3 * var3 * 128.0F);
 
-			imageData[i * 4] = (byte) var5;
-			imageData[i * 4 + 1] = (byte) _x;
-			imageData[i * 4 + 2] = (byte) _y;
-			imageData[i * 4 + 3] = -1;
+				imageData[i * 4] = (byte) var5;
+				imageData[i * 4 + 1] = (byte) _x;
+				imageData[i * 4 + 2] = (byte) _y;
+				imageData[i * 4 + 3] = -1;
+			}
 		}
 	}
 }
diff --git a/game/client/src/main/java/net/minecraft/client/render/dynamictexture/DynamicTextureWaterFlow.java b/game/client/src/main/java/net/minecraft/client/render/dynamictexture/DynamicTextureWaterFlow.java
index be34c4c97..7147476be 100644
--- a/game/client/src/main/java/net/minecraft/client/render/dynamictexture/DynamicTextureWaterFlow.java
+++ b/game/client/src/main/java/net/minecraft/client/render/dynamictexture/DynamicTextureWaterFlow.java
@@ -19,6 +19,9 @@ public class DynamicTextureWaterFlow extends DynamicTexture {
 	private float[] heata;
 	private int ticks = 0;
 
+	private int width;
+	private int height;
+
 	public DynamicTextureWaterFlow(@NotNull IconCoordinate targetTexture) {
 		super(targetTexture);
 	}
@@ -27,40 +30,42 @@ public class DynamicTextureWaterFlow extends DynamicTexture {
 	public void postInit() {
 		initTexture();
 
-        this.current = new float[this.targetTexture.getArea()];
-        this.next = new float[this.targetTexture.getArea()];
-        this.heat = new float[this.targetTexture.getArea()];
-        this.heata = new float[this.targetTexture.getArea()];
+		this.width = this.targetTexture.width/2;
+		this.height = this.targetTexture.height/2;
+        this.current = new float[this.width * this.height];
+        this.next = new float[this.width * this.height];
+        this.heat = new float[this.width * this.height];
+        this.heata = new float[this.width * this.height];
 	}
 
 	@Override
 	public void update() {
 		this.ticks++;
 
-		for (int x = 0; x < this.targetTexture.width; ++x) {
-			for (int y = 0; y < this.targetTexture.height; ++y) {
+		for (int x = 0; x < this.width; ++x) {
+			for (int y = 0; y < this.height; ++y) {
 				float pow = 0.0F;
 
 				for (int k = y - 2; k <= y; ++k) {
-					int xi = floorMod(x, this.targetTexture.width);
-					int yi = floorMod(k, this.targetTexture.height);
-					pow += this.current[xi + yi * this.targetTexture.width];
+					int xi = floorMod(x, this.width);
+					int yi = floorMod(k, this.height);
+					pow += this.current[xi + yi * this.width];
 				}
 
-				this.next[x + y * this.targetTexture.width] = pow / 3.2F + this.heat[x + y * this.targetTexture.width] * 0.8F;
+				this.next[x + y * this.width] = pow / 3.2F + this.heat[x + y * this.width] * 0.8F;
 			}
 		}
 
-		for (int x = 0; x < this.targetTexture.width; ++x) {
-			for (int y = 0; y < this.targetTexture.height; ++y) {
-				this.heat[x + y * this.targetTexture.width] += this.heata[x + y * this.targetTexture.width] * 0.05F;
-				if (this.heat[x + y * this.targetTexture.width] < 0.0F) {
-					this.heat[x + y * this.targetTexture.width] = 0.0F;
+		for (int x = 0; x < this.width; ++x) {
+			for (int y = 0; y < this.height; ++y) {
+				this.heat[x + y * this.width] += this.heata[x + y * this.width] * 0.05F;
+				if (this.heat[x + y * this.width] < 0.0F) {
+					this.heat[x + y * this.width] = 0.0F;
 				}
 
-				this.heata[x + y * this.targetTexture.width] -= 0.3F;
+				this.heata[x + y * this.width] -= 0.3F;
 				if (Math.random() < 0.2D) {
-					this.heata[x + y * this.targetTexture.width] = 0.5F;
+					this.heata[x + y * this.width] = 0.5F;
 				}
 			}
 		}
@@ -70,40 +75,45 @@ public class DynamicTextureWaterFlow extends DynamicTexture {
 		this.current = tmp;
 
         byte[] imageData = getImageData(this.layerColor);
-		for (int i = 0; i < this.targetTexture.getArea(); ++i) {
-			float pow = this.current[floorMod(i - this.ticks * this.targetTexture.width, this.targetTexture.getArea())];
-			pow = MathHelper.clamp(pow, 0.0f, 1.0f);
-			float pp = pow * pow;
-
-			int r = (int) (32.0F + pp * 32.0F);
-			int g = (int) (50.0F + pp * 64.0F);
-			int b = 255;
-			int a = (int) (146.0F + pp * 50.0F);
-
-			if (this.mc.currentWorld != null && this.mc.currentWorld.getWorldType().hasTag(WorldTypeTags.NETHER)) {
-				a = (int) (225.0F + pp * 50.0F);
-
-				if (!GameSettings.BIOME_WATER.value) {
-					final float br = 0.3F;
-					r = (int) (255.0F * br);
-					g = (int) ((50.0F + pp * 64.0F) * br);
-					b = (int) ((32.0F + pp * 32.0F) * br);
+		for (int x = 0; x < this.targetTexture.width; x++) {
+			for (int y = 0; y < this.targetTexture.height; y++) {
+				int i = y * this.targetTexture.width + x;
+				int i2 = (y % this.height) * this.width + (x % this.width);
+
+				float pow = this.current[floorMod(i2 - this.ticks * this.width, this.current.length)];
+				pow = MathHelper.clamp(pow, 0.0f, 1.0f);
+				float pp = pow * pow;
+
+				int r = (int) (32.0F + pp * 32.0F);
+				int g = (int) (50.0F + pp * 64.0F);
+				int b = 255;
+				int a = (int) (146.0F + pp * 50.0F);
+
+				if (this.mc.currentWorld != null && this.mc.currentWorld.getWorldType().hasTag(WorldTypeTags.NETHER)) {
+					a = (int) (225.0F + pp * 50.0F);
+
+					if (!GameSettings.BIOME_WATER.value) {
+						final float br = 0.3F;
+						r = (int) (255.0F * br);
+						g = (int) ((50.0F + pp * 64.0F) * br);
+						b = (int) ((32.0F + pp * 32.0F) * br);
+					}
 				}
-			}
 
-			r = MathHelper.clamp(r, 0, 255);
-			g = MathHelper.clamp(g, 0, 255);
-			b = MathHelper.clamp(b, 0, 255);
-			a = MathHelper.clamp(a, 0, 255);
+				r = MathHelper.clamp(r, 0, 255);
+				g = MathHelper.clamp(g, 0, 255);
+				b = MathHelper.clamp(b, 0, 255);
+				a = MathHelper.clamp(a, 0, 255);
 
-			if (GameSettings.BIOME_WATER.value && !(this.mc.currentWorld != null && this.mc.currentWorld.getWorldType().hasTag(WorldTypeTags.RETRO))) {
-				r = g = b = (r + g + b) / 3;
-			}
+				if (GameSettings.BIOME_WATER.value && !(this.mc.currentWorld != null && this.mc.currentWorld.getWorldType().hasTag(WorldTypeTags.RETRO))) {
+					r = g = b = (r + g + b) / 3;
+				}
 
-			imageData[i * 4] = (byte) r;
-			imageData[i * 4 + 1] = (byte) g;
-			imageData[i * 4 + 2] = (byte) b;
-			imageData[i * 4 + 3] = (byte) a;
+				imageData[i * 4] = (byte) r;
+				imageData[i * 4 + 1] = (byte) g;
+				imageData[i * 4 + 2] = (byte) b;
+				imageData[i * 4 + 3] = (byte) a;
+			}
 		}
 	}
 }
diff --git a/game/client/src/main/java/net/minecraft/client/render/item/model/ItemModelBlock.java b/game/client/src/main/java/net/minecraft/client/render/item/model/ItemModelBlock.java
index 2ed1db0aa..2062744b0 100644
--- a/game/client/src/main/java/net/minecraft/client/render/item/model/ItemModelBlock.java
+++ b/game/client/src/main/java/net/minecraft/client/render/item/model/ItemModelBlock.java
@@ -51,7 +51,7 @@ public class ItemModelBlock extends ItemModelStandard {
         GLRenderer.setShader(Shaders.ITEM);
         DisplayPos displayPos = getDisplayPos(displayPosId);
         var model = GLRenderer.modelM4f();
-        model.translate(displayPos.tx, displayPos.ty, displayPos.tz);
+        model.translate(leftHanded ? -displayPos.tx : displayPos.tx, displayPos.ty, displayPos.tz);
         model.rotateX(Math.toRadians(displayPos.rx));
         model.rotateY(Math.toRadians(leftHanded ? -displayPos.ry : displayPos.ry));
         model.rotateZ(Math.toRadians(leftHanded ? -displayPos.rz : displayPos.rz));
diff --git a/game/client/src/main/java/net/minecraft/client/render/item/model/ItemModelStandard.java b/game/client/src/main/java/net/minecraft/client/render/item/model/ItemModelStandard.java
index 7a975a202..0e30bd73c 100644
--- a/game/client/src/main/java/net/minecraft/client/render/item/model/ItemModelStandard.java
+++ b/game/client/src/main/java/net/minecraft/client/render/item/model/ItemModelStandard.java
@@ -76,7 +76,7 @@ public class ItemModelStandard extends ItemModel{
         final DisplayPos displayPos = getDisplayPos(displayPosId);
 
         var model = GLRenderer.modelM4f();
-        model.translate(displayPos.tx, displayPos.ty, displayPos.tz);
+        model.translate(leftHanded ? -displayPos.tx : displayPos.tx, displayPos.ty, displayPos.tz);
         model.rotateX(org.joml.Math.toRadians(displayPos.rx));
         model.rotateY(org.joml.Math.toRadians(leftHanded ? -displayPos.ry : displayPos.ry));
         model.rotateZ(org.joml.Math.toRadians(leftHanded ? -displayPos.rz : displayPos.rz));
diff --git a/game/client/src/main/java/net/minecraft/client/render/particle/ParticleAsh.java b/game/client/src/main/java/net/minecraft/client/render/particle/ParticleAsh.java
index 0f080c642..fb34a43f7 100644
--- a/game/client/src/main/java/net/minecraft/client/render/particle/ParticleAsh.java
+++ b/game/client/src/main/java/net/minecraft/client/render/particle/ParticleAsh.java
@@ -6,7 +6,7 @@ public class ParticleAsh extends Particle {
     public ParticleAsh(World world, double x, double y, double z, double xa, double ya, double za) {
         super(world, x, y, z, xa, ya, za);
         this.xd = xa + (Math.random() * 2D - 1.0D) * 0.05F;
-        this.yd = ya - Math.random() * 2D * 0.2F;
+        this.yd = ya - (0.02D + Math.random() * 0.05D);
         this.zd = za + (Math.random() * 2D - 1.0D) * 0.05F;
         this.rCol = this.gCol = this.bCol = 0.2F + random.nextFloat() * 0.3F;
         this.size = random.nextFloat() * random.nextFloat() * 6F + 1.0F;
diff --git a/game/client/src/main/java/net/minecraft/client/render/particle/ParticleDispatcher.java b/game/client/src/main/java/net/minecraft/client/render/particle/ParticleDispatcher.java
index 1eda33cc3..554208712 100644
--- a/game/client/src/main/java/net/minecraft/client/render/particle/ParticleDispatcher.java
+++ b/game/client/src/main/java/net/minecraft/client/render/particle/ParticleDispatcher.java
@@ -17,18 +17,14 @@ import net.minecraft.core.world.Dimension;
 import net.minecraft.core.world.World;
 import net.minecraft.core.world.pos.TilePos;
 import org.jetbrains.annotations.NotNull;
+import org.joml.Math;
 
 import java.util.ArrayList;
 import java.util.List;
 
 public final class ParticleDispatcher extends Dispatcher<String, ParticleEntry> {
     private static ParticleDispatcher INSTANCE = new ParticleDispatcher();
-    private static final ParticleEntry BLANK = new ParticleEntry() {
-        @Override
-        public Particle newParticle(@NotNull World world, double x, double y, double z, double motionX, double motionY, double motionZ, int data) {
-            return null;
-        }
-    };
+    private static final ParticleEntry BLANK = (world, x, y, z, motionX, motionY, motionZ, data) -> null;
 
     public static ParticleDispatcher getInstance() {
         return INSTANCE;
@@ -46,114 +42,24 @@ public final class ParticleDispatcher extends Dispatcher<String, ParticleEntry>
     private ParticleDispatcher() {
         INSTANCE = this;
         Minecraft mc = Minecraft.getMinecraft();
-        addDispatch("bubble", new ParticleEntry() {
-            @Override
-            public Particle newParticle(@NotNull World world, double x, double y, double z, double motionX, double motionY, double motionZ, int data) {
-                return new ParticleBubble(world, x, y, z, motionX, motionY, motionZ, Dimension.getDimensionList().getOrDefault(data, Dimension.OVERWORLD), true);
-            }
-        });
-        addDispatch("bubbleboiling", new ParticleEntry() {
-            @Override
-            public Particle newParticle(@NotNull World world, double x, double y, double z, double motionX, double motionY, double motionZ, int data) {
-                return new ParticleBubbleBoiling(world, x, y, z, motionX, motionY, motionZ, true);
-            }
-        });
-        addDispatch("smoke", new ParticleEntry() {
-            @Override
-            public Particle newParticle(@NotNull World world, double x, double y, double z, double motionX, double motionY, double motionZ, int data) {
-                return new ParticleSmoke(world, x, y, z, motionX, motionY, motionZ, 1.0f, data);
-            }
-        });
-        addDispatch("note", new ParticleEntry() {
-            @Override
-            public Particle newParticle(@NotNull World world, double x, double y, double z, double motionX, double motionY, double motionZ, int data) {
-                return new ParticleNote(world, x, y, z, motionX, motionY, motionZ, data);
-            }
-        });
-        addDispatch("portal", new ParticleEntry() {
-            @Override
-            public Particle newParticle(@NotNull World world, double x, double y, double z, double motionX, double motionY, double motionZ, int data) {
-                return new ParticlePortal(world, x, y, z, motionX, motionY, motionZ, data);
-            }
-        });
-        addDispatch("explode", new ParticleEntry() {
-            @Override
-            public Particle newParticle(@NotNull World world, double x, double y, double z, double motionX, double motionY, double motionZ, int data) {
-                return new ParticleExplode(world, x, y, z, motionX, motionY, motionZ);
-            }
-        });
-        addDispatch("flame", new ParticleEntry() {
-            @Override
-            public Particle newParticle(@NotNull World world, double x, double y, double z, double motionX, double motionY, double motionZ, int data) {
-                return new ParticleFlame(world, x, y, z, motionX, motionY, motionZ, ParticleFlame.Type.ORANGE);
-            }
-        });
-        addDispatch("blueflame", new ParticleEntry() {
-            @Override
-            public Particle newParticle(@NotNull World world, double x, double y, double z, double motionX, double motionY, double motionZ, int data) {
-                return new ParticleFlame(world, x, y, z, motionX, motionY, motionZ, ParticleFlame.Type.BLUE);
-            }
-        });
-        addDispatch("soulflame", new ParticleEntry() {
-            @Override
-            public Particle newParticle(@NotNull World world, double x, double y, double z, double motionX, double motionY, double motionZ, int data) {
-                return new ParticleFlame(world, x, y, z, motionX, motionY, motionZ, ParticleFlame.Type.SOUL);
-            }
-        });
-        addDispatch("lava", new ParticleEntry() {
-            @Override
-            public Particle newParticle(@NotNull World world, double x, double y, double z, double motionX, double motionY, double motionZ, int data) {
-                return new ParticleLava(world, x, y, z);
-            }
-        });
-        addDispatch("footstep", new ParticleEntry() {
-            @Override
-            public Particle newParticle(@NotNull World world, double x, double y, double z, double motionX, double motionY, double motionZ, int data) {
-                return new ParticleFootstep(mc.textureManager, world, x, y, z);
-            }
-        });
-        addDispatch("splash", new ParticleEntry() {
-            @Override
-            public Particle newParticle(@NotNull World world, double x, double y, double z, double motionX, double motionY, double motionZ, int data) {
-                return new ParticleSplash(world, x, y, z, motionX, motionY, motionZ, Dimension.getDimensionList().getOrDefault(data, Dimension.OVERWORLD));
-            }
-        });
-        addDispatch("largesmoke", new ParticleEntry() {
-            @Override
-            public Particle newParticle(@NotNull World world, double x, double y, double z, double motionX, double motionY, double motionZ, int data) {
-                return new ParticleSmoke(world, x, y, z, motionX, motionY, motionZ, 2.5f, data);
-            }
-        });
-        addDispatch("puffrgb", new ParticleEntry() {
-            @Override
-            public Particle newParticle(@NotNull World world, double x, double y, double z, double motionX, double motionY, double motionZ, int data) {
-                return new ParticlePuffRGB(world, x, y, z, (float) motionX, (float) motionY, (float) motionZ, data);
-            }
-        });
-        addDispatch("reddust", new ParticleEntry() {
-            @Override
-            public Particle newParticle(@NotNull World world, double x, double y, double z, double motionX, double motionY, double motionZ, int data) {
-                return new ParticleRedstoneDust(world, x, y, z, data);
-            }
-        });
-        addDispatch("item", new ParticleEntry() {
-            @Override
-            public Particle newParticle(@NotNull World world, double x, double y, double z, double motionX, double motionY, double motionZ, int data) {
-                return new ParticleItemBreaking(world, x, y, z, Item.itemsList[MathHelper.clamp(data, 0, Item.itemsList.length - 1)]);
-            }
-        });
-        addDispatch("block", new ParticleEntry() {
-            @Override
-            public Particle newParticle(@NotNull World world, double x, double y, double z, double motionX, double motionY, double motionZ, int data) {
-                return new ParticleBlockBreaking(world, x, y, z, motionX, motionY, motionZ, Blocks.blocksList[BlockParticleHelper.decodeBlockID(data)], BlockParticleHelper.decodeBlockSide(data), BlockParticleHelper.decodeBlockMeta(data)).init(new TilePos(x, y, z));
-            }
-        });
-        addDispatch("snowshovel", new ParticleEntry() {
-            @Override
-            public Particle newParticle(@NotNull World world, double x, double y, double z, double motionX, double motionY, double motionZ, int data) {
-                return new ParticleSnowShovel(world, x, y, z, motionX, motionY, motionZ);
-            }
-        });
+        addDispatch("bubble", (world, x, y, z, motionX, motionY, motionZ, data) -> new ParticleBubble(world, x, y, z, motionX, motionY, motionZ, Dimension.getDimensionList().getOrDefault(data, Dimension.OVERWORLD), true));
+        addDispatch("bubbleboiling", (world, x, y, z, motionX, motionY, motionZ, data) -> new ParticleBubbleBoiling(world, x, y, z, motionX, motionY, motionZ, true));
+        addDispatch("smoke", (world, x, y, z, motionX, motionY, motionZ, data) -> new ParticleSmoke(world, x, y, z, motionX, motionY, motionZ, 1.0f, data));
+        addDispatch("note", (world, x, y, z, motionX, motionY, motionZ, data) -> new ParticleNote(world, x, y, z, motionX, motionY, motionZ, data));
+        addDispatch("portal", (world, x, y, z, motionX, motionY, motionZ, data) -> new ParticlePortal(world, x, y, z, motionX, motionY, motionZ, data));
+        addDispatch("explode", (world, x, y, z, motionX, motionY, motionZ, data) -> new ParticleExplode(world, x, y, z, motionX, motionY, motionZ));
+        addDispatch("flame", (world, x, y, z, motionX, motionY, motionZ, data) -> new ParticleFlame(world, x, y, z, motionX, motionY, motionZ, ParticleFlame.Type.ORANGE));
+        addDispatch("blueflame", (world, x, y, z, motionX, motionY, motionZ, data) -> new ParticleFlame(world, x, y, z, motionX, motionY, motionZ, ParticleFlame.Type.BLUE));
+        addDispatch("soulflame", (world, x, y, z, motionX, motionY, motionZ, data) -> new ParticleFlame(world, x, y, z, motionX, motionY, motionZ, ParticleFlame.Type.SOUL));
+        addDispatch("lava", (world, x, y, z, motionX, motionY, motionZ, data) -> new ParticleLava(world, x, y, z));
+        addDispatch("footstep", (world, x, y, z, motionX, motionY, motionZ, data) -> new ParticleFootstep(mc.textureManager, world, x, y, z));
+        addDispatch("splash", (world, x, y, z, motionX, motionY, motionZ, data) -> new ParticleSplash(world, x, y, z, motionX, motionY, motionZ, Dimension.getDimensionList().getOrDefault(data, Dimension.OVERWORLD)));
+        addDispatch("largesmoke", (world, x, y, z, motionX, motionY, motionZ, data) -> new ParticleSmoke(world, x, y, z, motionX, motionY, motionZ, 2.5f, data));
+        addDispatch("puffrgb", (world, x, y, z, motionX, motionY, motionZ, data) -> new ParticlePuffRGB(world, x, y, z, (float) motionX, (float) motionY, (float) motionZ, data));
+        addDispatch("reddust", (world, x, y, z, motionX, motionY, motionZ, data) -> new ParticleRedstoneDust(world, x, y, z, data));
+        addDispatch("item", (world, x, y, z, motionX, motionY, motionZ, data) -> new ParticleItemBreaking(world, x, y, z, Item.itemsList[MathHelper.clamp(data, 0, Item.itemsList.length - 1)]));
+        addDispatch("block", (world, x, y, z, motionX, motionY, motionZ, data) -> new ParticleBlockBreaking(world, x, y, z, motionX, motionY, motionZ, Blocks.blocksList[BlockParticleHelper.decodeBlockID(data)], BlockParticleHelper.decodeBlockSide(data), BlockParticleHelper.decodeBlockMeta(data)).init(new TilePos(x, y, z)));
+        addDispatch("snowshovel", (world, x, y, z, motionX, motionY, motionZ, data) -> new ParticleSnowShovel(world, x, y, z, motionX, motionY, motionZ));
         addDispatch("heart", new ParticleEntry() {
             @Override
             public Particle newParticle(@NotNull World world, double x, double y, double z, double motionX, double motionY, double motionZ, int data) {
@@ -176,30 +82,10 @@ public final class ParticleDispatcher extends Dispatcher<String, ParticleEntry>
                 return GameSettings.SLIME_PARTICLES.value;
             }
         });
-        addDispatch("fireflyGreen", new ParticleEntry() {
-            @Override
-            public Particle newParticle(@NotNull World world, double x, double y, double z, double motionX, double motionY, double motionZ, int data) {
-                return new ParticleFirefly(world, x, y, z, motionX, motionY, motionZ, MobFireflyCluster.FireflyColor.GREEN.getId());
-            }
-        });
-        addDispatch("fireflyBlue", new ParticleEntry() {
-            @Override
-            public Particle newParticle(@NotNull World world, double x, double y, double z, double motionX, double motionY, double motionZ, int data) {
-                return new ParticleFirefly(world, x, y, z, motionX, motionY, motionZ, MobFireflyCluster.FireflyColor.BLUE.getId());
-            }
-        });
-        addDispatch("fireflyOrange", new ParticleEntry() {
-            @Override
-            public Particle newParticle(@NotNull World world, double x, double y, double z, double motionX, double motionY, double motionZ, int data) {
-                return new ParticleFirefly(world, x, y, z, motionX, motionY, motionZ, MobFireflyCluster.FireflyColor.ORANGE.getId());
-            }
-        });
-        addDispatch("fireflyRed", new ParticleEntry() {
-            @Override
-            public Particle newParticle(@NotNull World world, double x, double y, double z, double motionX, double motionY, double motionZ, int data) {
-                return new ParticleFirefly(world, x, y, z, motionX, motionY, motionZ, MobFireflyCluster.FireflyColor.RED.getId());
-            }
-        });
+        addDispatch("fireflyGreen", (world, x, y, z, motionX, motionY, motionZ, data) -> new ParticleFirefly(world, x, y, z, motionX, motionY, motionZ, MobFireflyCluster.FireflyColor.GREEN.getId()));
+        addDispatch("fireflyBlue", (world, x, y, z, motionX, motionY, motionZ, data) -> new ParticleFirefly(world, x, y, z, motionX, motionY, motionZ, MobFireflyCluster.FireflyColor.BLUE.getId()));
+        addDispatch("fireflyOrange", (world, x, y, z, motionX, motionY, motionZ, data) -> new ParticleFirefly(world, x, y, z, motionX, motionY, motionZ, MobFireflyCluster.FireflyColor.ORANGE.getId()));
+        addDispatch("fireflyRed", (world, x, y, z, motionX, motionY, motionZ, data) -> new ParticleFirefly(world, x, y, z, motionX, motionY, motionZ, MobFireflyCluster.FireflyColor.RED.getId()));
         addDispatch("arrowtrail", new ParticleEntry() {
             @Override
             public Particle newParticle(@NotNull World world, double x, double y, double z, double motionX, double motionY, double motionZ, int data) {
@@ -211,89 +97,53 @@ public final class ParticleDispatcher extends Dispatcher<String, ParticleEntry>
                 return GameSettings.PARTICLES_QUALITY.value != ParticlesQuality.NONE;
             }
         });
-        addDispatch("fallingleaf", new ParticleEntry() {
-            @Override
-            public Particle newParticle(@NotNull World world, double x, double y, double z, double motionX, double motionY, double motionZ, int data) {
-                TilePos tilePos = new TilePos(x, y, z);
-                if (!world.isAirBlock(tilePos)) {
-                    return new ParticleLeaf(world, x, y, z, motionX, motionY, motionZ).init(tilePos);
-                }
-                return null;
-            }
-        });
-        addDispatch("dripWater", new ParticleEntry() {
-            @Override
-            public Particle newParticle(@NotNull World world, double x, double y, double z, double motionX, double motionY, double motionZ, int data) {
-                int color = BlockColorDispatcher.getInstance().getDispatch(Blocks.FLUID_WATER_STILL).getWorldColor(world, new TilePos(x, y, z), 0);
-                return new ParticleFluidDrip(world, x, y, z, color, TextureRegistry.getTexture("minecraft:block/fluid/water/flowing"));
-            }
-        });
-        addDispatch("dripLava", new ParticleEntry() {
-            @Override
-            public Particle newParticle(@NotNull World world, double x, double y, double z, double motionX, double motionY, double motionZ, int data) {
-                return new ParticleFluidDrip(world, x, y, z, 0xFFFFFF, TextureRegistry.getTexture("minecraft:block/fluid/lava/flowing")).setFullBright();
-            }
-        });
-        addDispatch("dripAcid", new ParticleEntry() {
-            @Override
-            public Particle newParticle(@NotNull World world, double x, double y, double z, double motionX, double motionY, double motionZ, int data) {
-                return new ParticleFluidDrip(world, x, y, z, 0xFFFFFF, TextureRegistry.getTexture("minecraft:block/fluid/acid/flowing")).setFullBright();
-            }
-        });
-        addDispatch("ashmote", new ParticleEntry() {
-            @Override
-            public Particle newParticle(@NotNull World world, double x, double y, double z, double motionX, double motionY, double motionZ, int data) {
-                return new ParticleAsh(world, x, y, z, 0, 0, 0);
-            }
-        });
-
-        addDispatch("ventsmoke", new ParticleEntry() {
-            @Override
-            public Particle newParticle(@NotNull World world, double x, double y, double z, double motionX, double motionY, double motionZ, int data) {
-                return new ParticleVentSmoke(world, x, y, z, motionX, motionY, motionX, 2.5f, data);
+        addDispatch("fallingleaf", (world, x, y, z, motionX, motionY, motionZ, data) -> {
+            TilePos tilePos = new TilePos(x, y, z);
+            if (!world.isAirBlock(tilePos)) {
+                return new ParticleLeaf(world, x, y, z, motionX, motionY, motionZ).init(tilePos);
             }
+            return null;
         });
-
-        addDispatch("rubyglassLightning", new ParticleEntry() {
-            @Override
-            public Particle newParticle(@NotNull World world, double x, double y, double z, double motionX, double motionY, double motionZ, int data) {
-                float pitch = 0;
-                float yaw = 0;
-                switch (Direction.fromId(data)) {
-                    case DOWN -> {
-                        pitch = 0.0f;
-                        yaw = 0.0f;
-                    }
-                    case UP -> {
-                        pitch = MathHelper.PI;
-                        yaw = 0.0f;
-                    }
-                    case NORTH -> {
-                        pitch = MathHelper.PI / 2.0f;
-                        yaw = 0.0f;
-                    }
-                    case SOUTH -> {
-                        pitch = -MathHelper.PI / 2.0f;
-                        yaw = 0.0f;
-                    }
-                    case WEST -> {
-                        pitch = MathHelper.PI / 2.0f;
-                        yaw = MathHelper.PI / 2.0f;
-                    }
-                    case EAST -> {
-                        pitch = MathHelper.PI / 2.0f;
-                        yaw = -MathHelper.PI / 2.0f;
-                    }
+        addDispatch("dripWater", (world, x, y, z, motionX, motionY, motionZ, data) -> {
+            int color = BlockColorDispatcher.getInstance().getDispatch(Blocks.FLUID_WATER_STILL).getWorldColor(world, new TilePos(x, y, z), 0);
+            return new ParticleFluidDrip(world, x, y, z, color, TextureRegistry.getTexture("minecraft:block/fluid/water/flowing"));
+        });
+        addDispatch("dripLava", (world, x, y, z, motionX, motionY, motionZ, data) -> new ParticleFluidDrip(world, x, y, z, 0xFFFFFF, TextureRegistry.getTexture("minecraft:block/fluid/lava/flowing")).setFullBright());
+        addDispatch("dripAcid", (world, x, y, z, motionX, motionY, motionZ, data) -> new ParticleFluidDrip(world, x, y, z, 0xFFFFFF, TextureRegistry.getTexture("minecraft:block/fluid/acid/flowing")).setFullBright());
+        addDispatch("ashmote", (world, x, y, z, motionX, motionY, motionZ, data) -> new ParticleAsh(world, x, y, z, motionX, motionY, motionZ));
+        addDispatch("ventsmoke", (world, x, y, z, motionX, motionY, motionZ, data) -> new ParticleVentSmoke(world, x, y, z, motionX, motionY, motionX, 2.5f, data));
+        addDispatch("rubyglassLightning", (world, x, y, z, motionX, motionY, motionZ, data) -> {
+            float pitch = 0;
+            float yaw = 0;
+            switch (Direction.fromId(data)) {
+                case DOWN -> {
+                    pitch = 0.0f;
+                    yaw = 0.0f;
+                }
+                case UP -> {
+                    pitch = Math.PI_f;
+                    yaw = 0.0f;
+                }
+                case NORTH -> {
+                    pitch = Math.PI_OVER_2_f;
+                    yaw = 0.0f;
+                }
+                case SOUTH -> {
+                    pitch = -Math.PI_OVER_2_f;
+                    yaw = 0.0f;
+                }
+                case WEST -> {
+                    pitch = Math.PI_OVER_2_f;
+                    yaw = Math.PI_OVER_2_f;
+                }
+                case EAST -> {
+                    pitch = Math.PI_OVER_2_f;
+                    yaw = -Math.PI_OVER_2_f;
                 }
-                return new ParticleRubyglassLightning(world, x, y, z, pitch, yaw);
             }
+            return new ParticleRubyglassLightning(world, x, y, z, pitch, yaw);
         });
 
-        addDispatch("acidBoiling", new ParticleEntry() {
-            @Override
-            public Particle newParticle(@NotNull World world, double x, double y, double z, double motionX, double motionY, double motionZ, int data) {
-                return new ParticleAcidBoiling(world, x, y, z, motionX, motionY, motionZ, true);
-            }
-        });
+        addDispatch("acidBoiling", (world, x, y, z, motionX, motionY, motionZ, data) -> new ParticleAcidBoiling(world, x, y, z, motionX, motionY, motionZ, true));
     }
 }
diff --git a/game/client/src/main/java/net/minecraft/client/render/particle/ParticleEntry.java b/game/client/src/main/java/net/minecraft/client/render/particle/ParticleEntry.java
index 23b8917d5..a788d0b2c 100644
--- a/game/client/src/main/java/net/minecraft/client/render/particle/ParticleEntry.java
+++ b/game/client/src/main/java/net/minecraft/client/render/particle/ParticleEntry.java
@@ -5,9 +5,9 @@ import net.minecraft.client.option.enums.ParticlesQuality;
 import net.minecraft.core.world.World;
 import org.jetbrains.annotations.NotNull;
 
-public abstract class ParticleEntry {
-    public abstract Particle newParticle(@NotNull World world, double x, double y, double z, double motionX, double motionY, double motionZ, int data);
-    public boolean enabled() {
+public interface ParticleEntry {
+    Particle newParticle(@NotNull World world, double x, double y, double z, double motionX, double motionY, double motionZ, int data);
+    default boolean enabled() {
         return GameSettings.PARTICLES_QUALITY.value == ParticlesQuality.ALL;
     }
 }
diff --git a/game/client/src/main/java/net/minecraft/client/render/worldtype/WorldTypeFXNether.java b/game/client/src/main/java/net/minecraft/client/render/worldtype/WorldTypeFXNether.java
index da00230c5..355fcb8b5 100644
--- a/game/client/src/main/java/net/minecraft/client/render/worldtype/WorldTypeFXNether.java
+++ b/game/client/src/main/java/net/minecraft/client/render/worldtype/WorldTypeFXNether.java
@@ -63,7 +63,7 @@ public class WorldTypeFXNether extends WorldTypeFX {
                 double rz = (player.z - radius) + (world.rand.nextDouble() * radius * 2);
 
                 if (world.getBlockType(tilePos.set(MathHelper.floor(rx), MathHelper.floor(ry), MathHelper.floor(rz))) == Blocks.AIR) {
-                    world.spawnParticle("ashmote", rx, ry, rz, 0, 0, 0, 0, false);
+                    world.spawnParticle("ashmote", rx, ry, rz, 0, -world.rand.nextDouble() * 0.35D, 0, 0, false);
                 }
             }
         }
@@ -84,7 +84,7 @@ public class WorldTypeFXNether extends WorldTypeFX {
                 double ry = player.y - (double) radius + world.rand.nextDouble() * (double) radius * 2.0F;
                 double rz = player.z - (double) radius + world.rand.nextDouble() * (double) radius * 2.0F;
                 if (world.getBlockType(tilePos.set(MathHelper.floor(rx), MathHelper.floor(ry), MathHelper.floor(rz))) == Blocks.AIR) {
-                    world.spawnParticle("ashmote", rx, ry, rz, 0.0F, 0.0F, 0.0F, 0, false);
+                    world.spawnParticle("ashmote", rx, ry, rz, 0.0F, -world.rand.nextDouble() * 0.35D, 0.0F, 0, false);
                 }
             }
         }
diff --git a/game/client/src/main/java/net/minecraft/client/sound/NamedSoundRepository.java b/game/client/src/main/java/net/minecraft/client/sound/NamedSoundRepository.java
index 019e7433e..01c89eac6 100644
--- a/game/client/src/main/java/net/minecraft/client/sound/NamedSoundRepository.java
+++ b/game/client/src/main/java/net/minecraft/client/sound/NamedSoundRepository.java
@@ -7,6 +7,7 @@ import com.google.gson.JsonElement;
 import com.google.gson.JsonObject;
 import com.google.gson.JsonParseException;
 import com.google.gson.JsonParser;
+import net.minecraft.client.Minecraft;
 import net.minecraft.client.render.texturepack.TexturePack;
 import net.minecraft.core.data.registry.Registry;
 import net.minecraft.core.lang.I18n;
@@ -136,19 +137,23 @@ public class NamedSoundRepository extends Registry<SoundEvent> {
                     }
 
                     for (JsonElement element : sounds) {
-                        if (element.isJsonPrimitive()) {
-                            try {
-                                soundEvent.addSoundEntry(getSoundEntry(soundEvent, pack, element.getAsString()));
-                            } catch (RuntimeException ex) {
-                                throwableList.add(ex);
+                        SoundEntry entry = null;
+                        try {
+                            if (element.isJsonPrimitive()) {
+                                entry = getSoundEntry(soundEvent, pack, element.getAsString());
+                            } else if (element.isJsonObject()) {
+                                entry = getSoundEntry(soundEvent, pack, element.getAsJsonObject());
                             }
-                        } else if (element.isJsonObject()) {
-                            try {
-                                soundEvent.addSoundEntry(getSoundEntry(soundEvent, pack, element.getAsJsonObject()));
-                            } catch (RuntimeException ex) {
-                                throwableList.add(ex);
+                            if (entry != null) {
+                                soundEvent.addSoundEntry(entry);
+                                if (entry.preload) {
+                                    Minecraft.getMinecraft().sndManager.preload(entry);
+                                }
                             }
+                        } catch (RuntimeException ex) {
+                            throwableList.add(ex);
                         }
+
                     }
                 }
             }
@@ -169,6 +174,7 @@ public class NamedSoundRepository extends Registry<SoundEvent> {
                 1,
                 16,
                 false,
+                false,
                 SoundEntry.Type.FILE
             );
     }
@@ -211,10 +217,10 @@ public class NamedSoundRepository extends Registry<SoundEvent> {
             attenuation_distance = jObj.get("attenuation_distance").getAsInt();
         }
 
-//        boolean preload = false;
-//        if (jObj.has("preload")) {
-//            preload = jObj.get("preload").getAsBoolean();
-//        }
+        boolean preload = false;
+        if (jObj.has("preload")) {
+            preload = jObj.get("preload").getAsBoolean();
+        }
 
         String type = "file";
         if (jObj.has("type")) {
@@ -233,6 +239,7 @@ public class NamedSoundRepository extends Registry<SoundEvent> {
                 weight,
                 attenuation_distance,
                 stream,
+                preload,
                 SoundEntry.Type.valueOf(type.toUpperCase(Locale.ROOT)
                 ));
     }
diff --git a/game/client/src/main/java/net/minecraft/client/sound/SoundEngine.java b/game/client/src/main/java/net/minecraft/client/sound/SoundEngine.java
index 0ae61e2e2..03925b384 100644
--- a/game/client/src/main/java/net/minecraft/client/sound/SoundEngine.java
+++ b/game/client/src/main/java/net/minecraft/client/sound/SoundEngine.java
@@ -279,6 +279,34 @@ public class SoundEngine
 		return eligible.get(random.nextInt(eligible.size()));
 	}
 
+	public void preload(final String name) {
+		final SoundEvent event = SoundRepository.SOUNDS.getSoundEvent(name);
+		if (event == null) return;
+		for (SoundEntry e : event.getEntryBag().getEntries()) {
+			preload(e);
+		}
+	}
+
+
+	public void preload(final SoundEntry entry) {
+		try {
+			lock.lock();
+			if (!isLoaded()) return;
+			if (entry.type == SoundEntry.Type.EVENT) {
+				preload(entry.name);
+			} else if (entry.type == SoundEntry.Type.FILE && !entry.shouldStream) {
+				final URL url = entry.getURL();
+				if (url != null) {
+					soundSystem.loadSound(url, entry.name);
+				}
+			}
+		} catch (Exception e) {
+			LOGGER.error("Unexpected exception while preloading sound event '{}'!", entry.name, e);
+		} finally {
+			lock.unlock();
+		}
+	}
+
 	public void playSound(final String name, final SoundCategory category, final float volume, final float pitch) {
 		playSound(SoundRepository.SOUNDS.getSoundEntry(name), category, volume, pitch);
 	}
diff --git a/game/client/src/main/java/net/minecraft/client/sound/SoundEntry.java b/game/client/src/main/java/net/minecraft/client/sound/SoundEntry.java
index 7a175cf74..02b048933 100644
--- a/game/client/src/main/java/net/minecraft/client/sound/SoundEntry.java
+++ b/game/client/src/main/java/net/minecraft/client/sound/SoundEntry.java
@@ -42,6 +42,11 @@ public class SoundEntry {
      */
     public final boolean shouldStream;
 
+    /**
+     *  Whether the audio should be preloaded or not
+     */
+    public final boolean preload;
+
     /**
      * How name field is interpreted
      */
@@ -60,6 +65,7 @@ public class SoundEntry {
         int weight,
         int attenuationDistance,
         boolean shouldStream,
+        boolean preload,
         @NotNull Type type
     )
     {
@@ -73,20 +79,21 @@ public class SoundEntry {
         this.weight = weight;
         this.attenuationDistance = attenuationDistance;
         this.shouldStream = shouldStream;
+        this.preload = preload;
         this.type = type;
         if (type == Type.FILE) {
-            file = new File(parentEvent.parentRepo.rootFile, name);
+            this.file = new File(parentEvent.parentRepo.rootFile, name);
         } else {
-            file = null;
+            this.file = null;
         }
     }
 
     public @Nullable URL getURL() {
-        if (file == null || !file.exists()) return null;
+        if (this.file == null || !this.file.exists()) return null;
         try {
-            return file.toURI().toURL();
+            return this.file.toURI().toURL();
         } catch (MalformedURLException e) {
-            System.err.println("Error getting url for sound '" + name + "'");
+            System.err.println("Error getting url for sound '" + this.name + "'");
             e.printStackTrace();
             return null;
         }
@@ -95,10 +102,10 @@ public class SoundEntry {
     @Override
     public String toString() {
         return "SoundEntry{" +
-            "file=" + file +
-            ", name='" + name + '\'' +
-            ", pack=" + pack +
-            ", shouldStream=" + shouldStream +
+            "file=" + this.file +
+            ", name='" + this.name + '\'' +
+            ", pack=" + this.pack +
+            ", shouldStream=" + this.shouldStream +
             '}';
     }
 
@@ -110,6 +117,6 @@ public class SoundEntry {
         /**
          * Name interpreted as a link to a different Event pool
          */
-        EVENT;
+        EVENT
     }
 }
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/block.json b/game/client/src/main/resources/assets/minecraft/models/block/block.json
index cdb9058db..0568d4318 100644
--- a/game/client/src/main/resources/assets/minecraft/models/block/block.json
+++ b/game/client/src/main/resources/assets/minecraft/models/block/block.json
@@ -24,10 +24,6 @@
         "firstperson_righthand": {
             "rotation": [ 0, 135, 0 ],
             "translation": [ 0, 0, 0 ],
-            "scale": [ 0.40, 0.40, 0.40 ]
-        },
-        "firstperson_lefthand": {
-            "rotation": [0, 135, 0],
             "scale": [0.4, 0.4, 0.4]
         }
     }
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/grass_ashy.json b/game/client/src/main/resources/assets/minecraft/models/block/grass_ashy.json
new file mode 100644
index 000000000..a6e8e7c46
--- /dev/null
+++ b/game/client/src/main/resources/assets/minecraft/models/block/grass_ashy.json
@@ -0,0 +1,8 @@
+{
+  "parent": "minecraft:block/cube_bottom_top",
+  "textures": {
+    "top": "minecraft:block/ash",
+    "bottom": "minecraft:block/grass/bottom",
+    "side": "minecraft:block/grass/ashy_side"
+  }
+}
\ No newline at end of file
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/grass_snowy.json b/game/client/src/main/resources/assets/minecraft/models/block/grass_snowy.json
index c338eab0b..8723680c7 100644
--- a/game/client/src/main/resources/assets/minecraft/models/block/grass_snowy.json
+++ b/game/client/src/main/resources/assets/minecraft/models/block/grass_snowy.json
@@ -1,7 +1,7 @@
 {
   "parent": "minecraft:block/cube_bottom_top",
   "textures": {
-    "top": "minecraft:block/grass/top",
+    "top": "minecraft:block/snow",
     "bottom": "minecraft:block/grass/bottom",
     "side": "minecraft:block/grass/snowy_side"
   }
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/matcher/template.json b/game/client/src/main/resources/assets/minecraft/models/block/matcher/template.json
index ed758f73b..ea6dd3745 100644
--- a/game/client/src/main/resources/assets/minecraft/models/block/matcher/template.json
+++ b/game/client/src/main/resources/assets/minecraft/models/block/matcher/template.json
@@ -11,6 +11,29 @@
 		"particle_west": "#side",
 		"particle_east": "#side"
 	},
+	"display": {
+		"thirdperson_righthand": {
+			"rotation": [-15, 180, -135],
+			"translation": [0, 2.5, 0],
+			"scale": [0.375, 0.375, 0.375]
+		},
+		"firstperson_righthand": {
+			"rotation": [90, 0, 45],
+			"scale": [0.4, 0.4, 0.4]
+		},
+		"ground": {
+			"rotation": [90, 0, 0],
+			"translation": [0, 3, 0],
+			"scale": [0.25, 0.25, 0.25]
+		},
+		"gui": {
+			"rotation": [120, 0, -45],
+			"scale": [0.625, 0.625, 0.625]
+		},
+		"head": {
+			"rotation": [90, 0, 0]
+		}
+	},
 	"elements": [
 		{
 			"from": [0, 0, 0],
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/ash_ember.png b/game/client/src/main/resources/assets/minecraft/textures/block/ash_ember.png
index f33b0b7b1..475fbb695 100644
Binary files a/game/client/src/main/resources/assets/minecraft/textures/block/ash_ember.png and b/game/client/src/main/resources/assets/minecraft/textures/block/ash_ember.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/block_ash.png b/game/client/src/main/resources/assets/minecraft/textures/block/block_ash.png
index e9a8c4d44..0adbf66de 100644
Binary files a/game/client/src/main/resources/assets/minecraft/textures/block/block_ash.png and b/game/client/src/main/resources/assets/minecraft/textures/block/block_ash.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/grass/ashy_side.png b/game/client/src/main/resources/assets/minecraft/textures/block/grass/ashy_side.png
new file mode 100644
index 000000000..fdf680a76
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/block/grass/ashy_side.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/item/statue_basalt.png b/game/client/src/main/resources/assets/minecraft/textures/item/statue_basalt.png
index 202f121e6..d6c73a9cd 100644
Binary files a/game/client/src/main/resources/assets/minecraft/textures/item/statue_basalt.png and b/game/client/src/main/resources/assets/minecraft/textures/item/statue_basalt.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/item/statue_gloomstone.png b/game/client/src/main/resources/assets/minecraft/textures/item/statue_gloomstone.png
index 343922563..87bcf0ec3 100644
Binary files a/game/client/src/main/resources/assets/minecraft/textures/item/statue_gloomstone.png and b/game/client/src/main/resources/assets/minecraft/textures/item/statue_gloomstone.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/item/statue_granite.png b/game/client/src/main/resources/assets/minecraft/textures/item/statue_granite.png
index ff08200ca..6e1fd5615 100644
Binary files a/game/client/src/main/resources/assets/minecraft/textures/item/statue_granite.png and b/game/client/src/main/resources/assets/minecraft/textures/item/statue_granite.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/item/statue_limestone.png b/game/client/src/main/resources/assets/minecraft/textures/item/statue_limestone.png
index 725ffa1ed..6a7927924 100644
Binary files a/game/client/src/main/resources/assets/minecraft/textures/item/statue_limestone.png and b/game/client/src/main/resources/assets/minecraft/textures/item/statue_limestone.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/item/statue_marble.png b/game/client/src/main/resources/assets/minecraft/textures/item/statue_marble.png
index 69158337a..18bbadf4e 100644
Binary files a/game/client/src/main/resources/assets/minecraft/textures/item/statue_marble.png and b/game/client/src/main/resources/assets/minecraft/textures/item/statue_marble.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/item/statue_netherrack.png b/game/client/src/main/resources/assets/minecraft/textures/item/statue_netherrack.png
index 464179460..d05fcc9f0 100644
Binary files a/game/client/src/main/resources/assets/minecraft/textures/item/statue_netherrack.png and b/game/client/src/main/resources/assets/minecraft/textures/item/statue_netherrack.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/item/statue_permafrost.png b/game/client/src/main/resources/assets/minecraft/textures/item/statue_permafrost.png
index 5804232d7..2b22b6c0c 100644
Binary files a/game/client/src/main/resources/assets/minecraft/textures/item/statue_permafrost.png and b/game/client/src/main/resources/assets/minecraft/textures/item/statue_permafrost.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/item/statue_pigman.png b/game/client/src/main/resources/assets/minecraft/textures/item/statue_pigman.png
index 2acbb92b5..3abf26e5d 100644
Binary files a/game/client/src/main/resources/assets/minecraft/textures/item/statue_pigman.png and b/game/client/src/main/resources/assets/minecraft/textures/item/statue_pigman.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/item/statue_slate.png b/game/client/src/main/resources/assets/minecraft/textures/item/statue_slate.png
index 20ec97fcb..fee3e787a 100644
Binary files a/game/client/src/main/resources/assets/minecraft/textures/item/statue_slate.png and b/game/client/src/main/resources/assets/minecraft/textures/item/statue_slate.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/item/statue_stone.png b/game/client/src/main/resources/assets/minecraft/textures/item/statue_stone.png
index 1e46d5991..f5be4fa42 100644
Binary files a/game/client/src/main/resources/assets/minecraft/textures/item/statue_stone.png and b/game/client/src/main/resources/assets/minecraft/textures/item/statue_stone.png differ
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 e46945dd1..05e590e5b 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
@@ -353,6 +353,88 @@ public class BlockLogic implements BlockInterface, IItemConvertible {
         return v.x() >= bounds.minX() && v.x() <= bounds.maxX() && v.y() >= bounds.minY() && v.y() <= bounds.maxY();
     }
 
+    protected @Nullable HitResult traceRayAgainstLocalAABB(@NotNull TilePosc tilePos, @NotNull Vector3dc start, @NotNull Vector3dc end, @NotNull AABBdc bounds) {
+        double px = tilePos.x();
+        double py = tilePos.y();
+        double pz = tilePos.z();
+        Vector3d startInBlock = start.add(-px, -py, -pz, new Vector3d());
+        Vector3d endInBlock = end.add(-px, -py, -pz, new Vector3d());
+
+        @Nullable Vector3d minVX = MathHelper.vecClipX(startInBlock, endInBlock, bounds.minX(), new Vector3d());
+        @Nullable Vector3d maxVX = MathHelper.vecClipX(startInBlock, endInBlock, bounds.maxX(), new Vector3d());
+        @Nullable Vector3d minVY = MathHelper.vecClipY(startInBlock, endInBlock, bounds.minY(), new Vector3d());
+        @Nullable Vector3d maxVY = MathHelper.vecClipY(startInBlock, endInBlock, bounds.maxY(), new Vector3d());
+        @Nullable Vector3d minVZ = MathHelper.vecClipZ(startInBlock, endInBlock, bounds.minZ(), new Vector3d());
+        @Nullable Vector3d maxVZ = MathHelper.vecClipZ(startInBlock, endInBlock, bounds.maxZ(), new Vector3d());
+
+        if (!isVecInsideYZBounds(bounds, minVX)) minVX = null;
+        if (!isVecInsideYZBounds(bounds, maxVX)) maxVX = null;
+        if (!isVecInsideXZBounds(bounds, minVY)) minVY = null;
+        if (!isVecInsideXZBounds(bounds, maxVY)) maxVY = null;
+        if (!isVecInsideXYBounds(bounds, minVZ)) minVZ = null;
+        if (!isVecInsideXYBounds(bounds, maxVZ)) maxVZ = null;
+
+        @Nullable Vector3d clip = null;
+        @Nullable Side side = null;
+        double bestD2 = Double.POSITIVE_INFINITY;
+
+        if (minVX != null) {
+            double d2 = startInBlock.distanceSquared(minVX);
+            if (d2 < bestD2) {
+                bestD2 = d2;
+                clip = minVX;
+                side = Side.WEST;
+            }
+        }
+        if (maxVX != null) {
+            double d2 = startInBlock.distanceSquared(maxVX);
+            if (d2 < bestD2) {
+                bestD2 = d2;
+                clip = maxVX;
+                side = Side.EAST;
+            }
+        }
+        if (minVY != null) {
+            double d2 = startInBlock.distanceSquared(minVY);
+            if (d2 < bestD2) {
+                bestD2 = d2;
+                clip = minVY;
+                side = Side.BOTTOM;
+            }
+        }
+        if (maxVY != null) {
+            double d2 = startInBlock.distanceSquared(maxVY);
+            if (d2 < bestD2) {
+                bestD2 = d2;
+                clip = maxVY;
+                side = Side.TOP;
+            }
+        }
+        if (minVZ != null) {
+            double d2 = startInBlock.distanceSquared(minVZ);
+            if (d2 < bestD2) {
+                bestD2 = d2;
+                clip = minVZ;
+                side = Side.NORTH;
+            }
+        }
+        if (maxVZ != null) {
+            double d2 = startInBlock.distanceSquared(maxVZ);
+            if (d2 < bestD2) {
+                //leave for consistency, if ordering or anything else changes here
+                bestD2 = d2;
+                clip = maxVZ;
+                side = Side.SOUTH;
+            }
+        }
+
+        if (clip == null) {
+            return null;
+        }
+
+        return new HitResult.Tile(tilePos, side, clip.add(px, py, pz, new Vector3d()));
+    }
+
     @Override
     public void onDestroyedByExplosion(final @NotNull World world, final @NotNull TilePosc tilePos) { }
 
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicFire.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicFire.java
index 53b5267a0..7571fabb6 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicFire.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicFire.java
@@ -6,12 +6,12 @@ import net.minecraft.core.block.support.ISupport;
 import net.minecraft.core.block.support.PartialSupport;
 import net.minecraft.core.block.tag.BlockTags;
 import net.minecraft.core.data.gamerule.GameRules;
-import net.minecraft.core.data.registry.Registries;
 import net.minecraft.core.data.tag.Tag;
 import net.minecraft.core.entity.Entity;
-import net.minecraft.core.item.block.ItemBlock;
 import net.minecraft.core.sound.SoundCategory;
 import net.minecraft.core.util.helper.Side;
+import net.minecraft.core.util.phys.BoundingVolume;
+import net.minecraft.core.util.phys.HitResult;
 import net.minecraft.core.enums.EnumDropCause;
 import net.minecraft.core.item.ItemStack;
 import net.minecraft.core.world.WorldSource;
@@ -21,8 +21,12 @@ import net.minecraft.core.world.pos.TilePosc;
 import net.minecraft.core.world.weather.IPrecipitation;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
+import org.joml.Vector3dc;
+import org.joml.primitives.AABBd;
 import org.joml.primitives.AABBdc;
 
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Objects;
 import java.util.Random;
 
@@ -35,8 +39,10 @@ public class BlockLogicFire
     public static final int BURN_INSTANT = 100;
     public static final int BURN_EASY = 60;
     public static final int BURN_MEDIUM = 20;
-    public static final int BURN_HARD = 5;
+    public static final int BURN_HARD = 10;
     public static final int BURN_NEVER = 0;
+    private static final float ASH_LAYER_CHANCE = 0.50F;
+    private static final int ASH_GROUND_SCAN_RANGE = 16;
     private static final int @NotNull [] flameChance = new int[Blocks.blocksList.length];
     private static final int @NotNull [] burnChance = new int[Blocks.blocksList.length];
 
@@ -60,14 +66,14 @@ public class BlockLogicFire
         setFlammable(Blocks.SLAB_PLANKS_OAK, FLAME_HARD, BURN_MEDIUM);
         setFlammable(Blocks.SLAB_PLANKS_PAINTED, FLAME_HARD, BURN_MEDIUM);
         setFlammable(Blocks.STAIRS_PLANKS_OAK, FLAME_HARD, BURN_MEDIUM);
-        setFlammable(Blocks.LOG_OAK, FLAME_HARD, BURN_HARD);
-        setFlammable(Blocks.LOG_PINE, FLAME_HARD, BURN_HARD);
-        setFlammable(Blocks.LOG_BIRCH, FLAME_HARD, BURN_HARD);
-        setFlammable(Blocks.LOG_CHERRY, FLAME_HARD, BURN_HARD);
-        setFlammable(Blocks.LOG_OAK_MOSSY, FLAME_HARD, BURN_HARD);
-        setFlammable(Blocks.LOG_EUCALYPTUS, FLAME_HARD, BURN_HARD);
-        setFlammable(Blocks.LOG_THORN, FLAME_HARD, BURN_HARD);
-        setFlammable(Blocks.LOG_PALM, FLAME_HARD, BURN_HARD);
+        setFlammable(Blocks.LOG_OAK, FLAME_MEDIUM, BURN_HARD);
+        setFlammable(Blocks.LOG_PINE, FLAME_MEDIUM, BURN_HARD);
+        setFlammable(Blocks.LOG_BIRCH, FLAME_MEDIUM, BURN_HARD);
+        setFlammable(Blocks.LOG_CHERRY, FLAME_MEDIUM, BURN_HARD);
+        setFlammable(Blocks.LOG_OAK_MOSSY, FLAME_MEDIUM, BURN_HARD);
+        setFlammable(Blocks.LOG_EUCALYPTUS, FLAME_MEDIUM, BURN_HARD);
+        setFlammable(Blocks.LOG_THORN, FLAME_MEDIUM, BURN_HARD);
+        setFlammable(Blocks.LOG_PALM, FLAME_MEDIUM, BURN_HARD);
         setFlammable(Blocks.LEAVES_OAK, FLAME_EASY, BURN_EASY);
         setFlammable(Blocks.LEAVES_PINE, FLAME_EASY, BURN_EASY);
         setFlammable(Blocks.LEAVES_BIRCH, FLAME_EASY, BURN_EASY);
@@ -98,6 +104,85 @@ public class BlockLogicFire
         return null;
     }
 
+    private static final double PANEL_THICKNESS = 1.0 / 16.0;
+    private static final @NotNull AABBdc SHAPE_PART_GROUND = new AABBd(0.0, 0.0, 0.0, 1.0, 1.0, 1.0);
+    private static final @NotNull AABBdc SHAPE_PART_WEST = new AABBd(0.0, 0.0, 0.0, PANEL_THICKNESS, 1.0, 1.0);
+    private static final @NotNull AABBdc SHAPE_PART_EAST = new AABBd(1.0 - PANEL_THICKNESS, 0.0, 0.0, 1.0, 1.0, 1.0);
+    private static final @NotNull AABBdc SHAPE_PART_NORTH = new AABBd(0.0, 0.0, 0.0, 1.0, 1.0, PANEL_THICKNESS);
+    private static final @NotNull AABBdc SHAPE_PART_SOUTH = new AABBd(0.0, 0.0, 1.0 - PANEL_THICKNESS, 1.0, 1.0, 1.0);
+    private static final @NotNull AABBdc SHAPE_PART_UP = new AABBd(0.0, 1.0 - PANEL_THICKNESS, 0.0, 1.0, 1.0, 1.0);
+    private static final int SHAPE_WEST = 1;
+    private static final int SHAPE_EAST = 1 << 1;
+    private static final int SHAPE_NORTH = 1 << 2;
+    private static final int SHAPE_SOUTH = 1 << 3;
+    private static final int SHAPE_UP = 1 << 4;
+    private static final int SHAPE_GROUND = 1 << 5;
+    private static final @Nullable BoundingVolume [] SHAPE_VOLUMES = new BoundingVolume[SHAPE_GROUND + 1];
+
+    private static int getShapeMask(final @NotNull WorldSource source, final @NotNull TilePosc tilePos) {
+        final TilePos queryPos = new TilePos();
+        if (source.isBlockNormalCube(tilePos.down(queryPos)) || canBurn(source, tilePos.down(queryPos))) {
+            return SHAPE_GROUND;
+        }
+        int mask = 0;
+        if (canBurn(source, tilePos.west(queryPos))) mask |= SHAPE_WEST;
+        if (canBurn(source, tilePos.east(queryPos))) mask |= SHAPE_EAST;
+        if (canBurn(source, tilePos.north(queryPos))) mask |= SHAPE_NORTH;
+        if (canBurn(source, tilePos.south(queryPos))) mask |= SHAPE_SOUTH;
+        if (canBurn(source, tilePos.up(queryPos))) mask |= SHAPE_UP;
+        return mask == 0 ? SHAPE_GROUND : mask;
+    }
+
+    private static @NotNull List<@NotNull AABBdc> getShapeParts(final int mask) {
+        if (mask == SHAPE_GROUND) {
+            return List.of(SHAPE_PART_GROUND);
+        }
+        final List<AABBdc> parts = new ArrayList<>(5);
+        if ((mask & SHAPE_WEST) != 0) parts.add(SHAPE_PART_WEST);
+        if ((mask & SHAPE_EAST) != 0) parts.add(SHAPE_PART_EAST);
+        if ((mask & SHAPE_NORTH) != 0) parts.add(SHAPE_PART_NORTH);
+        if ((mask & SHAPE_SOUTH) != 0) parts.add(SHAPE_PART_SOUTH);
+        if ((mask & SHAPE_UP) != 0) parts.add(SHAPE_PART_UP);
+        return parts;
+    }
+
+    @Override
+    public @NotNull AABBdc getBoundsFromState(final @NotNull WorldSource source, final @NotNull TilePosc tilePos) {
+        final AABBd bounds = new AABBd();
+        for (final AABBdc part : getShapeParts(getShapeMask(source, tilePos))) {
+            bounds.union(part);
+        }
+        return bounds;
+    }
+
+    @Override
+    public @Nullable BoundingVolume getBoundingVolume(final @NotNull World world, final @NotNull TilePosc tilePos) {
+        final int mask = getShapeMask(world, tilePos);
+        BoundingVolume volume = SHAPE_VOLUMES[mask];
+        if (volume == null) {
+            volume = BoundingVolume.bakeFromBlockSpaceAabbs(getShapeParts(mask).toArray(new AABBdc[0]));
+            SHAPE_VOLUMES[mask] = volume;
+        }
+        return volume;
+    }
+
+    @Override
+    public @Nullable HitResult collisionRayTrace(final @NotNull World world, final @NotNull TilePosc tilePos, final @NotNull Vector3dc start, final @NotNull Vector3dc end, final boolean useSelectionAABB) {
+        @Nullable HitResult closest = null;
+        double closestDistSq = Double.POSITIVE_INFINITY;
+        for (final AABBdc part : getShapeParts(getShapeMask(world, tilePos))) {
+            final @Nullable HitResult hit = traceRayAgainstLocalAABB(tilePos, start, end, part);
+            if (hit != null) {
+                final double distSq = start.distanceSquared(hit.location);
+                if (distSq < closestDistSq) {
+                    closestDistSq = distSq;
+                    closest = hit;
+                }
+            }
+        }
+        return closest;
+    }
+
     @Override
     public boolean isSolidRender() {
         return false;
@@ -230,15 +315,19 @@ public class BlockLogicFire
             // Logs should always scorch when they burn, never convert into a fire block first.
             if (targetBlock.getLogic() instanceof BlockLogicLog) {
                 this.setBurnResult(world, tilePos);
+                this.onBlockBurnt(world, tilePos, random);
                 return;
             }
 
             if (random.nextInt(meta + 10) < 5 && !world.isBlockBeingRainedOn(tilePos)) {
                 if (this.getBurnResult(world, tilePos) == Blocks.AIR /* Fix for fire destroying moss blocks */) {
                     //Spread Fire with higher meta
-                    world.setBlockTypeDataNotify(tilePos, this.getSpreadFireBlock(), Math.min(meta + random.nextInt(5) / 4, 15));}
+                    world.setBlockTypeDataNotify(tilePos, this.getSpreadFireBlock(), Math.min(meta + random.nextInt(5) / 4, 15));
+                    this.onBlockBurnt(world, tilePos, random);
+                }
             } else {
                 this.setBurnResult(world, tilePos);
+                this.onBlockBurnt(world, tilePos, random);
             }
 
             if (isTNT) {
@@ -247,6 +336,51 @@ public class BlockLogicFire
         }
     }
 
+    protected void onBlockBurnt(final @NotNull World world, final @NotNull TilePosc tilePos, final @NotNull Random rand) {
+        for (int i = 0; i < 6; i++) {
+            world.spawnParticle("ashmote", tilePos.x() + rand.nextFloat(), tilePos.y() + rand.nextFloat(), tilePos.z() + rand.nextFloat(), 0.0D, 0.0D, 0.0D, 0, 32.0D, true);
+        }
+        if (rand.nextFloat() < ASH_LAYER_CHANCE) {
+            this.tryPlaceAshLayer(world, tilePos);
+        }
+    }
+
+    private void tryPlaceAshLayer(final @NotNull World world, final @NotNull TilePosc burntPos) {
+        final @NotNull TilePos probe = new TilePos(burntPos);
+        final @NotNull TilePos below = new TilePos();
+        for (int i = 0; i < ASH_GROUND_SCAN_RANGE && probe.y > 0; i++, probe.y--) {
+            final Block<?> blockAtProbe = world.getBlockType(probe);
+
+            if (blockAtProbe == Blocks.LAYER_ASH) {
+                final int height = world.getBlockData(probe) & BlockLogicLayerBase.MASK_HEIGHT;
+                if (height < 6) {
+                    world.setBlockData(probe, height + 1);
+                    world.markBlockNeedsUpdate(probe);
+                } else {
+                    world.setBlockTypeNotify(probe, Blocks.BLOCK_ASH);
+                }
+                return;
+            }
+
+            if (blockAtProbe.getLogic() instanceof BlockLogicFire) {
+                continue;
+            }
+
+            if (isAshGround(world, probe.down(below)) && Blocks.LAYER_ASH.canPlaceAt(world, probe)) {
+                world.setBlockTypeNotify(probe, Blocks.LAYER_ASH);
+                return;
+            }
+
+            if (isAshGround(world, probe) || blockAtProbe.getMaterial().isLiquid()) {
+                return;
+            }
+        }
+    }
+
+    private static boolean isAshGround(final @NotNull World world, final @NotNull TilePosc tilePos) {
+        return world.isBlockOpaqueCube(tilePos) || world.getBlockType(tilePos).getLogic() instanceof BlockLogicTransparent;
+    }
+
     protected boolean canNeighborCatchFire(final @NotNull World world, final @NotNull TilePosc tilePos) {
         TilePos queryPos = new TilePos();
         if (canBurn(world, tilePos.east(queryPos))) return true;
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicFluidFlowing.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicFluidFlowing.java
index 49e10c5e3..92852658d 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicFluidFlowing.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicFluidFlowing.java
@@ -93,7 +93,7 @@ public class BlockLogicFluidFlowing extends BlockLogicFluid implements BlockLogi
             final int meta = (localFlowDecay < MAX_SPREAD)
                 ? localFlowDecay + flowDecayMod
                 : 1;
-            final int spreadDistance = (MAX_SPREAD - localFlowDecay) / flowDecayMod - 1;
+            final int spreadDistance = (MAX_SPREAD - meta) / flowDecayMod;
             if (spreadDistance <= 0) return;
 
             final byte spreadFlags = getSpreadDirections(world, tilePos, spreadDistance);
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicLayerAsh.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicLayerAsh.java
index af63cfeea..a2c559481 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicLayerAsh.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicLayerAsh.java
@@ -7,7 +7,6 @@ import net.minecraft.core.entity.animal.MobWolf;
 import net.minecraft.core.entity.player.Player;
 import net.minecraft.core.enums.EnumDropCause;
 import net.minecraft.core.item.ItemStack;
-import net.minecraft.core.util.helper.Direction;
 import net.minecraft.core.world.World;
 import net.minecraft.core.world.pos.TilePos;
 import net.minecraft.core.world.pos.TilePosc;
@@ -37,23 +36,11 @@ public class BlockLogicLayerAsh extends BlockLogicLayerSupportable
         }
     }
 
-    public void accumulate(@NotNull World world, @NotNull TilePosc tilePos) {
-        int myMetadata = world.getBlockData(tilePos);
-        if (myMetadata == 0x7) return;
-
-        for (final var dir : Direction.horizontal) {
-            TilePos dirPos = tilePos.add(dir, new TilePos());
-            if (!((world.isBlockOpaqueCube(dirPos)) || (world.getBlockType(dirPos) == this.block && world.getBlockData(dirPos) >= myMetadata))) return;
-        }
-
-        world.setBlockData(tilePos, myMetadata + 1);
-        world.markBlockNeedsUpdate(tilePos);
-    }
-
     @Override
-    public @NotNull ItemStack @Nullable [] getBreakResult(@NotNull World world, @NotNull EnumDropCause dropCause, int data, @Nullable TileEntity tileEntity) {
+    public ItemStack[] getBreakResult(@NotNull World world, @NotNull EnumDropCause dropCause, int data, @Nullable TileEntity tileEntity) {
         return switch (dropCause) {
-            case SILK_TOUCH, PICK_BLOCK -> new ItemStack[]{new ItemStack(this)};
+            case SILK_TOUCH -> new ItemStack[]{new ItemStack(this, data + 1)};
+            case PICK_BLOCK -> new ItemStack[]{new ItemStack(this)};
             default -> null;
         };
     }
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicLayerBase.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicLayerBase.java
index f2d134527..fdcda18e8 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicLayerBase.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicLayerBase.java
@@ -63,7 +63,7 @@ public abstract class BlockLogicLayerBase extends BlockLogic implements BlockLog
     }
 
     protected @NotNull AABBd getBoundsFromStateMut(@NotNull WorldSource source, @NotNull TilePosc tilePos) {
-        int data = source.getBlockData(tilePos) & MASK_HEIGHT;
+        int data = Math.min(source.getBlockData(tilePos) & MASK_HEIGHT, 6);
         float f = (float)(2 * (1 + data)) / 16F;
         return new AABBd(0.0F, 0.0F, 0.0F, 1.0F, f, 1.0F);
     }
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicLayerLeaves.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicLayerLeaves.java
index 4c083affd..a9640185f 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicLayerLeaves.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicLayerLeaves.java
@@ -35,7 +35,7 @@ public class BlockLogicLayerLeaves extends BlockLogicLayerSupportable implements
 
     public void accumulate(@NotNull World world, @NotNull TilePosc tilePos) {
         int myMetadata = world.getBlockData(tilePos);
-        if (myMetadata == 0x7) return;
+        if (myMetadata >= 6) return;
 
         for (final var dir : Direction.horizontal) {
             TilePos p = tilePos.add(dir, new TilePos());
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicLayerSnow.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicLayerSnow.java
index 9f3b9809b..00d1ccab2 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicLayerSnow.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicLayerSnow.java
@@ -2,12 +2,14 @@ package net.minecraft.core.block;
 
 import net.minecraft.core.block.entity.TileEntity;
 import net.minecraft.core.block.material.Material;
-import net.minecraft.core.block.material.Materials;
+import net.minecraft.core.entity.player.Player;
 import net.minecraft.core.enums.EnumDropCause;
 import net.minecraft.core.enums.LightLayer;
 import net.minecraft.core.item.ItemStack;
 import net.minecraft.core.item.Items;
+import net.minecraft.core.item.block.ItemBlockLayerSnow;
 import net.minecraft.core.util.helper.Direction;
+import net.minecraft.core.util.helper.Side;
 import net.minecraft.core.world.World;
 import net.minecraft.core.world.WorldSource;
 import net.minecraft.core.world.biome.BiomeTags;
@@ -20,12 +22,14 @@ import org.joml.primitives.AABBdc;
 
 import java.util.Random;
 
-public class BlockLogicLayerSnow extends BlockLogicLayerSupportable {
+public class BlockLogicLayerSnow extends BlockLogicLayerSupportable implements BlockLogic.MatcherDataEquivalency.Masked {
+    public static final int MASK_PERMANENT = 0b1000_0000;
 
-    public BlockLogicLayerSnow(@NotNull Block<? extends BlockLogicLayerBase> block, @NotNull Block<?> fullBlock, @NotNull Material material) {
+    public BlockLogicLayerSnow(@NotNull Block<? extends BlockLogicLayerSnow> block, @NotNull Block<?> fullBlock, @NotNull Material material) {
         super(block, fullBlock, material);
         setBlockBounds(0.0F, 0.0F, 0.0F, 1.0F, 0.125F, 1.0F);
         block.setTicking(true);
+        block.setBlockItem(() -> new ItemBlockLayerSnow<>(block));
     }
 
     public void accumulate(@NotNull World world, @NotNull TilePosc tilePos) {
@@ -51,16 +55,19 @@ public class BlockLogicLayerSnow extends BlockLogicLayerSupportable {
 
     @Override
     public ItemStack[] getBreakResult(@NotNull World world, @NotNull EnumDropCause dropCause, int data, @Nullable TileEntity tileEntity) {
+        int layers = (data & MASK_HEIGHT) + 1;
         return switch (dropCause) {
-            case SILK_TOUCH -> new ItemStack[]{new ItemStack(this, data + 1)};
+            case SILK_TOUCH -> new ItemStack[]{new ItemStack(this, layers)};
             case PICK_BLOCK -> new ItemStack[]{new ItemStack(this)};
-            case EXPLOSION, PROPER_TOOL -> new ItemStack[]{new ItemStack(Items.AMMO_SNOWBALL, data + 1)};
+            case EXPLOSION, PROPER_TOOL -> new ItemStack[]{new ItemStack(Items.AMMO_SNOWBALL, layers)};
             default -> null;
         };
     }
 
     @Override
     public void updateTick(@NotNull World world, @NotNull TilePosc tilePos, @NotNull Random rand, boolean isRandomTick) {
+        if (isPermanent(world.getBlockData(tilePos))) return;
+
         if (world.getSavedLightValue(LightLayer.Block, tilePos) > 11) {
             dropWithCause(world, EnumDropCause.WORLD, tilePos, world.getBlockData(tilePos), null, null);
             world.setBlockTypeNotify(tilePos, Blocks.AIR);
@@ -70,4 +77,26 @@ public class BlockLogicLayerSnow extends BlockLogicLayerSupportable {
             world.setBlockTypeNotify(tilePos, Blocks.AIR);
         }
     }
+
+    @Override
+    public int getPlacedData(@Nullable Player player, @NotNull ItemStack itemStack, @NotNull World world, @NotNull TilePosc tilePos, @NotNull Side side, double xHit, double yHit) {
+        return MASK_PERMANENT;
+    }
+
+    public static boolean isPermanent(int meta) {
+        return (meta & MASK_PERMANENT) != 0;
+    }
+
+    public static int setPermanent(int meta, boolean flag) {
+        if (flag) {
+            return meta | MASK_PERMANENT;
+        } else {
+            return meta & ~MASK_PERMANENT;
+        }
+    }
+
+    @Override
+    public int getMatcherDataEquivalencyMask() {
+        return ~MASK_PERMANENT;
+    }
 }
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicStairs.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicStairs.java
index 599e968d3..413bfe898 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicStairs.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicStairs.java
@@ -17,7 +17,6 @@ import net.minecraft.core.entity.Entity;
 import net.minecraft.core.enums.EnumDropCause;
 import net.minecraft.core.item.ItemStack;
 import net.minecraft.core.util.helper.Direction;
-import net.minecraft.core.util.helper.MathHelper;
 import net.minecraft.core.util.helper.Side;
 import net.minecraft.core.util.phys.BoundingVolume;
 import net.minecraft.core.util.phys.HitResult;
@@ -232,7 +231,7 @@ public class BlockLogicStairs extends BlockLogic implements BlockLogic.MatcherDa
         @Nullable HitResult closest = null;
         double closestDistSq = Double.POSITIVE_INFINITY;
         for (AABBd part : stairLocalParts(hRotation, stepYOffset)) {
-            @Nullable HitResult hit = traceRayAgainstLocalAabb(tilePos, start, end, part);
+            @Nullable HitResult hit = traceRayAgainstLocalAABB(tilePos, start, end, part);
             if (hit != null) {
                 double d2 = start.distanceSquared(hit.location);
                 if (d2 < closestDistSq) {
@@ -376,105 +375,4 @@ public class BlockLogicStairs extends BlockLogic implements BlockLogic.MatcherDa
         return this.modelBlock.getMaterialColor();
     }
 
-    private static @Nullable HitResult traceRayAgainstLocalAabb(@NotNull TilePosc tilePos, @NotNull Vector3dc start, @NotNull Vector3dc end, @NotNull AABBdc bounds) {
-        double px = tilePos.x();
-        double py = tilePos.y();
-        double pz = tilePos.z();
-        Vector3d startInBlock = start.add(-px, -py, -pz, new Vector3d());
-        Vector3d endInBlock = end.add(-px, -py, -pz, new Vector3d());
-
-        @Nullable Vector3d minVX = MathHelper.vecClipX(startInBlock, endInBlock, bounds.minX(), new Vector3d());
-        @Nullable Vector3d maxVX = MathHelper.vecClipX(startInBlock, endInBlock, bounds.maxX(), new Vector3d());
-        @Nullable Vector3d minVY = MathHelper.vecClipY(startInBlock, endInBlock, bounds.minY(), new Vector3d());
-        @Nullable Vector3d maxVY = MathHelper.vecClipY(startInBlock, endInBlock, bounds.maxY(), new Vector3d());
-        @Nullable Vector3d minVZ = MathHelper.vecClipZ(startInBlock, endInBlock, bounds.minZ(), new Vector3d());
-        @Nullable Vector3d maxVZ = MathHelper.vecClipZ(startInBlock, endInBlock, bounds.maxZ(), new Vector3d());
-
-        if (!isVecInsideYzBounds(bounds, minVX)) minVX = null;
-        if (!isVecInsideYzBounds(bounds, maxVX)) maxVX = null;
-        if (!isVecInsideXzBounds(bounds, minVY)) minVY = null;
-        if (!isVecInsideXzBounds(bounds, maxVY)) maxVY = null;
-        if (!isVecInsideXyBounds(bounds, minVZ)) minVZ = null;
-        if (!isVecInsideXyBounds(bounds, maxVZ)) maxVZ = null;
-
-        @Nullable Vector3d clip = null;
-        @Nullable Side side = null;
-        double bestD2 = Double.POSITIVE_INFINITY;
-
-        if (minVX != null) {
-            double d2 = startInBlock.distanceSquared(minVX);
-            if (d2 < bestD2) {
-                bestD2 = d2;
-                clip = minVX;
-                side = Side.WEST;
-            }
-        }
-        if (maxVX != null) {
-            double d2 = startInBlock.distanceSquared(maxVX);
-            if (d2 < bestD2) {
-                bestD2 = d2;
-                clip = maxVX;
-                side = Side.EAST;
-            }
-        }
-        if (minVY != null) {
-            double d2 = startInBlock.distanceSquared(minVY);
-            if (d2 < bestD2) {
-                bestD2 = d2;
-                clip = minVY;
-                side = Side.BOTTOM;
-            }
-        }
-        if (maxVY != null) {
-            double d2 = startInBlock.distanceSquared(maxVY);
-            if (d2 < bestD2) {
-                bestD2 = d2;
-                clip = maxVY;
-                side = Side.TOP;
-            }
-        }
-        if (minVZ != null) {
-            double d2 = startInBlock.distanceSquared(minVZ);
-            if (d2 < bestD2) {
-                bestD2 = d2;
-                clip = minVZ;
-                side = Side.NORTH;
-            }
-        }
-        if (maxVZ != null) {
-            double d2 = startInBlock.distanceSquared(maxVZ);
-            if (d2 < bestD2) {
-                bestD2 = d2;
-                clip = maxVZ;
-                side = Side.SOUTH;
-            }
-        }
-
-        if (clip == null) {
-            return null;
-        }
-
-        return new HitResult.Tile(tilePos, side, clip.add(px, py, pz, new Vector3d()));
-    }
-
-    private static boolean isVecInsideYzBounds(AABBdc bounds, @Nullable Vector3dc v) {
-        if (v == null) {
-            return false;
-        }
-        return v.y() >= bounds.minY() && v.y() <= bounds.maxY() && v.z() >= bounds.minZ() && v.z() <= bounds.maxZ();
-    }
-
-    private static boolean isVecInsideXzBounds(AABBdc bounds, @Nullable Vector3dc v) {
-        if (v == null) {
-            return false;
-        }
-        return v.x() >= bounds.minX() && v.x() <= bounds.maxX() && v.z() >= bounds.minZ() && v.z() <= bounds.maxZ();
-    }
-
-    private static boolean isVecInsideXyBounds(AABBdc bounds, @Nullable Vector3dc v) {
-        if (v == null) {
-            return false;
-        }
-        return v.x() >= bounds.minX() && v.x() <= bounds.maxX() && v.y() >= bounds.minY() && v.y() <= bounds.maxY();
-    }
 }
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicStatue.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicStatue.java
index e32728ada..3b136a987 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicStatue.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicStatue.java
@@ -26,6 +26,7 @@ import java.util.List;
 import java.util.function.Supplier;
 
 public class BlockLogicStatue extends BlockLogic implements BlockLogic.MatcherEntityEquivalency<TileEntityStatue> {
+    private static final float HALF_WIDTH = 5.0f / 16.0f;
     private final boolean isBottom;
 
     public final @Nullable Supplier<@NotNull Item> droppedItem;
@@ -42,8 +43,7 @@ public class BlockLogicStatue extends BlockLogic implements BlockLogic.MatcherEn
 
         final float bottom = this.isBottom ? 0.0f : -1.0f;
         final float top = this.isBottom ? 2.0f : 1.0f;
-        setBlockBounds(0.0f, bottom, 0.0f, 1.0f, top, 1.0f);
-
+        setBlockBounds(0.5f - HALF_WIDTH, bottom, 0.5f - HALF_WIDTH, 0.5f + HALF_WIDTH, top, 0.5f + HALF_WIDTH);
     }
 
     @Override
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicWorkbench.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicWorkbench.java
index 5489d4f55..2c1e97e7c 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicWorkbench.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicWorkbench.java
@@ -8,7 +8,7 @@ import net.minecraft.core.world.pos.TilePosc;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
-public class BlockLogicWorkbench extends BlockLogic {
+public class BlockLogicWorkbench extends BlockLogicRotatable {
 
     public BlockLogicWorkbench(@NotNull Block<?> block) {
         super(block, Materials.WOOD);
diff --git a/game/core/src/main/java/net/minecraft/core/block/Blocks.java b/game/core/src/main/java/net/minecraft/core/block/Blocks.java
index d8009b276..c78a3e4f8 100644
--- a/game/core/src/main/java/net/minecraft/core/block/Blocks.java
+++ b/game/core/src/main/java/net/minecraft/core/block/Blocks.java
@@ -1054,7 +1054,7 @@ public final class Blocks {
         .withSound(BlockSounds.STONE).withHardness(1.5F).withBlastResistance(10F).withLitInteriorSurface(true)
         .withTags(BlockTags.MINEABLE_BY_PICKAXE);
     public static final @NotNull Block<BlockLogicLayerAsh> LAYER_ASH = register("layer.ash", "minecraft:block/layer_ash", 723, (BlockLogicSupplier<BlockLogicLayerAsh>)(b) -> new BlockLogicLayerAsh(b, Blocks.BLOCK_ASH, Materials.TOP_ASH))
-        .withSound(BlockSounds.CLOTH).withHardness(0.1F).withLitInteriorSurface(true)
+        .withSound(BlockSounds.SAND).withHardness(0.1F).withLitInteriorSurface(true)
         .withTags(BlockTags.BROKEN_BY_FLUIDS, BlockTags.PLACE_OVERWRITES, BlockTags.MINEABLE_BY_SHOVEL, BlockTags.OVERRIDE_STEPSOUND);
 
     public static final @NotNull Block<?> ICE = register("ice", "minecraft:block/ice", 730, BlockLogicIce::new)
@@ -1068,7 +1068,7 @@ public final class Blocks {
         .withSound(BlockSounds.CLOTH).withHardness(0.2F)
         .withTags(BlockTags.CAVES_CUT_THROUGH, BlockTags.MINEABLE_BY_SHOVEL, BlockTags.FIREFLIES_CAN_SPAWN);
     public static final @NotNull Block<?> BLOCK_ASH = register("block.ash", "minecraft:block/block_ash", 741, BlockLogicAsh::new)
-        .withSound(BlockSounds.CLOTH).withHardness(0.2F)
+        .withSound(BlockSounds.SAND).withHardness(0.2F)
         .withTags(BlockTags.CAVES_CUT_THROUGH, BlockTags.MINEABLE_BY_SHOVEL);
 
     public static final @NotNull Block<?> CACTUS = register("cactus", "minecraft:block/cactus", 750, BlockLogicCactus::new)
@@ -1374,7 +1374,7 @@ public final class Blocks {
     public static final Block<?> GLOOMSTONE = register("gloomstone", "minecraft:block/gloomstone", 1032, (b) -> new BlockLogicStone(b, Blocks.COBBLE_GLOOMSTONE, Materials.GLOOMSTONE))
         .withSound(BlockSounds.STONE).withHardness(2.0F).withBlastResistance(10F)
         .withTags(BlockTags.CAVES_CUT_THROUGH, BlockTags.NETHER_MOBS_SPAWN, BlockTags.NETHER_SURFACE_BLOCK, BlockTags.CHAINLINK_FENCES_CONNECT, BlockTags.MINEABLE_BY_PICKAXE, BlockTags.CAVES_CUT_THROUGH);
-    public static final Block<?> COBBLE_GLOOMSTONE = register("cobble.gloomstone", "minecraft:block/cobble_gloomstone", 1033, (b) -> new BlockLogicCobble(b, Materials.GLOOMSTONE, null))
+    public static final Block<?> COBBLE_GLOOMSTONE = register("cobble.gloomstone", "minecraft:block/cobble_gloomstone", 1033, (b) -> new BlockLogicCobble(b, Materials.GLOOMSTONE, () -> Blocks.BLOCK_ASH))
         .withSound(BlockSounds.STONE).withHardness(2.0F).withBlastResistance(10F)
         .withTags(BlockTags.MINEABLE_BY_PICKAXE, BlockTags.CHAINLINK_FENCES_CONNECT, BlockTags.CAVES_CUT_THROUGH);
     public static final Block<?> BRICK_GLOOMSTONE = register("brick.gloomstone", "minecraft:block/brick_gloomstone", 1034, (b) -> new BlockLogic(b, Materials.GLOOMSTONE))
diff --git a/game/core/src/main/java/net/minecraft/core/entity/Entity.java b/game/core/src/main/java/net/minecraft/core/entity/Entity.java
index 06ce3d305..51a312c6d 100644
--- a/game/core/src/main/java/net/minecraft/core/entity/Entity.java
+++ b/game/core/src/main/java/net/minecraft/core/entity/Entity.java
@@ -678,6 +678,9 @@ public abstract class Entity
             this.isWalking = this.walkDistO != this.walkDist;
 
             if (canInteract()) {
+                if ((float) this.nextStep > this.walkDist + 1.0F) {
+                    this.nextStep = (int) this.walkDist + 1;
+                }
                 int walkedSteps = (int) (this.walkDist - this.nextStep);
                 if (walkedSteps > 0 && blockWalkedOn != null) {
                     this.nextStep += walkedSteps;
diff --git a/game/core/src/main/java/net/minecraft/core/entity/EntityItem.java b/game/core/src/main/java/net/minecraft/core/entity/EntityItem.java
index 9f1ca5610..9e292b369 100644
--- a/game/core/src/main/java/net/minecraft/core/entity/EntityItem.java
+++ b/game/core/src/main/java/net/minecraft/core/entity/EntityItem.java
@@ -8,6 +8,8 @@ import net.minecraft.core.Global;
 import net.minecraft.core.achievement.Achievements;
 import net.minecraft.core.achievement.stat.StatList;
 import net.minecraft.core.block.Block;
+import net.minecraft.core.block.BlockLogicFluid;
+import net.minecraft.core.block.BlockLogicFluidStill;
 import net.minecraft.core.block.BlockLogicLog;
 import net.minecraft.core.block.Blocks;
 import net.minecraft.core.block.entity.TileEntityMesh;
@@ -135,14 +137,17 @@ public class EntityItem extends Entity
         final @NotNull Block<?> blockUnderItem = this.world.getBlockType(tilePosUnderItem);
         final @NotNull Block<?> blockItemIsIn = this.world.getBlockType(tilePosItemIsInside);
 
-        boolean isInFlowingWater = false;
-        if(blockItemIsIn == Blocks.FLUID_WATER_STILL)
+        //Water, lava and acid all share the same buoyancy/friction behavior for now
+        final BlockLogicFluid fluidItemIsIn = blockItemIsIn.getLogic() instanceof BlockLogicFluidStill logic ? logic : null;
+
+        boolean isInFlowingLiquid = false;
+        if(fluidItemIsIn != null)
         {
-            isInFlowingWater = Blocks.FLUID_WATER_STILL.getLogic().hasFlowVector(this.world, new TilePos(this.x, this.y, this.z));
+            isInFlowingLiquid = fluidItemIsIn.hasFlowVector(this.world, tilePosItemIsInside);
         }
 
-        handleFriction(blockUnderItem, blockItemIsIn, isInFlowingWater);
-        handleBuoyancy(blockUnderItem, tilePosUnderItem, isInFlowingWater);
+        handleFriction(blockUnderItem, isInFlowingLiquid);
+        handleBuoyancy(blockUnderItem, tilePosUnderItem, fluidItemIsIn, isInFlowingLiquid);
 
         this.xo = this.x;
         this.yo = this.y;
@@ -339,7 +344,7 @@ public class EntityItem extends Entity
     	}
     }
 
-    private void handleFriction(@NotNull Block<?> blockUnderItem, @NotNull Block<?> blockItemIsIn, boolean isInFlowingWater){
+    private void handleFriction(@NotNull Block<?> blockUnderItem, boolean isInFlowingLiquid){
 
         //All mentions of friction in comments use the ENGLISH sense of the word, as for some reason
         //  adding friction to any value in game actually LOWERS the literal friction the item is receiving, and vice versa
@@ -357,9 +362,9 @@ public class EntityItem extends Entity
         //Items in streams of water get their friction set to the block underneath them/the stream.
         //All blocks get are given significantly less friction, and ice is given slightly less friction to
         //  improve the efficiency/speed of each respective water stream
-        if(isInFlowingWater)
+        if(isInFlowingLiquid)
         {
-            if(blockUnderItem != Blocks.FLUID_WATER_STILL)
+            if(!(blockUnderItem.getLogic() instanceof BlockLogicFluidStill))
             {
                 friction = blockUnderItem.friction;
             }
@@ -380,20 +385,21 @@ public class EntityItem extends Entity
 
     }
 
-    private void handleBuoyancy(Block<?> blockUnderItem, TilePos tilePosUnderItem, boolean inFlowingLiquid){
-
-        double midpoint = this.y % 1d + 0.14;
-        float fluidHeight = Blocks.FLUID_WATER_STILL.getLogic().getFluidHeight(this.world, new TilePos(this.x, this.y, this.z));
+    private void handleBuoyancy(Block<?> blockUnderItem, TilePos tilePosUnderItem, BlockLogicFluid fluidItemIsIn, boolean inFlowingLiquid){
 
-        //Adjust the height at which the item believes itself to be in the water for buoyancy purposes
+        //Adjust the height at which the item believes itself to be in the fluid for buoyancy purposes
         //  (so that the bobbing height is closer to the center of its bb)
-        boolean isInWater = false;
-        if(fluidHeight > midpoint || fluidHeight == 1){
-            isInWater = this.isInWater();
+        boolean isInFluid = false;
+        if(fluidItemIsIn != null){
+            double midpointOffset = (this.y - Math.floor(this.y)) + 0.14;
+            float fluidHeight = fluidItemIsIn.getFluidHeight(this.world, new TilePos(this.x, this.y, this.z));
+            if(fluidHeight > midpointOffset || fluidHeight == 1){
+                isInFluid = this.world.isMaterialInBB(this.bb, fluidItemIsIn.getMaterial());
+            }
         }
 
         //self-explanatory :wolfblock:
-        if (isInWater)
+        if (isInFluid)
         {
             //gradually slows the item down laterally
             this.xd *= 0.98f;
@@ -419,9 +425,15 @@ public class EntityItem extends Entity
                     }
                 }
             }
-            this.yd -= 0.02D;
+
+            //in a stream, sink slightly so items fall through gaps instead of floating over them
+            if(inFlowingLiquid)
+            {
+                this.yd -= 0.02D;
+            }
         }
-        //gravity out of water
+
+        //gravity out of fluid
         else
         {
             this.yd -= 0.04D;
diff --git a/game/core/src/main/java/net/minecraft/core/entity/Mob.java b/game/core/src/main/java/net/minecraft/core/entity/Mob.java
index 153d56e63..76df3b522 100644
--- a/game/core/src/main/java/net/minecraft/core/entity/Mob.java
+++ b/game/core/src/main/java/net/minecraft/core/entity/Mob.java
@@ -936,19 +936,6 @@ public abstract class Mob extends Entity {
             this.newPosRotationIncrements--;
             setPos(d, d1, d2);
             setRot(this.yRot, this.xRot);
-            List<AABBdc> list1 = this.world.getCubes(this, MathHelper.aabbInsetBoundingBox(this.bb, 0.03125D, 0.0D, 0.03125D, new AABBd()));
-            if (!list1.isEmpty()) {
-                double d4 = 0.0D;
-                for (int j = 0; j < list1.size(); j++) {
-                    AABBdc aabb = list1.get(j);
-                    if (aabb.maxY() > d4) {
-                        d4 = aabb.maxY();
-                    }
-                }
-
-                d1 += d4 - this.bb.minY;
-                setPos(d, d1, d2);
-            }
         }
         if (isMovementBlocked()) {
             this.isJumping = false;
@@ -1060,6 +1047,8 @@ public abstract class Mob extends Entity {
         boolean inAcid = isInAcid();
         if (inWater || inLava || inAcid) {
             this.isJumping = this.random.nextFloat() < 0.8F;
+        } else {
+            this.isJumping = false;
         }
     }
 
diff --git a/game/core/src/main/java/net/minecraft/core/entity/animal/MobPig.java b/game/core/src/main/java/net/minecraft/core/entity/animal/MobPig.java
index 26ae0ce10..dfa5d25d7 100644
--- a/game/core/src/main/java/net/minecraft/core/entity/animal/MobPig.java
+++ b/game/core/src/main/java/net/minecraft/core/entity/animal/MobPig.java
@@ -102,6 +102,7 @@ public class MobPig
     private static final float PANIC_MOVE_SPEED = 1.3f;
     private static final int PANIC_DURATION_TICKS = 60;
     private int panicTicks = 0;
+    private boolean sitWhenBackOnLand = false;
 
     public float lookFadeAlpha = 1.0f;
     public long lookFadeLastRenderNanos = 0L;
@@ -114,6 +115,7 @@ public class MobPig
         if (passenger != null) {
             setSitting(false);
             this.panicTicks = 0;
+            this.sitWhenBackOnLand = false;
         }
         if (passenger == null) {
             this.xd = 0.0D;
@@ -206,9 +208,6 @@ public class MobPig
 
     @Override
     public boolean hurt(Entity attacker, int damage, DamageType type) {
-        if (attacker != null && attacker == this.passenger) {
-            return false;
-        }
         boolean hurt = super.hurt(attacker, damage, type);
         if (hurt) {
             this.speedProgress = 0f;
@@ -362,7 +361,15 @@ public class MobPig
         } else {
             this.footSize = 0.5f;
             this.moveSpeed = NORMAL_MOVE_SPEED;
+            if (getSitting() && isInWater()) {
+                setSitting(false);
+                this.sitWhenBackOnLand = true;
+            }
             super.updateAI();
+            if (this.sitWhenBackOnLand && getSaddled() && this.onGround && !isInWater()) {
+                this.sitWhenBackOnLand = false;
+                setSitting(true);
+            }
         }
     }
 
diff --git a/game/core/src/main/java/net/minecraft/core/entity/player/Player.java b/game/core/src/main/java/net/minecraft/core/entity/player/Player.java
index a06599cbe..63e154732 100644
--- a/game/core/src/main/java/net/minecraft/core/entity/player/Player.java
+++ b/game/core/src/main/java/net/minecraft/core/entity/player/Player.java
@@ -52,6 +52,7 @@ import net.minecraft.core.util.helper.DyeColor;
 import net.minecraft.core.util.helper.MathHelper;
 import net.minecraft.core.world.Dimension;
 import net.minecraft.core.world.ICarriable;
+import net.minecraft.core.world.IVehicle;
 import net.minecraft.core.world.SpawnerMobs;
 import net.minecraft.core.world.World;
 import net.minecraft.core.world.biome.Biome;
@@ -1250,8 +1251,12 @@ public abstract class Player
     public void attackTargetEntityWithCurrentItem(@NotNull Entity entity) {
         if (!getGamemode().canInteract()) return;
 
-        int damage = 1;
         final @Nullable ItemStack currentItem = this.inventory.getCurrentItem();
+        if (entity == this.vehicle && entity.passesThroughRiderTargeting(currentItem)) {
+            return;
+        }
+
+        int damage = 1;
         if (currentItem != null) {
             damage = currentItem.getDamageVsEntity(entity);
         }
@@ -1660,34 +1665,38 @@ public abstract class Player
         float randomizedPitch = 0.8F + (this.world.rand.nextFloat() - this.world.rand.nextFloat()) * 0.45F;
 
         if (distance <= oneFifthOfRadius) {
-            this.world.playSoundEffect(player, SoundCategory.WORLD_SOUNDS,
+            player.playSoundToSelf(SoundCategory.WORLD_SOUNDS,
                 ventCoordinateX, ventCoordinateY, ventCoordinateZ,
             "tile.crumble", 20.0F, randomizedPitch);
             if(isNether) {
-                this.world.playSoundEffect(player, SoundCategory.WORLD_SOUNDS,
+                player.playSoundToSelf(SoundCategory.WORLD_SOUNDS,
                     ventCoordinateX, ventCoordinateY, ventCoordinateZ,
                 "tile.deepboom", 20.0F, randomizedPitch);
             }
         }
         else if (distance <= threeFifthsOfRadius) {
-            this.world.playSoundEffect(player, SoundCategory.WORLD_SOUNDS,
+            player.playSoundToSelf(SoundCategory.WORLD_SOUNDS,
                 ventCoordinateX, ventCoordinateY, ventCoordinateZ,
             "tile.deepcrumble", 20.0F, randomizedPitch);
             if(isNether) {
-                this.world.playSoundEffect(player, SoundCategory.WORLD_SOUNDS,
+                player.playSoundToSelf(SoundCategory.WORLD_SOUNDS,
                     ventCoordinateX, ventCoordinateY, ventCoordinateZ,
                 "tile.deepquietboom", 20.0F, randomizedPitch);
             }
         }
         else {
             if(isNether) {
-                this.world.playSoundEffect(player, SoundCategory.WORLD_SOUNDS,
+                player.playSoundToSelf(SoundCategory.WORLD_SOUNDS,
                     ventCoordinateX, ventCoordinateY, ventCoordinateZ,
                 "tile.deepquietboom", 20.0F, randomizedPitch);
             }
         }
     }
 
+    public void playSoundToSelf(final SoundCategory category, final double x, final double y, final double z, final String soundPath, final float volume, final float pitch) {
+        this.world.playSoundEffect(null, category, x, y, z, soundPath, volume, pitch);
+    }
+
     @Override
     public boolean canBreatheUnderwater() {
         return this.gamemode.hasInvulnerablePlayer();
@@ -1728,6 +1737,14 @@ public abstract class Player
         }
     }
 
+    @Override
+    public void startRiding(IVehicle vehicle) {
+        super.startRiding(vehicle);
+        if (this.vehicle != null) {
+            setNoPhysics(false);
+        }
+    }
+
     public void setLastDeathPoint(TilePos lastDeathPos) {
         this.lastDeathPoint = lastDeathPos;
     }
diff --git a/game/core/src/main/java/net/minecraft/core/item/block/ItemBlockLayerLeaves.java b/game/core/src/main/java/net/minecraft/core/item/block/ItemBlockLayerLeaves.java
index 030a7e430..1af656fe0 100644
--- a/game/core/src/main/java/net/minecraft/core/item/block/ItemBlockLayerLeaves.java
+++ b/game/core/src/main/java/net/minecraft/core/item/block/ItemBlockLayerLeaves.java
@@ -34,6 +34,7 @@ public class ItemBlockLayerLeaves<T extends BlockLogicLayerLeaves> extends ItemB
         if ((result.data & 0xF) < 6) {
             result.data += 1;
         } else {
+            assert this.block.getLogic().fullBlock != null;
             result.block = this.block.getLogic().fullBlock;
             result.data &= ~0xF;
         }
diff --git a/game/core/src/main/java/net/minecraft/core/item/block/ItemBlockLayerSnow.java b/game/core/src/main/java/net/minecraft/core/item/block/ItemBlockLayerSnow.java
new file mode 100644
index 000000000..16304898e
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/item/block/ItemBlockLayerSnow.java
@@ -0,0 +1,44 @@
+package net.minecraft.core.item.block;
+
+import net.minecraft.core.block.Block;
+import net.minecraft.core.block.BlockLogicLayerBase;
+import net.minecraft.core.block.BlockLogicLayerSnow;
+import net.minecraft.core.entity.player.Player;
+import net.minecraft.core.item.ItemStack;
+import net.minecraft.core.util.helper.Side;
+import net.minecraft.core.world.World;
+import net.minecraft.core.world.pos.TilePosc;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+public class ItemBlockLayerSnow<T extends BlockLogicLayerSnow> extends ItemBlockLayer<T> {
+    public ItemBlockLayerSnow(@NotNull Block<T> block) {
+        super(block);
+    }
+
+    @Override
+    public boolean canAccumulateInto(
+        @NotNull ItemStack selfStack,
+        @NotNull Block<?> block, int data, @NotNull Side side, double xHit, double yHit
+    ) {
+        return block == this.block && !(yHit - (((data & BlockLogicLayerBase.MASK_HEIGHT) + 1) * 0.125) <= -0x1p-12);
+    }
+
+    @Override
+    public @NotNull BlockDataResult getAccumulationResult(
+        @NotNull ItemStack selfStack,
+        @NotNull World world, @Nullable Player player, @NotNull TilePosc tilePos,
+        @NotNull Side side, double xHit, double yHit,
+        @NotNull BlockDataResult result
+    ){
+        result.data |= BlockLogicLayerSnow.MASK_PERMANENT;
+        if ((result.data & BlockLogicLayerBase.MASK_HEIGHT) < 6) {
+            result.data += 1;
+        } else {
+            assert this.block.getLogic().fullBlock != null;
+            result.block = this.block.getLogic().fullBlock;
+            result.data = 0;
+        }
+        return result;
+    }
+}
diff --git a/game/core/src/main/java/net/minecraft/core/net/packet/PacketExplosion.java b/game/core/src/main/java/net/minecraft/core/net/packet/PacketExplosion.java
index 65b01f637..82915d09e 100644
--- a/game/core/src/main/java/net/minecraft/core/net/packet/PacketExplosion.java
+++ b/game/core/src/main/java/net/minecraft/core/net/packet/PacketExplosion.java
@@ -2,6 +2,7 @@ package net.minecraft.core.net.packet;
 
 import net.minecraft.core.world.chunk.ChunkPosition;
 import net.minecraft.core.net.handler.PacketHandler;
+import net.minecraft.core.world.pos.TilePos;
 
 import java.io.*;
 import java.util.*;
@@ -13,14 +14,14 @@ public class PacketExplosion extends Packet
     public double explosionY;
     public double explosionZ;
     public float explosionSize;
-    public Set<ChunkPosition> destroyedBlockPositions;
+    public Set<TilePos> destroyedBlockPositions;
     public boolean isCannonball;
 
     public PacketExplosion()
     {
     }
 
-    public PacketExplosion(double d, double d1, double d2, float f, Set<ChunkPosition> set, boolean isCannonball)
+    public PacketExplosion(double d, double d1, double d2, float f, Set<TilePos> set, boolean isCannonball)
     {
         explosionX = d;
         explosionY = d1;
@@ -47,7 +48,7 @@ public class PacketExplosion extends Packet
             int j1 = dis.readByte() + j;
             int k1 = dis.readByte() + k;
             int l1 = dis.readByte() + l;
-            destroyedBlockPositions.add(new ChunkPosition(j1, k1, l1));
+            destroyedBlockPositions.add(new TilePos(j1, k1, l1));
         }
         isCannonball = dis.readBoolean();
 
@@ -64,11 +65,11 @@ public class PacketExplosion extends Packet
         int i = (int)explosionX;
         int j = (int)explosionY;
         int k = (int)explosionZ;
-        for (ChunkPosition chunkPosition : destroyedBlockPositions)
+        for (TilePos pos : destroyedBlockPositions)
         {
-            dos.writeByte(chunkPosition.x - i);
-            dos.writeByte(chunkPosition.y - j);
-            dos.writeByte(chunkPosition.z - k);
+            dos.writeByte(pos.x - i);
+            dos.writeByte(pos.y - j);
+            dos.writeByte(pos.z - k);
         }
         dos.writeBoolean(isCannonball);
 
diff --git a/game/core/src/main/java/net/minecraft/core/util/phys/BoundingVolume.java b/game/core/src/main/java/net/minecraft/core/util/phys/BoundingVolume.java
index 236a8860e..c194dd3f2 100644
--- a/game/core/src/main/java/net/minecraft/core/util/phys/BoundingVolume.java
+++ b/game/core/src/main/java/net/minecraft/core/util/phys/BoundingVolume.java
@@ -251,11 +251,11 @@ public class BoundingVolume
             }
         }
 
-        System.out.println("Before line simplification: " + lineCoords.size() / 6 + " lines");
+        //System.out.println("Before line simplification: " + lineCoords.size() / 6 + " lines");
 
         IntList mergedLineCoords = mergeLines(lineCoords);
 
-        System.out.println("After line simplification: " + mergedLineCoords.size() / 6 + " lines");
+        //System.out.println("After line simplification: " + mergedLineCoords.size() / 6 + " lines");
 
         FloatList floatLineCoords = new FloatArrayList();
         for (int i : mergedLineCoords)
diff --git a/game/core/src/main/java/net/minecraft/core/world/Explosion.java b/game/core/src/main/java/net/minecraft/core/world/Explosion.java
index 7a0caad5e..ed6faa107 100644
--- a/game/core/src/main/java/net/minecraft/core/world/Explosion.java
+++ b/game/core/src/main/java/net/minecraft/core/world/Explosion.java
@@ -2,6 +2,7 @@ package net.minecraft.core.world;
 
 import net.minecraft.core.block.Block;
 import net.minecraft.core.block.entity.TileEntity;
+import net.minecraft.core.block.tag.BlockTags;
 import net.minecraft.core.data.gamerule.GameRules;
 import net.minecraft.core.entity.Entity;
 import net.minecraft.core.entity.IArmorWearing;
@@ -10,8 +11,8 @@ import net.minecraft.core.enums.EnumDropCause;
 import net.minecraft.core.sound.SoundCategory;
 import net.minecraft.core.util.helper.MathHelper;
 import net.minecraft.core.block.Blocks;
-import net.minecraft.core.world.chunk.ChunkPosition;
 import net.minecraft.core.util.helper.DamageType;
+import net.minecraft.core.world.pos.TilePos;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 import org.joml.Vector3d;
@@ -30,7 +31,7 @@ public class Explosion {
     public double explosionY;
     public double explosionZ;
     public float explosionSize;
-    public Set<ChunkPosition> destroyedBlockPositions;
+    public Set<TilePos> destroyedBlockPositions;
     public boolean destroyBlocks;
 
     public Explosion(@NotNull World world, @Nullable Entity exploder, double x, double y, double z, float explosionSize) {
@@ -53,27 +54,18 @@ public class Explosion {
         calculateBlocksToDestroy();
 
         damageEntities();
-
-        if (this.destroyBlocks && this.isFlaming) createFire();
     }
 
     public void addEffects(boolean particles) {
         if (!this.world.isClientSide) {
             this.world.playSoundEffect(null, SoundCategory.WORLD_SOUNDS, this.explosionX, this.explosionY, this.explosionZ, "random.explode", 4F, (1.0F + (this.world.rand.nextFloat() - this.world.rand.nextFloat()) * 0.2F) * 0.7F);
         }
-        List<ChunkPosition> arraylist = new ArrayList<>(this.destroyedBlockPositions);
-        for (int i = arraylist.size() - 1; i >= 0; i--) {
-            ChunkPosition chunkposition = arraylist.get(i);
-
-            int x = chunkposition.x;
-            int y = chunkposition.y;
-            int z = chunkposition.z;
-
-            TileEntity tileEntity = this.world.getTileEntity(x, y, z);
+        for (var pos : this.destroyedBlockPositions) {
+            TileEntity tileEntity = this.world.getTileEntity(pos);
             if (particles) {
-                double xPos = (double) x + this.world.rand.nextFloat();
-                double yPos = (double) y + this.world.rand.nextFloat();
-                double zPos = (double) z + this.world.rand.nextFloat();
+                double xPos = (double) pos.x + this.world.rand.nextFloat();
+                double yPos = (double) pos.y + this.world.rand.nextFloat();
+                double zPos = (double) pos.z + this.world.rand.nextFloat();
                 double d3 = xPos - this.explosionX;
                 double d4 = yPos - this.explosionY;
                 double d5 = zPos - this.explosionZ;
@@ -90,14 +82,16 @@ public class Explosion {
                 this.world.spawnParticle("smoke", xPos, yPos, zPos, d3, d4, d5, 0, false);
             }
             if (this.destroyBlocks) {
-                Block<?> block = this.world.getBlock(x, y, z);
-                if (block != null) {
-                    block.dropBlockWithCause(this.world, EnumDropCause.EXPLOSION, x, y, z, this.world.getBlockMetadata(x, y, z), tileEntity, null);
-                    this.world.setBlockWithNotify(x, y, z, 0);
-                    block.onBlockDestroyedByExplosion(this.world, x, y, z);
+                Block<?> block = this.world.getBlockType(pos);
+                if (block != Blocks.AIR) {
+                    block.dropWithCause(this.world, EnumDropCause.EXPLOSION, pos, this.world.getBlockData(pos), tileEntity, null);
+                    this.world.setBlockTypeNotify(pos, Blocks.AIR);
+                    block.onDestroyedByExplosion(this.world, pos);
                 }
             }
         }
+
+        if (!this.world.isClientSide && this.destroyBlocks && this.isFlaming) createFire();
     }
 
     protected void calculateBlocksToDestroy() {
@@ -117,27 +111,25 @@ public class Explosion {
                     d1 /= d3;
                     d2 /= d3;
                     float f1 = this.explosionSize * (0.7F + this.world.rand.nextFloat() * 0.6F);
-                    double d5 = this.explosionX;
-                    double d7 = this.explosionY;
-                    double d9 = this.explosionZ;
+                    double ex = this.explosionX;
+                    double ey = this.explosionY;
+                    double ez = this.explosionZ;
                     float f2 = 0.3F;
                     while (true) {
                         if (f1 <= 0.0F) {
                             continue label0;
                         }
-                        int j4 = MathHelper.floor(d5);
-                        int k4 = MathHelper.floor(d7);
-                        int l4 = MathHelper.floor(d9);
-                        int i5 = this.world.getBlockId(j4, k4, l4);
-                        if (i5 > 0) {
-                            f1 -= (Blocks.blocksList[i5].getBlastResistance(this.exploder) + 0.3F) * f2;
+                        var tilePos = new TilePos(MathHelper.floor(ex), MathHelper.floor(ey), MathHelper.floor(ez));
+                        var block = this.world.getBlockType(tilePos);
+                        if (block != Blocks.AIR) {
+                            f1 -= (block.getBlastResistance(this.exploder) + 0.3F) * f2;
                         }
                         if (f1 > 0.0F) {
-                            this.destroyedBlockPositions.add(new ChunkPosition(j4, k4, l4));
+                            this.destroyedBlockPositions.add(tilePos);
                         }
-                        d5 += d * (double) f2;
-                        d7 += d1 * (double) f2;
-                        d9 += d2 * (double) f2;
+                        ex += d * (double) f2;
+                        ey += d1 * (double) f2;
+                        ez += d2 * (double) f2;
                         f1 -= f2 * 0.75F;
                     }
                 }
@@ -183,16 +175,15 @@ public class Explosion {
     }
 
     protected void createFire() {
-        List<ChunkPosition> arraylist = new ArrayList<>(this.destroyedBlockPositions);
-        for (int l2 = arraylist.size() - 1; l2 >= 0; l2--) {
-            ChunkPosition chunkposition = arraylist.get(l2);
-
-            int x1 = chunkposition.x;
-            int y1 = chunkposition.y;
-            int z1 = chunkposition.z;
-
-            if (this.world.getBlockId(x1, y1, z1) == Blocks.AIR.id() && Blocks.solid[this.world.getBlockId(x1, y1 - 1, z1)] && this.explosionRNG.nextInt(3) == 0) {
-                this.world.setBlockWithNotify(x1, y1, z1, Blocks.FIRE.id());
+        TilePos queryPos = new TilePos();
+        for (var pos : this.destroyedBlockPositions) {
+            Block<?> below;
+            if (this.world.isAirBlock(pos) && (below = this.world.getBlockType(pos.down(queryPos))).isSolidRender() && this.explosionRNG.nextInt(3) == 0) {
+                if (below.hasTag(BlockTags.INFINITE_BURN_SULFURIC)) {
+                    this.world.setBlockTypeNotify(pos, Blocks.FIRE_SULFURIC);
+                } else {
+                    this.world.setBlockTypeNotify(pos, Blocks.FIRE);
+                }
             }
         }
     }
diff --git a/game/core/src/main/java/net/minecraft/core/world/weather/Weather.java b/game/core/src/main/java/net/minecraft/core/world/weather/Weather.java
index f69505d13..c0c8f9342 100644
--- a/game/core/src/main/java/net/minecraft/core/world/weather/Weather.java
+++ b/game/core/src/main/java/net/minecraft/core/world/weather/Weather.java
@@ -1,5 +1,6 @@
 package net.minecraft.core.world.weather;
 
+import net.minecraft.core.block.BlockLogicLayerSnow;
 import net.minecraft.core.block.Blocks;
 import net.minecraft.core.world.World;
 import net.minecraft.core.world.biome.BiomeTags;
@@ -73,7 +74,7 @@ public abstract class Weather {
             if (world.getBlockBiome(x, y, z).hasTag(BiomeTags.HAS_SURFACE_SNOW)) {
                 if (blockId == Blocks.LAYER_SNOW.id()) {
                     final int meta = world.getBlockMetadata(x, y, z);
-                    if (meta != 0) {
+                    if (!BlockLogicLayerSnow.isPermanent(meta) && meta != 0) {
                         world.setBlockMetadata(x, y, z, meta - 1);
                         world.markBlockNeedsUpdate(x, y, z);
                     }
@@ -81,7 +82,9 @@ public abstract class Weather {
             } else {
                 if (blockId == Blocks.LAYER_SNOW.id()) {
                     final int meta = world.getBlockMetadata(x, y, z);
-                    if (meta != 0) {
+                    if (BlockLogicLayerSnow.isPermanent(meta)) {
+                        //do nothing, layer is permanent
+                    } else if (meta != 0) {
                         world.setBlockMetadata(x, y, z, meta - 1);
                         world.markBlockNeedsUpdate(x, y, z);
                     } else {
@@ -108,12 +111,14 @@ public abstract class Weather {
                 int blockIdBelow = chunk.getBlockID(x, y - 1, z);
 
                 if (world.getBlockBiome(chunk.pos.x * 16 + x, y, chunk.pos.z * 16 + z).hasTag(BiomeTags.HAS_SURFACE_SNOW)) {
-                    if (blockId == Blocks.LAYER_SNOW.id()) {
+                    if (blockId == Blocks.LAYER_SNOW.id() && !BlockLogicLayerSnow.isPermanent(chunk.getBlockMetadata(x, y, z))) {
                         chunk.setBlockMetadata(x, y, z, 0);
                     }
                 } else {
                     if (blockId == Blocks.LAYER_SNOW.id()) {
-                        chunk.setBlockID(x, y, z, 0);
+                        if (!BlockLogicLayerSnow.isPermanent(chunk.getBlockMetadata(x, y, z))) {
+                            chunk.setBlockID(x, y, z, 0);
+                        }
                     } else if (blockIdBelow == Blocks.ICE.id()) {
                         chunk.setBlockID(x, y - 1, z, Blocks.FLUID_WATER_STILL.id());
                     }
diff --git a/game/core/src/main/resources/assets/minecraft/lang/en_US/options.lang b/game/core/src/main/resources/assets/minecraft/lang/en_US/options.lang
index 11bdbdeb3..b5b68bd73 100644
--- a/game/core/src/main/resources/assets/minecraft/lang/en_US/options.lang
+++ b/game/core/src/main/resources/assets/minecraft/lang/en_US/options.lang
@@ -137,9 +137,8 @@ options.borderlessFullscreen.unavailable=Not Available
 options.enableVbos=Enable VBOs
 options.discordRichPresence=Discord Rich Presence
 options.blockOutlineWidth=Outline Visibility
-options.colorCorrection=Color Correction
-options.colorCorrection.low=Low
-options.colorCorrection.high=High
+options.colorCorrectionOverworld=Overworld Color Correction
+options.colorCorrectionNether=Nether Color Correction
 options.sneakToggle=Toggle Sneaking
 options.tooltipStyle=Tooltip Style
 options.font=Font
diff --git a/game/core/src/main/resources/assets/minecraft/sounds/sounds.json b/game/core/src/main/resources/assets/minecraft/sounds/sounds.json
index b15e45ff1..0b9e1b8b5 100644
--- a/game/core/src/main/resources/assets/minecraft/sounds/sounds.json
+++ b/game/core/src/main/resources/assets/minecraft/sounds/sounds.json
@@ -907,13 +907,19 @@
   "tile.deepboom": {
     "subtitle": "subtitles.tile.deepboom",
     "sounds": [
-      "tile/deepboom.ogg"
+      {
+        "name": "tile/deepboom.ogg",
+        "preload": true
+      }
     ]
   },
   "tile.deepquietboom": {
     "subtitle": "subtitles.tile.deepquietboom",
     "sounds": [
-      "tile/deepquietboom.ogg"
+      {
+        "name": "tile/deepquietboom.ogg",
+        "preload": true
+      }
     ]
   },
   "tile.crumble": {
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 676660862..130067434 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
@@ -32,6 +32,8 @@ import net.minecraft.core.net.packet.*;
 import net.minecraft.server.world.ServerPlayerController;
 import net.minecraft.core.player.gamemode.Gamemode;
 import net.minecraft.core.player.inventory.slot.SlotResult;
+import net.minecraft.core.sound.SoundCategory;
+import net.minecraft.core.sound.SoundTypes;
 import net.minecraft.core.util.helper.AES;
 import net.minecraft.core.util.helper.DamageType;
 import net.minecraft.core.world.World;
@@ -802,6 +804,18 @@ public class PlayerServer extends Player implements ContainerListener {
         playerNetServerHandler.teleportAndRotate(x, y, z, yaw, pitch);
     }
 
+    @Override
+    public void playSoundToSelf(SoundCategory category, double x, double y, double z, String soundPath, float volume, float pitch) {
+        if (soundPath == null || playerNetServerHandler == null) {
+            return;
+        }
+        int soundId = SoundTypes.getSoundId(soundPath);
+        if (soundId == -1) {
+            return;
+        }
+        playerNetServerHandler.sendPacket(new PacketPlaySoundDirect(soundId, category, x, y, z, volume, pitch));
+    }
+
     @Override
     public boolean deferVehicleBehavior() {
         return true;
diff --git a/util/datagen/src/main/java/net/minecraft/datagen/SmeltingGenerator.java b/util/datagen/src/main/java/net/minecraft/datagen/SmeltingGenerator.java
index bf4db129c..853bf1272 100644
--- a/util/datagen/src/main/java/net/minecraft/datagen/SmeltingGenerator.java
+++ b/util/datagen/src/main/java/net/minecraft/datagen/SmeltingGenerator.java
@@ -57,6 +57,9 @@ public class SmeltingGenerator {
 
         blastRecipe("log_to_scorched_log", "minecraft:logs", Blocks.BLOCK_ASH.getDefaultStack(), Blocks.LOG_SCORCHED.getDefaultStack());
 
+        blastRecipe("rubyglass_column_and_brimsand_to_rubyglass_crystal", Blocks.RUBYGLASS_COLUMN.getDefaultStack(), Blocks.BRIMSAND.getDefaultStack(), Blocks.RUBYGLASS_CRYSTAL.getDefaultStack());
+
+
         addOreConversions(BlockLogicOreCoal.variantMap, "minecraft:coal_ores");
         addOreConversions(BlockLogicOreIron.variantMap, "minecraft:iron_ores");
         addOreConversions(BlockLogicOreGold.variantMap, "minecraft:gold_ores");
diff --git a/util/datagen/src/main/java/net/minecraft/datagen/WorkbenchGenerator.java b/util/datagen/src/main/java/net/minecraft/datagen/WorkbenchGenerator.java
index 4d7899c9d..cf27cb6f6 100644
--- a/util/datagen/src/main/java/net/minecraft/datagen/WorkbenchGenerator.java
+++ b/util/datagen/src/main/java/net/minecraft/datagen/WorkbenchGenerator.java
@@ -1555,7 +1555,7 @@ class WorkbenchGenerator {
         // Misc additions
         RecipeBuilder.Shaped(CORE_NAMESPACE).setShape("SSS").addInput('S', Blocks.SLATE_POLISHED).create("polished_slate_slab", new ItemStack(Blocks.SLAB_SLATE_POLISHED, 6));
         RecipeBuilder.Shaped(CORE_NAMESPACE).setShape("PO", "OP").addInput('P', Items.AMMO_PEBBLE).addInput('O', Blocks.OBSIDIAN).create("pebbles_to_gloomstone", new ItemStack(Blocks.COBBLE_GLOOMSTONE, 2));
-        RecipeBuilder.Shaped(CORE_NAMESPACE).setShape(" S ", "SAS", " S ").addInput('S', Items.SULFUR).addInput('A', Items.AMMO_ARROW).create("flaming_arrow", new ItemStack(Items.AMMO_ARROW_FLAMING, 4));
+        RecipeBuilder.Shapeless(CORE_NAMESPACE).addInput(Items.SULFUR).addInput(Items.PAPER).addInput(Items.AMMO_ARROW).addInput(Items.SULFUR).create("flaming_arrow", new ItemStack(Items.AMMO_ARROW_FLAMING, 1));
         RecipeBuilder.Shaped(CORE_NAMESPACE).setShape("SS", "SS").addInput('S', Items.SULFUR).create("sulfur_block", new ItemStack(Blocks.SULFUR, 1));
 
         RecipeBuilder.Shapeless(CORE_NAMESPACE).addInput(Blocks.LOG_SCORCHED).create("scorched_log_to_black_wooden_planks", new ItemStack(Blocks.PLANKS_OAK_PAINTED, 4, 15));
