diff --git a/game/client/devPacks/inTestingSoundsPack/assets/minecraft/sounds/mob/player/fem_hurt.ogg b/game/client/devPacks/inTestingSoundsPack/assets/minecraft/sounds/mob/player/fem_hurt.ogg
deleted file mode 100644
index 53408820b..000000000
Binary files a/game/client/devPacks/inTestingSoundsPack/assets/minecraft/sounds/mob/player/fem_hurt.ogg and /dev/null differ
diff --git a/game/client/devPacks/inTestingSoundsPack/assets/minecraft/sounds/mob/player/masc_hurt.ogg b/game/client/devPacks/inTestingSoundsPack/assets/minecraft/sounds/mob/player/masc_hurt.ogg
deleted file mode 100644
index b4cbc12e5..000000000
Binary files a/game/client/devPacks/inTestingSoundsPack/assets/minecraft/sounds/mob/player/masc_hurt.ogg and /dev/null differ
diff --git a/game/client/devPacks/inTestingSoundsPack/assets/minecraft/sounds/random/scrape.ogg b/game/client/devPacks/inTestingSoundsPack/assets/minecraft/sounds/random/scrape.ogg
new file mode 100644
index 000000000..8fa559342
Binary files /dev/null and b/game/client/devPacks/inTestingSoundsPack/assets/minecraft/sounds/random/scrape.ogg differ
diff --git a/game/client/devPacks/inTestingSoundsPack/assets/minecraft/sounds/sounds.json b/game/client/devPacks/inTestingSoundsPack/assets/minecraft/sounds/sounds.json
index 1fcd6f301..f751a32b5 100644
--- a/game/client/devPacks/inTestingSoundsPack/assets/minecraft/sounds/sounds.json
+++ b/game/client/devPacks/inTestingSoundsPack/assets/minecraft/sounds/sounds.json
@@ -1,23 +1,53 @@
 {
-    "mob.player.masculine.hurt": {
-        "replace": true,
-        "subtitle": "subtitles.mob.player.hurt",
-        "sounds": [
-            "mob/player/masc_hurt.ogg"
-        ]
-    },
-    "mob.player.feminine.hurt": {
-        "replace": true,
-        "subtitle": "subtitles.mob.player.hurt",
-        "sounds": [
-            "mob/player/fem_hurt.ogg"
-        ]
-    },
-    "mob.player.neutral.hurt": {
-        "replace": true,
-        "subtitle": "subtitles.mob.player.hurt",
-        "sounds": [
-            "mob/player/neutral_hurt.ogg"
-        ]
-    }
-}
\ No newline at end of file
+  "tile.acidbubble": {
+    "subtitle": "subtitles.tile.acidbubble",
+    "sounds": [
+      "tile/bubble_1.ogg",
+      "tile/bubble_2.ogg",
+      "tile/bubble_3.ogg",
+      "tile/bubble_4.ogg"
+    ]
+  },
+  "step.crystal": {
+    "subtitle": "subtitles.step.crystal",
+    "sounds": [
+      "step/crystal_1.ogg",
+      "step/crystal_2.ogg",
+      "step/crystal_3.ogg",
+      "step/crystal_4.ogg",
+      "step/crystal_5.ogg",
+      "step/crystal_6.ogg"
+    ]
+  },
+  "tile.deep.boom": {
+    "subtitle": "subtitles.tile.deepboom",
+    "sounds": [
+      "tile/deepboom.ogg"
+    ]
+  },
+  "tile.deepquietboom": {
+    "subtitle": "subtitles.tile.deepquietboom",
+    "sounds": [
+      "tile/deepquietboom.ogg"
+    ]
+  },
+  "tile.crumble": {
+    "subtitle": "subtitles.crumble",
+    "sounds": [
+      "tile/crumble_1.ogg",
+      "tile/crumble_2.ogg"
+    ]
+  },
+  "tile.deepcrumble": {
+    "subtitle": "subtitles.deep.crumble",
+    "sounds": [
+      "tile/deepcrumble.ogg"
+    ]
+  },
+  "random.scrape": {
+    "subtitle": "subtitles.random.scrape",
+    "sounds": [
+      "random/scrape.ogg"
+    ]
+  }
+}
diff --git a/game/client/devPacks/inTestingSoundsPack/assets/minecraft/sounds/step/crystal_1.ogg b/game/client/devPacks/inTestingSoundsPack/assets/minecraft/sounds/step/crystal_1.ogg
new file mode 100644
index 000000000..995cc2916
Binary files /dev/null and b/game/client/devPacks/inTestingSoundsPack/assets/minecraft/sounds/step/crystal_1.ogg differ
diff --git a/game/client/devPacks/inTestingSoundsPack/assets/minecraft/sounds/step/crystal_2.ogg b/game/client/devPacks/inTestingSoundsPack/assets/minecraft/sounds/step/crystal_2.ogg
new file mode 100644
index 000000000..f51006afe
Binary files /dev/null and b/game/client/devPacks/inTestingSoundsPack/assets/minecraft/sounds/step/crystal_2.ogg differ
diff --git a/game/client/devPacks/inTestingSoundsPack/assets/minecraft/sounds/step/crystal_3.ogg b/game/client/devPacks/inTestingSoundsPack/assets/minecraft/sounds/step/crystal_3.ogg
new file mode 100644
index 000000000..723f5ffa2
Binary files /dev/null and b/game/client/devPacks/inTestingSoundsPack/assets/minecraft/sounds/step/crystal_3.ogg differ
diff --git a/game/client/devPacks/inTestingSoundsPack/assets/minecraft/sounds/step/crystal_4.ogg b/game/client/devPacks/inTestingSoundsPack/assets/minecraft/sounds/step/crystal_4.ogg
new file mode 100644
index 000000000..ec5761e33
Binary files /dev/null and b/game/client/devPacks/inTestingSoundsPack/assets/minecraft/sounds/step/crystal_4.ogg differ
diff --git a/game/client/devPacks/inTestingSoundsPack/assets/minecraft/sounds/step/crystal_5.ogg b/game/client/devPacks/inTestingSoundsPack/assets/minecraft/sounds/step/crystal_5.ogg
new file mode 100644
index 000000000..9d7f41e57
Binary files /dev/null and b/game/client/devPacks/inTestingSoundsPack/assets/minecraft/sounds/step/crystal_5.ogg differ
diff --git a/game/client/devPacks/inTestingSoundsPack/assets/minecraft/sounds/step/crystal_6.ogg b/game/client/devPacks/inTestingSoundsPack/assets/minecraft/sounds/step/crystal_6.ogg
new file mode 100644
index 000000000..e1921002b
Binary files /dev/null and b/game/client/devPacks/inTestingSoundsPack/assets/minecraft/sounds/step/crystal_6.ogg differ
diff --git a/game/client/devPacks/inTestingSoundsPack/assets/minecraft/sounds/tile/bubble_1.ogg b/game/client/devPacks/inTestingSoundsPack/assets/minecraft/sounds/tile/bubble_1.ogg
new file mode 100644
index 000000000..b39fabe86
Binary files /dev/null and b/game/client/devPacks/inTestingSoundsPack/assets/minecraft/sounds/tile/bubble_1.ogg differ
diff --git a/game/client/devPacks/inTestingSoundsPack/assets/minecraft/sounds/tile/bubble_2.ogg b/game/client/devPacks/inTestingSoundsPack/assets/minecraft/sounds/tile/bubble_2.ogg
new file mode 100644
index 000000000..6972fa491
Binary files /dev/null and b/game/client/devPacks/inTestingSoundsPack/assets/minecraft/sounds/tile/bubble_2.ogg differ
diff --git a/game/client/devPacks/inTestingSoundsPack/assets/minecraft/sounds/tile/bubble_3.ogg b/game/client/devPacks/inTestingSoundsPack/assets/minecraft/sounds/tile/bubble_3.ogg
new file mode 100644
index 000000000..05d29b468
Binary files /dev/null and b/game/client/devPacks/inTestingSoundsPack/assets/minecraft/sounds/tile/bubble_3.ogg differ
diff --git a/game/client/devPacks/inTestingSoundsPack/assets/minecraft/sounds/tile/bubble_4.ogg b/game/client/devPacks/inTestingSoundsPack/assets/minecraft/sounds/tile/bubble_4.ogg
new file mode 100644
index 000000000..8f7166ab2
Binary files /dev/null and b/game/client/devPacks/inTestingSoundsPack/assets/minecraft/sounds/tile/bubble_4.ogg differ
diff --git a/game/client/devPacks/inTestingSoundsPack/assets/minecraft/sounds/tile/crumble_1.ogg b/game/client/devPacks/inTestingSoundsPack/assets/minecraft/sounds/tile/crumble_1.ogg
new file mode 100644
index 000000000..bfa72fe8a
Binary files /dev/null and b/game/client/devPacks/inTestingSoundsPack/assets/minecraft/sounds/tile/crumble_1.ogg differ
diff --git a/game/client/devPacks/inTestingSoundsPack/assets/minecraft/sounds/tile/crumble_2.ogg b/game/client/devPacks/inTestingSoundsPack/assets/minecraft/sounds/tile/crumble_2.ogg
new file mode 100644
index 000000000..7c58c5d8e
Binary files /dev/null and b/game/client/devPacks/inTestingSoundsPack/assets/minecraft/sounds/tile/crumble_2.ogg differ
diff --git a/game/client/devPacks/inTestingSoundsPack/assets/minecraft/sounds/tile/deepboom.ogg b/game/client/devPacks/inTestingSoundsPack/assets/minecraft/sounds/tile/deepboom.ogg
new file mode 100644
index 000000000..abf6b6711
Binary files /dev/null and b/game/client/devPacks/inTestingSoundsPack/assets/minecraft/sounds/tile/deepboom.ogg differ
diff --git a/game/client/devPacks/inTestingSoundsPack/assets/minecraft/sounds/tile/deepcrumble.ogg b/game/client/devPacks/inTestingSoundsPack/assets/minecraft/sounds/tile/deepcrumble.ogg
new file mode 100644
index 000000000..b6bb0c261
Binary files /dev/null and b/game/client/devPacks/inTestingSoundsPack/assets/minecraft/sounds/tile/deepcrumble.ogg differ
diff --git a/game/client/devPacks/inTestingSoundsPack/assets/minecraft/sounds/tile/deepquietboom.ogg b/game/client/devPacks/inTestingSoundsPack/assets/minecraft/sounds/tile/deepquietboom.ogg
new file mode 100644
index 000000000..41b6290cf
Binary files /dev/null and b/game/client/devPacks/inTestingSoundsPack/assets/minecraft/sounds/tile/deepquietboom.ogg differ
diff --git a/game/client/devPacks/inTestingSoundsPack/manifest.json b/game/client/devPacks/inTestingSoundsPack/manifest.json
index ad1ee857f..7654d1b25 100644
--- a/game/client/devPacks/inTestingSoundsPack/manifest.json
+++ b/game/client/devPacks/inTestingSoundsPack/manifest.json
@@ -3,7 +3,7 @@
         "name": "§5In Testing Sounds",
         "line1": "Sounds which we are considering adding",
         "line2": "but havent added to the sound server yet.",
-        "packVersion": "7.3",
-        "format": 1
+        "packVersion": "7.4",
+        "format": 2
     }
 }
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 84665e3d8..b26feec9f 100644
--- a/game/client/src/main/java/net/minecraft/client/Minecraft.java
+++ b/game/client/src/main/java/net/minecraft/client/Minecraft.java
@@ -2266,7 +2266,13 @@ public class Minecraft
         int v = 0;
         while (this.currentWorld.updatingLighting()) {
             v++;
-            this.loadingScreen.progressStagePercentage((Math.min(v, count) * 100) / count);
+
+            //safety check to prevent dividing by zero
+            if (count > 0) {
+                this.loadingScreen.progressStagePercentage((Math.min(v, count) * 100) / count);
+            } else {
+                this.loadingScreen.progressStagePercentage(100);
+            }
 
             if (v >= 10000) {
                 LOGGER.error("Updating lights took to long panic skipping!");
diff --git a/game/client/src/main/java/net/minecraft/client/gui/TooltipElement.java b/game/client/src/main/java/net/minecraft/client/gui/TooltipElement.java
index a8a760262..c639d33fe 100644
--- a/game/client/src/main/java/net/minecraft/client/gui/TooltipElement.java
+++ b/game/client/src/main/java/net/minecraft/client/gui/TooltipElement.java
@@ -231,14 +231,14 @@ public class TooltipElement extends Gui {
                 }
             }
 
-            if (itemStack.isItemStackDamageable() && !showDescription && GameSettings.SHOW_ITEM_DEBUG_INFO.value) {
+            if (itemStack.isItemStackDamageable() && GameSettings.SHOW_ITEM_DEBUG_INFO.value) {
                 int durability = itemStack.getMaxDamage();
                 int remainingUses = durability - itemStack.getMetadata();
 
                 text.append('\n').append(TextFormatting.LIGHT_GRAY).append(remainingUses + 1).append(" / ").append(durability + 1);
             }
 
-            if (showDescription && !debug) {
+            if (showDescription) {
                 text.append('\n').append(formatDescription(itemStack.getItemDescription()));
             } else if (!(slot instanceof SlotResult) && GameSettings.ITEM_DESCRIPTIONS.value != DescriptionPromptEnum.NEVER_PROMPT) {
                 text.append('\n').append(formatDescription(trans.translateKeyAndFormat("gui.tooltip.prompt.description", GameSettings.KEY_DESCRIPTION.getKeyName())));
diff --git a/game/client/src/main/java/net/minecraft/client/gui/achievements/data/AchievementPages.java b/game/client/src/main/java/net/minecraft/client/gui/achievements/data/AchievementPages.java
index bcbcff900..9d8b31348 100644
--- a/game/client/src/main/java/net/minecraft/client/gui/achievements/data/AchievementPages.java
+++ b/game/client/src/main/java/net/minecraft/client/gui/achievements/data/AchievementPages.java
@@ -61,6 +61,8 @@ public class AchievementPages {
 //        netherPage.addAchievement(Achievements.ENTER_WARRENS, 0, 5);
         netherPage.addAchievement(Achievements.MOST_WANTED, 0, -1);
         netherPage.addAchievement(Achievements.FAST_TRAVEL, 0, 1);
+        netherPage.addAchievement(Achievements.GET_STEEL_BLAST_FURNACE, 0, 4);
+        netherPage.addAchievement(Achievements.OBTAIN_STEEL, -2, 4);
     }
     static {
         init();
diff --git a/game/client/src/main/java/net/minecraft/client/gui/guidebook/mobs/MobInfoRegistry.java b/game/client/src/main/java/net/minecraft/client/gui/guidebook/mobs/MobInfoRegistry.java
index d9e4ea1b1..c84a32a5d 100644
--- a/game/client/src/main/java/net/minecraft/client/gui/guidebook/mobs/MobInfoRegistry.java
+++ b/game/client/src/main/java/net/minecraft/client/gui/guidebook/mobs/MobInfoRegistry.java
@@ -176,7 +176,7 @@ public abstract class MobInfoRegistry {
             20,
             1000,
             new MobDrop[] {
-                new MobDrop(new ItemStack(Items.GUNPOWDER), 0.66f, 1, 2)
+                new MobDrop(new ItemStack(Items.GUNPOWDER), 1f, 3, 16),
             }
         );
         register(MobZombiePig.class,
diff --git a/game/client/src/main/java/net/minecraft/client/gui/hud/HudIngame.java b/game/client/src/main/java/net/minecraft/client/gui/hud/HudIngame.java
index 221b99f5e..579acb765 100644
--- a/game/client/src/main/java/net/minecraft/client/gui/hud/HudIngame.java
+++ b/game/client/src/main/java/net/minecraft/client/gui/hud/HudIngame.java
@@ -65,6 +65,11 @@ public class HudIngame extends Gui {
     private static final Logger LOGGER = LogUtils.getLogger();
     public static final Direction[] directions = new Direction[]{Direction.NORTH, Direction.EAST, Direction.SOUTH, Direction.WEST};
 
+    private static final float ACID_OVERLAY_IMMERSION_RAMP_UP = 0.055F;
+    private static final float ACID_OVERLAY_IMMERSION_RAMP_DOWN = 0.22F;
+    private static final float ACID_OVERLAY_LERP_UP = 0.13F;
+    private static final float ACID_OVERLAY_LERP_DOWN = 0.038F;
+
     private final @NotNull List<@NotNull ChatLine> chatMessageList = new ArrayList<>();
     protected Minecraft mc;
     public int updateCounter = 0;
@@ -73,6 +78,8 @@ public class HudIngame extends Gui {
     private boolean colorString = false;
     public float destroyProgress;
     float prevVignetteBrightness = 1.0f;
+    protected float acidOverlayAlpha;
+    protected float acidImmersionRamp;
     protected FontRenderer fontRenderer;
     public int chatScroll = 0;
 
@@ -110,6 +117,8 @@ public class HudIngame extends Gui {
             renderPortalOverlay(portalIntensity, width, height);
         }
 
+        updateAcidOverlayForFrame(width, height, partialTicks);
+
         for (final HudComponent component : HudComponents.INSTANCE.getComponents()) {
             if (component.isVisible()) {
                 GLRenderer.pushFrame();
@@ -701,6 +710,63 @@ public class HudIngame extends Gui {
         GLRenderer.setColor4f(1.0F, 1.0F, 1.0F, 1.0F);
     }
 
+    protected void updateAcidOverlayForFrame(int width, int height, float partialTicks) {
+        final Player p = this.mc.thePlayer;
+        final boolean show = p.shouldShowAcidVisualEffects() && p.getGamemode().hasToolDurability() && GameSettings.THIRD_PERSON_VIEW.value == 0;
+
+        if (p.isEffectiveInAcid() && show) {
+            this.acidImmersionRamp += (1.0F - this.acidImmersionRamp) * ACID_OVERLAY_IMMERSION_RAMP_UP;
+        } else {
+            this.acidImmersionRamp += (0.0F - this.acidImmersionRamp) * ACID_OVERLAY_IMMERSION_RAMP_DOWN;
+        }
+        if (!show) {
+            this.acidImmersionRamp = 0.0F;
+        }
+
+        final float fillNorm = Player.ACID_CYCLE_MAX > 0
+            ? MathHelper.clamp((float) p.getAcidHudBarFillTicks() / (float) Player.ACID_CYCLE_MAX, 0.0F, 1.0F)
+            : 0.0F;
+
+        final float acidTargetAlpha;
+        if (!show) {
+            acidTargetAlpha = 0.0F;
+        } else if (p.isEffectiveInAcid()) {
+            acidTargetAlpha = Math.max(fillNorm, this.acidImmersionRamp);
+        } else {
+            // Outside acid: keep overlay at full strength for the whole post-exit drain, then fade once ticks hit 0.
+            acidTargetAlpha = fillNorm > 0.0F ? 1.0F : 0.0F;
+        }
+
+        final float lerpK = acidTargetAlpha > this.acidOverlayAlpha ? ACID_OVERLAY_LERP_UP : ACID_OVERLAY_LERP_DOWN;
+        this.acidOverlayAlpha += (acidTargetAlpha - this.acidOverlayAlpha) * lerpK;
+        if (this.acidOverlayAlpha > 0.001F) {
+            renderAcidOverlay(width, height, partialTicks);
+        }
+    }
+
+    protected void renderAcidOverlay(int xSize, int ySize, float partialTicks) {
+        GLRenderer.pushFrame();
+
+        GLRenderer.disableState(State.DEPTH_TEST);
+        GLRenderer.setDepthMask(false);
+        GLRenderer.enableState(State.BLEND);
+        GLRenderer.setBlendFunc(BlendFactor.SRC_ALPHA, BlendFactor.ONE_MINUS_SRC_ALPHA);
+
+        // Animated acid overlay layer (vignette + drips)
+        GLRenderer.setShader(Shaders.ACID_OVERLAY);
+        GLRenderer.setColor4f(1.0F, 1.0F, 1.0F, this.acidOverlayAlpha);
+
+        TessellatorGeneral tess = GLRenderer.getTessellator();
+        tess.startDrawingQuads();
+        tess.addVertexWithUV(0,     ySize, -90D, 0D, 1D);
+        tess.addVertexWithUV(xSize, ySize, -90D, 1D, 1D);
+        tess.addVertexWithUV(xSize, 0,     -90D, 1D, 0D);
+        tess.addVertexWithUV(0,     0,     -90D, 0D, 0D);
+        tess.draw();
+
+        GLRenderer.popFrame();
+    }
+
     public void updateTick() {
         if (this.recordPlayingUpFor > 0) {
             this.recordPlayingUpFor--;
diff --git a/game/client/src/main/java/net/minecraft/client/gui/hud/HudIngameDoom.java b/game/client/src/main/java/net/minecraft/client/gui/hud/HudIngameDoom.java
index 32c731878..85beb83ee 100644
--- a/game/client/src/main/java/net/minecraft/client/gui/hud/HudIngameDoom.java
+++ b/game/client/src/main/java/net/minecraft/client/gui/hud/HudIngameDoom.java
@@ -57,6 +57,8 @@ public class HudIngameDoom
             renderPortalOverlay(portalIntensity, width, height);
         }
 
+        updateAcidOverlayForFrame(width, height, partialTicks);
+
         drawBackground();
         drawFace();
         drawAmmo();
diff --git a/game/client/src/main/java/net/minecraft/client/gui/hud/component/HudComponentAcidBar.java b/game/client/src/main/java/net/minecraft/client/gui/hud/component/HudComponentAcidBar.java
new file mode 100644
index 000000000..841b38716
--- /dev/null
+++ b/game/client/src/main/java/net/minecraft/client/gui/hud/component/HudComponentAcidBar.java
@@ -0,0 +1,124 @@
+package net.minecraft.client.gui.hud.component;
+
+import net.minecraft.client.gui.Gui;
+import net.minecraft.client.gui.hud.HudIngame;
+import net.minecraft.client.gui.hud.component.layout.Layout;
+import net.minecraft.client.option.GameSettings;
+import net.minecraft.client.render.renderer.GLRenderer;
+import net.minecraft.client.render.renderer.State;
+import net.minecraft.client.render.texture.stitcher.TextureRegistry;
+import net.minecraft.core.entity.player.Player;
+
+public class HudComponentAcidBar extends HudComponentMovable {
+
+    private static final int ACID_BAR_MAX_ICONS = 10;
+
+    public HudComponentAcidBar(String key, Layout layout) {
+        super(key, 81, 9, layout);
+    }
+
+    private boolean isVertical() {
+        return GameSettings.VERTICAL_ACID_BAR.value;
+    }
+
+    private boolean isFlipped() {
+        return GameSettings.FLIP_ACID_BAR.value;
+    }
+
+    @Override
+    public int getDisplayedXSize() {
+        return this.isVertical() ? super.getDisplayedYSize() : super.getDisplayedXSize();
+    }
+
+    @Override
+    public int getDisplayedYSize() {
+        return this.isVertical() ? super.getDisplayedXSize() : super.getDisplayedYSize();
+    }
+
+    @Override
+    public int getTrueXSize() {
+        return this.isVertical() ? super.getTrueYSize() : super.getTrueXSize();
+    }
+
+    @Override
+    public int getTrueYSize() {
+        return this.isVertical() ? super.getTrueXSize() : super.getTrueYSize();
+    }
+
+    @Override
+    public boolean isVisible() {
+        if (mc.thePlayer == null) {
+            return GameSettings.IMMERSIVE_MODE.drawHotbar();
+        }
+        return GameSettings.IMMERSIVE_MODE.drawHotbar() &&
+            !mc.thePlayer.getGamemode().hasInvulnerablePlayer() &&
+            mc.thePlayer.getGamemode().hasToolDurability() &&
+            mc.thePlayer.shouldShowAcidVisualEffects() &&
+            !GameSettings.HIDE_ACID_BAR.value;
+    }
+
+    @Override
+    public boolean isPreviewTranslucent() {
+        return GameSettings.HIDE_ACID_BAR.value;
+    }
+
+    @Override
+    public void render(HudIngame hud, int xSizeScreen, int ySizeScreen, float partialTick) {
+        int x = this.getLayout().getComponentX(this, xSizeScreen);
+        int y = this.getLayout().getComponentY(this, ySizeScreen);
+
+        GLRenderer.setColor4f(1.0f, 1.0f, 1.0f, 1.0f);
+        GLRenderer.enableState(State.BLEND);
+
+        // One icon per acid buildup segment in fluid; after leaving fluid the bar eases down over about one second.
+        final int cycles = mc.thePlayer.getAcidHudBarFillTicks();
+        final int iconCount = cycles <= 0 ? 0 : Math.min(ACID_BAR_MAX_ICONS, cycles);
+
+        boolean isVertical = this.isVertical();
+        boolean isFlipped = this.isFlipped();
+
+        for (int i = 0; i < iconCount; i++) {
+            int xIcon;
+            int yIcon;
+
+            if (isVertical) {
+                xIcon = x;
+                yIcon = isFlipped ? (y + i * 8) : (y + (9 - i) * 8);
+            } else {
+                xIcon = isFlipped ? (x + (9 - i) * 8) : (x + i * 8);
+                yIcon = y;
+            }
+
+            hud.drawGuiIcon(xIcon, yIcon, 9, 9, TextureRegistry.getTexture("minecraft:gui/hud/acid"));
+        }
+    }
+
+    @Override
+    public void renderPreview(Gui gui, Layout layout, int xSizeScreen, int ySizeScreen) {
+        int x = layout.getComponentX(this, xSizeScreen);
+        int y = layout.getComponentY(this, ySizeScreen);
+
+        GLRenderer.setColor4f(1.0f, 1.0f, 1.0f, 1.0f);
+        renderTranslucency();
+        GLRenderer.enableState(State.BLEND);
+
+        boolean isVertical = this.isVertical();
+        boolean isFlipped = this.isFlipped();
+
+        int iconCount = 6;
+        for (int i = 0; i < iconCount; i++) {
+            int xIcon;
+            int yIcon;
+
+            if (isVertical) {
+                xIcon = x;
+                yIcon = isFlipped ? (y + i * 8) : (y + (9 - i) * 8);
+            } else {
+                xIcon = isFlipped ? (x + (9 - i) * 8) : (x + i * 8);
+                yIcon = y;
+            }
+
+            gui.drawGuiIcon(xIcon, yIcon, 9, 9, TextureRegistry.getTexture("minecraft:gui/hud/acid"));
+        }
+    }
+}
diff --git a/game/client/src/main/java/net/minecraft/client/gui/hud/component/HudComponentArmorBar.java b/game/client/src/main/java/net/minecraft/client/gui/hud/component/HudComponentArmorBar.java
index e0cb5f672..881f9afa0 100644
--- a/game/client/src/main/java/net/minecraft/client/gui/hud/component/HudComponentArmorBar.java
+++ b/game/client/src/main/java/net/minecraft/client/gui/hud/component/HudComponentArmorBar.java
@@ -197,8 +197,9 @@ public class HudComponentArmorBar extends HudComponentMovable {
 
         int iconY = y;
 
-        if (piece.maxDurability() > 0 && (piece.currentDurability() / piece.maxDurability()) <= 0.15 && piece.state() != ArmorState.EMPTY) {
-            this.random.setSeed(hud.updateCounter * 0x4c627L);
+        boolean inAcidWarning = mc.thePlayer != null && mc.thePlayer.shouldShowAcidVisualEffects() && mc.thePlayer.getGamemode().hasToolDurability();
+        if (piece.maxDurability() > 0 && ((piece.currentDurability() / piece.maxDurability()) <= 0.15 || inAcidWarning) && piece.state() != ArmorState.EMPTY) {
+            this.random.setSeed(hud.updateCounter * 0x4c627L + (long) this.targetShape.getSlotIndex() * 0x9e3779b9L);
             iconY += this.random.nextInt(2);
         }
 
@@ -208,7 +209,7 @@ public class HudComponentArmorBar extends HudComponentMovable {
         boolean isFlipped = this.isFlipped();
 
         hud.drawGuiIcon(x, iconY, 9, 9, getIconForState(piece, isFlipped));
-        drawDurabilityBar(hud, x, y, piece.state(), piece.currentDurability(), piece.maxDurability(), 0xFF, isFlipped);
+        drawDurabilityBar(hud, x, y, piece.state(), piece.currentDurability(), piece.maxDurability(), 0xFF, isFlipped, inAcidWarning);
 
         GLRenderer.disableState(State.BLEND);
     }
@@ -237,12 +238,12 @@ public class HudComponentArmorBar extends HudComponentMovable {
         boolean isFlipped = this.isFlipped();
 
         gui.drawGuiIcon(x, y, 9, 9, getIconForState(previewPiece, isFlipped));
-        drawDurabilityBar(gui, x, y, previewPiece.state(), previewPiece.currentDurability(), previewPiece.maxDurability(), alpha, isFlipped);
+        drawDurabilityBar(gui, x, y, previewPiece.state(), previewPiece.currentDurability(), previewPiece.maxDurability(), alpha, isFlipped, false);
 
         GLRenderer.disableState(State.BLEND);
     }
 
-    private void drawDurabilityBar(Gui gui, int x, int y, ArmorState state, double currentDurability, double maxDurability, int alpha, boolean isFlipped) {
+    private void drawDurabilityBar(Gui gui, int x, int y, ArmorState state, double currentDurability, double maxDurability, int alpha, boolean isFlipped, boolean acidWarning) {
         int barMaxWidth = 9;
         int barY = y + 10;
 
@@ -261,8 +262,16 @@ public class HudComponentArmorBar extends HudComponentMovable {
         int barWidth = (int) Math.round((currentDurability / maxDurability) * barMaxWidth);
         int progress = (int) Math.round((currentDurability / maxDurability) * 255D);
 
-        int colorFG = java.awt.Color.HSBtoRGB(progress / 255F / 3F, 1f, 1f);
-        int colorBG = ((0xFF - progress) / 4) << 16 | 0x3f00;
+        int colorFG;
+        int colorBG;
+        if (acidWarning) {
+            float t = progress / 255F;
+            colorFG = java.awt.Color.HSBtoRGB(0.11F + t * 0.06F, 1f, 1f);
+            colorBG = java.awt.Color.HSBtoRGB(0.11F, 1f, 0.35f);
+        } else {
+            colorFG = java.awt.Color.HSBtoRGB(progress / 255F / 3F, 1f, 1f);
+            colorBG = ((0xFF - progress) / 4) << 16 | 0x3f00;
+        }
 
         colorFG = (colorFG & 0x00FFFFFF) | a;
         colorBG = (colorBG & 0x00FFFFFF) | a;
diff --git a/game/client/src/main/java/net/minecraft/client/gui/hud/component/HudComponentArmorDurability.java b/game/client/src/main/java/net/minecraft/client/gui/hud/component/HudComponentArmorDurability.java
index d6526d6fd..61b62f7dc 100644
--- a/game/client/src/main/java/net/minecraft/client/gui/hud/component/HudComponentArmorDurability.java
+++ b/game/client/src/main/java/net/minecraft/client/gui/hud/component/HudComponentArmorDurability.java
@@ -12,6 +12,8 @@ import net.minecraft.core.item.ItemStack;
 import net.minecraft.core.item.Items;
 import net.minecraft.core.util.helper.LightIndexHelper;
 
+import java.awt.*;
+
 public class HudComponentArmorDurability
     extends HudComponentMovable {
     private static final ItemStack[] PREVIEW_STACKS = new ItemStack[]{
@@ -53,7 +55,10 @@ public class HudComponentArmorDurability
                     float durability = (stack.getMaxDamage() - stack.getMetadata()) / (float) stack.getMaxDamage();
 
                     int l = (int) (durability * 255);
-                    final int i1 = 255 - l << 16 | l << 8;
+                    final boolean inAcidWarning = mc.thePlayer != null && mc.thePlayer.shouldShowAcidVisualEffects() && mc.thePlayer.getGamemode().hasToolDurability();
+                    final int i1 = inAcidWarning
+                        ? Color.HSBtoRGB(0.11F + (durability * 0.06F), 1f, 1f)
+                        : (255 - l << 16 | l << 8);
 
                     mc.font.render(String.valueOf((stack.getMaxDamage() - stack.getMetadata()) + 1), xx + 20, yy + 4).setShadow().setColor(i1).call();
                 }
diff --git a/game/client/src/main/java/net/minecraft/client/gui/hud/component/HudComponents.java b/game/client/src/main/java/net/minecraft/client/gui/hud/component/HudComponents.java
index 6dbdda565..7e410dbd0 100644
--- a/game/client/src/main/java/net/minecraft/client/gui/hud/component/HudComponents.java
+++ b/game/client/src/main/java/net/minecraft/client/gui/hud/component/HudComponents.java
@@ -104,6 +104,12 @@ public class HudComponents {
             .addAttachedOption(GameSettings.VERTICAL_FIRE_BAR, () -> new BooleanOptionComponent(GameSettings.VERTICAL_FIRE_BAR))
             .addAttachedOption(GameSettings.FLIP_FIRE_BAR, () -> new BooleanOptionComponent(GameSettings.FLIP_FIRE_BAR))
     );
+    public static final @NotNull HudComponentAcidBar ACID_BAR = register(
+        new HudComponentAcidBar("acid_bar", new LayoutSnap(FIRE_BAR, ComponentAnchor.TOP_LEFT, ComponentAnchor.BOTTOM_LEFT, 0, -1))
+            .addAttachedOption(GameSettings.HIDE_ACID_BAR, () -> new BooleanOptionComponent(GameSettings.HIDE_ACID_BAR))
+            .addAttachedOption(GameSettings.VERTICAL_ACID_BAR, () -> new BooleanOptionComponent(GameSettings.VERTICAL_ACID_BAR))
+            .addAttachedOption(GameSettings.FLIP_ACID_BAR, () -> new BooleanOptionComponent(GameSettings.FLIP_ACID_BAR))
+    );
     public static final @NotNull HudComponentRotationLock ROTATION_LOCK = register(
         new HudComponentRotationLock("rotation_lock", new LayoutSnap(CROSSHAIR, ComponentAnchor.CENTER_RIGHT, ComponentAnchor.CENTER_LEFT))
             .addAttachedKeyBinding(GameSettings.KEY_LOCK_ROTATION, () -> new KeyBindingComponent(GameSettings.KEY_LOCK_ROTATION))
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 601296ecc..0b24fbbc7 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
@@ -181,7 +181,7 @@ public abstract class OptionsPages {
                     .withComponent(new ToggleableOptionComponent<>(GameSettings.LEAVES_QUALITY).applyOnRelease())
                     .withComponent(new ToggleableOptionComponent<>(GameSettings.PARTICLES_QUALITY))
                     .withComponent(new ToggleableOptionComponent<>(GameSettings.PARTICLES_CAP))
-                    .withComponent(new FloatOptionComponent(GameSettings.PARTICLE_RENDER_DISTANCE, 0.1f, 3f))
+                    .withComponent(new ToggleableOptionComponent<>(GameSettings.PARTICLE_RENDER_DISTANCE))
                     .withComponent(new FloatOptionComponent(GameSettings.ENTITY_RENDER_DISTANCE, 0.1f, 3f))
                     .withComponent(new BooleanOptionComponent(GameSettings.ENTITY_SHADOWS))
                     .withComponent(new ToggleableOptionComponent<>(GameSettings.CLOUD_QUALITY))
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 c1a723abd..7710c043e 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
@@ -132,14 +132,13 @@ public final class GameSettings {
     public static final @NotNull OptionBoolean VIEW_BOBBING = register(new OptionBoolean("bobView", true));
     public static final @NotNull OptionBoolean TRANSPARENCY_DEPTH_CHECK = register(new OptionBoolean("transparencyDepthCheck", true));
     public static final @NotNull OptionEnum<ParticlesQuality> PARTICLES_QUALITY = register(new OptionEnum<>("particleQuality", ParticlesQuality.class, ParticlesQuality.ALL));
-    public static final @NotNull OptionRange PARTICLES_CAP = register(new OptionRange("particleCap", 4000, 500, 15000));
-    public static final @NotNull OptionFloat PARTICLE_RENDER_DISTANCE = register(new OptionFloat("particleRenderDistance", 1f)
-        .withDisplayStringProvider((mc, i18n, option) -> {
-            int percent = MathHelper.round(option.value * 100.0f);
-            if (percent == 100) {
-                return i18n.translateKey("options.particleRenderDistance.default");
+    public static final @NotNull OptionRange PARTICLES_CAP = register(new OptionRange("particleCap", 50000, 1000, 100000));
+    public static final @NotNull OptionRange PARTICLE_RENDER_DISTANCE = register(new OptionRange("particleRenderDistance", 12, 1, 24)
+        .addOnChangeCallback((mc, option) -> {
+            if (mc.currentScreen instanceof ScreenOptions) {
+                ScreenOptions.queueReload = true;
             } else {
-                return percent + "%";
+                mc.renderGlobal.allChanged();
             }
         }));
     public static final @NotNull OptionRange CHUNK_REBUILDS_PER_FRAME = register(new OptionRange("chunkRebuilds", 6, 2, 12));
@@ -472,6 +471,7 @@ public final class GameSettings {
     public static final @NotNull OptionBoolean HIDE_HEALTH_BAR = register(new OptionBoolean("hideHealthBar", false));
     public static final @NotNull OptionBoolean HIDE_OXYGEN_BAR = register(new OptionBoolean("hideOxygenBar", false));
     public static final @NotNull OptionBoolean HIDE_FIRE_BAR = register(new OptionBoolean("hideFireBar", false));
+    public static final @NotNull OptionBoolean HIDE_ACID_BAR = register(new OptionBoolean("hideAcidBar", false));
     public static final @NotNull OptionBoolean HIDE_VEHICLE_BAR = register(new OptionBoolean("hideVehicleBar", false));
     public static final @NotNull OptionBoolean HIDE_DYNAMIC_TOOLTIP = register(new OptionBoolean("hideDynamicTooltip", false));
     public static final @NotNull OptionBoolean HIDE_HELD_ITEM_COUNT = register(new OptionBoolean("hideHeldItemCount", true));
@@ -499,6 +499,7 @@ public final class GameSettings {
     public static final @NotNull OptionBoolean VERTICAL_OXYGEN_BAR = register(new OptionBoolean("verticalOxygenBar", false));
     public static final @NotNull OptionBoolean VERTICAL_VEHICLE_BAR = register(new OptionBoolean("verticalVehicleBar", false));
     public static final @NotNull OptionBoolean VERTICAL_FIRE_BAR = register(new OptionBoolean("verticalFireBar", false));
+    public static final @NotNull OptionBoolean VERTICAL_ACID_BAR = register(new OptionBoolean("verticalAcidBar", false));
 
     public static final @NotNull OptionBoolean FLIP_HELD_ITEM_COUNT = register(new OptionBoolean("flipHeldItemCount", false));
     public static final @NotNull OptionBoolean FLIP_HELMET_BAR = register(new OptionBoolean("flipHelmetBar", false));
@@ -509,6 +510,7 @@ public final class GameSettings {
     public static final @NotNull OptionBoolean FLIP_OXYGEN_BAR = register(new OptionBoolean("flipOxygenBar", false));
     public static final @NotNull OptionBoolean FLIP_VEHICLE_BAR = register(new OptionBoolean("flipVehicleBar", false));
     public static final @NotNull OptionBoolean FLIP_FIRE_BAR = register(new OptionBoolean("flipFireBar", false));
+    public static final @NotNull OptionBoolean FLIP_ACID_BAR = register(new OptionBoolean("flipAcidBar", false));
 
     public static final @NotNull OptionEnum<HorizontalTextAlignment> HORIZONTAL_INFO_TEXT_ALIGNMENT = register(
         new OptionEnum<>("horizontalInfoTextAlignment", HorizontalTextAlignment.class, HorizontalTextAlignment.LEFT)
@@ -693,7 +695,11 @@ public final class GameSettings {
 
                 Option<?> option = getOption(key);
                 if (option != null) {
-                    option.fromOptionsString(value);
+                    try {
+                        option.fromOptionsString(value);
+                    } catch (Exception e) {
+                        LOGGER.error("Failed to load option '{}'", option.id, e);
+                    }
                     continue;
                 }
 
diff --git a/game/client/src/main/java/net/minecraft/client/player/controller/PlayerController.java b/game/client/src/main/java/net/minecraft/client/player/controller/PlayerController.java
index 118ad0166..a9a0ac06d 100644
--- a/game/client/src/main/java/net/minecraft/client/player/controller/PlayerController.java
+++ b/game/client/src/main/java/net/minecraft/client/player/controller/PlayerController.java
@@ -8,8 +8,6 @@ import net.minecraft.core.InventoryAction;
 import net.minecraft.core.block.Block;
 import net.minecraft.core.block.BlockLogic;
 import net.minecraft.core.block.BlockLogicDoor;
-import net.minecraft.core.block.BlockLogicFenceGate;
-import net.minecraft.core.block.BlockLogicTrapDoor;
 import net.minecraft.core.block.Blocks;
 import net.minecraft.core.block.entity.TileEntity;
 import net.minecraft.core.entity.Entity;
diff --git a/game/client/src/main/java/net/minecraft/client/render/ClientFireOverlayHelper.java b/game/client/src/main/java/net/minecraft/client/render/ClientFireOverlayHelper.java
new file mode 100644
index 000000000..ee6f726ce
--- /dev/null
+++ b/game/client/src/main/java/net/minecraft/client/render/ClientFireOverlayHelper.java
@@ -0,0 +1,25 @@
+package net.minecraft.client.render;
+
+import net.minecraft.core.entity.Entity;
+import net.minecraft.core.block.Blocks;
+import net.minecraft.core.block.tag.BlockTags;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+public final class ClientFireOverlayHelper {
+    private ClientFireOverlayHelper() {
+    }
+
+    public static @NotNull String getFireOverlayTexture(final @Nullable Entity entity) {
+        if (entity == null || entity.activeFireBlock == null) {
+            return "minecraft:block/fire";
+        }
+
+        if (entity.activeFireBlock == Blocks.FIRE_SULFURIC ||
+            entity.activeFireBlock.hasTag(BlockTags.SULFURIC_FIRE_OVERLAY_SOURCE)) {
+            return "minecraft:block/fire_sulfuric";
+        }
+
+        return "minecraft:block/fire";
+    }
+}
\ No newline at end of file
diff --git a/game/client/src/main/java/net/minecraft/client/render/FogManager.java b/game/client/src/main/java/net/minecraft/client/render/FogManager.java
index f179204db..f18de7261 100644
--- a/game/client/src/main/java/net/minecraft/client/render/FogManager.java
+++ b/game/client/src/main/java/net/minecraft/client/render/FogManager.java
@@ -17,7 +17,6 @@ import net.minecraft.client.gui.modelviewer.ScreenModelViewer;
 import net.minecraft.client.render.camera.CameraUtil;
 import net.minecraft.client.render.colorizer.Colorizers;
 import net.minecraft.core.util.helper.MathHelper;
-import net.minecraft.core.world.Dimension;
 import org.lwjgl.opengl.GL41;
 
 public class FogManager {
@@ -32,6 +31,7 @@ public class FogManager {
 
     public float fogBrightnessOld;
     public float fogBrightness;
+    private float oldWorldFogTransition = 0.0f;
 
     public FogManager(@NotNull Minecraft mc) {
         this.mc = mc;
@@ -60,6 +60,8 @@ public class FogManager {
             cameraState = 1;
         } else if (CameraUtil.isUnderLiquid(this.mc.activeCamera, this.mc.currentWorld, Materials.LAVA, partialTick)) {
             cameraState = 2;
+        } else if (CameraUtil.isUnderLiquid(this.mc.activeCamera, this.mc.currentWorld, Materials.ACID, partialTick)) {
+            cameraState = 3;
         } else {
             cameraState = 0;
         }
@@ -79,6 +81,13 @@ public class FogManager {
                 dest.fogDensity = 2.0f;
                 break;
             }
+            case 3: {
+                dest.fogMode = FogState.Mode.EXP;
+                dest.fogDensity = 1.2F;
+                dest.fogStart = 0.0f;
+                dest.fogEnd = 0.1f;
+                break;
+            }
             default: {
                 float fogDistance = farPlaneDistance;
                 float maxFogDistance = GameSettings.RENDER_DISTANCE.highest * 16;
@@ -105,6 +114,23 @@ public class FogManager {
                 }
             }
         }
+
+        TilePos cameraTile = this.mc.activeCamera.getTilePos();
+        float fogTransitionSpeed = 0.0005f;
+
+        if (this.mc.currentWorld.getBlockBiome(cameraTile) == net.minecraft.core.world.biome.Biomes.NETHER_OLD_WORLD) {
+            oldWorldFogTransition += fogTransitionSpeed;
+        } else {
+            oldWorldFogTransition -= fogTransitionSpeed;
+        }
+
+        oldWorldFogTransition = MathHelper.clamp(oldWorldFogTransition, 0.0f, 1.0f);
+
+        if (oldWorldFogTransition > 0.0f) {
+            dest.fogStart = dest.fogStart + (farPlaneDistance * 0.15F - dest.fogStart) * oldWorldFogTransition;
+            dest.fogEnd = dest.fogEnd + (farPlaneDistance * 0.45F - dest.fogEnd) * oldWorldFogTransition;
+        }
+
         return dest;
     }
 
@@ -167,6 +193,10 @@ public class FogManager {
             this.fogRed = 0.6F;
             this.fogGreen = 0.1F;
             this.fogBlue = 0.0F;
+        } else if (CameraUtil.isUnderLiquid(this.mc.activeCamera, world, Materials.ACID, partialTick)) {
+            this.fogRed = 0.2F;
+            this.fogGreen = 0.2F;
+            this.fogBlue = 0.1F;
         }
 
         float brightness = this.fogBrightnessOld + (this.fogBrightness - this.fogBrightnessOld) * partialTick;
diff --git a/game/client/src/main/java/net/minecraft/client/render/ItemRenderer.java b/game/client/src/main/java/net/minecraft/client/render/ItemRenderer.java
index c6d75ba27..98ec47006 100644
--- a/game/client/src/main/java/net/minecraft/client/render/ItemRenderer.java
+++ b/game/client/src/main/java/net/minecraft/client/render/ItemRenderer.java
@@ -20,6 +20,7 @@ import net.minecraft.client.render.tessellator.TessellatorGeneral;
 import net.minecraft.core.block.Block;
 import net.minecraft.core.entity.Entity;
 import net.minecraft.core.item.ItemStack;
+import net.minecraft.core.util.helper.LightIndexHelper;
 import net.minecraft.core.util.helper.MathHelper;
 import net.minecraft.core.world.pos.TilePos;
 import org.joml.Math;
@@ -142,7 +143,6 @@ public class ItemRenderer {
         GLRenderer.disableState(State.DEPTH_TEST);
 
         if (this.mc.thePlayer.isOnFire()) {
-            TextureRegistry.worldAtlas.bind();
             renderFireInFirstPerson(partialTicks);
         }
 
@@ -242,35 +242,49 @@ public class ItemRenderer {
         GLRenderer.popFrame();
     }
 
-    private void renderFireInFirstPerson(float f) {
-        TessellatorGeneral tessellator = GLRenderer.getTessellator();
+    private void renderFireInFirstPerson(float partialTick) {
+        final TessellatorGeneral tessellator = GLRenderer.getTessellator();
+
+        final String texturePath = ClientFireOverlayHelper.getFireOverlayTexture(this.mc.thePlayer);
+        final IconCoordinate texture = TextureRegistry.getTexture(texturePath);
+
+        texture.parentAtlas.bind();
+
         GLRenderer.setColor4f(1.0F, 1.0F, 1.0F, 0.85F);
         GLRenderer.enableState(State.BLEND);
         GLRenderer.setBlendFunc(BlendFactor.SRC_ALPHA, BlendFactor.ONE_MINUS_SRC_ALPHA);
-        float scale = 1.5f;
+
+        final boolean lightingWasOn = GLRenderer.globalGetLightEnabled();
+        GLRenderer.globalSetLightEnabled(false);
+        final byte restoreLightmap = this.mc.thePlayer.getLightIndex(partialTick);
+        GLRenderer.setLightmapCoord1i(LightIndexHelper.lightIndex2i(15, 15));
+
+        final float scale = 1.5f;
         for (int i = 0; i < 2; i++) {
             GLRenderer.pushFrame();
-            IconCoordinate texture = TextureRegistry.getTexture("minecraft:block/fire");
-            float uMin = (float) texture.getIconUMin();
-            float uMax = (float) texture.getIconUMax();
-            float vMin = (float) texture.getIconVMin();
-            float vMax = (float) texture.getIconVMax();
-            float xMin = (0.0F - scale) / 2.0F;
-            float xMax = xMin + scale;
-            float yMin = 0.0F - scale / 2.0F;
-            float yMax = yMin + scale;
-            float f10 = -0.5F;
+            final float uMin = (float) texture.getIconUMin();
+            final float uMax = (float) texture.getIconUMax();
+            final float vMin = (float) texture.getIconVMin();
+            final float vMax = (float) texture.getIconVMax();
+            final float xMin = (0.0F - scale) / 2.0F;
+            final float xMax = xMin + scale;
+            final float yMin = 0.0F - scale / 2.0F;
+            final float yMax = yMin + scale;
+            final float zPlane = -0.5F;
             GLRenderer.modelM4f().translate((float) (-(i * 2 - 1)) * 0.4F, -0.3F, 0.0F);
             GLRenderer.modelM4f().rotateY(Math.toRadians((float) (i * 2 - 1) * 10F));
             tessellator.startDrawingQuads();
-            tessellator.addVertexWithUV(xMin, yMin - 0.5, f10, uMax, vMax);
-            tessellator.addVertexWithUV(xMax, yMin - 0.5, f10, uMin, vMax);
-            tessellator.addVertexWithUV(xMax, yMax, f10, uMin, vMin);
-            tessellator.addVertexWithUV(xMin, yMax, f10, uMax, vMin);
+            tessellator.addVertexWithUV(xMin, yMin - 0.5, zPlane, uMax, vMax);
+            tessellator.addVertexWithUV(xMax, yMin - 0.5, zPlane, uMin, vMax);
+            tessellator.addVertexWithUV(xMax, yMax, zPlane, uMin, vMin);
+            tessellator.addVertexWithUV(xMin, yMax, zPlane, uMax, vMin);
             tessellator.draw();
             GLRenderer.popFrame();
         }
 
+        GLRenderer.setLightmapCoord1i(restoreLightmap);
+        GLRenderer.globalSetLightEnabled(lightingWasOn);
+
         GLRenderer.setColor4f(1.0F, 1.0F, 1.0F, 1.0F);
         GLRenderer.disableState(State.BLEND);
     }
diff --git a/game/client/src/main/java/net/minecraft/client/render/ParticleEngine.java b/game/client/src/main/java/net/minecraft/client/render/ParticleEngine.java
index 3158261e4..79a2c9bfa 100644
--- a/game/client/src/main/java/net/minecraft/client/render/ParticleEngine.java
+++ b/game/client/src/main/java/net/minecraft/client/render/ParticleEngine.java
@@ -1,11 +1,13 @@
 package net.minecraft.client.render;
 
 import it.unimi.dsi.fastutil.objects.ObjectArrayList;
+import net.minecraft.client.Minecraft;
 import net.minecraft.client.option.GameSettings;
 import net.minecraft.client.option.enums.ParticlesQuality;
 import net.minecraft.client.render.camera.ICamera;
 import net.minecraft.client.render.particle.Particle;
 import net.minecraft.client.render.particle.ParticleBlockBreaking;
+import net.minecraft.client.render.particle.ParticleFlame;
 import net.minecraft.client.render.renderer.BlendFactor;
 import net.minecraft.client.render.renderer.CompareFunc;
 import net.minecraft.client.render.renderer.GLRenderer;
@@ -16,15 +18,20 @@ import net.minecraft.client.render.tessellator.TessellatorParticle;
 import net.minecraft.client.render.tessellator.TessellatorParticleShader;
 import net.minecraft.client.render.texture.stitcher.TextureRegistry;
 import net.minecraft.core.block.Block;
+import net.minecraft.core.block.BlockLogicFire;
 import net.minecraft.core.block.Blocks;
+import net.minecraft.core.entity.player.Player;
 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.joml.primitives.AABBd;
 import org.joml.primitives.AABBdc;
 import org.lwjgl.opengl.GL41;
 
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Random;
 
 /**
@@ -40,6 +47,11 @@ public final class ParticleEngine {
     private final List<Particle>[] particles;
     private final Random rand;
 
+    private final Map<Class<?>, ObjectArrayList<Particle>> particlePools = new HashMap<>();
+    private final AABBd cullingAABB = new AABBd();
+    private int globalTick = 0;
+    private double lastCamX, lastCamY, lastCamZ;
+
     public ParticleEngine(World world) {
         this.particles = new ObjectArrayList[TEXTURE_COUNT];
         this.rand = new Random();
@@ -54,27 +66,76 @@ public final class ParticleEngine {
     /**
      * Adds a new particle to the engine.
      *
-     * @param p The particle to add.
+     * @param particle The particle to add.
      */
-    public void add(Particle p) {
-        int t = p.getParticleTexture();
+    public void add(@NotNull Particle particle) {
+        int t = particle.getParticleTexture();
+
         if (this.particles[t].size() >= GameSettings.PARTICLES_CAP.value) {
-            this.particles[t].remove(0);
+            // Find the furthest particle to delete instead of just the oldest
+            int indexToRemove = getIndexToRemove(t);
+
+            // Return the furthest particle to the Object Pool
+            Particle removed = this.particles[t].remove(indexToRemove);
+            ObjectArrayList<Particle> pool = this.particlePools.computeIfAbsent(removed.getClass(), k -> new ObjectArrayList<>());
+            if (pool.size() < GameSettings.PARTICLES_CAP.value) {
+                pool.add(removed);
+            }
+        }
+
+        this.particles[t].add(particle);
+        particle.init();
+    }
+
+    private int getIndexToRemove(int t) {
+        int indexToRemove = 0;
+        double maxDistSq = -1;
+
+        // Only scan a handful of particles to avoid CPU lag when overflowing
+        int scanLimit = Math.min(50, this.particles[t].size());
+
+        for (int i = 0; i < scanLimit; i++) {
+            Particle check = this.particles[t].get(i);
+            double distSq = (check.x - lastCamX)*(check.x - lastCamX) +
+                    (check.y - lastCamY)*(check.y - lastCamY) +
+                    (check.z - lastCamZ)*(check.z - lastCamZ);
+
+            if (distSq > maxDistSq) {
+                maxDistSq = distSq;
+                indexToRemove = i;
+            }
         }
-        this.particles[t].add(p);
-        p.init();
+        return indexToRemove;
     }
 
     /**
      * Ticks all the particles in the engine.
      */
     public void tick() {
+        this.globalTick++;
+
+        Player localPlayer = this.world.players.isEmpty() ? null : this.world.players.get(0);
+        if (localPlayer != null) {
+            this.world.particleEmitters.tick(this.world, localPlayer, GameSettings.PARTICLE_RENDER_DISTANCE.value);
+        }
+
         for (int tt = 0; tt < this.particles.length; tt++) {
             for (int i = 0; i < this.particles[tt].size(); i++) {
                 Particle p = this.particles[tt].get(i);
+
                 p.tick();
                 if (p.removed) {
                     this.particles[tt].remove(i--);
+
+                    Class<?> c = p.getClass();
+                    ObjectArrayList<Particle> pl = this.particlePools.get(c);
+                    if (pl == null) {
+                        pl = new ObjectArrayList<>();
+                        this.particlePools.put(c, pl);
+                    }
+                    if (pl.size() < GameSettings.PARTICLES_CAP.value) {
+                        pl.add(p);
+                    }
                 }
             }
         }
@@ -107,14 +168,40 @@ public final class ParticleEngine {
         double yOff = camera.getY(partialTick);
         double zOff = camera.getZ(partialTick);
 
-        for (int tt = 0; tt <= WORLD_TEXTURE; tt++) {
-            if (this.particles[tt].isEmpty()) {
-                continue;
-            }
+        this.lastCamX = xOff;
+        this.lastCamY = yOff;
+        this.lastCamZ = zOff;
+
+        double maxDist = GameSettings.PARTICLE_RENDER_DISTANCE.value * 16.0;
+        double maxRenderDistSq = maxDist * maxDist;
+        boolean frustumCull = GameSettings.FRUSTUM_CULLING.value;
+
+        for (int tt = 0; tt == WORLD_TEXTURE; tt++) {
+            if (this.particles[tt].isEmpty()) continue;
+
             tessellator.setTranslation(-xOff, -yOff, -zOff);
             tessellator.startDrawingParticles();
+
             for (int i = 0; i < this.particles[tt].size(); i++) {
                 Particle p = this.particles[tt].get(i);
+
+                double distSq = (p.x - xOff)*(p.x - xOff) + (p.y - yOff)*(p.y - yOff) + (p.z - zOff)*(p.z - zOff);
+                if (distSq > maxRenderDistSq) continue;
+
+                if (frustumCull) {
+                    double lerpX = net.minecraft.core.util.helper.MathHelper.lerp(p.xo, p.x, partialTick);
+                    double lerpY = net.minecraft.core.util.helper.MathHelper.lerp(p.yo, p.y, partialTick);
+                    double lerpZ = net.minecraft.core.util.helper.MathHelper.lerp(p.zo, p.z, partialTick);
+                    double r = p.size * 0.1;
+
+                    this.cullingAABB.setMin(lerpX - r, lerpY - r, lerpZ - r);
+                    this.cullingAABB.setMax(lerpX + r, lerpY + r, lerpZ + r);
+
+                    if (!camera.getFrustum().isVisible(this.cullingAABB, partialTick)) {
+                        continue;
+                    }
+                }
+
                 p.render(tessellator, partialTick);
             }
             tessellator.drawParticles();
@@ -123,6 +210,15 @@ public final class ParticleEngine {
         GLRenderer.popFrame();
     }
 
+    @SuppressWarnings("unchecked")
+    private <T extends Particle> T getPooledParticle(Class<T> type) {
+        ObjectArrayList<Particle> pool = this.particlePools.computeIfAbsent(type, k -> new ObjectArrayList<>());
+        if (!pool.isEmpty()) {
+            return (T) pool.remove(pool.size() - 1);
+        }
+        return null;
+    }
+
     /**
      * Renders all the particles in the engine whose texture type is ENTITY_PARTICLE_TEXTURE.
      * These particles are drawn using OpenGL normal-based lighting, unlike the other particles,
@@ -139,17 +235,14 @@ public final class ParticleEngine {
         double xOff = camera.getX(partialTick);
         double yOff = camera.getY(partialTick);
         double zOff = camera.getZ(partialTick);
-        TessellatorGeneral tessellator = GLRenderer.getTessellator();
         for (int i = 0; i < this.particles[tt].size(); i++) {
             Particle p = this.particles[tt].get(i);
             p.renderLit(partialTick, xOff, yOff, zOff);
         }
-
     }
 
     /**
      * Sets the world used to contain any managed particles.
-     *
      * @param world The world to use.
      */
     public void setWorld(World world) {
@@ -169,19 +262,41 @@ public final class ParticleEngine {
     public void destroy(@NotNull TilePosc blockPos, int id, int data) {
         if (GameSettings.PARTICLES_QUALITY.value == ParticlesQuality.NONE) return;
         Block<?> block = Blocks.blocksList[id];
-        if (block == null) return;
+        if (block == null || block.getLogic() instanceof BlockLogicFire) return;
         int amount = GameSettings.PARTICLES_QUALITY.value == ParticlesQuality.REDUCED ? 2 : 4;
-        if (block == Blocks.FIRE) {
-            return;
-        }
+
+        AABBdc bounds = block.getLogic().getBoundsFromState(this.world, blockPos);
+
+        double centerX = (bounds.minX() + bounds.maxX()) / 2.0D;
+        double centerY = (bounds.minY() + bounds.maxY()) / 2.0D;
+        double centerZ = (bounds.minZ() + bounds.maxZ()) / 2.0D;
+
         for (int xx = 0; xx < amount; xx++) {
             for (int yy = 0; yy < amount; yy++) {
                 for (int zz = 0; zz < amount; zz++) {
-                    double xp = blockPos.x() + (xx + 0.5D) / amount;
-                    double yp = blockPos.y() + (yy + 0.5D) / amount;
-                    double zp = blockPos.z() + (zz + 0.5D) / amount;
+
+                    double percentX = (xx + 0.5D) / amount;
+                    double percentY = (yy + 0.5D) / amount;
+                    double percentZ = (zz + 0.5D) / amount;
+
+                    double xp = blockPos.x() + bounds.minX() + (bounds.maxX() - bounds.minX()) * percentX;
+                    double yp = blockPos.y() + bounds.minY() + (bounds.maxY() - bounds.minY()) * percentY;
+                    double zp = blockPos.z() + bounds.minZ() + (bounds.maxZ() - bounds.minZ()) * percentZ;
+
                     Side side = Side.fromId(this.rand.nextInt(6));
-                    add((new ParticleBlockBreaking(this.world, xp, yp, zp, xp - blockPos.x() - 0.5D, yp - blockPos.y() - 0.5D, zp - blockPos.z() - 0.5D, block, side, data)).init(blockPos));
+
+                    double velX = xp - blockPos.x() - centerX;
+                    double velY = yp - blockPos.y() - centerY;
+                    double velZ = zp - blockPos.z() - centerZ;
+
+                    ParticleBlockBreaking particle = getPooledParticle(ParticleBlockBreaking.class);
+                    if (particle == null) {
+                        particle = new ParticleBlockBreaking(this.world, xp, yp, zp, velX, velY, velZ, block, side, data);
+                    } else {
+                        particle.reset(this.world, xp, yp, zp, velX, velY, velZ, block, side, data);
+                    }
+
+                    add(particle.init(blockPos));
                 }
             }
         }
@@ -199,7 +314,9 @@ public final class ParticleEngine {
         if (block == Blocks.AIR) {
             return;
         }
-        AABBdc bounds = block.getBounds();
+
+        AABBdc bounds = block.getLogic().getBoundsFromState(this.world, blockPos);
+
         float r = 0.1F;
         double xp = blockPos.x() + this.rand.nextDouble() * (bounds.maxX() - bounds.minX() - (r * 2.0F)) + r + bounds.minX();
         double yp = blockPos.y() + this.rand.nextDouble() * (bounds.maxY() - bounds.minY() - (r * 2.0F)) + r + bounds.minY();
@@ -217,10 +334,26 @@ public final class ParticleEngine {
         } else if (side == Side.EAST) {
             xp = blockPos.x() + bounds.maxX() + r;
         }
-        add((new ParticleBlockBreaking(this.world, xp, yp, zp, 0.0D, 0.0D, 0.0D, block, side, this.world.getBlockData(blockPos))).init(blockPos).setPower(0.2F).setScale(0.6F));
+
+        ParticleBlockBreaking p = getPooledParticle(ParticleBlockBreaking.class);
+        if (p == null) {
+            p = new ParticleBlockBreaking(this.world, xp, yp, zp, 0.0D, 0.0D, 0.0D, block, side, this.world.getBlockData(blockPos));
+        } else {
+            p.reset(this.world, xp, yp, zp, 0.0D, 0.0D, 0.0D, block, side, this.world.getBlockData(blockPos));
+        }
+
+        add(p.init(blockPos).setPower(0.2F).setScale(0.6F));
     }
 
     public int countParticles() {
         return this.particles[WORLD_TEXTURE].size() + this.particles[ENTITY_PARTICLE_TEXTURE].size();
     }
+
+    public int getGlobalTick() {
+        return globalTick;
+    }
+
+    public void setGlobalTick(int globalTick) {
+        this.globalTick = globalTick;
+    }
 }
diff --git a/game/client/src/main/java/net/minecraft/client/render/RenderGlobal.java b/game/client/src/main/java/net/minecraft/client/render/RenderGlobal.java
index 42dd596ca..0dae10083 100644
--- a/game/client/src/main/java/net/minecraft/client/render/RenderGlobal.java
+++ b/game/client/src/main/java/net/minecraft/client/render/RenderGlobal.java
@@ -1995,6 +1995,19 @@ public class RenderGlobal
                 }
 
                 break;
+
+            case EVENT_ACID_SPREAD:
+                this.world.playSoundEffect(null, SoundCategory.WORLD_SOUNDS, (double) x + 0.5D, (double) y + 0.5D, (double) z + 0.5D, "tile.acidbubble", 0.75F, 1.0F);
+                this.world.playSoundEffect(null, SoundCategory.WORLD_SOUNDS, (double) x + 0.5D, (double) y + 0.5D, (double) z + 0.5D, "random.fizz", 0.5F, 2.6F + (random.nextFloat() - random.nextFloat()) * 0.8F);
+
+                for (int i = 0; i < 10; i++) {
+                    double fx = (double) x + random.nextFloat();
+                    double fy = (double) y + random.nextFloat() * 0.5D;
+                    double fz = (double) z + random.nextFloat();
+
+                    addParticle("acidBoiling", fx, fy + 0.15, fz, 0.0D, 0.0D, 0.0D, 0, false);
+                }
+                break;
             default:
                 break;
         }
diff --git a/game/client/src/main/java/net/minecraft/client/render/TextureManager.java b/game/client/src/main/java/net/minecraft/client/render/TextureManager.java
index 46f862f48..9bb97417d 100644
--- a/game/client/src/main/java/net/minecraft/client/render/TextureManager.java
+++ b/game/client/src/main/java/net/minecraft/client/render/TextureManager.java
@@ -25,18 +25,7 @@ import net.minecraft.client.option.GameSettings;
 import net.minecraft.client.option.enums.TextureAnimation;
 import net.minecraft.client.render.colorizer.Colorizers;
 import net.minecraft.client.render.customatlas.CustomAtlasHandler;
-import net.minecraft.client.render.dynamictexture.DynamicTexture;
-import net.minecraft.client.render.dynamictexture.DynamicTextureCalendar;
-import net.minecraft.client.render.dynamictexture.DynamicTextureClock;
-import net.minecraft.client.render.dynamictexture.DynamicTextureCompass;
-import net.minecraft.client.render.dynamictexture.DynamicTextureCustom;
-import net.minecraft.client.render.dynamictexture.DynamicTextureFire;
-import net.minecraft.client.render.dynamictexture.DynamicTextureFireflyJar;
-import net.minecraft.client.render.dynamictexture.DynamicTextureLavaFlowing;
-import net.minecraft.client.render.dynamictexture.DynamicTextureLavaStill;
-import net.minecraft.client.render.dynamictexture.DynamicTextureTrommel;
-import net.minecraft.client.render.dynamictexture.DynamicTextureWaterFlow;
-import net.minecraft.client.render.dynamictexture.DynamicTextureWaterStill;
+import net.minecraft.client.render.dynamictexture.*;
 import net.minecraft.client.render.font.Fonts;
 import net.minecraft.client.render.texture.meta.AnimationProperties;
 import net.minecraft.client.render.texture.stitcher.AtlasStitcher;
@@ -108,12 +97,13 @@ public class TextureManager {
 	}
 
 	protected void addNativeDynamicTextures(@NotNull TextureAnimation state) {
-		if (state != TextureAnimation.OFF) {addDynamicTexture(new DynamicTextureWaterStill(TextureRegistry.getTexture("minecraft:block/water_still"), TextureRegistry.getTexture("minecraft:block/water_overlay_boiling")));
-			addDynamicTexture(new DynamicTextureWaterFlow(TextureRegistry.getTexture("minecraft:block/water_flowing")));
-			addDynamicTexture(new DynamicTextureLavaStill(TextureRegistry.getTexture("minecraft:block/lava_still")));
-			addDynamicTexture(new DynamicTextureLavaFlowing(TextureRegistry.getTexture("minecraft:block/lava_flowing")));
-            addDynamicTexture(new DynamicTextureFire(TextureRegistry.getTexture("minecraft:block/fire"), false));
-            addDynamicTexture(new DynamicTextureFire(TextureRegistry.getTexture("minecraft:block/fire_cold"), true));
+		if (state != TextureAnimation.OFF) {addDynamicTexture(new DynamicTextureWaterStill(TextureRegistry.getTexture("minecraft:block/fluid/water/still"), TextureRegistry.getTexture("minecraft:block/fluid/water/overlay_boiling")));
+			addDynamicTexture(new DynamicTextureWaterFlow(TextureRegistry.getTexture("minecraft:block/fluid/water/flowing")));
+			addDynamicTexture(new DynamicTextureLavaStill(TextureRegistry.getTexture("minecraft:block/fluid/lava/still")));
+			addDynamicTexture(new DynamicTextureLavaFlowing(TextureRegistry.getTexture("minecraft:block/fluid/lava/flowing")));
+            addDynamicTexture(new DynamicTextureFire(TextureRegistry.getTexture("minecraft:block/fire"), false, false));
+            addDynamicTexture(new DynamicTextureFire(TextureRegistry.getTexture("minecraft:block/fire_cold"), true, false));
+			addDynamicTexture(new DynamicTextureFire(TextureRegistry.getTexture("minecraft:block/fire_sulfuric"), false, true));
 
 			final String[] fireflyColors = {"blue", "orange", "green", "red"};
 			for (final String color : fireflyColors) {
diff --git a/game/client/src/main/java/net/minecraft/client/render/WorldRenderer.java b/game/client/src/main/java/net/minecraft/client/render/WorldRenderer.java
index fed8bba1c..cc17ccdd1 100644
--- a/game/client/src/main/java/net/minecraft/client/render/WorldRenderer.java
+++ b/game/client/src/main/java/net/minecraft/client/render/WorldRenderer.java
@@ -111,6 +111,7 @@ public class WorldRenderer {
         this.tick++;
         this.itemRenderer.updateEquippedItem();
         addRainParticles();
+
         this.lightmapHelper.tick();
     }
 
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 92e9a6ad9..d2da4d3e4 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
@@ -171,6 +171,7 @@ public final class BlockModelDispatcher
         addDispatch(new BlockModelGenericSlab<>(Blocks.SLAB_BRICK_SANDSTONE, loadDataModel("minecraft:block/slab/brick_sandstone/lower"), loadDataModel("minecraft:block/slab/brick_sandstone/upper"), loadDataModel("minecraft:block/slab/brick_sandstone/full")));
         addDispatch(new BlockModelGenericSlab<>(Blocks.SLAB_BRICK_PERMAFROST, loadDataModel("minecraft:block/slab/brick_permafrost/lower"), loadDataModel("minecraft:block/slab/brick_permafrost/upper"), loadDataModel("minecraft:block/slab/brick_permafrost/full")));
 
+        addDispatch(new BlockModelGenericSlab<>(Blocks.SLAB_BRICK_RUBYGLASS, loadDataModel("minecraft:block/rubyglass/slab/brick/lower"), loadDataModel("minecraft:block/rubyglass/slab/brick/upper"), loadDataModel("minecraft:block/rubyglass/slab/brick/full")));
         addDispatch(new BlockModelGenericSlab<>(Blocks.SLAB_BRICK_DIAMOND, loadDataModel("minecraft:block/slab/brick_diamond/lower"), loadDataModel("minecraft:block/slab/brick_diamond/upper"), loadDataModel("minecraft:block/slab/brick_diamond/full")));
         addDispatch(new BlockModelGenericSlab<>(Blocks.SLAB_BRICK_IRON, loadDataModel("minecraft:block/slab/brick_iron/lower"), loadDataModel("minecraft:block/slab/brick_iron/upper"), loadDataModel("minecraft:block/slab/brick_iron/full")));
         addDispatch(new BlockModelGenericSlab<>(Blocks.SLAB_BRICK_GOLD, loadDataModel("minecraft:block/slab/brick_gold/lower"), loadDataModel("minecraft:block/slab/brick_gold/upper"), loadDataModel("minecraft:block/slab/brick_gold/full")));
@@ -198,6 +199,7 @@ public final class BlockModelDispatcher
         addDispatch(new BlockModelGenericStairs<>(Blocks.STAIRS_BRICK_SANDSTONE, loadDataModel("minecraft:block/stairs/brick_sandstone")));
         addDispatch(new BlockModelGenericStairs<>(Blocks.STAIRS_BRICK_PERMAFROST, loadDataModel("minecraft:block/stairs/brick_permafrost")));
 
+        addDispatch(new BlockModelGenericStairs<>(Blocks.STAIRS_BRICK_RUBYGLASS, loadDataModel("minecraft:block/rubyglass/stairs/brick")));
         addDispatch(new BlockModelGenericStairs<>(Blocks.STAIRS_BRICK_DIAMOND, loadDataModel("minecraft:block/stairs/brick_diamond")));
         addDispatch(new BlockModelGenericStairs<>(Blocks.STAIRS_BRICK_IRON, loadDataModel("minecraft:block/stairs/brick_iron")));
         addDispatch(new BlockModelGenericStairs<>(Blocks.STAIRS_BRICK_GOLD, loadDataModel("minecraft:block/stairs/brick_gold")));
@@ -242,12 +244,12 @@ public final class BlockModelDispatcher
         addDispatch(new BlockModelGenericFullyRotatable<>(Blocks.BONESHALE,
                 loadDataModel("minecraft:block/bone_shale")));
 
-        addDispatch(new BlockModelFluid<>(Blocks.FLUID_WATER_FLOWING, "minecraft:block/water_still", "minecraft:block/water_flowing")
+        addDispatch(new BlockModelFluid<>(Blocks.FLUID_WATER_FLOWING, "minecraft:block/fluid/water/still", "minecraft:block/fluid/water/flowing")
             .onRenderLayer(1));
-        addDispatch(new BlockModelFluid<>(Blocks.FLUID_WATER_STILL, "minecraft:block/water_still", "minecraft:block/water_flowing")
+        addDispatch(new BlockModelFluid<>(Blocks.FLUID_WATER_STILL, "minecraft:block/fluid/water/still", "minecraft:block/fluid/water/flowing")
             .onRenderLayer(1));
-        addDispatch(new BlockModelFluid<>(Blocks.FLUID_LAVA_FLOWING, "minecraft:block/lava_still", "minecraft:block/lava_flowing"));
-        addDispatch(new BlockModelFluid<>(Blocks.FLUID_LAVA_STILL, "minecraft:block/lava_still", "minecraft:block/lava_flowing"));
+        addDispatch(new BlockModelFluid<>(Blocks.FLUID_LAVA_FLOWING, "minecraft:block/fluid/lava/still", "minecraft:block/fluid/lava/flowing"));
+        addDispatch(new BlockModelFluid<>(Blocks.FLUID_LAVA_STILL, "minecraft:block/fluid/lava/still", "minecraft:block/fluid/lava/flowing"));
 
         addDispatch(new BlockModelGenericAxis<>(Blocks.LOG_OAK, loadDataModel("minecraft:block/log/oak")));
         addDispatch(new BlockModelGenericAxis<>(Blocks.LOG_PINE, loadDataModel("minecraft:block/log/pine")));
@@ -289,7 +291,7 @@ public final class BlockModelDispatcher
         addDispatch(new BlockModelGeneric<>(Blocks.ALGAE, loadDataModel("minecraft:block/algae")).render3D(false));
 
         addDispatch(new BlockModelGeneric<>(Blocks.BONE_PILE, loadDataModel("minecraft:block/bone_pile")).render3D(false));
-        addDispatch(new BlockModelGeneric<>(Blocks.SOULCATCHER, loadDataModel("minecraft:block/soul_catcher")).render3D(false));
+        addDispatch(new BlockModelGeneric<>(Blocks.SOUL_CATCHER, loadDataModel("minecraft:block/soul_catcher")).render3D(false));
 
         addDispatch(new BlockModelGenericFlowerStackable<>(Blocks.FLOWER_YELLOW, "minecraft:block/flower_yellow/").render3D(false));
         addDispatch(new BlockModelGenericFlowerStackable<>(Blocks.FLOWER_RED, "minecraft:block/flower_red/").render3D(false));
@@ -344,10 +346,12 @@ public final class BlockModelDispatcher
         addDispatch(new BlockModelGeneric<>(Blocks.ORE_DIAMOND_PERMAFROST, loadDataModel("minecraft:block/ore/diamond/permafrost")));
 
         addDispatch(new BlockModelGeneric<>(Blocks.RUBYGLASS_COLUMN, loadDataModel("minecraft:block/rubyglass/column")));
-        addDispatch(new BlockModelGeneric<>(Blocks.RUBYGLASS_NODE, loadDataModel("minecraft:block/rubyglass/node")));
+        addDispatch(new BlockModelGeneric<>(Blocks.RUBYGLASS_NODE, loadDataModel("minecraft:block/rubyglass/node")).forceCullSelf(true));
+        addDispatch(new BlockModelGeneric<>(Blocks.BRICK_RUBYGLASS, loadDataModel("minecraft:block/rubyglass/brick")));
         addDispatch(new BlockModelGeneric<>(Blocks.BLOCK_RUBYGLASS, loadDataModel("minecraft:block/rubyglass/block")).forceCullSelf(true));
-        addDispatch(new BlockModelCrystalBud<>(Blocks.RUBYGLASS_SPROUT, loadDataModel("minecraft:block/rubyglass/sprout")).render3D(false));
-        addDispatch(new BlockModelConduit<>(Blocks.CONDUIT));
+        addDispatch(new BlockModelConduit<>(Blocks.CONDUIT).forceCullSelf(true));
+        addDispatch(new BlockModelGenericRubyglassCrystal<>(Blocks.RUBYGLASS_CRYSTAL, loadDataModel("minecraft:block/rubyglass/crystal/0")).forceCullSelf(true));
+        addDispatch(new BlockModelGenericRubyglassGrowth<>(Blocks.RUBYGLASS_GROWTH));
 
         addDispatch(new BlockModelGeneric<>(Blocks.ORE_NETHERCOAL_NETHERRACK, loadDataModel("minecraft:block/ore/nethercoal/netherrack")));
         addDispatch(new BlockModelGeneric<>(Blocks.ORE_NETHERCOAL_BASALT, loadDataModel("minecraft:block/ore/nethercoal/basalt")));
@@ -425,7 +429,7 @@ public final class BlockModelDispatcher
         addDispatch(new BlockModelGenericSpikes<>(Blocks.SPIKES).render3D(false));
 
         addDispatch(new BlockModelGenericVeryRotatable<>(Blocks.DISPENSER_COBBLE_STONE, loadDataModel("minecraft:block/dispenser")));
-        addDispatch(new BlockModelGenericActivator<>(Blocks.ACTIVATOR_COBBLE_NETHERRACK));
+        addDispatch(new BlockModelGenericActivator<>(Blocks.ACTIVATOR));
 
         addDispatch(new BlockModelGenericTrapdoor<>(Blocks.TRAPDOOR_PLANKS_OAK, "minecraft:block/trapdoor/planks/oak"));
         addDispatch(new BlockModelGenericTrapdoor<>(Blocks.TRAPDOOR_IRON, "minecraft:block/trapdoor/iron"));
@@ -457,6 +461,7 @@ public final class BlockModelDispatcher
         addDispatch(new BlockModelGeneric<>(Blocks.BRAZIER_INACTIVE, loadDataModel("minecraft:block/brazier")));
         addDispatch(new BlockModelGenericBrazier<>(Blocks.BRAZIER_ACTIVE, loadDataModel("minecraft:block/brazier")));
         addDispatch(new BlockModelFire<>(Blocks.FIRE_COLD).setAllTextures("minecraft:block/fire_cold"));
+        addDispatch(new BlockModelFire<>(Blocks.FIRE_SULFURIC).setAllTextures("minecraft:block/fire_sulfuric"));
 
         addDispatch(new BlockModelGeneric<>(Blocks.MOBSPAWNER, loadDataModel("minecraft:block/mobspawner")));
         addDispatch(new BlockModelGeneric<>(Blocks.MOBSPAWNER_DEACTIVATED, loadDataModel("minecraft:block/mobspawner_deactivated")));
@@ -490,7 +495,7 @@ public final class BlockModelDispatcher
         addDispatch(new BlockModelGenericLayer<>(Blocks.LAYER_SNOW, "minecraft:block/layer/snow"));
         addDispatch(new BlockModelGenericLayer<>(Blocks.LAYER_LEAVES_OAK, "minecraft:block/layer/leaves_oak"));
         addDispatch(new BlockModelGenericLayer<>(Blocks.LAYER_SLATE, "minecraft:block/layer/slate"));
-        addDispatch(new BlockModelGenericLayer<>(Blocks.LAYER_ASH, "minecraft:block/layer/ash"));
+        addDispatch(new BlockModelGenericLayerAsh<>(Blocks.LAYER_ASH));
 
         addDispatch(new BlockModelGeneric<>(Blocks.ICE, loadDataModel("minecraft:block/ice")).forceCullSelf(true));
         addDispatch(new BlockModelGeneric<>(Blocks.PERMAICE, loadDataModel("minecraft:block/permaice")));
@@ -588,7 +593,39 @@ public final class BlockModelDispatcher
         addDispatch(new BlockModelGenericStairs<>(Blocks.STAIRS_COBBLE_GLOOMSTONE, loadDataModel("minecraft:block/stairs/cobbled_gloomstone")));
         addDispatch(new BlockModelGenericStairs<>(Blocks.STAIRS_BRICK_GLOOMSTONE, loadDataModel("minecraft:block/stairs/brick_gloomstone")));
 
-        addDispatch(new BlockModelGenericFlowstone<>(Blocks.FLOWSTONE, "minecraft:block/flowstone"));
+        addDispatch(new BlockModelGeneric<>(Blocks.BOULDER_MAGMATIC, loadDataModel("minecraft:block/boulder_magmatic")).render3D(false));
+        addDispatch(new BlockModelGeneric<>(Blocks.BOULDER_SULFURIC, loadDataModel("minecraft:block/boulder_sulfuric")).render3D(false));
+        addDispatch(new BlockModelGeneric<>(Blocks.SULFUR, loadDataModel("minecraft:block/sulfur")));
+        addDispatch(new BlockModelGeneric<>(Blocks.SULFUR_HARDENED, loadDataModel("minecraft:block/sulfur_hardened")));
+        addDispatch(new BlockModelGeneric<>(Blocks.EMBER, loadDataModel("minecraft:block/ember")));
+        addDispatch(new BlockModelGeneric<>(Blocks.SLATE_CARVED, loadDataModel("minecraft:block/carved_slate")));
+        addDispatch(new BlockModelGeneric<>(Blocks.BRIMSTONE, loadDataModel("minecraft:block/brimstone")));
+        addDispatch(new BlockModelGeneric<>(Blocks.BRIMTHAW, loadDataModel("minecraft:block/brimthaw")));
+        addDispatch(new BlockModelGeneric<>(Blocks.BRICK_BRIMSTONE, loadDataModel("minecraft:block/brick_brimstone")));
+
+        addDispatch(new BlockModelGenericAxis<>(Blocks.LOG_SCORCHED, loadDataModel("minecraft:block/log/scorched")));
+
+        addDispatch(new BlockModelGenericSlab<>(Blocks.SLAB_BRIMSTONE, loadDataModel("minecraft:block/slab/brimstone/lower"), loadDataModel("minecraft:block/slab/brimstone/upper"), loadDataModel("minecraft:block/slab/brimstone/full")));
+        addDispatch(new BlockModelGenericStairs<>(Blocks.STAIRS_BRIMSTONE, loadDataModel("minecraft:block/stairs/brimstone")));
+
+        addDispatch(new BlockModelGenericSlab<>(Blocks.SLAB_BRICK_BRIMSTONE, loadDataModel("minecraft:block/slab/brick_brimstone/lower"), loadDataModel("minecraft:block/slab/brick_brimstone/upper"), loadDataModel("minecraft:block/slab/brick_brimstone/full")));
+        addDispatch(new BlockModelGenericStairs<>(Blocks.STAIRS_BRICK_BRIMSTONE, loadDataModel("minecraft:block/stairs/brick_brimstone")));
+
+        addDispatch(new BlockModelGenericSlab<>(Blocks.SLAB_SLATE_POLISHED, loadDataModel("minecraft:block/slab/polished_slate/lower"), loadDataModel("minecraft:block/slab/polished_slate/upper"), loadDataModel("minecraft:block/slab/polished_slate/full")));
+
+        addDispatch(new BlockModelFluid<>(Blocks.FLUID_ACID_FLOWING, "minecraft:block/fluid/acid/still", "minecraft:block/fluid/acid/flowing").onRenderLayer(1));
+        addDispatch(new BlockModelFluid<>(Blocks.FLUID_ACID_STILL, "minecraft:block/fluid/acid/still", "minecraft:block/fluid/acid/flowing").onRenderLayer(1));
+
+        addDispatch(new BlockModelEmpty<>(Blocks.STATUE_SLATE_LOWER).setAllTextures("minecraft:block/slate_side"));
+        addDispatch(new BlockModelEmpty<>(Blocks.STATUE_SLATE_UPPER).setAllTextures("minecraft:block/slate_side"));
+        addDispatch(new BlockModelEmpty<>(Blocks.STATUE_PERMAFROST_LOWER).setAllTextures("minecraft:block/permafrost"));
+        addDispatch(new BlockModelEmpty<>(Blocks.STATUE_PERMAFROST_UPPER).setAllTextures("minecraft:block/permafrost"));
+        addDispatch(new BlockModelEmpty<>(Blocks.STATUE_NETHERRACK_LOWER).setAllTextures("minecraft:block/netherrack"));
+        addDispatch(new BlockModelEmpty<>(Blocks.STATUE_NETHERRACK_UPPER).setAllTextures("minecraft:block/netherrack"));
+        addDispatch(new BlockModelEmpty<>(Blocks.STATUE_GLOOMSTONE_LOWER).setAllTextures("minecraft:block/gloomstone"));
+        addDispatch(new BlockModelEmpty<>(Blocks.STATUE_GLOOMSTONE_UPPER).setAllTextures("minecraft:block/gloomstone"));
+
+
     }
 }
 
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 5e9a2d954..1dba8e7f7 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
@@ -36,7 +36,7 @@ public class BlockModelFluid<T extends BlockLogicFluid> extends BlockModelStanda
         super(block);
         this.still = TextureRegistry.getTexture(still);
         this.flowing = TextureRegistry.getTexture(flowing);
-        this.bubbles = TextureRegistry.getTexture("minecraft:block/water_overlay_boiling");
+        this.bubbles = TextureRegistry.getTexture("minecraft:block/fluid/water/overlay_boiling");
 
         if (block.getLogic() instanceof BlockLogicFluidFlowing) {
             setAllTextures(this.flowing);
@@ -94,10 +94,12 @@ public class BlockModelFluid<T extends BlockLogicFluid> extends BlockModelStanda
             } else {
                 rotation = 0.0F;
             }
-            byte light = this.block.getLightIndex(worldSource, tilePos);
-            byte lightUp = this.block.getLightIndex(worldSource, tilePos.up(queryPos));
 
-            tessellator.setLightmapCoord1i(LightIndexHelper.max(light, lightUp));
+            byte lightNW = getFluidVertexLight(worldSource, (TilePos) tilePos, -1, -1, queryPos);
+            byte lightSW = getFluidVertexLight(worldSource, (TilePos) tilePos, -1, 1, queryPos);
+            byte lightSE = getFluidVertexLight(worldSource, (TilePos) tilePos, 1, 1, queryPos);
+            byte lightNE = getFluidVertexLight(worldSource, (TilePos) tilePos, 1, -1, queryPos);
+
             if (tex == this.flowing) {
                 float cos = Math.cos(-rotation + Math.PI_OVER_2_f);
                 float sin = Math.sin(-rotation + Math.PI_OVER_2_f);
@@ -112,27 +114,43 @@ public class BlockModelFluid<T extends BlockLogicFluid> extends BlockModelStanda
                 this.b.cross(this.a, this.norm).normalize();
 
                 tessellator.setNormal(this.norm);
+
+                tessellator.setLightmapCoord1i(lightNW);
                 tessellator.addVertexWithUV(x, (float) y + h, z,
                     tex.getSubIconU(((-0.25) * cos - (-0.25) * sin) + 0.5),
                     tex.getSubIconV(((-0.25) * sin + (-0.25) * cos) + 0.5)
                 );
+
+                tessellator.setLightmapCoord1i(lightSW);
                 tessellator.addVertexWithUV(x, (float) y + hs, z + 1,
                     tex.getSubIconU(((-0.25) * cos - (0.25) * sin) + 0.5),
                     tex.getSubIconV(((-0.25) * sin + (0.25) * cos) + 0.5)
                 );
+
+                tessellator.setLightmapCoord1i(lightSE);
                 tessellator.addVertexWithUV(x + 1, (float) 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,
                     tex.getSubIconU(((0.25) * cos - (-0.25) * sin) + 0.5),
                     tex.getSubIconV(((0.25) * sin + (-0.25) * cos) + 0.5)
                 );
             } else {
                 tessellator.setNormal(0, 1, 0);
+
+                tessellator.setLightmapCoord1i(lightNW);
                 tessellator.addVertexWithUV(x, (float) y + h, z, tex.getIconUMin(), tex.getIconVMax());
+
+                tessellator.setLightmapCoord1i(lightSW);
                 tessellator.addVertexWithUV(x, (float) 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.setLightmapCoord1i(lightNE);
                 tessellator.addVertexWithUV(x + 1, (float) y + he, z, tex.getIconUMin(), tex.getIconVMin());
 
                 if (worldSource.getWorldType().hasTag(WorldTypeTags.NETHER) && this.block.getLogic().fluid instanceof FluidWater) {
@@ -288,4 +306,15 @@ public class BlockModelFluid<T extends BlockLogicFluid> extends BlockModelStanda
             return super.shouldSideBeRendered(source, bounds, tilePos, side);
         }
     }
+
+    private byte getFluidVertexLight(WorldSource world, TilePos center, int dx, int dz, TilePos queryPos) {
+        byte l1 = this.block.getLightIndex(world, center.up(queryPos));
+        byte l2 = this.block.getLightIndex(world, center.add(dx, 1, 0, queryPos));
+        byte l3 = this.block.getLightIndex(world, center.add(0, 1, dz, queryPos));
+        byte l4 = this.block.getLightIndex(world, center.add(dx, 1, dz, queryPos));
+
+        byte max1 = LightIndexHelper.max(l1, l2);
+        byte max2 = LightIndexHelper.max(l3, l4);
+        return LightIndexHelper.max(max1, max2);
+    }
 }
diff --git a/game/client/src/main/java/net/minecraft/client/render/block/model/generic/BlockModelGeneric.java b/game/client/src/main/java/net/minecraft/client/render/block/model/generic/BlockModelGeneric.java
index 16fac720f..a5be6ed64 100644
--- a/game/client/src/main/java/net/minecraft/client/render/block/model/generic/BlockModelGeneric.java
+++ b/game/client/src/main/java/net/minecraft/client/render/block/model/generic/BlockModelGeneric.java
@@ -6,6 +6,7 @@ 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.BlockLogicPumpkin;
 import net.minecraft.core.util.helper.Direction;
 import net.minecraft.core.util.helper.Side;
 import net.minecraft.core.world.WorldSource;
@@ -48,7 +49,7 @@ public class BlockModelGeneric<T extends BlockLogic> extends BlockModel<T> {
     }
 
     @Override
-    public final boolean render(final @NotNull TessellatorGeneral tessellator, @NotNull final WorldSource worldSource, @NotNull TilePosc tilePos) {
+    public boolean render(final @NotNull TessellatorGeneral tessellator, @NotNull final WorldSource worldSource, @NotNull TilePosc tilePos) {
         return renderAttached(tessellator, worldSource, tilePos, true, null);
     }
 
diff --git a/game/client/src/main/java/net/minecraft/client/render/block/model/generic/BlockModelGenericFlowstone.java b/game/client/src/main/java/net/minecraft/client/render/block/model/generic/BlockModelGenericFlowstone.java
deleted file mode 100644
index b9b41076c..000000000
--- a/game/client/src/main/java/net/minecraft/client/render/block/model/generic/BlockModelGenericFlowstone.java
+++ /dev/null
@@ -1,36 +0,0 @@
-package net.minecraft.client.render.block.model.generic;
-
-import net.minecraft.client.render.block.model.BlockModelDispatcher;
-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.BlockLogicFlowstone;
-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.models.block.StaticBlockModel;
-
-public class BlockModelGenericFlowstone<T extends BlockLogicFlowstone> extends BlockModelGeneric<T> {
-    public final @NotNull StaticBlockModel[] sizes;
-
-    public BlockModelGenericFlowstone(final @NotNull Block<T> block, final @NotNull String baseKey) {
-        super(block, BlockModelDispatcher.loadDataModel(baseKey + "/16"));
-
-        this.sizes = new StaticBlockModel[] {
-            BlockModelDispatcher.loadDataModel(baseKey + "/16").asModel(),
-            BlockModelDispatcher.loadDataModel(baseKey + "/12").asModel(),
-            BlockModelDispatcher.loadDataModel(baseKey + "/8").asModel(),
-            BlockModelDispatcher.loadDataModel(baseKey + "/4").asModel(),
-        };
-    }
-
-    @Override
-    public @NotNull StaticBlockModel getModelFromData(final int data) {
-        if (data < this.sizes.length) {
-            return this.sizes[data];
-        } else {
-            return this.sizes[0];
-        }
-    }
-}
diff --git a/game/client/src/main/java/net/minecraft/client/render/block/model/generic/BlockModelGenericLayerAsh.java b/game/client/src/main/java/net/minecraft/client/render/block/model/generic/BlockModelGenericLayerAsh.java
new file mode 100644
index 000000000..11258d9a0
--- /dev/null
+++ b/game/client/src/main/java/net/minecraft/client/render/block/model/generic/BlockModelGenericLayerAsh.java
@@ -0,0 +1,28 @@
+package net.minecraft.client.render.block.model.generic;
+
+import net.minecraft.client.render.tessellator.TessellatorGeneral;
+import net.minecraft.core.block.Block;
+import net.minecraft.core.block.BlockLogic;
+import net.minecraft.core.block.Blocks;
+import net.minecraft.core.world.WorldSource;
+import net.minecraft.core.world.pos.TilePosc;
+import org.jetbrains.annotations.NotNull;
+
+public class BlockModelGenericLayerAsh<T extends BlockLogic> extends BlockModelGenericLayer<T> {
+
+    public final BlockModelGenericLayer<T> emberModel;
+
+    public BlockModelGenericLayerAsh(@NotNull Block<T> block) {
+        super(block, "minecraft:block/ash");
+        this.emberModel = new BlockModelGenericLayer<>(block, "minecraft:block/ash_ember");
+    }
+
+    @Override
+    public boolean render(@NotNull TessellatorGeneral tessellator, @NotNull WorldSource worldSource, @NotNull TilePosc tilePos) {
+        if (worldSource.getBlockId(tilePos.x(), tilePos.y() - 1, tilePos.z()) == Blocks.EMBER.id()) {
+            return this.emberModel.render(tessellator, worldSource, tilePos);
+        }
+
+        return super.render(tessellator, worldSource, tilePos);
+    }
+}
\ No newline at end of file
diff --git a/game/client/src/main/java/net/minecraft/client/render/block/model/generic/BlockModelGenericRocks.java b/game/client/src/main/java/net/minecraft/client/render/block/model/generic/BlockModelGenericRocks.java
new file mode 100644
index 000000000..832e3b8f5
--- /dev/null
+++ b/game/client/src/main/java/net/minecraft/client/render/block/model/generic/BlockModelGenericRocks.java
@@ -0,0 +1,25 @@
+package net.minecraft.client.render.block.model.generic;
+
+import net.minecraft.client.render.block.model.BlockModelDispatcher;
+import net.minecraft.core.block.Block;
+import net.minecraft.core.block.BlockLogic;
+import org.jetbrains.annotations.NotNull;
+import org.useless.dragonfly.models.block.StaticBlockModel;
+
+public class BlockModelGenericRocks<T extends BlockLogic> extends BlockModelGeneric<T> {
+	public final @NotNull StaticBlockModel[] models = new StaticBlockModel[3];
+
+	public BlockModelGenericRocks(@NotNull Block<T> block) {
+		super(block, BlockModelDispatcher.loadDataModel("minecraft:block/rocks/0"));
+
+		for(int i = 0; i < 3; ++i) {
+			this.models[i] = BlockModelDispatcher.loadDataModel("minecraft:block/rocks/" + i).asModel();
+		}
+
+	}
+
+	@Override
+	public @NotNull StaticBlockModel getModelFromData(int data) {
+		return this.models[data % 3];
+	}
+}
diff --git a/game/client/src/main/java/net/minecraft/client/render/block/model/generic/BlockModelGenericRubyglassBud.java b/game/client/src/main/java/net/minecraft/client/render/block/model/generic/BlockModelGenericRubyglassBud.java
deleted file mode 100644
index e8ab93388..000000000
--- a/game/client/src/main/java/net/minecraft/client/render/block/model/generic/BlockModelGenericRubyglassBud.java
+++ /dev/null
@@ -1,23 +0,0 @@
-package net.minecraft.client.render.block.model.generic;
-
-import net.minecraft.client.render.block.model.BlockModelDispatcher;
-import net.minecraft.core.block.Block;
-import net.minecraft.core.block.BlockLogic;
-import net.minecraft.core.block.BlockLogicRubyglassCrop;
-import org.jetbrains.annotations.NotNull;
-import org.useless.dragonfly.models.block.StaticBlockModel;
-
-public class BlockModelGenericRubyglassBud<T extends BlockLogic> extends BlockModelGeneric<T> {
-    public final StaticBlockModel[] models = new StaticBlockModel[BlockLogicRubyglassCrop.MAX_GROWTH_STATE + 1];
-    public BlockModelGenericRubyglassBud(@NotNull Block<T> block) {
-        super(block, BlockModelDispatcher.loadDataModel("minecraft:block/rubyglass_bud/stage0"));
-        for (int i = 0; i < this.models.length; i++) {
-            this.models[i] = BlockModelDispatcher.loadDataModel("minecraft:block/rubyglass_bud/stage" + i).asModel();
-        }
-    }
-
-    @Override
-    public @NotNull StaticBlockModel getModelFromData(int data) {
-        return this.models[data % this.models.length];
-    }
-}
diff --git a/game/client/src/main/java/net/minecraft/client/render/block/model/generic/BlockModelGenericRubyglassCrystal.java b/game/client/src/main/java/net/minecraft/client/render/block/model/generic/BlockModelGenericRubyglassCrystal.java
new file mode 100644
index 000000000..0a5e7a394
--- /dev/null
+++ b/game/client/src/main/java/net/minecraft/client/render/block/model/generic/BlockModelGenericRubyglassCrystal.java
@@ -0,0 +1,33 @@
+package net.minecraft.client.render.block.model.generic;
+
+import net.minecraft.client.render.block.model.BlockModelDispatcher;
+import net.minecraft.core.block.Block;
+import net.minecraft.core.block.BlockLogic;
+import org.jetbrains.annotations.NotNull;
+import org.useless.dragonfly.data.block.BlockModelData;
+import org.useless.dragonfly.models.block.StaticBlockModel;
+
+public class BlockModelGenericRubyglassCrystal<T extends BlockLogic> extends BlockModelGeneric<T> {
+	public final StaticBlockModel[] models = new StaticBlockModel[4];
+
+	public BlockModelGenericRubyglassCrystal(@NotNull Block<T> block, @NotNull StaticBlockModel staticModel) {
+		super(block, staticModel);
+		loadModels();
+	}
+
+	public BlockModelGenericRubyglassCrystal(@NotNull Block<T> block, @NotNull BlockModelData staticModel) {
+		super(block, staticModel);
+		loadModels();
+	}
+
+	private void loadModels() {
+		for (int i = 0; i < 4; i++) {
+			this.models[i] = BlockModelDispatcher.loadDataModel("minecraft:block/rubyglass/crystal/" + i).asModel();
+		}
+	}
+
+	@Override
+	public @NotNull StaticBlockModel getModelFromData(int data) {
+		return this.models[data & 3];
+	}
+}
diff --git a/game/client/src/main/java/net/minecraft/client/render/block/model/generic/BlockModelGenericRubyglassGrowth.java b/game/client/src/main/java/net/minecraft/client/render/block/model/generic/BlockModelGenericRubyglassGrowth.java
new file mode 100644
index 000000000..f0488a930
--- /dev/null
+++ b/game/client/src/main/java/net/minecraft/client/render/block/model/generic/BlockModelGenericRubyglassGrowth.java
@@ -0,0 +1,85 @@
+package net.minecraft.client.render.block.model.generic;
+
+import net.minecraft.client.render.block.model.BlockModel;
+import net.minecraft.client.render.block.model.BlockModelDispatcher;
+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.util.helper.Side;
+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.DisplayPos;
+
+import static net.minecraft.core.block.BlockLogicGrowthRubyglass.*;
+
+public class BlockModelGenericRubyglassGrowth<T extends BlockLogic> extends BlockModel<T> {
+
+    public final BlockModelGenericFullyRotatable<T> budModel;
+    public final BlockModelGenericFullyRotatable<T> bottomModel;
+    public final BlockModelGenericFullyRotatable<T> topModel;
+
+    public BlockModelGenericRubyglassGrowth(@NotNull Block<T> block) {
+        super(block);
+        this.budModel = new BlockModelGenericFullyRotatable<>(block, BlockModelDispatcher.loadDataModel("minecraft:block/rubyglass/growth/bud"));
+        this.bottomModel = new BlockModelGenericFullyRotatable<>(block, BlockModelDispatcher.loadDataModel("minecraft:block/rubyglass/growth/bottom"));
+        this.topModel = new BlockModelGenericFullyRotatable<>(block, BlockModelDispatcher.loadDataModel("minecraft:block/rubyglass/growth/top"));
+    }
+
+    @Override
+    public boolean render(@NotNull TessellatorGeneral tessellator, @NotNull WorldSource worldSource, @NotNull TilePosc tilePos) {
+        int data = worldSource.getBlockData(tilePos);
+
+        int shape = data & SHAPE_MASK;
+
+        if (shape == STATE_TOP) {
+            return this.topModel.render(tessellator, worldSource, tilePos);
+        } else if (shape == STATE_BOTTOM) {
+            return this.bottomModel.render(tessellator, worldSource, tilePos);
+        } else {
+            return this.budModel.render(tessellator, worldSource, tilePos);
+        }
+    }
+
+    @Override
+    public boolean renderNoCulling(@NotNull TessellatorGeneral tessellator, @NotNull WorldSource worldSource, @NotNull TilePosc tilePos) {
+        return false;
+    }
+
+    @Override
+    public boolean renderWithOverrideTexture(@NotNull TessellatorGeneral tessellator, @NotNull WorldSource worldSource, @NotNull TilePosc tilePos, IconCoordinate overrideTexture) {
+        return false;
+    }
+
+    @Override
+    public void renderStandalone(@NotNull TessellatorGeneral tessellator, int metadata, byte lightIndex) {
+        this.budModel.renderStandalone(tessellator, metadata, lightIndex);
+    }
+
+    @Override
+    public boolean shouldItemRender3d() {
+        return false;
+    }
+
+    @Override
+    public @NotNull DisplayPos getItemDisplayPos(@NotNull String id) {
+        return this.budModel.getItemDisplayPos(id);
+    }
+
+    @Override
+    public @Nullable IconCoordinate getParticleTexture(@NotNull Side side, int meta) {
+        return this.budModel.getParticleTexture(side, meta);
+    }
+
+    @Override
+    public @Nullable IconCoordinate getOverlayTexture(int meta) {
+        return this.budModel.getOverlayTexture(meta);
+    }
+
+    @Override
+    public int particleColorIndex(@NotNull WorldSource worldSource, @NotNull TilePosc tilePos, @NotNull Side side, int meta) {
+        return this.budModel.particleColorIndex(worldSource, tilePos, side, meta);
+    }
+}
\ No newline at end of file
diff --git a/game/client/src/main/java/net/minecraft/client/render/dynamictexture/DynamicTextureFire.java b/game/client/src/main/java/net/minecraft/client/render/dynamictexture/DynamicTextureFire.java
index 034c2746f..92bd27c87 100644
--- a/game/client/src/main/java/net/minecraft/client/render/dynamictexture/DynamicTextureFire.java
+++ b/game/client/src/main/java/net/minecraft/client/render/dynamictexture/DynamicTextureFire.java
@@ -10,11 +10,13 @@ public class DynamicTextureFire extends DynamicTexture{
 	private int height;
 	private float scaleFactor;
 
+	private final boolean sulfuric;
 	private final boolean cold;
 
-	public DynamicTextureFire(@NotNull IconCoordinate targetTexture, boolean cold) {
+	public DynamicTextureFire(@NotNull IconCoordinate targetTexture, boolean cold, boolean sulfuric) {
 		super(targetTexture);
 
+		this.sulfuric = sulfuric;
 		this.cold = cold;
 	}
 
@@ -73,6 +75,10 @@ public class DynamicTextureFire extends DynamicTexture{
 				r = (int)(l * 155.0F + 100.0F);
 				g = (int)(l * 155.0F + 100.0F);
 				b = (int)(l * 155.0F + 100.0F);
+			} else if (this.sulfuric) {
+				r = (int)(l * 200.0F + 55.0F);
+				g = (int)(l * 155.0F + 100.0F);
+				b = (int)(l * l * l * l * l * l * l * l * l * l * 255.0F);
 			} else {
 				r = (int)(l * 155.0F + 100.0F);
 				g = (int)(l * l * 255.0F);
diff --git a/game/client/src/main/java/net/minecraft/client/render/entity/EntityRenderer.java b/game/client/src/main/java/net/minecraft/client/render/entity/EntityRenderer.java
index 865195a21..28ea1fa62 100644
--- a/game/client/src/main/java/net/minecraft/client/render/entity/EntityRenderer.java
+++ b/game/client/src/main/java/net/minecraft/client/render/entity/EntityRenderer.java
@@ -15,6 +15,7 @@ import org.jetbrains.annotations.NotNull;
 import org.joml.Math;
 import org.joml.primitives.AABBdc;
 
+import net.minecraft.client.render.ClientFireOverlayHelper;
 import net.minecraft.client.render.EntityRendererDispatcher;
 import net.minecraft.client.render.ImageParser;
 import net.minecraft.client.render.TextureManager;
@@ -103,7 +104,8 @@ public abstract class EntityRenderer<T extends Entity> {
 
     private void renderFire(final @NotNull TessellatorGeneral tessellator, final @NotNull T entity, final double x, final double y, final double z, final float partialTick) {
         GLRenderer.globalSetLightEnabled(false);
-        final IconCoordinate texture = TextureRegistry.getTexture("minecraft:block/fire");
+        final String texturePath = ClientFireOverlayHelper.getFireOverlayTexture(entity);
+        final IconCoordinate texture = TextureRegistry.getTexture(texturePath);
         texture.parentAtlas.bind();
         GLRenderer.pushFrame();
         GLRenderer.modelM4f().translate((float) x, (float) y, (float) z);
@@ -118,6 +120,7 @@ public abstract class EntityRenderer<T extends Entity> {
         GLRenderer.setColor4f(1.0F, 1.0F, 1.0F, 1.0F);
         byte lightIndex = entity.getLightIndex(partialTick);
         lightIndex = LightIndexHelper.setBlockLight(lightIndex, 15);
+        lightIndex = LightIndexHelper.setSkyLight(lightIndex, 15);
         GLRenderer.setLightmapCoord1i(lightIndex);
         float f14 = 0.0F;
         int l = 0;
diff --git a/game/client/src/main/java/net/minecraft/client/render/item/model/ItemModelDispatcher.java b/game/client/src/main/java/net/minecraft/client/render/item/model/ItemModelDispatcher.java
index a7c79b435..20241c95e 100644
--- a/game/client/src/main/java/net/minecraft/client/render/item/model/ItemModelDispatcher.java
+++ b/game/client/src/main/java/net/minecraft/client/render/item/model/ItemModelDispatcher.java
@@ -269,6 +269,7 @@ public class ItemModelDispatcher extends Dispatcher<Item, ItemModel> {
             .setDisplayPos(DisplayPos.THIRD_PERSON_RIGHT_HAND, HANDHELD_ROD_THIRD_PERSON_RIGHT_HAND)
             .setDisplayPos(DisplayPos.THIRD_PERSON_LEFT_HAND, HANDHELD_ROD_THIRD_PERSON_LEFT_HAND));
         addDispatch(new ItemModelStandard(Items.TOOL_CLOCK, "minecraft"));
+        addDispatch(new ItemModelBlock((ItemBlock<?>) Blocks.GLOWSTONE.asItem()).setFullBright());
         addDispatch(new ItemModelStandard(Items.DUST_GLOWSTONE, "minecraft").setFullBright());
         addDispatch(new ItemModelStandard(Items.FOOD_FISH_RAW, "minecraft"));
         addDispatch(new ItemModelStandard(Items.FOOD_FISH_COOKED, "minecraft"));
@@ -358,6 +359,11 @@ public class ItemModelDispatcher extends Dispatcher<Item, ItemModel> {
         addDispatch(new ItemModelStandard(Items.LANTERN_FIREFLY_ORANGE, "minecraft").setFullBright());
         addDispatch(new ItemModelStandard(Items.LANTERN_FIREFLY_RED, "minecraft").setFullBright());
 
+        addDispatch(new ItemModelBlock((ItemBlock<?>) Blocks.LAMP_ACTIVE.asItem()).setFullBright());
+        addDispatch(new ItemModelBlock((ItemBlock<?>) Blocks.LAMP_IDLE.asItem()).setFullBright());
+        addDispatch(new ItemModelBlock((ItemBlock<?>) Blocks.LAMP_INVERTED_ACTIVE.asItem()).setFullBright());
+        addDispatch(new ItemModelBlock((ItemBlock<?>) Blocks.LAMP_INVERTED_IDLE.asItem()).setFullBright());
+
         addDispatch(new ItemModelStandard(Items.BASKET, "minecraft"));
         addDispatch(new ItemModelStandard(Items.FLAG, "minecraft")
             .setDisplayPos(DisplayPos.FIRST_PERSON_RIGHT_HAND, HANDHELD_FIRST_PERSON_RIGHT_HAND)
@@ -427,8 +433,15 @@ public class ItemModelDispatcher extends Dispatcher<Item, ItemModel> {
 
         addDispatch(new ItemModelStandard(Items.FOOD_VENISON_RAW, "minecraft"));
         addDispatch(new ItemModelStandard(Items.FOOD_VENISON_COOKED, "minecraft"));
-//        addDispatch(new ItemModelStandard(Items.RUBYGLASS_DUST, "minecraft"));
-        addDispatch(new ItemModelStandard(Items.RUBYGLASS_CRYSTAL, "minecraft"));
+
+        addDispatch(new ItemModelStandard(Items.RUBYGLASS, "minecraft").setFullBright());
+        addDispatch(new ItemModelBlock((ItemBlock<?>) Blocks.RUBYGLASS_COLUMN.asItem()).setFullBright());
+        addDispatch(new ItemModelBlock((ItemBlock<?>) Blocks.BLOCK_RUBYGLASS.asItem()).setFullBright());
+        addDispatch(new ItemModelBlock((ItemBlock<?>) Blocks.RUBYGLASS_GROWTH.asItem()).setFullBright());
+        addDispatch(new ItemModelBlock((ItemBlock<?>) Blocks.RUBYGLASS_CRYSTAL.asItem()).setFullBright());
+
+        addDispatch(new ItemModelBlock((ItemBlock<?>) Blocks.BOULDER_MAGMATIC.asItem()));
+        addDispatch(new ItemModelBlock((ItemBlock<?>) Blocks.BOULDER_SULFURIC.asItem()));
 
         addDispatch(new ItemModelStandard(Items.DOUGH, "minecraft"));
 
@@ -439,6 +452,15 @@ public class ItemModelDispatcher extends Dispatcher<Item, ItemModel> {
         addDispatch(new ItemModelStandard(Items.ARMOR_WOLF_GOLD, "minecraft"));
         addDispatch(new ItemModelStandard(Items.ARMOR_WOLF_STEEL, "minecraft"));
 
+        addDispatch(new ItemModelStandard(Items.AMMO_ARROW_FLAMING, "minecraft"));
+
+        addDispatch(new ItemModelStandard(Items.SULFUR, "minecraft"));
+
+        addDispatch(new ItemModelStandard(Items.STATUE_GLOOMSTONE, "minecraft"));
+        addDispatch(new ItemModelStandard(Items.STATUE_NETHERRACK, "minecraft"));
+        addDispatch(new ItemModelStandard(Items.STATUE_PERMAFROST, "minecraft"));
+        addDispatch(new ItemModelStandard(Items.STATUE_SLATE, "minecraft"));
+
         for (int i = 0; i < Blocks.blocksList.length; i++) {
             if (Blocks.blocksList[i] == null) continue;
             Item item = Blocks.blocksList[i].asItem();
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 bc60db4a4..7789cc5ab 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
@@ -1,5 +1,6 @@
 package net.minecraft.client.render.item.model;
 
+import net.minecraft.client.Minecraft;
 import net.minecraft.client.render.TextureManager;
 import net.minecraft.client.render.font.FontRenderer;
 import net.minecraft.client.render.renderer.BlendFactor;
@@ -216,8 +217,20 @@ public class ItemModelStandard extends ItemModel{
             GLRenderer.pushFrame();
             GLRenderer.disableState(State.DEPTH_TEST);
             GLRenderer.setShader(Shaders.COLOR);
-            final int colorFG = Color.HSBtoRGB(progress / 255F / 3F, 1f, 1f);
-            final int colorBG = (0xFF - progress) / 4 << 16 | 0x3f00;
+
+            final Minecraft mc = Minecraft.getMinecraft();
+            final boolean isAcidic = mc.thePlayer != null && mc.thePlayer.shouldShowAcidVisualEffects() && mc.thePlayer.getGamemode().hasToolDurability();
+            final int colorFG;
+            final int colorBG;
+            if (isAcidic) {
+                final float t = progress / 255F;
+                colorFG = Color.HSBtoRGB(0.11F + t * 0.06F, 1f, 1f);
+                colorBG = Color.HSBtoRGB(0.11F, 1f, 0.35f);
+            } else {
+                colorFG = Color.HSBtoRGB(progress / 255F / 3F, 1f, 1f);
+                colorBG = (0xFF - progress) / 4 << 16 | 0x3f00;
+            }
+
             tessellator.startDrawingQuads();
             renderColoredQuad(tessellator, x + 2, y + 13, 13, 2, 0, a);
             renderColoredQuad(tessellator, x + 2, y + 13, 12, 1, colorBG, a);
diff --git a/game/client/src/main/java/net/minecraft/client/render/particle/Particle.java b/game/client/src/main/java/net/minecraft/client/render/particle/Particle.java
index 66f7294e6..4410a5f79 100644
--- a/game/client/src/main/java/net/minecraft/client/render/particle/Particle.java
+++ b/game/client/src/main/java/net/minecraft/client/render/particle/Particle.java
@@ -1,28 +1,20 @@
 package net.minecraft.client.render.particle;
 
-import it.unimi.dsi.fastutil.objects.ObjectArrayList;
 import net.minecraft.client.render.ParticleEngine;
 import net.minecraft.client.render.tessellator.TessellatorParticle;
 import net.minecraft.client.render.texture.stitcher.IconCoordinate;
 import net.minecraft.core.Global;
 import net.minecraft.core.block.Block;
-import net.minecraft.core.block.BlockLogicMesh;
-import net.minecraft.core.util.helper.Direction;
 import net.minecraft.core.util.helper.LightIndexHelper;
 import net.minecraft.core.util.helper.MathHelper;
-import net.minecraft.core.util.phys.HitResult;
 import net.minecraft.core.world.World;
 import net.minecraft.core.world.pos.TilePos;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 import org.joml.Random;
-import org.joml.Vector2d;
-import org.joml.Vector3d;
-import org.joml.primitives.AABBd;
 import org.joml.primitives.AABBdc;
-import org.joml.primitives.Rayd;
 
-import java.util.List;
+import java.util.concurrent.ThreadLocalRandom;
 
 /**
  * Displays a small, temporary texture in the world, used as a visual effect. Managed by {@link ParticleEngine}.
@@ -34,7 +26,7 @@ public class Particle {
     protected float vo;
     protected int age;
     protected int lifetime;
-    protected float size;
+    public float size;
     protected float gravity;
     protected float rCol;
     protected float gCol;
@@ -51,11 +43,17 @@ public class Particle {
     public double yd = 0;
     public double zd = 0;
 
+    protected int bX = Integer.MAX_VALUE;
+    protected int bY = Integer.MAX_VALUE;
+    protected int bZ = Integer.MAX_VALUE;
+
     public boolean noPhysics = false;
     public boolean onGround = false;
     public boolean removed = false;
 
-    public final @NotNull World world;
+    private final TilePos qPos = new TilePos();
+
+    public @NotNull World world;
 
     /**
      * Creates a new Particle.
@@ -74,10 +72,33 @@ public class Particle {
         this.y = y;
         this.z = z;
         this.rCol = this.gCol = this.bCol = 1.0F;
-        this.xd = xa + (float) (Math.random() * 2D - 1.0D) * 0.4F;
-        this.yd = ya + (float) (Math.random() * 2D - 1.0D) * 0.4F;
-        this.zd = za + (float) (Math.random() * 2D - 1.0D) * 0.4F;
-        float speed = (float) (Math.random() + Math.random() + 1.0D) * 0.15F;
+        ThreadLocalRandom tlr = ThreadLocalRandom.current();
+        this.xd = xa + (float) (tlr.nextDouble() * 2D - 1.0D) * 0.4F;
+        this.yd = ya + (float) (tlr.nextDouble() * 2D - 1.0D) * 0.4F;
+        this.zd = za + (float) (tlr.nextDouble() * 2D - 1.0D) * 0.4F;
+        float speed = (float) (tlr.nextDouble() + tlr.nextDouble() + 1.0D) * 0.15F;
+        float dd = MathHelper.sqrt(this.xd * this.xd + this.yd * this.yd + this.zd * this.zd);
+        this.xd = (this.xd / dd) * speed * 0.4D;
+        this.yd = (this.yd / dd) * speed * 0.4D + 0.1D;
+        this.zd = (this.zd / dd) * speed * 0.4D;
+        this.uo = random.nextFloat() * 3F;
+        this.vo = random.nextFloat() * 3F;
+        this.size = (random.nextFloat() * 0.5F + 0.5F) * 2.0F;
+        this.lifetime = (int) (4F / (random.nextFloat() * 0.9F + 0.1F));
+        this.age = 0;
+    }
+
+    public Particle resetBase(@NotNull World world, double x, double y, double z, double xa, double ya, double za) {
+        this.world = world;
+        this.x = x;
+        this.y = y;
+        this.z = z;
+        this.rCol = this.gCol = this.bCol = 1.0F;
+        ThreadLocalRandom tlr = ThreadLocalRandom.current();
+        this.xd = xa + (float) (tlr.nextDouble() * 2D - 1.0D) * 0.4F;
+        this.yd = ya + (float) (tlr.nextDouble() * 2D - 1.0D) * 0.4F;
+        this.zd = za + (float) (tlr.nextDouble() * 2D - 1.0D) * 0.4F;
+        float speed = (float) (tlr.nextDouble() + tlr.nextDouble() + 1.0D) * 0.15F;
         float dd = MathHelper.sqrt(this.xd * this.xd + this.yd * this.yd + this.zd * this.zd);
         this.xd = (this.xd / dd) * speed * 0.4D;
         this.yd = (this.yd / dd) * speed * 0.4D + 0.1D;
@@ -87,6 +108,11 @@ public class Particle {
         this.size = (random.nextFloat() * 0.5F + 0.5F) * 2.0F;
         this.lifetime = (int) (4F / (random.nextFloat() * 0.9F + 0.1F));
         this.age = 0;
+
+        this.removed = false;
+        this.noPhysics = false;
+        this.onGround = false;
+        return this;
     }
 
     public void init() {
@@ -119,169 +145,146 @@ public class Particle {
     }
 
     public void tick() {
-        this.cachedLightmapCoord = calcLightIndex(1f);
+        int cX = MathHelper.floor(this.x);
+        int cY = MathHelper.floor(this.y);
+        int cZ = MathHelper.floor(this.z);
+
+        if (cX != this.bX || cY != this.bY || cZ != this.bZ) {
+            this.bX = cX;
+            this.bY = cY;
+            this.bZ = cZ;
+            this.cachedLightmapCoord = calcLightIndex(1f);
+        }
+
         this.xo = this.x;
         this.yo = this.y;
         this.zo = this.z;
+
         if (this.age++ >= this.lifetime) {
             remove();
         }
+
         this.yd -= 0.04D * this.gravity;
+
         move(this.xd, this.yd, this.zd);
+
         this.xd *= 0.98D;
         this.yd *= 0.98D;
         this.zd *= 0.98D;
-        if (this.onGround) {
-            this.xd *= 0.7D;
-            this.zd *= 0.7D;
-        }
     }
 
-    public void move(double xd, double yd, double zd) {
+    public void move(double dx, double dy, double dz) {
         if (this.noPhysics) {
-            this.onGround = false;
-            this.x += xd;
-            this.y += yd;
-            this.z += zd;
+            this.x += dx;
+            this.y += dy;
+            this.z += dz;
             return;
         }
-        HitResult hitResult = raycastBlocks(this.x, this.y, this.z, xd, yd, zd);
-        if (hitResult instanceof HitResult.Tile hitTile) {
-            switch (hitTile.side) {
-                case EAST:
-                case WEST: {
-                    this.x = hitTile.location.x() + hitTile.side.offsetX() * 0.1d * this.size;
-                    this.y += yd;
-                    this.z += zd;
-                    this.xd = 0;
-                    break;
-                }
-                case TOP:
+
+        double buffer = 0.001D;
+
+        // Y-Axis
+        if (dy != 0) {
+            this.qPos.set(MathHelper.floor(this.x), MathHelper.floor(this.y + dy), MathHelper.floor(this.z));
+            AABBdc bb = getCollisionBox(this.qPos);
+
+            boolean collided = false;
+            if (bb != null && this.x >= bb.minX() && this.x <= bb.maxX() && this.z >= bb.minZ() && this.z <= bb.maxZ()) {
+                if (dy < 0 && this.y >= bb.maxY() && this.y + dy <= bb.maxY()) {
                     this.onGround = true;
-                case BOTTOM: {
-                    this.x += xd;
-                    this.y = hitTile.location.y() + hitTile.side.offsetY() * 0.1d * this.size;
-                    this.z += zd;
+                    this.y = bb.maxY() + buffer;
+                    this.xd = 0;
+                    this.zd = 0;
                     this.yd = 0;
-                    break;
+                    collided = true;
                 }
-                case SOUTH:
-                case NORTH: {
-                    this.x += xd;
-                    this.y += yd;
-                    this.z = hitTile.location.z() + hitTile.side.offsetZ() * 0.1d * this.size;
-                    this.zd = 0;
-                    break;
+                else if (dy > 0 && this.y <= bb.minY() && this.y + dy >= bb.minY()) {
+                    this.y = bb.minY() - buffer;
+                    this.yd = 0;
+                    collided = true;
                 }
             }
-        } else {
-            this.x += xd;
-            this.y += yd;
-            this.z += zd;
+
+            if (!collided) {
+                this.y += dy;
+                if (dy < 0) this.onGround = false;
+            }
         }
-    }
 
-    private final Vector2d __r = new Vector2d();
-    private final Rayd __ray = new Rayd();
-    private final List<AABBdc> aabbs = new ObjectArrayList<>();
-    private final AABBd refAABB = new AABBd();
-
-    public @Nullable HitResult raycastBlocks(final double ox, final double oy, final double oz, final double xd, final double yd, final double zd) {
-        this.__ray.oX = ox;
-        this.__ray.oY = oy;
-        this.__ray.oZ = oz;
-        double length = org.joml.Math.sqrt(xd * xd + yd * yd + zd * zd);
-        if (length <= 0) return null;
-        this.__ray.dX = (xd / length);
-        this.__ray.dY = (yd / length);
-        this.__ray.dZ = (zd / length);
-        Rayd ray = this.__ray;
-
-        final double maxT = length; // how many 1 block steps
-        final double dDistX = ray.dX == 0 ? Double.MAX_VALUE : org.joml.Math.abs(1 / ray.dX);
-        final double dDistY = ray.dY == 0 ? Double.MAX_VALUE : org.joml.Math.abs(1 / ray.dY);
-        final double dDistZ = ray.dZ == 0 ? Double.MAX_VALUE : org.joml.Math.abs(1 / ray.dZ);
-
-        final int signX = (int) org.joml.Math.signum(ray.dX);
-        final int signY = (int) org.joml.Math.signum(ray.dY);
-        final int signZ = (int) org.joml.Math.signum(ray.dZ);
-
-        TilePos qPos = new TilePos();
-        TilePos tilePos = new TilePos(ray.oX, ray.oY, ray.oZ);
-
-        double sideDistX = -(ray.oX - (tilePos.x + 0.5)) * (1 / ray.dX) + 0.5 * dDistX;
-        double sideDistY = -(ray.oY - (tilePos.y + 0.5)) * (1 / ray.dY) + 0.5 * dDistY;
-        double sideDistZ = -(ray.oZ - (tilePos.z + 0.5)) * (1 / ray.dZ) + 0.5 * dDistZ;
-
-        double dx = 0.0;
-        double dy = 0.0;
-        double dz = 0.0;
-
-        Direction direction = Direction.NONE;
-        while (dx * dx + dy * dy + dz * dz < length * length + 4) {
-            this.aabbs.clear();
-            for (int _x = -1; _x <= 1; _x++) {
-                for (int _y = -1; _y <= 1; _y++) {
-                    for (int _z = -1; _z <= 1; _z++) {
-                        Block<?> block = this.world.getBlockType(tilePos.add(_x, _y, _z, qPos));
-                        if (collidesWithBlock(block, this.world.getBlockData(qPos))) {
-                            block.getCollisionAABBs(this.world, qPos, this.refAABB.setMin(qPos.x, qPos.y, qPos.z).setMax(qPos.x + 1, qPos.y + 1, qPos.z + 1), this.aabbs);
-                        }
-                    }
+        // X-Axis
+        if (dx != 0) {
+            this.qPos.set(MathHelper.floor(this.x + dx), MathHelper.floor(this.y), MathHelper.floor(this.z));
+            AABBdc bb = getCollisionBox(this.qPos);
+
+            boolean collided = false;
+            if (bb != null && this.y >= bb.minY() && this.y <= bb.maxY() && this.z >= bb.minZ() && this.z <= bb.maxZ()) {
+                if (dx < 0 && this.x >= bb.maxX() && this.x + dx <= bb.maxX()) {
+                    this.x = bb.maxX() + buffer;
+                    this.xd *= -0.3D;
+                    collided = true;
+                } else if (dx > 0 && this.x <= bb.minX() && this.x + dx >= bb.minX()) {
+                    this.x = bb.minX() - buffer;
+                    this.xd *= -0.3D;
+                    collided = true;
                 }
             }
-            if (!this.aabbs.isEmpty()) {
-                double tMin = Double.MAX_VALUE;
-                for (int i = 0, aabbsSize = this.aabbs.size(); i < aabbsSize; i++) {
-                    if (this.aabbs.get(i).intersectsRay(ray, this.__r)) {
-                        tMin = org.joml.Math.min(this.__r.x, tMin);
-                    }
-                }
-                if (tMin < maxT) {
-                    return new HitResult.Tile(
-                        tilePos,
-                        direction.side(),
-                        new Vector3d(ray.oX + tMin * ray.dX, ray.oY + tMin * ray.dY, ray.oZ + tMin * ray.dZ));
+
+            if (!collided) {
+                this.x += dx;
+            }
+        }
+
+        // Z-Axis
+        if (dz != 0) {
+            this.qPos.set(MathHelper.floor(this.x), MathHelper.floor(this.y), MathHelper.floor(this.z + dz));
+            AABBdc bb = getCollisionBox(this.qPos);
+
+            boolean collided = false;
+            if (bb != null && this.x >= bb.minX() && this.x <= bb.maxX() && this.y >= bb.minY() && this.y <= bb.maxY()) {
+                if (dz < 0 && this.z >= bb.maxZ() && this.z + dz <= bb.maxZ()) {
+                    this.z = bb.maxZ() + buffer;
+                    this.zd *= -0.3D;
+                    collided = true;
+                } else if (dz > 0 && this.z <= bb.minZ() && this.z + dz >= bb.minZ()) {
+                    this.z = bb.minZ() - buffer;
+                    this.zd *= -0.3D;
+                    collided = true;
                 }
             }
 
-            if (Double.isFinite(sideDistX) && sideDistX < sideDistY && sideDistX < sideDistZ) {
-                tilePos.x += signX;
-                dx = tilePos.x - ray.oX;
-                sideDistX += dDistX;
-                direction = signX > 0 ? Direction.WEST : Direction.EAST;
-            } else if (Double.isFinite(sideDistY) && sideDistY < sideDistZ) {
-                tilePos.y += signY;
-                dy = tilePos.y - ray.oY;
-                sideDistY += dDistY;
-                direction = signY > 0 ? Direction.DOWN : Direction.UP;
-            } else if (Double.isFinite(sideDistZ)) {
-                tilePos.z += signZ;
-                dz = tilePos.z - ray.oZ;
-                sideDistZ += dDistZ;
-                direction = signZ > 0 ? Direction.NORTH : Direction.SOUTH;
-            } else {
-                return null;
+            if (!collided) {
+                this.z += dz;
             }
         }
-        return null;
     }
 
-    public boolean collidesWithBlock(@NotNull Block<?> block, int data) {
-        return !Block.hasLogicClass(block, BlockLogicMesh.class);
+    protected @Nullable AABBdc getCollisionBox(TilePos pos) {
+        if (this.world.isAirBlock(pos)) return null;
+
+        Block<?> blk = this.world.getBlockType(pos);
+        if (!blk.getMaterial().blocksMotion()) {
+            return null;
+        }
+
+        AABBdc bb = blk.getLogic().getCollisionAABB(this.world, pos);
+        if (bb != null && bb.isValid()) {
+            return bb;
+        }
+
+        return null;
     }
 
     /**
      * Renders the particle in the world.
      *
-     * @param t           The tessellator to use to draw the particle.
+     * @param tessellatorParticle The tessellator to use to draw the particle.
      * @param partialTick The time since the last tick.
      */
-    public void render(@NotNull TessellatorParticle t, float partialTick) {
+    public void render(@NotNull TessellatorParticle tessellatorParticle, float partialTick) {
         if (this.tex == null) return;
-        t.setLightmapCoord1i(getLightIndex(partialTick));
-        t.setColorOpaque3f(this.rCol, this.gCol, this.bCol);
-        t.addParticle(
+        tessellatorParticle.setLightmapCoord1i(getLightIndex(partialTick));
+        tessellatorParticle.setColorOpaque3f(this.rCol, this.gCol, this.bCol);
+        tessellatorParticle.addParticle(
             MathHelper.lerp(this.xo, this.x, partialTick),
             MathHelper.lerp(this.yo, this.y, partialTick),
             MathHelper.lerp(this.zo, this.z, partialTick),
@@ -298,16 +301,19 @@ public class Particle {
         return ParticleEngine.WORLD_TEXTURE;
     }
 
-    public float calcBrightness(float partialTick) {
+    public float calcBrightness(float pt) {
         if (Global.accessor.isFullbrightEnabled()) {
             return 1.0f;
         }
-        TilePos pos = new TilePos(
-            MathHelper.lerp(this.xo, this.x, partialTick),
-            MathHelper.lerp(this.yo, this.y, partialTick),
-            MathHelper.lerp(this.zo, this.z, partialTick));
-        if (this.world.areBlocksLoaded(pos, pos)) {
-            return this.world.getLightBrightness(pos);
+
+        this.qPos.set(
+            MathHelper.floor(MathHelper.lerp(this.xo, this.x, pt)),
+            MathHelper.floor(MathHelper.lerp(this.yo, this.y, pt)),
+            MathHelper.floor(MathHelper.lerp(this.zo, this.z, pt))
+        );
+
+        if (this.world.areBlocksLoaded(this.qPos, this.qPos)) {
+            return this.world.getLightBrightness(this.qPos);
         }
         return 0;
     }
@@ -316,27 +322,24 @@ public class Particle {
         return calcBrightness(partialTick);
     }
 
-    public byte calcLightIndex(float partialTick) {
+    public byte calcLightIndex(float pt) {
         if (Global.accessor.isFullbrightEnabled()) return LightIndexHelper.lightIndex2i(15, 15);
-        TilePos blockPos = new TilePos(
-            MathHelper.lerp(this.xo, this.x, partialTick),
-            MathHelper.lerp(this.yo, this.y, partialTick),
-            MathHelper.lerp(this.zo, this.z, partialTick));
-
-        byte lightIndex = 0;
-        if (this.world.getBlockLitInteriorSurface(blockPos)) {
-            TilePos queryPos = new TilePos();
-
-            lightIndex = LightIndexHelper.max(lightIndex, this.world.getSavedLightIndex(blockPos.up(queryPos)));
-            lightIndex = LightIndexHelper.max(lightIndex, this.world.getSavedLightIndex(blockPos.west(queryPos)));
-            lightIndex = LightIndexHelper.max(lightIndex, this.world.getSavedLightIndex(blockPos.east(queryPos)));
-            lightIndex = LightIndexHelper.max(lightIndex, this.world.getSavedLightIndex(blockPos.south(queryPos)));
-            lightIndex = LightIndexHelper.max(lightIndex, this.world.getSavedLightIndex(blockPos.north(queryPos)));
-        } else {
-            lightIndex = this.world.getSavedLightIndex(blockPos);
+
+        int bX = MathHelper.floor(MathHelper.lerp(this.xo, this.x, pt));
+        int bY = MathHelper.floor(MathHelper.lerp(this.yo, this.y, pt));
+        int bZ = MathHelper.floor(MathHelper.lerp(this.zo, this.z, pt));
+
+        if (this.world.getBlockLitInteriorSurface(this.qPos.set(bX, bY, bZ))) {
+            byte lIdx = 0;
+            lIdx = LightIndexHelper.max(lIdx, this.world.getSavedLightIndex(this.qPos.set(bX, bY + 1, bZ)));
+            lIdx = LightIndexHelper.max(lIdx, this.world.getSavedLightIndex(this.qPos.set(bX - 1, bY, bZ)));
+            lIdx = LightIndexHelper.max(lIdx, this.world.getSavedLightIndex(this.qPos.set(bX + 1, bY, bZ)));
+            lIdx = LightIndexHelper.max(lIdx, this.world.getSavedLightIndex(this.qPos.set(bX, bY, bZ + 1)));
+            lIdx = LightIndexHelper.max(lIdx, this.world.getSavedLightIndex(this.qPos.set(bX, bY, bZ - 1)));
+            return lIdx;
         }
 
-        return lightIndex;
+        return this.world.getSavedLightIndex(this.qPos.set(bX, bY, bZ));
     }
 
     public byte getLightIndex(float partialTick) {
diff --git a/game/client/src/main/java/net/minecraft/client/render/particle/ParticleAcidBoiling.java b/game/client/src/main/java/net/minecraft/client/render/particle/ParticleAcidBoiling.java
new file mode 100644
index 000000000..131c3a8a2
--- /dev/null
+++ b/game/client/src/main/java/net/minecraft/client/render/particle/ParticleAcidBoiling.java
@@ -0,0 +1,72 @@
+package net.minecraft.client.render.particle;
+
+import net.minecraft.client.render.texture.stitcher.IconCoordinate;
+import net.minecraft.client.render.texture.stitcher.TextureRegistry;
+import net.minecraft.core.block.material.Materials;
+import net.minecraft.core.util.helper.MathHelper;
+import net.minecraft.core.world.World;
+import net.minecraft.core.world.pos.TilePos;
+import org.jetbrains.annotations.NotNull;
+
+public class ParticleAcidBoiling extends Particle {
+
+	private float sinTimer = 0.0F;
+	private final boolean isReversed;
+	private final boolean needsWater;
+	private final TilePos qpos = new TilePos();
+	private final @NotNull IconCoordinate popping = TextureRegistry.getTexture("minecraft:particle/acid_boiling_pop");
+
+	public ParticleAcidBoiling(World world, double x, double y, double z, double xa, double ya, double za, boolean needsWater) {
+		super(world, x, y, z, xa, ya, za);
+		this.needsWater = needsWater;
+		this.rCol = 1.0F;
+		this.gCol = 1.0F;
+		this.bCol = 1.0F;
+		this.tex = TextureRegistry.getTexture("minecraft:particle/acid_boiling");
+		this.size *= random.nextFloat() * 0.7F + 0.3F;
+		this.xd = xa * 0.2 + (Math.random() * (double) 2.0F - (double) 1.0F) * (double) 0.02F;
+		this.yd = ya * 0.2 + (Math.random() * (double) 2.0F - (double) 1.0F) * (double) 0.02F;
+		this.zd = za * 0.2 + (Math.random() * (double) 2.0F - (double) 1.0F) * (double) 0.02F;
+		this.lifetime = (int) ((double) 16.0F / (Math.random() * 0.4 + 0.1));
+		this.isReversed = random.nextInt(2) == 0;
+	}
+
+	@Override
+	public void tick() {
+		++this.sinTimer;
+		if (this.sinTimer > 100.0F) {
+			this.sinTimer = 0.0F;
+		}
+
+		this.cachedLightmapCoord = this.calcLightIndex(1.0F);
+		this.xo = this.x;
+		this.yo = this.y;
+		this.zo = this.z;
+		double expand = 0.05;
+		boolean inWater = this.world.getBlockMaterial(this.qpos.set((int) this.x, (int) this.y, (int) this.z)) == Materials.ACID;
+		if (inWater) {
+			this.yd = 0.2;
+			this.xd = MathHelper.sin(this.sinTimer * 0.95F) / 30.0F * (float) (this.isReversed ? -1 : 1);
+			this.zd = MathHelper.cos(this.sinTimer * 0.95F) / 30.0F * (float) (this.isReversed ? -1 : 1);
+		} else {
+			this.yd = MathHelper.clamp(this.yd, -expand, expand);
+		}
+
+		this.move(this.xd, this.yd, this.zd);
+		this.xd *= 0.85;
+		this.yd *= 0.85;
+		this.zd *= 0.85;
+		if (this.needsWater && !inWater) {
+			this.lifetime = MathHelper.clamp(this.lifetime, 0, 8);
+		}
+
+		if (this.lifetime == 6) {
+			this.tex = this.popping;
+			this.world.spawnParticle("arrowtrail", this.x, this.y, this.z, 0.0F, 0.0F, 0.0F, 1, false);
+		} else if (this.lifetime <= 0) {
+			this.remove();
+		}
+
+		--this.lifetime;
+	}
+}
diff --git a/game/client/src/main/java/net/minecraft/client/render/particle/ParticleArrowGolden.java b/game/client/src/main/java/net/minecraft/client/render/particle/ParticleArrowGolden.java
index 7c01c1ae3..dc1c3086e 100644
--- a/game/client/src/main/java/net/minecraft/client/render/particle/ParticleArrowGolden.java
+++ b/game/client/src/main/java/net/minecraft/client/render/particle/ParticleArrowGolden.java
@@ -31,7 +31,7 @@ public class ParticleArrowGolden extends Particle {
     }
 
     @Override
-    public void render(@NotNull TessellatorParticle t, float partialTick) {
+    public void render(@NotNull TessellatorParticle tessellatorParticle, float partialTick) {
         float l = ((this.age + partialTick) / this.lifetime) * 32F;
         if (l < 0.0F) {
             l = 0.0F;
@@ -40,7 +40,7 @@ public class ParticleArrowGolden extends Particle {
             l = 1.0F;
         }
         this.size = this.oSize * l;
-        super.render(t, partialTick);
+        super.render(tessellatorParticle, partialTick);
     }
 
     @Override
diff --git a/game/client/src/main/java/net/minecraft/client/render/particle/ParticleBlockBreaking.java b/game/client/src/main/java/net/minecraft/client/render/particle/ParticleBlockBreaking.java
index e03155d13..7c2fc09f4 100644
--- a/game/client/src/main/java/net/minecraft/client/render/particle/ParticleBlockBreaking.java
+++ b/game/client/src/main/java/net/minecraft/client/render/particle/ParticleBlockBreaking.java
@@ -15,18 +15,24 @@ import net.minecraft.core.world.pos.TilePosc;
 import org.jetbrains.annotations.NotNull;
 
 public class ParticleBlockBreaking extends Particle {
-    private final Block<?> block;
-    private final Side side;
+    private Block<?> block;
+    private Side side;
     private final @NotNull TilePos initPos = new TilePos(Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE);
 
     public ParticleBlockBreaking(World world, double x, double y, double z, double xa, double ya, double za, Block<?> block, Side side, int meta) {
         super(world, x, y, z, xa, ya, za);
+        this.reset(world, x, y, z, xa, ya, za, block, side, meta);
+    }
+
+    public ParticleBlockBreaking reset(World world, double x, double y, double z, double xa, double ya, double za, Block<?> block, Side side, int meta) {
+        super.resetBase(world, x, y, z, xa, ya, za);
         this.side = side;
         this.block = block;
         this.tex = BlockModelDispatcher.getInstance().getDispatch(block).getParticleTexture(this.side, meta);
         this.gravity = block.blockParticleGravity;
         this.rCol = this.gCol = this.bCol = 0.6F;
         this.size /= 2.0F;
+        return this;
     }
 
     public ParticleBlockBreaking init(@NotNull TilePosc tilePos) {
@@ -44,11 +50,11 @@ public class ParticleBlockBreaking extends Particle {
     }
 
     @Override
-    public void render(@NotNull TessellatorParticle t, float partialTick) {
+    public void render(@NotNull TessellatorParticle tessellatorParticle, float partialTick) {
         if (this.tex == null) return;
-        t.setLightmapCoord1i(getLightIndex(partialTick));
-        t.setColorOpaque3f(this.rCol, this.gCol, this.bCol);
-        t.addParticle(
+        tessellatorParticle.setLightmapCoord1i(getLightIndex(partialTick));
+        tessellatorParticle.setColorOpaque3f(this.rCol, this.gCol, this.bCol);
+        tessellatorParticle.addParticle(
             MathHelper.lerp(this.xo, this.x, partialTick),
             MathHelper.lerp(this.yo, this.y, partialTick),
             MathHelper.lerp(this.zo, this.z, partialTick), this.size * 0.1f,
@@ -78,4 +84,4 @@ public class ParticleBlockBreaking extends Particle {
 
         return lightIndex;
     }
-}
+}
\ No newline at end of file
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 d1a1dbea5..f30ce7554 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
@@ -225,13 +225,13 @@ public final class ParticleDispatcher extends Dispatcher<String, 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/water_flowing"));
+                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/lava_flowing")).setFullBright();
+                return new ParticleFluidDrip(world, x, y, z, 0xFFFFFF, TextureRegistry.getTexture("minecraft:block/fluid/lava/flowing")).setFullBright();
             }
         });
         addDispatch("ashmote", new ParticleEntry() {
@@ -282,5 +282,12 @@ public final class ParticleDispatcher extends Dispatcher<String, ParticleEntry>
                 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);
+            }
+        });
     }
 }
diff --git a/game/client/src/main/java/net/minecraft/client/render/particle/ParticleFirefly.java b/game/client/src/main/java/net/minecraft/client/render/particle/ParticleFirefly.java
index aef5fb994..53bcb7bef 100644
--- a/game/client/src/main/java/net/minecraft/client/render/particle/ParticleFirefly.java
+++ b/game/client/src/main/java/net/minecraft/client/render/particle/ParticleFirefly.java
@@ -83,7 +83,7 @@ public class ParticleFirefly extends Particle {
     }
 
     @Override
-    public void render(@NotNull TessellatorParticle t, float partialTick) {
+    public void render(@NotNull TessellatorParticle tessellatorParticle, float partialTick) {
         float ibr = getInternalBrightness(partialTick);
         if (ibr < 0.5f) {
             this.rCol = MathHelper.lerp(this.minR, this.midR, ibr * 2);
@@ -99,7 +99,7 @@ public class ParticleFirefly extends Particle {
         this.gCol += this.oG;
         this.bCol += this.oB;
 
-        super.render(t, partialTick);
+        super.render(tessellatorParticle, partialTick);
     }
 
     public void renderParticleInGui(TessellatorGeneral t, float partialTick) {
diff --git a/game/client/src/main/java/net/minecraft/client/render/particle/ParticleFlame.java b/game/client/src/main/java/net/minecraft/client/render/particle/ParticleFlame.java
index a2fc5df57..a95fe7539 100644
--- a/game/client/src/main/java/net/minecraft/client/render/particle/ParticleFlame.java
+++ b/game/client/src/main/java/net/minecraft/client/render/particle/ParticleFlame.java
@@ -28,10 +28,10 @@ public class ParticleFlame extends Particle {
     }
 
     @Override
-    public void render(@NotNull TessellatorParticle t, float partialTick) {
+    public void render(@NotNull TessellatorParticle tessellatorParticle, float partialTick) {
         float s = ((float) this.age + partialTick) / (float) this.lifetime;
         this.size = this.oSize * (1.0F - s * s * 0.5F);
-        super.render(t, partialTick);
+        super.render(tessellatorParticle, partialTick);
     }
 
     @Override
diff --git a/game/client/src/main/java/net/minecraft/client/render/particle/ParticleFluidDrip.java b/game/client/src/main/java/net/minecraft/client/render/particle/ParticleFluidDrip.java
index a319b1db4..b5b6b997b 100644
--- a/game/client/src/main/java/net/minecraft/client/render/particle/ParticleFluidDrip.java
+++ b/game/client/src/main/java/net/minecraft/client/render/particle/ParticleFluidDrip.java
@@ -59,11 +59,11 @@ public class ParticleFluidDrip extends Particle {
     }
 
     @Override
-    public void render(@NotNull TessellatorParticle t, float partialTick) {
+    public void render(@NotNull TessellatorParticle tessellatorParticle, float partialTick) {
         if (this.tex == null) return;
-        t.setLightmapCoord1i(getLightIndex(partialTick));
-        t.setColorOpaque3f(this.rCol, this.gCol, this.bCol);
-        t.addParticle(
+        tessellatorParticle.setLightmapCoord1i(getLightIndex(partialTick));
+        tessellatorParticle.setColorOpaque3f(this.rCol, this.gCol, this.bCol);
+        tessellatorParticle.addParticle(
             MathHelper.lerp(this.xo, this.x, partialTick),
             MathHelper.lerp(this.yo, this.y, partialTick),
             MathHelper.lerp(this.zo, this.z, partialTick), this.size * 0.1f,
diff --git a/game/client/src/main/java/net/minecraft/client/render/particle/ParticleHeart.java b/game/client/src/main/java/net/minecraft/client/render/particle/ParticleHeart.java
index 3e3255b48..b7af7e7af 100644
--- a/game/client/src/main/java/net/minecraft/client/render/particle/ParticleHeart.java
+++ b/game/client/src/main/java/net/minecraft/client/render/particle/ParticleHeart.java
@@ -27,7 +27,7 @@ public class ParticleHeart extends Particle {
     }
 
     @Override
-    public void render(@NotNull TessellatorParticle t, float partialTick) {
+    public void render(@NotNull TessellatorParticle tessellatorParticle, float partialTick) {
         float l = ((this.age + partialTick) / this.lifetime) * 32F;
         if (l < 0.0F) {
             l = 0.0F;
@@ -36,7 +36,7 @@ public class ParticleHeart extends Particle {
             l = 1.0F;
         }
         this.size = this.oSize * l;
-        super.render(t, partialTick);
+        super.render(tessellatorParticle, partialTick);
     }
 
     @Override
diff --git a/game/client/src/main/java/net/minecraft/client/render/particle/ParticleItemBreaking.java b/game/client/src/main/java/net/minecraft/client/render/particle/ParticleItemBreaking.java
index 7125f5740..53fa14750 100644
--- a/game/client/src/main/java/net/minecraft/client/render/particle/ParticleItemBreaking.java
+++ b/game/client/src/main/java/net/minecraft/client/render/particle/ParticleItemBreaking.java
@@ -18,11 +18,11 @@ public class ParticleItemBreaking extends Particle {
     }
 
     @Override
-    public void render(@NotNull TessellatorParticle t, float partialTick) {
+    public void render(@NotNull TessellatorParticle tessellatorParticle, float partialTick) {
         if (this.tex == null) return;
-        t.setLightmapCoord1i(getLightIndex(partialTick));
-        t.setColorOpaque3f(this.rCol, this.gCol, this.bCol);
-        t.addParticle(MathHelper.lerp(this.xo, this.x, partialTick),
+        tessellatorParticle.setLightmapCoord1i(getLightIndex(partialTick));
+        tessellatorParticle.setColorOpaque3f(this.rCol, this.gCol, this.bCol);
+        tessellatorParticle.addParticle(MathHelper.lerp(this.xo, this.x, partialTick),
             MathHelper.lerp(this.yo, this.y, partialTick),
             MathHelper.lerp(this.zo, this.z, partialTick), this.size * 0.1f,
             this.tex.getIconUMin() + (this.tex.getIconUSize() * this.uo / 4F), this.tex.getIconVMin() + (this.tex.getIconVSize() * this.uo / 4F), this.tex.getIconUSize() * 0.25f, this.tex.getIconVSize() * 0.25f);
diff --git a/game/client/src/main/java/net/minecraft/client/render/particle/ParticleLava.java b/game/client/src/main/java/net/minecraft/client/render/particle/ParticleLava.java
index 79c11ed13..cda09824a 100644
--- a/game/client/src/main/java/net/minecraft/client/render/particle/ParticleLava.java
+++ b/game/client/src/main/java/net/minecraft/client/render/particle/ParticleLava.java
@@ -28,10 +28,10 @@ public class ParticleLava extends Particle {
     }
 
     @Override
-    public void render(@NotNull TessellatorParticle t, float partialTick) {
+    public void render(@NotNull TessellatorParticle tessellatorParticle, float partialTick) {
         float l = (this.age + partialTick) / this.lifetime;
         this.size = this.oSize * (1.0F - l * l);
-        super.render(t, partialTick);
+        super.render(tessellatorParticle, partialTick);
     }
 
     @Override
diff --git a/game/client/src/main/java/net/minecraft/client/render/particle/ParticleLeaf.java b/game/client/src/main/java/net/minecraft/client/render/particle/ParticleLeaf.java
index 7a768d0f6..c06799116 100644
--- a/game/client/src/main/java/net/minecraft/client/render/particle/ParticleLeaf.java
+++ b/game/client/src/main/java/net/minecraft/client/render/particle/ParticleLeaf.java
@@ -9,6 +9,8 @@ import net.minecraft.core.util.helper.MathHelper;
 import net.minecraft.core.world.World;
 import net.minecraft.core.world.pos.TilePos;
 import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.joml.primitives.AABBdc;
 
 public class ParticleLeaf extends Particle {
     public ParticleLeaf(World world, double x, double y, double z, double xa, double ya, double za) {
@@ -23,8 +25,14 @@ public class ParticleLeaf extends Particle {
     }
 
     @Override
-    public boolean collidesWithBlock(@NotNull Block<?> block, int data) {
-        return !Block.hasLogicClass(block, BlockLogicLeavesBase.class);
+    protected @Nullable AABBdc getCollisionBox(TilePos pos) {
+        Block<?> block = this.world.getBlockType(pos);
+
+        if (Block.hasLogicClass(block, BlockLogicLeavesBase.class)) {
+            return null;
+        }
+
+        return super.getCollisionBox(pos);
     }
 
     public ParticleLeaf init(@NotNull TilePos tilePos) {
@@ -52,26 +60,40 @@ public class ParticleLeaf extends Particle {
         this.xo = this.x;
         this.yo = this.y;
         this.zo = this.z;
+
         if (this.age++ >= this.lifetime) {
             this.remove();
         }
+
         this.yd -= 0.04D * this.gravity;
+
         if (this.onGround) {
             this.xd *= 0.0D;
             this.zd *= 0.0D;
         }
+
         this.move(this.xd, this.yd, this.zd);
+
+        this.xd *= 0.98D;
+        this.yd *= 0.98D;
+        this.zd *= 0.98D;
     }
 
     @Override
-    public void render(@NotNull TessellatorParticle t, float partialTick) {
+    public void render(@NotNull TessellatorParticle tessellatorParticle, float partialTick) {
         if (this.tex == null) return;
-        t.setLightmapCoord1i(getLightIndex(partialTick));
-        t.setColorOpaque3f(this.rCol, this.gCol, this.bCol);
-        t.addParticle(
+        tessellatorParticle.setLightmapCoord1i(getLightIndex(partialTick));
+        tessellatorParticle.setColorOpaque3f(this.rCol, this.gCol, this.bCol);
+
+        tessellatorParticle.addParticle(
             MathHelper.lerp(this.xo, this.x, partialTick),
             MathHelper.lerp(this.yo, this.y, partialTick),
-            MathHelper.lerp(this.zo, this.z, partialTick), this.size * 0.1f,
-            this.tex.getIconUMin() + (this.tex.getIconUSize() * this.uo / 4F), this.tex.getIconVMin() + (this.tex.getIconVSize() * this.uo / 4F), this.tex.getIconUSize() * 0.25f, this.tex.getIconVSize() * 0.25f);
+            MathHelper.lerp(this.zo, this.z, partialTick),
+            this.size * 0.1f,
+            this.tex.getIconUMin() + (this.tex.getIconUSize() * this.uo / 4F),
+            this.tex.getIconVMin() + (this.tex.getIconVSize() * this.vo / 4F),
+            this.tex.getIconUSize() * 0.25f,
+            this.tex.getIconVSize() * 0.25f
+        );
     }
 }
diff --git a/game/client/src/main/java/net/minecraft/client/render/particle/ParticleNote.java b/game/client/src/main/java/net/minecraft/client/render/particle/ParticleNote.java
index 15338e4c7..3f44568c7 100644
--- a/game/client/src/main/java/net/minecraft/client/render/particle/ParticleNote.java
+++ b/game/client/src/main/java/net/minecraft/client/render/particle/ParticleNote.java
@@ -32,7 +32,7 @@ public class ParticleNote extends Particle {
     }
 
     @Override
-    public void render(@NotNull TessellatorParticle t, float partialTick) {
+    public void render(@NotNull TessellatorParticle tessellatorParticle, float partialTick) {
         float l = ((this.age + partialTick) / this.lifetime) * 32F;
         if (l < 0.0F) {
             l = 0.0F;
@@ -41,7 +41,7 @@ public class ParticleNote extends Particle {
             l = 1.0F;
         }
         this.size = this.oSize * l;
-        super.render(t, partialTick);
+        super.render(tessellatorParticle, partialTick);
     }
 
     @Override
diff --git a/game/client/src/main/java/net/minecraft/client/render/particle/ParticlePortal.java b/game/client/src/main/java/net/minecraft/client/render/particle/ParticlePortal.java
index eaaa968d6..f063bd052 100644
--- a/game/client/src/main/java/net/minecraft/client/render/particle/ParticlePortal.java
+++ b/game/client/src/main/java/net/minecraft/client/render/particle/ParticlePortal.java
@@ -34,13 +34,13 @@ public class ParticlePortal extends Particle {
     }
 
     @Override
-    public void render(@NotNull TessellatorParticle t, float partialTick) {
+    public void render(@NotNull TessellatorParticle tessellatorParticle, float partialTick) {
         float s = (this.age + partialTick) / this.lifetime;
         s = 1.0F - s;
         s *= s;
         s = 1.0F - s;
         this.size = this.oSize * s;
-        super.render(t, partialTick);
+        super.render(tessellatorParticle, partialTick);
     }
 
     @Override
diff --git a/game/client/src/main/java/net/minecraft/client/render/particle/ParticlePuffRGB.java b/game/client/src/main/java/net/minecraft/client/render/particle/ParticlePuffRGB.java
index a0adf6a45..aed803848 100644
--- a/game/client/src/main/java/net/minecraft/client/render/particle/ParticlePuffRGB.java
+++ b/game/client/src/main/java/net/minecraft/client/render/particle/ParticlePuffRGB.java
@@ -38,7 +38,7 @@ public class ParticlePuffRGB extends Particle {
     }
 
     @Override
-    public void render(@NotNull TessellatorParticle t, float partialTick) {
+    public void render(@NotNull TessellatorParticle tessellatorParticle, float partialTick) {
         float l = ((this.age + partialTick) / this.lifetime) * 32F;
         if (l < 0.0F) {
             l = 0.0F;
@@ -57,9 +57,9 @@ public class ParticlePuffRGB extends Particle {
         } else {
             lmc = getLightIndex(partialTick);
         }
-        t.setLightmapCoord1i(lmc);
-        t.setColorOpaque3f(this.rCol, this.gCol, this.bCol);
-        t.addParticle(
+        tessellatorParticle.setLightmapCoord1i(lmc);
+        tessellatorParticle.setColorOpaque3f(this.rCol, this.gCol, this.bCol);
+        tessellatorParticle.addParticle(
             MathHelper.lerp(this.xo, this.x, partialTick),
             MathHelper.lerp(this.yo, this.y, partialTick),
             MathHelper.lerp(this.zo, this.z, partialTick), this.size * 0.1f,
diff --git a/game/client/src/main/java/net/minecraft/client/render/particle/ParticleRedstoneDust.java b/game/client/src/main/java/net/minecraft/client/render/particle/ParticleRedstoneDust.java
index d3f35182f..2ba5837e9 100644
--- a/game/client/src/main/java/net/minecraft/client/render/particle/ParticleRedstoneDust.java
+++ b/game/client/src/main/java/net/minecraft/client/render/particle/ParticleRedstoneDust.java
@@ -47,7 +47,7 @@ public class ParticleRedstoneDust extends Particle {
     }
 
     @Override
-    public void render(@NotNull TessellatorParticle t, float partialTick) {
+    public void render(@NotNull TessellatorParticle tessellatorParticle, float partialTick) {
         if (this.tex == null) return;
         float l = ((this.age + partialTick) / this.lifetime) * 32F;
         if (l < 0.0F) {
@@ -66,9 +66,9 @@ public class ParticleRedstoneDust extends Particle {
         } else {
             lmc = getLightIndex(partialTick);
         }
-        t.setLightmapCoord1i(lmc);
-        t.setColorOpaque3f(this.rCol, this.gCol, this.bCol);
-        t.addParticle(
+        tessellatorParticle.setLightmapCoord1i(lmc);
+        tessellatorParticle.setColorOpaque3f(this.rCol, this.gCol, this.bCol);
+        tessellatorParticle.addParticle(
             MathHelper.lerp(this.xo, this.x, partialTick),
             MathHelper.lerp(this.yo, this.y, partialTick),
             MathHelper.lerp(this.zo, this.z, partialTick),
diff --git a/game/client/src/main/java/net/minecraft/client/render/particle/ParticleSlimeChunk.java b/game/client/src/main/java/net/minecraft/client/render/particle/ParticleSlimeChunk.java
index d83182881..9ae87306d 100644
--- a/game/client/src/main/java/net/minecraft/client/render/particle/ParticleSlimeChunk.java
+++ b/game/client/src/main/java/net/minecraft/client/render/particle/ParticleSlimeChunk.java
@@ -18,12 +18,12 @@ public class ParticleSlimeChunk extends Particle {
     }
 
     @Override
-    public void render(@NotNull TessellatorParticle t, float partialTick) {
+    public void render(@NotNull TessellatorParticle tessellatorParticle, float partialTick) {
         if (this.tex == null) return;
         float br = 1.0f;
-        t.setLightmapCoord1i(getLightIndex(partialTick));
-        t.setColorOpaque3f(this.rCol * br, this.gCol * br, this.bCol * br);
-        t.addParticle(MathHelper.lerp(this.xo, this.x, partialTick),
+        tessellatorParticle.setLightmapCoord1i(getLightIndex(partialTick));
+        tessellatorParticle.setColorOpaque3f(this.rCol * br, this.gCol * br, this.bCol * br);
+        tessellatorParticle.addParticle(MathHelper.lerp(this.xo, this.x, partialTick),
             MathHelper.lerp(this.yo, this.y, partialTick),
             MathHelper.lerp(this.zo, this.z, partialTick),
             this.size * 0.1f,
diff --git a/game/client/src/main/java/net/minecraft/client/render/particle/ParticleSmoke.java b/game/client/src/main/java/net/minecraft/client/render/particle/ParticleSmoke.java
index 1c15b7412..0e0ab3ff5 100644
--- a/game/client/src/main/java/net/minecraft/client/render/particle/ParticleSmoke.java
+++ b/game/client/src/main/java/net/minecraft/client/render/particle/ParticleSmoke.java
@@ -30,7 +30,7 @@ public class ParticleSmoke extends Particle {
     }
 
     @Override
-    public void render(@NotNull TessellatorParticle t, float partialTick) {
+    public void render(@NotNull TessellatorParticle tessellatorParticle, float partialTick) {
         float l = ((this.age + partialTick) / this.lifetime) * 32F;
         if (l < 0.0F) {
             l = 0.0F;
@@ -39,7 +39,7 @@ public class ParticleSmoke extends Particle {
             l = 1.0F;
         }
         this.size = this.oSize * l;
-        super.render(t, partialTick);
+        super.render(tessellatorParticle, partialTick);
     }
 
     @Override
diff --git a/game/client/src/main/java/net/minecraft/client/render/particle/ParticleSnowShovel.java b/game/client/src/main/java/net/minecraft/client/render/particle/ParticleSnowShovel.java
index e89fe76f4..20a4154d8 100644
--- a/game/client/src/main/java/net/minecraft/client/render/particle/ParticleSnowShovel.java
+++ b/game/client/src/main/java/net/minecraft/client/render/particle/ParticleSnowShovel.java
@@ -31,7 +31,7 @@ public class ParticleSnowShovel extends Particle {
     }
 
     @Override
-    public void render(@NotNull TessellatorParticle t, float partialTick) {
+    public void render(@NotNull TessellatorParticle tessellatorParticle, float partialTick) {
         float l = ((this.age + partialTick) / this.lifetime) * 32F;
         if(l < 0.0F) {
             l = 0.0F;
@@ -40,7 +40,7 @@ public class ParticleSnowShovel extends Particle {
             l = 1.0F;
         }
         this.size = this.oSize * l;
-        super.render(t, partialTick);
+        super.render(tessellatorParticle, partialTick);
     }
 
     @Override
diff --git a/game/client/src/main/java/net/minecraft/client/render/particle/ParticleVentSmoke.java b/game/client/src/main/java/net/minecraft/client/render/particle/ParticleVentSmoke.java
index 05518fc88..5d0b75f4d 100644
--- a/game/client/src/main/java/net/minecraft/client/render/particle/ParticleVentSmoke.java
+++ b/game/client/src/main/java/net/minecraft/client/render/particle/ParticleVentSmoke.java
@@ -30,7 +30,7 @@ public class ParticleVentSmoke extends Particle {
     }
 
     @Override
-    public void render(@NotNull TessellatorParticle t, float partialTick) {
+    public void render(@NotNull TessellatorParticle tessellatorParticle, float partialTick) {
         float size = ((this.age + partialTick) / this.lifetime) * 32F;
         if (size < 0.0F) {
             size = 0.0F;
@@ -39,7 +39,7 @@ public class ParticleVentSmoke extends Particle {
             size = 1.0F;
         }
         this.size = this.oSize * size;
-        super.render(t, partialTick);
+        super.render(tessellatorParticle, partialTick);
     }
 
     @Override
diff --git a/game/client/src/main/java/net/minecraft/client/render/renderer/GLRenderer.java b/game/client/src/main/java/net/minecraft/client/render/renderer/GLRenderer.java
index 78e748be1..0cbf0a55f 100644
--- a/game/client/src/main/java/net/minecraft/client/render/renderer/GLRenderer.java
+++ b/game/client/src/main/java/net/minecraft/client/render/renderer/GLRenderer.java
@@ -49,6 +49,10 @@ public class GLRenderer {
         LIGHT_ENABLED = flag;
     }
 
+    public static boolean globalGetLightEnabled() {
+        return LIGHT_ENABLED;
+    }
+
     public static @NotNull Vector4f globalGetLightModelAmbient() {
         return LIGHT_MODEL_AMBIENT;
     }
diff --git a/game/client/src/main/java/net/minecraft/client/render/renderer/Shaders.java b/game/client/src/main/java/net/minecraft/client/render/renderer/Shaders.java
index e7f023344..9f17936e6 100644
--- a/game/client/src/main/java/net/minecraft/client/render/renderer/Shaders.java
+++ b/game/client/src/main/java/net/minecraft/client/render/renderer/Shaders.java
@@ -29,10 +29,11 @@ public final class Shaders {
     public static final @NotNull Shader ITEM = register("item", new Shader());
     public static final @NotNull Shader INTERFACE = register("interface", new Shader());
     public static final @NotNull Shader COLOR = register("color", new Shader());
+    public static final @NotNull Shader ACID_OVERLAY = register("acid_overlay", new Shader());
     public static final @NotNull Shader COLOR_WORLD = register("color_world", new Shader());
     public static final @NotNull Shader CLOUDS = register("clouds", new Shader());
-    public static final @NotNull ShaderGeometry PARTICLE = register("particle", new ShaderGeometry());
-    public static final @NotNull ShaderGeometry FONT = register("font", new ShaderGeometry());
+    public static final @NotNull Shader PARTICLE = register("particle", new Shader());
+    public static final @NotNull Shader FONT = register("font", new Shader());
     public static final @NotNull ShaderGeometry LINES = register("lines", new ShaderGeometry());
     public static final @NotNull ShaderGeometry LOD = register("lod", new ShaderGeometry());
 
diff --git a/game/client/src/main/java/net/minecraft/client/render/terrain/ChunkRendererMultiDraw.java b/game/client/src/main/java/net/minecraft/client/render/terrain/ChunkRendererMultiDraw.java
index 7b02ffd9b..a49744192 100644
--- a/game/client/src/main/java/net/minecraft/client/render/terrain/ChunkRendererMultiDraw.java
+++ b/game/client/src/main/java/net/minecraft/client/render/terrain/ChunkRendererMultiDraw.java
@@ -2,7 +2,6 @@ package net.minecraft.client.render.terrain;
 
 import java.util.HashSet;
 import java.util.List;
-import java.util.Objects;
 import java.util.Set;
 
 import net.minecraft.client.render.TileEntityRenderDispatcher;
@@ -10,7 +9,6 @@ import net.minecraft.client.render.block.model.BlockModel;
 import net.minecraft.client.render.block.model.BlockModelDispatcher;
 import net.minecraft.client.render.culling.CameraFrustum;
 import net.minecraft.client.render.terrain.VertexBuffer.Entry;
-import net.minecraft.client.render.tessellator.TessellatorGeneral;
 import net.minecraft.client.render.tessellator.TessellatorTerrainImpl;
 import net.minecraft.core.block.Block;
 import net.minecraft.core.block.entity.TileEntity;
@@ -18,6 +16,7 @@ import net.minecraft.core.world.World;
 import net.minecraft.core.world.WorldSource;
 import net.minecraft.core.world.chunk.Chunk;
 import net.minecraft.core.world.chunk.ChunkCache;
+import net.minecraft.core.world.particle.ParticleEmitterRange;
 import net.minecraft.core.world.pos.TilePos;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
@@ -83,6 +82,15 @@ public class ChunkRendererMultiDraw extends ChunkRenderer {
                 for (pos.z = this.min.z; pos.z < this.max.z; pos.z++) {
                     for (pos.x = this.min.x; pos.x < this.max.x; pos.x++) {
                         final @NotNull Block<?> block = chunkCache.getBlockType(pos);
+
+                        if (renderPass == 0) {
+                            ParticleEmitterRange type = block.getLogic().getParticleEmitterRange();
+
+                            if (type != null) {
+                                this.getWorld().particleEmitters.add(new TilePos(pos), type);
+                            }
+                        }
+
                         if (!hasStartedDrawing) {
                             hasStartedDrawing = true;
                             TESSELLATOR.startDrawingQuads();
diff --git a/game/client/src/main/java/net/minecraft/client/render/tessellator/TessellatorFont.java b/game/client/src/main/java/net/minecraft/client/render/tessellator/TessellatorFont.java
index f2ca1c8a2..a5a6a24dc 100644
--- a/game/client/src/main/java/net/minecraft/client/render/tessellator/TessellatorFont.java
+++ b/game/client/src/main/java/net/minecraft/client/render/tessellator/TessellatorFont.java
@@ -25,6 +25,7 @@ public class TessellatorFont {
 
     private final int vao;
     private final int vbo;
+    private final int vboMesh;
 
     private final @NotNull String name;
     private ByteBuffer data;
@@ -37,24 +38,39 @@ public class TessellatorFont {
 
         this.vao = GL41.glGenVertexArrays();
         this.vbo = GL41.glGenBuffers();
+        this.vboMesh = GL41.glGenBuffers();
 
         GL41.glBindVertexArray(this.vao);
+
+        GL41.glBindBuffer(GL41.GL_ARRAY_BUFFER, this.vboMesh);
+        float[] mesh = {
+            0,  1,
+            1,  1,
+            0,  0,
+            1,  0,
+        };
+        GL41.glBufferData(GL41.GL_ARRAY_BUFFER, mesh, GL41.GL_STATIC_DRAW);
+        GL41.glVertexAttribPointer(0, 2, GL41.GL_FLOAT, false, 0, 0);
+
         GL41.glBindBuffer(GL41.GL_ARRAY_BUFFER, this.vbo);
 
-        int vertSize = 2 * Float.BYTES + 9;
-        int offset = 0;
-        GL41.glVertexAttribPointer(0, 2, GL41.GL_FLOAT, false, vertSize, offset); offset += Float.BYTES * 2; // Pos XY
-        GL41.glVertexAttribPointer(1, 2, GL41.GL_UNSIGNED_BYTE, false, vertSize, offset); offset += 2; // Width Height
-        GL41.glVertexAttribPointer(2, 4, GL41.GL_UNSIGNED_BYTE, true, vertSize, offset); offset += 4; // Color
-        GL41.glVertexAttribPointer(3, 2, GL41.GL_UNSIGNED_BYTE, false, vertSize, offset); offset += 2; // UV
-        GL41.glVertexAttribPointer(4, 1, GL41.GL_UNSIGNED_BYTE, false, vertSize, offset); offset += 1; // Config
-        GL41.glVertexAttrib1f(5, LightIndexHelper.lightIndex2i(15, 15));
+        setAttribOffset(0);
+        GL41.glVertexAttrib1f(6, LightIndexHelper.lightIndex2i(15, 15));
 
         GL41.glEnableVertexAttribArray(0);
         GL41.glEnableVertexAttribArray(1);
         GL41.glEnableVertexAttribArray(2);
         GL41.glEnableVertexAttribArray(3);
         GL41.glEnableVertexAttribArray(4);
+        GL41.glEnableVertexAttribArray(5);
+
+        GL41.glVertexAttribDivisor(0, 0); // Vertices, always reuse the same 4 vertices
+        GL41.glVertexAttribDivisor(1, 1); // Data, one per quad
+        GL41.glVertexAttribDivisor(2, 1); // Data, one per quad
+        GL41.glVertexAttribDivisor(3, 1); // Data, one per quad
+        GL41.glVertexAttribDivisor(4, 1); // Data, one per quad
+        GL41.glVertexAttribDivisor(5, 1); // Data, one per quad
+
         GL41.glBindVertexArray(0);
     }
 
@@ -122,12 +138,14 @@ public class TessellatorFont {
             int start = this.renderRanges.getInt(i + 1);
             int count = this.renderRanges.getInt(i + 2);
             GL41.glBindTexture(GL41.GL_TEXTURE_2D, texture);
-            GL41.glDrawArrays(DrawMode.POINTS.cap, start, count);
+            setAttribOffset(start);
+            GL41.glDrawArraysInstanced(DrawMode.TRIANGLE_STRIP.cap, 0, 4, count);
         }
 
         if (this.texture != -1) {
             GL41.glBindTexture(GL41.GL_TEXTURE_2D, this.texture);
-            GL41.glDrawArrays(DrawMode.POINTS.cap, this.start, this.vertexCount);
+            setAttribOffset(this.start);
+            GL41.glDrawArraysInstanced(DrawMode.TRIANGLE_STRIP.cap, 0, 4, this.vertexCount);
         }
 
         GL41.glBindVertexArray(0);
@@ -216,6 +234,16 @@ public class TessellatorFont {
         }
     }
 
+    private void setAttribOffset(int offset) {
+        int vertSize = 2 * Float.BYTES + 9;
+        int _offset = offset * vertSize;
+        GL41.glVertexAttribPointer(1, 2, GL41.GL_FLOAT, false, vertSize, _offset); _offset += Float.BYTES * 2; // Pos XY
+        GL41.glVertexAttribPointer(2, 2, GL41.GL_UNSIGNED_BYTE, false, vertSize, _offset); _offset += 2; // Width Height
+        GL41.glVertexAttribPointer(3, 4, GL41.GL_UNSIGNED_BYTE, true, vertSize, _offset); _offset += 4; // Color
+        GL41.glVertexAttribPointer(4, 2, GL41.GL_UNSIGNED_BYTE, false, vertSize, _offset); _offset += 2; // UV
+        GL41.glVertexAttribPointer(5, 1, GL41.GL_UNSIGNED_BYTE, false, vertSize, _offset); _offset += 1; // Config
+    }
+
     public record FontRenderBuffer(int vao, int vbo, int[] metadata) {
         public void delete() {
             GL41.glDeleteVertexArrays(this.vao);
@@ -236,7 +264,7 @@ public class TessellatorFont {
                 int start = this.metadata[i + 1];
                 int count = this.metadata[i + 2];
                 GL41.glBindTexture(GL41.GL_TEXTURE_2D, texture);
-                GL41.glDrawArrays(DrawMode.POINTS.cap, start, count);
+                GL41.glDrawArraysInstanced(DrawMode.TRIANGLE_STRIP.cap, start, 4, count);
             }
 
 
diff --git a/game/client/src/main/java/net/minecraft/client/render/tessellator/TessellatorParticleShader.java b/game/client/src/main/java/net/minecraft/client/render/tessellator/TessellatorParticleShader.java
index 1f5dd4dba..6f7866d07 100644
--- a/game/client/src/main/java/net/minecraft/client/render/tessellator/TessellatorParticleShader.java
+++ b/game/client/src/main/java/net/minecraft/client/render/tessellator/TessellatorParticleShader.java
@@ -22,6 +22,7 @@ public class TessellatorParticleShader implements TessellatorParticle {
     private int count = 0;
     private final int vao;
     private final int vbo;
+    private final int vboMesh;
 
     private int color;
     private byte lightIndex;
@@ -34,18 +35,29 @@ public class TessellatorParticleShader implements TessellatorParticle {
         this.buffer = BufferUtils.createByteBuffer(size);
         this.vao = GL41.glGenVertexArrays();
         this.vbo = GL41.glGenBuffers();
+        this.vboMesh = GL41.glGenBuffers();
 
         GL41.glBindVertexArray(this.vao);
+        GL41.glBindBuffer(GL41.GL_ARRAY_BUFFER, this.vboMesh);
+        float[] mesh = {
+            -1,  1,
+            -1, -1,
+             1,  1,
+             1, -1,
+        };
+        GL41.glBufferData(GL41.GL_ARRAY_BUFFER, mesh, GL41.GL_STATIC_DRAW);
+        GL41.glVertexAttribPointer(0, 2, GL41.GL_FLOAT, false, 0, 0);
+
         GL41.glBindBuffer(GL41.GL_ARRAY_BUFFER, this.vbo);
 
         int offset = 0;
         int vertexSize = 37;
-        GL41.glVertexAttribPointer(0, 3, GL41.GL_FLOAT, false, vertexSize, offset); offset += 3 * Float.BYTES;    // Pos
-        GL41.glVertexAttribPointer(1, 1, GL41.GL_FLOAT, false, vertexSize, offset); offset += Float.BYTES;        // Scale
-        GL41.glVertexAttribPointer(2, 4, GL41.GL_UNSIGNED_BYTE, true, vertexSize, offset);  offset += 4;          // Color
-        GL41.glVertexAttribPointer(3, 2, GL41.GL_FLOAT, false, vertexSize, offset); offset += 2 * Float.BYTES;    // UV
-        GL41.glVertexAttribPointer(4, 2, GL41.GL_FLOAT, false, vertexSize, offset); offset += 2 * Float.BYTES;    // UVSizes
-        GL41.glVertexAttribPointer(5, 1, GL41.GL_UNSIGNED_BYTE, false, vertexSize, offset); offset += 1;           // Lightmap
+        GL41.glVertexAttribPointer(1, 3, GL41.GL_FLOAT, false, vertexSize, offset); offset += 3 * Float.BYTES;    // Pos
+        GL41.glVertexAttribPointer(2, 1, GL41.GL_FLOAT, false, vertexSize, offset); offset += Float.BYTES;        // Scale
+        GL41.glVertexAttribPointer(3, 4, GL41.GL_UNSIGNED_BYTE, true, vertexSize, offset);  offset += 4;          // Color
+        GL41.glVertexAttribPointer(4, 2, GL41.GL_FLOAT, false, vertexSize, offset); offset += 2 * Float.BYTES;    // UV
+        GL41.glVertexAttribPointer(5, 2, GL41.GL_FLOAT, false, vertexSize, offset); offset += 2 * Float.BYTES;    // UVSizes
+        GL41.glVertexAttribPointer(6, 1, GL41.GL_UNSIGNED_BYTE, false, vertexSize, offset); offset += 1;          // Lightmap
 
         GL41.glEnableVertexAttribArray(0);
         GL41.glEnableVertexAttribArray(1);
@@ -53,6 +65,15 @@ public class TessellatorParticleShader implements TessellatorParticle {
         GL41.glEnableVertexAttribArray(3);
         GL41.glEnableVertexAttribArray(4);
         GL41.glEnableVertexAttribArray(5);
+        GL41.glEnableVertexAttribArray(6);
+
+        GL41.glVertexAttribDivisor(0, 0); // Vertices, always reuse the same 4 vertices
+        GL41.glVertexAttribDivisor(1, 1); // Data, one per quad
+        GL41.glVertexAttribDivisor(2, 1); // Data, one per quad
+        GL41.glVertexAttribDivisor(3, 1); // Data, one per quad
+        GL41.glVertexAttribDivisor(4, 1); // Data, one per quad
+        GL41.glVertexAttribDivisor(5, 1); // Data, one per quad
+        GL41.glVertexAttribDivisor(6, 1); // Data, one per quad
 
         GL41.glBindBuffer(GL41.GL_ARRAY_BUFFER, 0);
         GL41.glBindVertexArray(0);
@@ -108,7 +129,13 @@ public class TessellatorParticleShader implements TessellatorParticle {
         GL41.glBindBuffer(GL41.GL_ARRAY_BUFFER, this.vbo);
         GL41.glBufferData(GL41.GL_ARRAY_BUFFER, this.buffer.flip(), GL41.GL_STREAM_DRAW);
 
-        GLRenderer.render(this.vao, this.vbo, 0, this.count, DrawMode.POINTS);
+        GLRenderer.getShader().bind();
+        GLRenderer.globalSetUniforms(GLRenderer.getShader());
+
+        GL41.glDrawArraysInstanced(DrawMode.TRIANGLE_STRIP.cap, 0, 4, this.count);
+
+        GL41.glUseProgram(0);
+        GL41.glBindVertexArray(0);
     }
 
     public void checkIsDrawing() {
diff --git a/game/client/src/main/java/net/minecraft/client/render/tileentity/TileEntityRendererStatue.java b/game/client/src/main/java/net/minecraft/client/render/tileentity/TileEntityRendererStatue.java
index 3fb385560..40d2af336 100644
--- a/game/client/src/main/java/net/minecraft/client/render/tileentity/TileEntityRendererStatue.java
+++ b/game/client/src/main/java/net/minecraft/client/render/tileentity/TileEntityRendererStatue.java
@@ -39,6 +39,10 @@ public class TileEntityRendererStatue extends TileEntityRenderer<TileEntityStatu
         BLOCK_SKIN_MAP.put(Blocks.STATUE_GRANITE_LOWER, basePath + "granite.png");
         BLOCK_SKIN_MAP.put(Blocks.STATUE_MARBLE_LOWER, basePath + "marble.png");
         BLOCK_SKIN_MAP.put(Blocks.STATUE_PIGMAN_LOWER, basePath + "pigman.png");
+        BLOCK_SKIN_MAP.put(Blocks.STATUE_SLATE_LOWER, basePath + "slate.png");
+        BLOCK_SKIN_MAP.put(Blocks.STATUE_PERMAFROST_LOWER, basePath + "permafrost.png");
+        BLOCK_SKIN_MAP.put(Blocks.STATUE_NETHERRACK_LOWER, basePath + "netherrack.png");
+        BLOCK_SKIN_MAP.put(Blocks.STATUE_GLOOMSTONE_LOWER, basePath + "gloomstone.png");
     }
 
     public TileEntityRendererStatue() {
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 f0115da51..da00230c5 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
@@ -68,7 +68,7 @@ public class WorldTypeFXNether extends WorldTypeFX {
             }
         }
         if (localBiome == Biomes.NETHER_VOLCANIC_ISLANDS) {
-            final int radius = 24;
+            int radius = 24;
             for (int i = 0; i < 2; i++) {
                 double rx = (player.x - radius) + (world.rand.nextDouble() * radius * 2);
                 double ry = (player.y - radius) + (world.rand.nextDouble() * radius * 2);
@@ -78,6 +78,15 @@ public class WorldTypeFXNether extends WorldTypeFX {
                     world.spawnParticle("flame", rx, ry, rz, 0, 0, 0, 0, false);
                 }
             }
+            radius = 12;
+            for (int i = 0; i < 3; ++i) {
+                double rx = player.x - (double) radius + world.rand.nextDouble() * (double) radius * 2.0F;
+                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);
+                }
+            }
         }
     }
 }
diff --git a/game/client/src/main/java/net/minecraft/client/world/WorldClient.java b/game/client/src/main/java/net/minecraft/client/world/WorldClient.java
index c2bc17f4a..a6b3f34af 100644
--- a/game/client/src/main/java/net/minecraft/client/world/WorldClient.java
+++ b/game/client/src/main/java/net/minecraft/client/world/WorldClient.java
@@ -173,4 +173,9 @@ public class WorldClient extends World {
     public int renderDistance() {
         return GameSettings.RENDER_DISTANCE.value;
     }
+
+    @Override
+    public int getParticleRenderDistance() {
+        return GameSettings.PARTICLE_RENDER_DISTANCE.value;
+    }
 }
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/ash/0.json b/game/client/src/main/resources/assets/minecraft/models/block/ash/0.json
new file mode 100644
index 000000000..6c1496f6c
--- /dev/null
+++ b/game/client/src/main/resources/assets/minecraft/models/block/ash/0.json
@@ -0,0 +1,8 @@
+{
+  "parent": "minecraft:block/layer/height0",
+  "textures": {
+    "top": "minecraft:block/block_ash",
+    "bottom": "minecraft:block/block_ash",
+    "side": "minecraft:block/block_ash"
+  }
+}
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/ash/1.json b/game/client/src/main/resources/assets/minecraft/models/block/ash/1.json
new file mode 100644
index 000000000..3e177dfc1
--- /dev/null
+++ b/game/client/src/main/resources/assets/minecraft/models/block/ash/1.json
@@ -0,0 +1,8 @@
+{
+  "parent": "minecraft:block/layer/height1",
+  "textures": {
+    "top": "minecraft:block/block_ash",
+    "bottom": "minecraft:block/block_ash",
+    "side": "minecraft:block/block_ash"
+  }
+}
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/ash/2.json b/game/client/src/main/resources/assets/minecraft/models/block/ash/2.json
new file mode 100644
index 000000000..7d2aaf400
--- /dev/null
+++ b/game/client/src/main/resources/assets/minecraft/models/block/ash/2.json
@@ -0,0 +1,8 @@
+{
+  "parent": "minecraft:block/layer/height2",
+  "textures": {
+    "top": "minecraft:block/block_ash",
+    "bottom": "minecraft:block/block_ash",
+    "side": "minecraft:block/block_ash"
+  }
+}
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/ash/3.json b/game/client/src/main/resources/assets/minecraft/models/block/ash/3.json
new file mode 100644
index 000000000..d575d297b
--- /dev/null
+++ b/game/client/src/main/resources/assets/minecraft/models/block/ash/3.json
@@ -0,0 +1,8 @@
+{
+  "parent": "minecraft:block/layer/height3",
+  "textures": {
+    "top": "minecraft:block/block_ash",
+    "bottom": "minecraft:block/block_ash",
+    "side": "minecraft:block/block_ash"
+  }
+}
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/ash/4.json b/game/client/src/main/resources/assets/minecraft/models/block/ash/4.json
new file mode 100644
index 000000000..95fe581c7
--- /dev/null
+++ b/game/client/src/main/resources/assets/minecraft/models/block/ash/4.json
@@ -0,0 +1,8 @@
+{
+  "parent": "minecraft:block/layer/height4",
+  "textures": {
+    "top": "minecraft:block/block_ash",
+    "bottom": "minecraft:block/block_ash",
+    "side": "minecraft:block/block_ash"
+  }
+}
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/ash/5.json b/game/client/src/main/resources/assets/minecraft/models/block/ash/5.json
new file mode 100644
index 000000000..14a6a5b67
--- /dev/null
+++ b/game/client/src/main/resources/assets/minecraft/models/block/ash/5.json
@@ -0,0 +1,8 @@
+{
+  "parent": "minecraft:block/layer/height5",
+  "textures": {
+    "top": "minecraft:block/block_ash",
+    "bottom": "minecraft:block/block_ash",
+    "side": "minecraft:block/block_ash"
+  }
+}
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/ash/6.json b/game/client/src/main/resources/assets/minecraft/models/block/ash/6.json
new file mode 100644
index 000000000..14aed920d
--- /dev/null
+++ b/game/client/src/main/resources/assets/minecraft/models/block/ash/6.json
@@ -0,0 +1,8 @@
+{
+  "parent": "minecraft:block/layer/height6",
+  "textures": {
+    "top": "minecraft:block/block_ash",
+    "bottom": "minecraft:block/block_ash",
+    "side": "minecraft:block/block_ash"
+  }
+}
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/ash_ember/0.json b/game/client/src/main/resources/assets/minecraft/models/block/ash_ember/0.json
new file mode 100644
index 000000000..2fcb0c69f
--- /dev/null
+++ b/game/client/src/main/resources/assets/minecraft/models/block/ash_ember/0.json
@@ -0,0 +1,8 @@
+{
+  "parent": "minecraft:block/layer/height0",
+  "textures": {
+    "top": "minecraft:block/ash_ember",
+    "bottom": "minecraft:block/ash_ember",
+    "side": "minecraft:block/block_ash"
+  }
+}
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/ash_ember/1.json b/game/client/src/main/resources/assets/minecraft/models/block/ash_ember/1.json
new file mode 100644
index 000000000..84d496fd3
--- /dev/null
+++ b/game/client/src/main/resources/assets/minecraft/models/block/ash_ember/1.json
@@ -0,0 +1,8 @@
+{
+  "parent": "minecraft:block/layer/height1",
+  "textures": {
+    "top": "minecraft:block/ash_ember",
+    "bottom": "minecraft:block/ash_ember",
+    "side": "minecraft:block/block_ash"
+  }
+}
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/ash_ember/2.json b/game/client/src/main/resources/assets/minecraft/models/block/ash_ember/2.json
new file mode 100644
index 000000000..700f787fc
--- /dev/null
+++ b/game/client/src/main/resources/assets/minecraft/models/block/ash_ember/2.json
@@ -0,0 +1,8 @@
+{
+  "parent": "minecraft:block/layer/height2",
+  "textures": {
+    "top": "minecraft:block/ash_ember",
+    "bottom": "minecraft:block/ash_ember",
+    "side": "minecraft:block/block_ash"
+  }
+}
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/ash_ember/3.json b/game/client/src/main/resources/assets/minecraft/models/block/ash_ember/3.json
new file mode 100644
index 000000000..b98584bcc
--- /dev/null
+++ b/game/client/src/main/resources/assets/minecraft/models/block/ash_ember/3.json
@@ -0,0 +1,8 @@
+{
+  "parent": "minecraft:block/layer/height3",
+  "textures": {
+    "top": "minecraft:block/ash_ember",
+    "bottom": "minecraft:block/ash_ember",
+    "side": "minecraft:block/block_ash"
+  }
+}
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/ash_ember/4.json b/game/client/src/main/resources/assets/minecraft/models/block/ash_ember/4.json
new file mode 100644
index 000000000..3dc6c0a0f
--- /dev/null
+++ b/game/client/src/main/resources/assets/minecraft/models/block/ash_ember/4.json
@@ -0,0 +1,8 @@
+{
+  "parent": "minecraft:block/layer/height4",
+  "textures": {
+    "top": "minecraft:block/ash_ember",
+    "bottom": "minecraft:block/ash_ember",
+    "side": "minecraft:block/block_ash"
+  }
+}
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/ash_ember/5.json b/game/client/src/main/resources/assets/minecraft/models/block/ash_ember/5.json
new file mode 100644
index 000000000..eebb1c415
--- /dev/null
+++ b/game/client/src/main/resources/assets/minecraft/models/block/ash_ember/5.json
@@ -0,0 +1,8 @@
+{
+  "parent": "minecraft:block/layer/height5",
+  "textures": {
+    "top": "minecraft:block/ash_ember",
+    "bottom": "minecraft:block/ash_ember",
+    "side": "minecraft:block/block_ash"
+  }
+}
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/ash_ember/6.json b/game/client/src/main/resources/assets/minecraft/models/block/ash_ember/6.json
new file mode 100644
index 000000000..a3f262558
--- /dev/null
+++ b/game/client/src/main/resources/assets/minecraft/models/block/ash_ember/6.json
@@ -0,0 +1,8 @@
+{
+  "parent": "minecraft:block/layer/height6",
+  "textures": {
+    "top": "minecraft:block/ash_ember",
+    "bottom": "minecraft:block/ash_ember",
+    "side": "minecraft:block/block_ash"
+  }
+}
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/rubyglass_bud/stage0.json b/game/client/src/main/resources/assets/minecraft/models/block/boulder_magmatic.json
similarity index 52%
rename from game/client/src/main/resources/assets/minecraft/models/block/rubyglass_bud/stage0.json
rename to game/client/src/main/resources/assets/minecraft/models/block/boulder_magmatic.json
index a0f63fe67..fe58a879a 100644
--- a/game/client/src/main/resources/assets/minecraft/models/block/rubyglass_bud/stage0.json
+++ b/game/client/src/main/resources/assets/minecraft/models/block/boulder_magmatic.json
@@ -1,6 +1,6 @@
 {
   "parent": "minecraft:block/cross",
   "textures": {
-    "cross": "minecraft:block/rubyglass_bud/stage0"
+    "cross": "minecraft:block/boulder_magmatic"
   }
-}
\ No newline at end of file
+}
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/rubyglass_bud/stage1.json b/game/client/src/main/resources/assets/minecraft/models/block/boulder_sulfuric.json
similarity index 52%
rename from game/client/src/main/resources/assets/minecraft/models/block/rubyglass_bud/stage1.json
rename to game/client/src/main/resources/assets/minecraft/models/block/boulder_sulfuric.json
index df2924802..49dca5ead 100644
--- a/game/client/src/main/resources/assets/minecraft/models/block/rubyglass_bud/stage1.json
+++ b/game/client/src/main/resources/assets/minecraft/models/block/boulder_sulfuric.json
@@ -1,6 +1,6 @@
 {
   "parent": "minecraft:block/cross",
   "textures": {
-    "cross": "minecraft:block/rubyglass_bud/stage1"
+    "cross": "minecraft:block/boulder_sulfuric"
   }
-}
\ No newline at end of file
+}
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/brick_brimstone.json b/game/client/src/main/resources/assets/minecraft/models/block/brick_brimstone.json
new file mode 100644
index 000000000..d760ce9cd
--- /dev/null
+++ b/game/client/src/main/resources/assets/minecraft/models/block/brick_brimstone.json
@@ -0,0 +1,6 @@
+{
+  "parent": "minecraft:block/cube_all",
+  "textures": {
+    "all": "minecraft:block/brick_brimstone"
+  }
+}
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/brimstone.json b/game/client/src/main/resources/assets/minecraft/models/block/brimstone.json
new file mode 100644
index 000000000..18691455d
--- /dev/null
+++ b/game/client/src/main/resources/assets/minecraft/models/block/brimstone.json
@@ -0,0 +1,8 @@
+{
+  "parent": "minecraft:block/cube_bottom_top",
+  "textures": {
+    "top": "minecraft:block/brimstone/top",
+    "bottom": "minecraft:block/brimstone/bottom",
+    "side": "minecraft:block/brimstone/side"
+  }
+}
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/brimthaw.json b/game/client/src/main/resources/assets/minecraft/models/block/brimthaw.json
new file mode 100644
index 000000000..fd4ca2ccc
--- /dev/null
+++ b/game/client/src/main/resources/assets/minecraft/models/block/brimthaw.json
@@ -0,0 +1,6 @@
+{
+  "parent": "minecraft:block/cube_all",
+  "textures": {
+    "all": "minecraft:block/brimthaw"
+  }
+}
\ No newline at end of file
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/carved_slate.json b/game/client/src/main/resources/assets/minecraft/models/block/carved_slate.json
new file mode 100644
index 000000000..e682fee40
--- /dev/null
+++ b/game/client/src/main/resources/assets/minecraft/models/block/carved_slate.json
@@ -0,0 +1,7 @@
+{
+	"parent": "minecraft:block/cube_column",
+	"textures": {
+		"end": "minecraft:block/polished_slate_top",
+		"side": "minecraft:block/carved_slate"
+	}
+}
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/crystalline.json b/game/client/src/main/resources/assets/minecraft/models/block/crystalline.json
new file mode 100644
index 000000000..559240d22
--- /dev/null
+++ b/game/client/src/main/resources/assets/minecraft/models/block/crystalline.json
@@ -0,0 +1,6 @@
+{
+  "parent": "minecraft:block/cube_all",
+  "textures": {
+    "all": "minecraft:block/crystalline"
+  }
+}
\ No newline at end of file
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/ember.json b/game/client/src/main/resources/assets/minecraft/models/block/ember.json
new file mode 100644
index 000000000..4a83382ea
--- /dev/null
+++ b/game/client/src/main/resources/assets/minecraft/models/block/ember.json
@@ -0,0 +1,6 @@
+{
+  "parent": "minecraft:block/cube_all",
+  "textures": {
+    "all": "minecraft:block/ember"
+  }
+}
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/log/scorched.json b/game/client/src/main/resources/assets/minecraft/models/block/log/scorched.json
new file mode 100644
index 000000000..7ceb18315
--- /dev/null
+++ b/game/client/src/main/resources/assets/minecraft/models/block/log/scorched.json
@@ -0,0 +1,7 @@
+{
+	"parent": "minecraft:block/cube_column",
+	"textures": {
+		"end": "minecraft:block/log/scorched_top",
+		"side": "minecraft:block/log/scorched_side"
+	}
+}
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/mobspawner_nether.json b/game/client/src/main/resources/assets/minecraft/models/block/mobspawner_nether.json
new file mode 100644
index 000000000..5c6045deb
--- /dev/null
+++ b/game/client/src/main/resources/assets/minecraft/models/block/mobspawner_nether.json
@@ -0,0 +1,6 @@
+{
+  "parent": "minecraft:block/cube_all",
+  "textures": {
+    "all": "minecraft:block/mobspawner_nether"
+  }
+}
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/mobspawner_nether_deactivated.json b/game/client/src/main/resources/assets/minecraft/models/block/mobspawner_nether_deactivated.json
new file mode 100644
index 000000000..5c6045deb
--- /dev/null
+++ b/game/client/src/main/resources/assets/minecraft/models/block/mobspawner_nether_deactivated.json
@@ -0,0 +1,6 @@
+{
+  "parent": "minecraft:block/cube_all",
+  "textures": {
+    "all": "minecraft:block/mobspawner_nether"
+  }
+}
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/nethercoal/basalt.json b/game/client/src/main/resources/assets/minecraft/models/block/nethercoal/basalt.json
new file mode 100644
index 000000000..8ce4383f5
--- /dev/null
+++ b/game/client/src/main/resources/assets/minecraft/models/block/nethercoal/basalt.json
@@ -0,0 +1,6 @@
+{
+  "parent": "minecraft:block/cube_all",
+  "textures": {
+    "all": "minecraft:block/ore/nethercoal/basalt"
+  }
+}
\ No newline at end of file
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/rubyglass/crystal_netherrack.json b/game/client/src/main/resources/assets/minecraft/models/block/netherrack_crystal.json
similarity index 100%
rename from game/client/src/main/resources/assets/minecraft/models/block/rubyglass/crystal_netherrack.json
rename to game/client/src/main/resources/assets/minecraft/models/block/netherrack_crystal.json
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/node.json b/game/client/src/main/resources/assets/minecraft/models/block/node.json
new file mode 100644
index 000000000..b8f8d8ccc
--- /dev/null
+++ b/game/client/src/main/resources/assets/minecraft/models/block/node.json
@@ -0,0 +1,6 @@
+{
+  "parent": "minecraft:block/cube_all",
+  "textures": {
+    "all": "minecraft:block/rubyglass_node"
+  }
+}
\ No newline at end of file
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/pumice_wet.json b/game/client/src/main/resources/assets/minecraft/models/block/pumice_wet.json
index 11f9ed704..3d6de1d5f 100644
--- a/game/client/src/main/resources/assets/minecraft/models/block/pumice_wet.json
+++ b/game/client/src/main/resources/assets/minecraft/models/block/pumice_wet.json
@@ -3,7 +3,7 @@
 	"credit": "Made with Blockbench",
 	"parent": "block/cube",
 	"textures": {
-		"base": "minecraft:block/lava_still",
+		"base": "minecraft:block/fluid/lava/still",
 		"overlay": "minecraft:block/pumice_overlay",
 		"down": "#overlay",
 		"up": "#overlay",
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/rubyglass/block.json b/game/client/src/main/resources/assets/minecraft/models/block/rubyglass/block.json
index 6ae39a4d7..45c55adbc 100644
--- a/game/client/src/main/resources/assets/minecraft/models/block/rubyglass/block.json
+++ b/game/client/src/main/resources/assets/minecraft/models/block/rubyglass/block.json
@@ -2,6 +2,6 @@
   "parent": "minecraft:block/cube_all",
   "renderlayer": 1,
   "textures": {
-    "all": "minecraft:block/rubyglass/block_pure"
+    "all": "minecraft:block/rubyglass/block"
   }
 }
\ No newline at end of file
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/rubyglass/brick.json b/game/client/src/main/resources/assets/minecraft/models/block/rubyglass/brick.json
new file mode 100644
index 000000000..d005ede1a
--- /dev/null
+++ b/game/client/src/main/resources/assets/minecraft/models/block/rubyglass/brick.json
@@ -0,0 +1,6 @@
+{
+  "parent": "minecraft:block/cube_all",
+  "textures": {
+    "all": "minecraft:block/rubyglass/brick"
+  }
+}
\ No newline at end of file
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/rubyglass/column.json b/game/client/src/main/resources/assets/minecraft/models/block/rubyglass/column.json
index 61e5c9a68..6894a56ea 100644
--- a/game/client/src/main/resources/assets/minecraft/models/block/rubyglass/column.json
+++ b/game/client/src/main/resources/assets/minecraft/models/block/rubyglass/column.json
@@ -1,6 +1,6 @@
 {
   "parent": "minecraft:block/cube_all",
   "textures": {
-    "all": "minecraft:block/rubyglass/block"
+    "all": "minecraft:block/rubyglass/column"
   }
 }
\ No newline at end of file
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/rubyglass/crystal/0.json b/game/client/src/main/resources/assets/minecraft/models/block/rubyglass/crystal/0.json
new file mode 100644
index 000000000..c542012c2
--- /dev/null
+++ b/game/client/src/main/resources/assets/minecraft/models/block/rubyglass/crystal/0.json
@@ -0,0 +1,7 @@
+{
+  "renderlayer": 1,
+  "parent": "minecraft:block/cube_all",
+  "textures": {
+    "all": "minecraft:block/rubyglass/crystal/0"
+  }
+}
\ No newline at end of file
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/rubyglass/crystal/1.json b/game/client/src/main/resources/assets/minecraft/models/block/rubyglass/crystal/1.json
new file mode 100644
index 000000000..cd9334c17
--- /dev/null
+++ b/game/client/src/main/resources/assets/minecraft/models/block/rubyglass/crystal/1.json
@@ -0,0 +1,7 @@
+{
+  "renderlayer": 1,
+  "parent": "minecraft:block/cube_all",
+  "textures": {
+    "all": "minecraft:block/rubyglass/crystal/1"
+  }
+}
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/rubyglass/crystal/2.json b/game/client/src/main/resources/assets/minecraft/models/block/rubyglass/crystal/2.json
new file mode 100644
index 000000000..aeaae4738
--- /dev/null
+++ b/game/client/src/main/resources/assets/minecraft/models/block/rubyglass/crystal/2.json
@@ -0,0 +1,7 @@
+{
+  "renderlayer": 1,
+  "parent": "minecraft:block/cube_all",
+  "textures": {
+    "all": "minecraft:block/rubyglass/crystal/2"
+  }
+}
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/rubyglass/crystal/3.json b/game/client/src/main/resources/assets/minecraft/models/block/rubyglass/crystal/3.json
new file mode 100644
index 000000000..97aad3edc
--- /dev/null
+++ b/game/client/src/main/resources/assets/minecraft/models/block/rubyglass/crystal/3.json
@@ -0,0 +1,7 @@
+{
+  "renderlayer": 1,
+  "parent": "minecraft:block/cube_all",
+  "textures": {
+    "all": "minecraft:block/rubyglass/crystal/3"
+  }
+}
\ No newline at end of file
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/rubyglass/growth/bottom.json b/game/client/src/main/resources/assets/minecraft/models/block/rubyglass/growth/bottom.json
new file mode 100644
index 000000000..772b983d6
--- /dev/null
+++ b/game/client/src/main/resources/assets/minecraft/models/block/rubyglass/growth/bottom.json
@@ -0,0 +1,6 @@
+{
+  "parent": "minecraft:block/cross",
+  "textures": {
+    "cross": "minecraft:block/rubyglass/growth/bottom"
+  }
+}
\ No newline at end of file
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/rubyglass_bud/stage2.json b/game/client/src/main/resources/assets/minecraft/models/block/rubyglass/growth/bud.json
similarity index 52%
rename from game/client/src/main/resources/assets/minecraft/models/block/rubyglass_bud/stage2.json
rename to game/client/src/main/resources/assets/minecraft/models/block/rubyglass/growth/bud.json
index 58ea9f65c..bfe69e833 100644
--- a/game/client/src/main/resources/assets/minecraft/models/block/rubyglass_bud/stage2.json
+++ b/game/client/src/main/resources/assets/minecraft/models/block/rubyglass/growth/bud.json
@@ -1,6 +1,6 @@
 {
   "parent": "minecraft:block/cross",
   "textures": {
-    "cross": "minecraft:block/rubyglass_bud/stage2"
+    "cross": "minecraft:block/rubyglass/growth/bud"
   }
 }
\ No newline at end of file
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/rubyglass/growth/top.json b/game/client/src/main/resources/assets/minecraft/models/block/rubyglass/growth/top.json
new file mode 100644
index 000000000..16b80fb8d
--- /dev/null
+++ b/game/client/src/main/resources/assets/minecraft/models/block/rubyglass/growth/top.json
@@ -0,0 +1,6 @@
+{
+  "parent": "minecraft:block/cross",
+  "textures": {
+    "cross": "minecraft:block/rubyglass/growth/top"
+  }
+}
\ No newline at end of file
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/rubyglass/slab/brick/full.json b/game/client/src/main/resources/assets/minecraft/models/block/rubyglass/slab/brick/full.json
new file mode 100644
index 000000000..d005ede1a
--- /dev/null
+++ b/game/client/src/main/resources/assets/minecraft/models/block/rubyglass/slab/brick/full.json
@@ -0,0 +1,6 @@
+{
+  "parent": "minecraft:block/cube_all",
+  "textures": {
+    "all": "minecraft:block/rubyglass/brick"
+  }
+}
\ No newline at end of file
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/rubyglass/slab/brick/lower.json b/game/client/src/main/resources/assets/minecraft/models/block/rubyglass/slab/brick/lower.json
new file mode 100644
index 000000000..4166f0add
--- /dev/null
+++ b/game/client/src/main/resources/assets/minecraft/models/block/rubyglass/slab/brick/lower.json
@@ -0,0 +1,8 @@
+{
+  "parent": "minecraft:block/slab/lower",
+  "textures": {
+    "top": "minecraft:block/rubyglass/brick",
+    "bottom": "minecraft:block/rubyglass/brick",
+    "side": "minecraft:block/rubyglass/brick"
+  }
+}
\ No newline at end of file
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/rubyglass/slab/brick/upper.json b/game/client/src/main/resources/assets/minecraft/models/block/rubyglass/slab/brick/upper.json
new file mode 100644
index 000000000..e5fa68065
--- /dev/null
+++ b/game/client/src/main/resources/assets/minecraft/models/block/rubyglass/slab/brick/upper.json
@@ -0,0 +1,8 @@
+{
+  "parent": "minecraft:block/slab/upper",
+  "textures": {
+    "top": "minecraft:block/rubyglass/brick",
+    "bottom": "minecraft:block/rubyglass/brick",
+    "side": "minecraft:block/rubyglass/brick"
+  }
+}
\ No newline at end of file
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/rubyglass/stairs/brick.json b/game/client/src/main/resources/assets/minecraft/models/block/rubyglass/stairs/brick.json
new file mode 100644
index 000000000..85fe3b1c4
--- /dev/null
+++ b/game/client/src/main/resources/assets/minecraft/models/block/rubyglass/stairs/brick.json
@@ -0,0 +1,8 @@
+{
+	"parent": "minecraft:block/stairs",
+	"textures": {
+		"top": "minecraft:block/rubyglass/brick",
+		"bottom": "minecraft:block/rubyglass/brick",
+		"side": "minecraft:block/rubyglass/brick"
+	}
+}
\ No newline at end of file
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/rubyglass_bud/stage3.json b/game/client/src/main/resources/assets/minecraft/models/block/rubyglass_bud/stage3.json
deleted file mode 100644
index 2904f9da3..000000000
--- a/game/client/src/main/resources/assets/minecraft/models/block/rubyglass_bud/stage3.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
-  "parent": "minecraft:block/cross",
-  "textures": {
-    "cross": "minecraft:block/rubyglass_bud/stage3"
-  }
-}
\ No newline at end of file
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/slab/brick_brimstone/full.json b/game/client/src/main/resources/assets/minecraft/models/block/slab/brick_brimstone/full.json
new file mode 100644
index 000000000..d760ce9cd
--- /dev/null
+++ b/game/client/src/main/resources/assets/minecraft/models/block/slab/brick_brimstone/full.json
@@ -0,0 +1,6 @@
+{
+  "parent": "minecraft:block/cube_all",
+  "textures": {
+    "all": "minecraft:block/brick_brimstone"
+  }
+}
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/slab/brick_brimstone/lower.json b/game/client/src/main/resources/assets/minecraft/models/block/slab/brick_brimstone/lower.json
new file mode 100644
index 000000000..dcd981acd
--- /dev/null
+++ b/game/client/src/main/resources/assets/minecraft/models/block/slab/brick_brimstone/lower.json
@@ -0,0 +1,8 @@
+{
+  "parent": "minecraft:block/slab/lower",
+  "textures": {
+    "top": "minecraft:block/brick_brimstone",
+    "bottom": "minecraft:block/brick_brimstone",
+    "side": "minecraft:block/brick_brimstone"
+  }
+}
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/slab/brick_brimstone/upper.json b/game/client/src/main/resources/assets/minecraft/models/block/slab/brick_brimstone/upper.json
new file mode 100644
index 000000000..02cc2c4af
--- /dev/null
+++ b/game/client/src/main/resources/assets/minecraft/models/block/slab/brick_brimstone/upper.json
@@ -0,0 +1,8 @@
+{
+  "parent": "minecraft:block/slab/upper",
+  "textures": {
+    "top": "minecraft:block/brick_brimstone",
+    "bottom": "minecraft:block/brick_brimstone",
+    "side": "minecraft:block/brick_brimstone"
+  }
+}
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/slab/brimstone/full.json b/game/client/src/main/resources/assets/minecraft/models/block/slab/brimstone/full.json
new file mode 100644
index 000000000..18691455d
--- /dev/null
+++ b/game/client/src/main/resources/assets/minecraft/models/block/slab/brimstone/full.json
@@ -0,0 +1,8 @@
+{
+  "parent": "minecraft:block/cube_bottom_top",
+  "textures": {
+    "top": "minecraft:block/brimstone/top",
+    "bottom": "minecraft:block/brimstone/bottom",
+    "side": "minecraft:block/brimstone/side"
+  }
+}
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/slab/brimstone/lower.json b/game/client/src/main/resources/assets/minecraft/models/block/slab/brimstone/lower.json
new file mode 100644
index 000000000..b49bfdf8e
--- /dev/null
+++ b/game/client/src/main/resources/assets/minecraft/models/block/slab/brimstone/lower.json
@@ -0,0 +1,8 @@
+{
+  "parent": "minecraft:block/slab/lower",
+  "textures": {
+    "top": "minecraft:block/brimstone/top",
+    "bottom": "minecraft:block/brimstone/bottom",
+    "side": "minecraft:block/brimstone/side"
+  }
+}
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/slab/brimstone/upper.json b/game/client/src/main/resources/assets/minecraft/models/block/slab/brimstone/upper.json
new file mode 100644
index 000000000..1cd45548a
--- /dev/null
+++ b/game/client/src/main/resources/assets/minecraft/models/block/slab/brimstone/upper.json
@@ -0,0 +1,8 @@
+{
+  "parent": "minecraft:block/slab/upper",
+  "textures": {
+    "top": "minecraft:block/brimstone/top",
+    "bottom": "minecraft:block/brimstone/bottom",
+    "side": "minecraft:block/brimstone/side"
+  }
+}
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/slab/polished_slate/full.json b/game/client/src/main/resources/assets/minecraft/models/block/slab/polished_slate/full.json
new file mode 100644
index 000000000..232c7efb5
--- /dev/null
+++ b/game/client/src/main/resources/assets/minecraft/models/block/slab/polished_slate/full.json
@@ -0,0 +1,7 @@
+{
+  "parent": "minecraft:block/cube_column",
+  "textures": {
+    "end": "minecraft:block/polished_slate_top",
+    "side": "minecraft:block/carved_slate"
+  }
+}
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/slab/polished_slate/lower.json b/game/client/src/main/resources/assets/minecraft/models/block/slab/polished_slate/lower.json
new file mode 100644
index 000000000..c3f96400e
--- /dev/null
+++ b/game/client/src/main/resources/assets/minecraft/models/block/slab/polished_slate/lower.json
@@ -0,0 +1,8 @@
+{
+  "parent": "minecraft:block/slab/lower",
+  "textures": {
+    "top": "minecraft:block/polished_slate_top",
+    "bottom": "minecraft:block/polished_slate_top",
+    "side": "minecraft:block/carved_slate"
+  }
+}
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/slab/polished_slate/upper.json b/game/client/src/main/resources/assets/minecraft/models/block/slab/polished_slate/upper.json
new file mode 100644
index 000000000..866fcf26e
--- /dev/null
+++ b/game/client/src/main/resources/assets/minecraft/models/block/slab/polished_slate/upper.json
@@ -0,0 +1,8 @@
+{
+  "parent": "minecraft:block/slab/upper",
+  "textures": {
+    "top": "minecraft:block/polished_slate_top",
+    "bottom": "minecraft:block/polished_slate_top",
+    "side": "minecraft:block/carved_slate"
+  }
+}
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/rubyglass/sprout.json b/game/client/src/main/resources/assets/minecraft/models/block/sprout.json
similarity index 54%
rename from game/client/src/main/resources/assets/minecraft/models/block/rubyglass/sprout.json
rename to game/client/src/main/resources/assets/minecraft/models/block/sprout.json
index d976ff3d8..a1ee5eee6 100644
--- a/game/client/src/main/resources/assets/minecraft/models/block/rubyglass/sprout.json
+++ b/game/client/src/main/resources/assets/minecraft/models/block/sprout.json
@@ -1,6 +1,6 @@
 {
   "parent": "minecraft:block/cross",
   "textures": {
-    "cross": "minecraft:block/rubyglass/sprout"
+    "cross": "minecraft:block/bud"
   }
 }
\ No newline at end of file
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/stairs/brick_brimstone.json b/game/client/src/main/resources/assets/minecraft/models/block/stairs/brick_brimstone.json
new file mode 100644
index 000000000..778c6de71
--- /dev/null
+++ b/game/client/src/main/resources/assets/minecraft/models/block/stairs/brick_brimstone.json
@@ -0,0 +1,8 @@
+{
+	"parent": "minecraft:block/stairs",
+	"textures": {
+		"top": "minecraft:block/brick_brimstone",
+		"bottom": "minecraft:block/brick_brimstone",
+		"side": "minecraft:block/brick_brimstone"
+	}
+}
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/stairs/brimstone.json b/game/client/src/main/resources/assets/minecraft/models/block/stairs/brimstone.json
new file mode 100644
index 000000000..83fd316a9
--- /dev/null
+++ b/game/client/src/main/resources/assets/minecraft/models/block/stairs/brimstone.json
@@ -0,0 +1,8 @@
+{
+	"parent": "minecraft:block/stairs",
+	"textures": {
+		"top": "minecraft:block/brimstone/top",
+		"bottom": "minecraft:block/brimstone/bottom",
+		"side": "minecraft:block/brimstone/side"
+	}
+}
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/sulfur_hardened.json b/game/client/src/main/resources/assets/minecraft/models/block/sulfur_hardened.json
new file mode 100644
index 000000000..474fe82dd
--- /dev/null
+++ b/game/client/src/main/resources/assets/minecraft/models/block/sulfur_hardened.json
@@ -0,0 +1,6 @@
+{
+  "parent": "minecraft:block/cube_all",
+  "textures": {
+    "all": "minecraft:block/sulfur_hardened"
+  }
+}
\ No newline at end of file
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
new file mode 100644
index 000000000..f33b0b7b1
Binary files /dev/null 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 f0ca0bf7a..e9a8c4d44 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/boulder_magmatic.emiss.png b/game/client/src/main/resources/assets/minecraft/textures/block/boulder_magmatic.emiss.png
new file mode 100644
index 000000000..440aea0d4
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/block/boulder_magmatic.emiss.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/boulder_magmatic.png b/game/client/src/main/resources/assets/minecraft/textures/block/boulder_magmatic.png
new file mode 100644
index 000000000..80f1c6152
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/block/boulder_magmatic.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/boulder_sulfuric.emiss.png b/game/client/src/main/resources/assets/minecraft/textures/block/boulder_sulfuric.emiss.png
new file mode 100644
index 000000000..f2215bcfe
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/block/boulder_sulfuric.emiss.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/boulder_sulfuric.png b/game/client/src/main/resources/assets/minecraft/textures/block/boulder_sulfuric.png
new file mode 100644
index 000000000..6ef689519
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/block/boulder_sulfuric.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/brick_brimstone.png b/game/client/src/main/resources/assets/minecraft/textures/block/brick_brimstone.png
new file mode 100644
index 000000000..8a417b510
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/block/brick_brimstone.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/brick_olivine.png b/game/client/src/main/resources/assets/minecraft/textures/block/brick_olivine.png
index a3f6ce45f..ac7621dea 100644
Binary files a/game/client/src/main/resources/assets/minecraft/textures/block/brick_olivine.png and b/game/client/src/main/resources/assets/minecraft/textures/block/brick_olivine.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/brick_quartz.png b/game/client/src/main/resources/assets/minecraft/textures/block/brick_quartz.png
index 45cc4e060..110248b9b 100644
Binary files a/game/client/src/main/resources/assets/minecraft/textures/block/brick_quartz.png and b/game/client/src/main/resources/assets/minecraft/textures/block/brick_quartz.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/brimstone/bottom.png b/game/client/src/main/resources/assets/minecraft/textures/block/brimstone/bottom.png
new file mode 100644
index 000000000..91abb3e2d
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/block/brimstone/bottom.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/brimstone/side.png b/game/client/src/main/resources/assets/minecraft/textures/block/brimstone/side.png
new file mode 100644
index 000000000..9e19f2059
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/block/brimstone/side.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/brimstone/top.png b/game/client/src/main/resources/assets/minecraft/textures/block/brimstone/top.png
new file mode 100644
index 000000000..11f61afe6
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/block/brimstone/top.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/brimthaw.emiss.png b/game/client/src/main/resources/assets/minecraft/textures/block/brimthaw.emiss.png
new file mode 100644
index 000000000..8c84666f2
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/block/brimthaw.emiss.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/brimthaw.png b/game/client/src/main/resources/assets/minecraft/textures/block/brimthaw.png
new file mode 100644
index 000000000..b79e88329
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/block/brimthaw.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/cobbled_basalt.png b/game/client/src/main/resources/assets/minecraft/textures/block/cobbled_basalt.png
index 7b815594f..d59e92560 100644
Binary files a/game/client/src/main/resources/assets/minecraft/textures/block/cobbled_basalt.png and b/game/client/src/main/resources/assets/minecraft/textures/block/cobbled_basalt.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/cobbled_netherrack/ashen_side.png b/game/client/src/main/resources/assets/minecraft/textures/block/cobbled_netherrack/ashen_side.png
deleted file mode 100644
index bca29a482..000000000
Binary files a/game/client/src/main/resources/assets/minecraft/textures/block/cobbled_netherrack/ashen_side.png and /dev/null differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/cobbled_netherrack/crystalline.emiss.png b/game/client/src/main/resources/assets/minecraft/textures/block/cobbled_netherrack/crystalline.emiss.png
index d5d729778..cae20fc2a 100644
Binary files a/game/client/src/main/resources/assets/minecraft/textures/block/cobbled_netherrack/crystalline.emiss.png and b/game/client/src/main/resources/assets/minecraft/textures/block/cobbled_netherrack/crystalline.emiss.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/cobbled_netherrack/crystalline.png b/game/client/src/main/resources/assets/minecraft/textures/block/cobbled_netherrack/crystalline.png
index b78b4837a..1eb63aeae 100644
Binary files a/game/client/src/main/resources/assets/minecraft/textures/block/cobbled_netherrack/crystalline.png and b/game/client/src/main/resources/assets/minecraft/textures/block/cobbled_netherrack/crystalline.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/ember.emiss.png b/game/client/src/main/resources/assets/minecraft/textures/block/ember.emiss.png
new file mode 100644
index 000000000..0409b2403
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/block/ember.emiss.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/block_pure.png.mcmeta b/game/client/src/main/resources/assets/minecraft/textures/block/ember.emiss.png.mcmeta
similarity index 54%
rename from game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/block_pure.png.mcmeta
rename to game/client/src/main/resources/assets/minecraft/textures/block/ember.emiss.png.mcmeta
index af30dc0cf..57a968853 100644
--- a/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/block_pure.png.mcmeta
+++ b/game/client/src/main/resources/assets/minecraft/textures/block/ember.emiss.png.mcmeta
@@ -1,6 +1,7 @@
 {
   "animation": {
     "interpolate": true,
-    "frametime": 16
+    "random": true,
+    "frametime": 3
   }
 }
\ No newline at end of file
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/ember.png b/game/client/src/main/resources/assets/minecraft/textures/block/ember.png
new file mode 100644
index 000000000..62006cab7
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/block/ember.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/ember.png.mcmeta b/game/client/src/main/resources/assets/minecraft/textures/block/ember.png.mcmeta
new file mode 100644
index 000000000..57a968853
--- /dev/null
+++ b/game/client/src/main/resources/assets/minecraft/textures/block/ember.png.mcmeta
@@ -0,0 +1,7 @@
+{
+  "animation": {
+    "interpolate": true,
+    "random": true,
+    "frametime": 3
+  }
+}
\ No newline at end of file
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/fire_sulfuric.png b/game/client/src/main/resources/assets/minecraft/textures/block/fire_sulfuric.png
new file mode 100644
index 000000000..ff940ecae
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/block/fire_sulfuric.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/fluid/acid/flowing.emiss.png b/game/client/src/main/resources/assets/minecraft/textures/block/fluid/acid/flowing.emiss.png
new file mode 100644
index 000000000..8201df983
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/block/fluid/acid/flowing.emiss.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/fluid/acid/flowing.emiss.png.mcmeta b/game/client/src/main/resources/assets/minecraft/textures/block/fluid/acid/flowing.emiss.png.mcmeta
new file mode 100644
index 000000000..8dabee5ed
--- /dev/null
+++ b/game/client/src/main/resources/assets/minecraft/textures/block/fluid/acid/flowing.emiss.png.mcmeta
@@ -0,0 +1,24 @@
+{
+  "animation": {
+    "interpolate": false,
+    "random": false,
+    "frametime": 7,
+    "frames": [
+      0,
+      1,
+      2,
+      3,
+      4,
+      5,
+      6,
+      7,
+      8,
+      9,
+      10,
+      11,
+      12,
+      13,
+      14
+    ]
+  }
+}
\ No newline at end of file
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/fluid/acid/flowing.png b/game/client/src/main/resources/assets/minecraft/textures/block/fluid/acid/flowing.png
new file mode 100644
index 000000000..f93a9ab79
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/block/fluid/acid/flowing.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/fluid/acid/flowing.png.mcmeta b/game/client/src/main/resources/assets/minecraft/textures/block/fluid/acid/flowing.png.mcmeta
new file mode 100644
index 000000000..8dabee5ed
--- /dev/null
+++ b/game/client/src/main/resources/assets/minecraft/textures/block/fluid/acid/flowing.png.mcmeta
@@ -0,0 +1,24 @@
+{
+  "animation": {
+    "interpolate": false,
+    "random": false,
+    "frametime": 7,
+    "frames": [
+      0,
+      1,
+      2,
+      3,
+      4,
+      5,
+      6,
+      7,
+      8,
+      9,
+      10,
+      11,
+      12,
+      13,
+      14
+    ]
+  }
+}
\ No newline at end of file
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/fluid/acid/still.emiss.png b/game/client/src/main/resources/assets/minecraft/textures/block/fluid/acid/still.emiss.png
new file mode 100644
index 000000000..a3ddce1ed
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/block/fluid/acid/still.emiss.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/fluid/acid/still.emiss.png.mcmeta b/game/client/src/main/resources/assets/minecraft/textures/block/fluid/acid/still.emiss.png.mcmeta
new file mode 100644
index 000000000..9d8c68748
--- /dev/null
+++ b/game/client/src/main/resources/assets/minecraft/textures/block/fluid/acid/still.emiss.png.mcmeta
@@ -0,0 +1,12 @@
+{
+  "animation": {
+    "interpolate": true,
+    "frametime": 20,
+    "frames": [
+      0,
+      1,
+      2,
+      3
+    ]
+  }
+}
\ No newline at end of file
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/fluid/acid/still.png b/game/client/src/main/resources/assets/minecraft/textures/block/fluid/acid/still.png
new file mode 100644
index 000000000..9044a1ce7
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/block/fluid/acid/still.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/fluid/acid/still.png.mcmeta b/game/client/src/main/resources/assets/minecraft/textures/block/fluid/acid/still.png.mcmeta
new file mode 100644
index 000000000..9d8c68748
--- /dev/null
+++ b/game/client/src/main/resources/assets/minecraft/textures/block/fluid/acid/still.png.mcmeta
@@ -0,0 +1,12 @@
+{
+  "animation": {
+    "interpolate": true,
+    "frametime": 20,
+    "frames": [
+      0,
+      1,
+      2,
+      3
+    ]
+  }
+}
\ No newline at end of file
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/lava_flowing.png b/game/client/src/main/resources/assets/minecraft/textures/block/fluid/lava/flowing.png
similarity index 100%
rename from game/client/src/main/resources/assets/minecraft/textures/block/lava_flowing.png
rename to game/client/src/main/resources/assets/minecraft/textures/block/fluid/lava/flowing.png
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/lava_still.png b/game/client/src/main/resources/assets/minecraft/textures/block/fluid/lava/still.png
similarity index 100%
rename from game/client/src/main/resources/assets/minecraft/textures/block/lava_still.png
rename to game/client/src/main/resources/assets/minecraft/textures/block/fluid/lava/still.png
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/water_flowing.png b/game/client/src/main/resources/assets/minecraft/textures/block/fluid/water/flowing.png
similarity index 100%
rename from game/client/src/main/resources/assets/minecraft/textures/block/water_flowing.png
rename to game/client/src/main/resources/assets/minecraft/textures/block/fluid/water/flowing.png
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/water_overlay_boiling.png b/game/client/src/main/resources/assets/minecraft/textures/block/fluid/water/overlay_boiling.png
similarity index 100%
rename from game/client/src/main/resources/assets/minecraft/textures/block/water_overlay_boiling.png
rename to game/client/src/main/resources/assets/minecraft/textures/block/fluid/water/overlay_boiling.png
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/water_overlay_boiling.png.mcmeta b/game/client/src/main/resources/assets/minecraft/textures/block/fluid/water/overlay_boiling.png.mcmeta
similarity index 100%
rename from game/client/src/main/resources/assets/minecraft/textures/block/water_overlay_boiling.png.mcmeta
rename to game/client/src/main/resources/assets/minecraft/textures/block/fluid/water/overlay_boiling.png.mcmeta
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/water_still.png b/game/client/src/main/resources/assets/minecraft/textures/block/fluid/water/still.png
similarity index 100%
rename from game/client/src/main/resources/assets/minecraft/textures/block/water_still.png
rename to game/client/src/main/resources/assets/minecraft/textures/block/fluid/water/still.png
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/log/scorched_side.png b/game/client/src/main/resources/assets/minecraft/textures/block/log/scorched_side.png
new file mode 100644
index 000000000..5225f106f
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/block/log/scorched_side.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/log/scorched_top.png b/game/client/src/main/resources/assets/minecraft/textures/block/log/scorched_top.png
new file mode 100644
index 000000000..68202d3a7
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/block/log/scorched_top.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/magma.emiss.png b/game/client/src/main/resources/assets/minecraft/textures/block/magma.emiss.png
index 6241874e0..ae458a0cd 100644
Binary files a/game/client/src/main/resources/assets/minecraft/textures/block/magma.emiss.png and b/game/client/src/main/resources/assets/minecraft/textures/block/magma.emiss.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/magma.png b/game/client/src/main/resources/assets/minecraft/textures/block/magma.png
index 642956f96..12830bef4 100644
Binary files a/game/client/src/main/resources/assets/minecraft/textures/block/magma.png and b/game/client/src/main/resources/assets/minecraft/textures/block/magma.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/mobspawner_nether.png b/game/client/src/main/resources/assets/minecraft/textures/block/mobspawner_nether.png
new file mode 100644
index 000000000..c370adc5b
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/block/mobspawner_nether.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/ore/nethercoal/basalt.png b/game/client/src/main/resources/assets/minecraft/textures/block/ore/nethercoal/basalt.png
index 6b28a2f0e..7a81bf184 100644
Binary files a/game/client/src/main/resources/assets/minecraft/textures/block/ore/nethercoal/basalt.png and b/game/client/src/main/resources/assets/minecraft/textures/block/ore/nethercoal/basalt.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/block.emiss.png b/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/block.emiss.png
index 3bb31a38f..a230152c3 100644
Binary files a/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/block.emiss.png and b/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/block.emiss.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/block.emiss.png.mcmeta b/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/block.emiss.png.mcmeta
new file mode 100644
index 000000000..b13a44198
--- /dev/null
+++ b/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/block.emiss.png.mcmeta
@@ -0,0 +1,16 @@
+{
+  "animation": {
+    "interpolate": true,
+    "frametime": 16,
+    "frames": [
+      0,
+      1,
+      2,
+      3,
+      4,
+      5,
+      6,
+      7
+    ]
+  }
+}
\ No newline at end of file
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/block.png b/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/block.png
index 6c83619ef..6f6baa4a7 100644
Binary files a/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/block.png and b/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/block.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/block.png.mcmeta b/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/block.png.mcmeta
new file mode 100644
index 000000000..b13a44198
--- /dev/null
+++ b/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/block.png.mcmeta
@@ -0,0 +1,16 @@
+{
+  "animation": {
+    "interpolate": true,
+    "frametime": 16,
+    "frames": [
+      0,
+      1,
+      2,
+      3,
+      4,
+      5,
+      6,
+      7
+    ]
+  }
+}
\ No newline at end of file
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/block_pure.emiss.png b/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/block_pure.emiss.png
deleted file mode 100644
index ac0c8dc15..000000000
Binary files a/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/block_pure.emiss.png and /dev/null differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/block_pure.emiss.png.mcmeta b/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/block_pure.emiss.png.mcmeta
deleted file mode 100644
index 394983571..000000000
--- a/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/block_pure.emiss.png.mcmeta
+++ /dev/null
@@ -1,19 +0,0 @@
-{
-    "animation": {
-        "frametime": 1,
-        "frames": [
-            0,
-            1,
-            2,
-            3,
-            4,
-            5,
-            6,
-            7,
-            8,
-            9,
-            10,
-            11
-        ]
-    }
-}
\ No newline at end of file
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/block_pure.png b/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/block_pure.png
deleted file mode 100644
index f4722740d..000000000
Binary files a/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/block_pure.png and /dev/null differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/brick.emiss.png b/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/brick.emiss.png
new file mode 100644
index 000000000..a230152c3
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/brick.emiss.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/brick.emiss.png.mcmeta b/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/brick.emiss.png.mcmeta
new file mode 100644
index 000000000..b13a44198
--- /dev/null
+++ b/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/brick.emiss.png.mcmeta
@@ -0,0 +1,16 @@
+{
+  "animation": {
+    "interpolate": true,
+    "frametime": 16,
+    "frames": [
+      0,
+      1,
+      2,
+      3,
+      4,
+      5,
+      6,
+      7
+    ]
+  }
+}
\ No newline at end of file
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/brick.png b/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/brick.png
new file mode 100644
index 000000000..2b5326feb
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/brick.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/brick.png.mcmeta b/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/brick.png.mcmeta
new file mode 100644
index 000000000..b13a44198
--- /dev/null
+++ b/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/brick.png.mcmeta
@@ -0,0 +1,16 @@
+{
+  "animation": {
+    "interpolate": true,
+    "frametime": 16,
+    "frames": [
+      0,
+      1,
+      2,
+      3,
+      4,
+      5,
+      6,
+      7
+    ]
+  }
+}
\ No newline at end of file
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/column.emiss.png b/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/column.emiss.png
new file mode 100644
index 000000000..3bb31a38f
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/column.emiss.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/column.png b/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/column.png
new file mode 100644
index 000000000..6c83619ef
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/column.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/crystal/0.emiss.png b/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/crystal/0.emiss.png
new file mode 100644
index 000000000..3bb31a38f
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/crystal/0.emiss.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/crystal/0.png b/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/crystal/0.png
new file mode 100644
index 000000000..b695be6d5
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/crystal/0.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/crystal/1.emiss.png b/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/crystal/1.emiss.png
new file mode 100644
index 000000000..3bb31a38f
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/crystal/1.emiss.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/crystal/1.png b/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/crystal/1.png
new file mode 100644
index 000000000..c080a73b8
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/crystal/1.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/crystal/2.emiss.png b/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/crystal/2.emiss.png
new file mode 100644
index 000000000..3bb31a38f
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/crystal/2.emiss.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/crystal/2.png b/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/crystal/2.png
new file mode 100644
index 000000000..aa261f5ca
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/crystal/2.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/crystal/3.emiss.png b/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/crystal/3.emiss.png
new file mode 100644
index 000000000..3bb31a38f
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/crystal/3.emiss.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/crystal/3.png b/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/crystal/3.png
new file mode 100644
index 000000000..c1e7e9f42
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/crystal/3.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/growth/bottom.emiss.png b/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/growth/bottom.emiss.png
new file mode 100644
index 000000000..ae063e2c5
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/growth/bottom.emiss.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/growth/bottom.png b/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/growth/bottom.png
new file mode 100644
index 000000000..0c2ceaab8
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/growth/bottom.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/growth/bud.emiss.png b/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/growth/bud.emiss.png
new file mode 100644
index 000000000..7c9882fd3
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/growth/bud.emiss.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/growth/bud.png b/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/growth/bud.png
new file mode 100644
index 000000000..671523fcb
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/growth/bud.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/growth/top.emiss.png b/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/growth/top.emiss.png
new file mode 100644
index 000000000..ac25643b2
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/growth/top.emiss.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/growth/top.png b/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/growth/top.png
new file mode 100644
index 000000000..d2140f2c0
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/growth/top.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/node.emiss.png b/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/node.emiss.png
index 455401597..a230152c3 100644
Binary files a/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/node.emiss.png and b/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/node.emiss.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/node.emiss.png.mcmeta b/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/node.emiss.png.mcmeta
new file mode 100644
index 000000000..b13a44198
--- /dev/null
+++ b/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/node.emiss.png.mcmeta
@@ -0,0 +1,16 @@
+{
+  "animation": {
+    "interpolate": true,
+    "frametime": 16,
+    "frames": [
+      0,
+      1,
+      2,
+      3,
+      4,
+      5,
+      6,
+      7
+    ]
+  }
+}
\ No newline at end of file
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/node.png b/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/node.png
index 5e7094598..961512bbb 100644
Binary files a/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/node.png and b/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/node.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/node.png.mcmeta b/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/node.png.mcmeta
index af30dc0cf..b13a44198 100644
--- a/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/node.png.mcmeta
+++ b/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/node.png.mcmeta
@@ -1,6 +1,16 @@
 {
   "animation": {
     "interpolate": true,
-    "frametime": 16
+    "frametime": 16,
+    "frames": [
+      0,
+      1,
+      2,
+      3,
+      4,
+      5,
+      6,
+      7
+    ]
   }
 }
\ No newline at end of file
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/sprout.emiss.png b/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/sprout.emiss.png
deleted file mode 100644
index e057b6b4b..000000000
Binary files a/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/sprout.emiss.png and /dev/null differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/sprout.png b/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/sprout.png
deleted file mode 100644
index ef6ade001..000000000
Binary files a/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/sprout.png and /dev/null differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass_bud/stage0.png b/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass_bud/stage0.png
deleted file mode 100644
index ea878ac2a..000000000
Binary files a/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass_bud/stage0.png and /dev/null differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass_bud/stage1.png b/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass_bud/stage1.png
deleted file mode 100644
index 729a7f644..000000000
Binary files a/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass_bud/stage1.png and /dev/null differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass_bud/stage2.png b/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass_bud/stage2.png
deleted file mode 100644
index a0a3ef8ef..000000000
Binary files a/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass_bud/stage2.png and /dev/null differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass_bud/stage3.png b/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass_bud/stage3.png
deleted file mode 100644
index e3b7520dc..000000000
Binary files a/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass_bud/stage3.png and /dev/null differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/sulfur.png b/game/client/src/main/resources/assets/minecraft/textures/block/sulfur.png
new file mode 100644
index 000000000..a584bc2ac
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/block/sulfur.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/sulfur_hardened.png b/game/client/src/main/resources/assets/minecraft/textures/block/sulfur_hardened.png
new file mode 100644
index 000000000..f48d7c828
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/block/sulfur_hardened.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/entity/arrowflaming.png b/game/client/src/main/resources/assets/minecraft/textures/entity/arrowflaming.png
new file mode 100644
index 000000000..1e4cc0c8e
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/entity/arrowflaming.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/entity/statue/gloomstone.png b/game/client/src/main/resources/assets/minecraft/textures/entity/statue/gloomstone.png
new file mode 100644
index 000000000..ec40e9072
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/entity/statue/gloomstone.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/entity/statue/netherrack.png b/game/client/src/main/resources/assets/minecraft/textures/entity/statue/netherrack.png
new file mode 100644
index 000000000..f7aec7931
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/entity/statue/netherrack.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/entity/statue/permafrost.png b/game/client/src/main/resources/assets/minecraft/textures/entity/statue/permafrost.png
new file mode 100644
index 000000000..85f3f00d3
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/entity/statue/permafrost.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/entity/statue/slate.png b/game/client/src/main/resources/assets/minecraft/textures/entity/statue/slate.png
new file mode 100644
index 000000000..cd4ad31c0
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/entity/statue/slate.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/hud/acid.png b/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/hud/acid.png
new file mode 100644
index 000000000..f372d84a3
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/hud/acid.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/item/ammo_arrow_flaming.png b/game/client/src/main/resources/assets/minecraft/textures/item/ammo_arrow_flaming.png
new file mode 100644
index 000000000..6e2dd14ae
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/item/ammo_arrow_flaming.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/item/bucket_acid.png b/game/client/src/main/resources/assets/minecraft/textures/item/bucket_acid.png
new file mode 100644
index 000000000..7cdb73011
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/item/bucket_acid.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/item/bucket_iron/acid_1.png b/game/client/src/main/resources/assets/minecraft/textures/item/bucket_iron/acid_1.png
new file mode 100644
index 000000000..1f8595682
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/item/bucket_iron/acid_1.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/item/bucket_steel/acid_1.png b/game/client/src/main/resources/assets/minecraft/textures/item/bucket_steel/acid_1.png
new file mode 100644
index 000000000..b67d66f86
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/item/bucket_steel/acid_1.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/item/bucket_steel/acid_2.png b/game/client/src/main/resources/assets/minecraft/textures/item/bucket_steel/acid_2.png
new file mode 100644
index 000000000..f592c440e
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/item/bucket_steel/acid_2.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/item/bucket_steel/acid_3.png b/game/client/src/main/resources/assets/minecraft/textures/item/bucket_steel/acid_3.png
new file mode 100644
index 000000000..3802eafb7
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/item/bucket_steel/acid_3.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/item/rubyglass.emiss.png b/game/client/src/main/resources/assets/minecraft/textures/item/rubyglass.emiss.png
new file mode 100644
index 000000000..a6241045f
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/item/rubyglass.emiss.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/item/rubyglass.emiss.png.mcmeta b/game/client/src/main/resources/assets/minecraft/textures/item/rubyglass.emiss.png.mcmeta
new file mode 100644
index 000000000..7f7bda9f6
--- /dev/null
+++ b/game/client/src/main/resources/assets/minecraft/textures/item/rubyglass.emiss.png.mcmeta
@@ -0,0 +1,16 @@
+{
+  "animation": {
+    "interpolate": true,
+    "frametime": 4,
+    "frames": [
+      0,
+      1,
+      2,
+      3,
+      4,
+      5,
+      6,
+      7
+    ]
+  }
+}
\ No newline at end of file
diff --git a/game/client/src/main/resources/assets/minecraft/textures/item/rubyglass.png b/game/client/src/main/resources/assets/minecraft/textures/item/rubyglass.png
new file mode 100644
index 000000000..668d6260f
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/item/rubyglass.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/item/rubyglass.png.mcmeta b/game/client/src/main/resources/assets/minecraft/textures/item/rubyglass.png.mcmeta
new file mode 100644
index 000000000..7f7bda9f6
--- /dev/null
+++ b/game/client/src/main/resources/assets/minecraft/textures/item/rubyglass.png.mcmeta
@@ -0,0 +1,16 @@
+{
+  "animation": {
+    "interpolate": true,
+    "frametime": 4,
+    "frames": [
+      0,
+      1,
+      2,
+      3,
+      4,
+      5,
+      6,
+      7
+    ]
+  }
+}
\ No newline at end of file
diff --git a/game/client/src/main/resources/assets/minecraft/textures/item/rubyglass_crystal_cracked.png b/game/client/src/main/resources/assets/minecraft/textures/item/rubyglass_crystal_cracked.png
deleted file mode 100644
index 2e320ce07..000000000
Binary files a/game/client/src/main/resources/assets/minecraft/textures/item/rubyglass_crystal_cracked.png and /dev/null differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/item/rubyglass_crystal_pure.png b/game/client/src/main/resources/assets/minecraft/textures/item/rubyglass_crystal_pure.png
deleted file mode 100644
index 5a6e464a1..000000000
Binary files a/game/client/src/main/resources/assets/minecraft/textures/item/rubyglass_crystal_pure.png and /dev/null 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
new file mode 100644
index 000000000..343922563
Binary files /dev/null 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_netherrack.png b/game/client/src/main/resources/assets/minecraft/textures/item/statue_netherrack.png
new file mode 100644
index 000000000..464179460
Binary files /dev/null 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
new file mode 100644
index 000000000..5804232d7
Binary files /dev/null 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_slate.png b/game/client/src/main/resources/assets/minecraft/textures/item/statue_slate.png
new file mode 100644
index 000000000..20ec97fcb
Binary files /dev/null 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/sulfur.png b/game/client/src/main/resources/assets/minecraft/textures/item/sulfur.png
index 073de48ea..3c8f42ce5 100644
Binary files a/game/client/src/main/resources/assets/minecraft/textures/item/sulfur.png and b/game/client/src/main/resources/assets/minecraft/textures/item/sulfur.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/particle/acid_boiling.png b/game/client/src/main/resources/assets/minecraft/textures/particle/acid_boiling.png
new file mode 100644
index 000000000..cf51117f5
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/particle/acid_boiling.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/particle/acid_boiling_pop.png b/game/client/src/main/resources/assets/minecraft/textures/particle/acid_boiling_pop.png
new file mode 100644
index 000000000..e213a1a03
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/particle/acid_boiling_pop.png differ
diff --git a/game/client/src/main/resources/shaders/acid_overlay.fsh b/game/client/src/main/resources/shaders/acid_overlay.fsh
new file mode 100644
index 000000000..adc932444
--- /dev/null
+++ b/game/client/src/main/resources/shaders/acid_overlay.fsh
@@ -0,0 +1,64 @@
+#version 410 core
+
+out vec4 FragColor;
+
+in vec4 Color;
+in vec2 TexCoord;
+
+uniform float uCurrentTick;
+uniform vec4  uColor;
+uniform float uAlphaTest;
+uniform vec2  uViewPortSize;
+
+void main() {
+    // Vignette
+    vec2 centerCoords = TexCoord - 0.5;
+    float dist = length(centerCoords);
+
+    float minVignette = 0.04;
+    float vignetteRamp = smoothstep(0.25, 0.6, dist) * 0.6;
+    float vignetteAlpha = minVignette + vignetteRamp;
+
+    // Droplets
+    float pixelScale = 8.0;
+    vec2 dropUV = floor(TexCoord * (uViewPortSize / pixelScale)) / (uViewPortSize / pixelScale);
+
+    float cols = floor(uViewPortSize.x / 24.0);
+    float streamIndex = floor(dropUV.x * cols);
+    float localX = fract(dropUV.x * cols) - 0.5;
+    float r1 = fract(sin(dot(vec2(streamIndex), vec2(12.9898, 78.233))) * 43758.5453);
+    float r2 = fract(sin(dot(vec2(streamIndex), vec2(39.346, 14.653))) * 28412.123);
+    float speed = 0.3 + 0.3 * r1;
+
+    float rawY = fract(dropUV.y - 0.003 * uCurrentTick * speed + r2);
+    float dropHeight = 0.06 + 0.06 * r2;
+    float t = (rawY - (0.5 - dropHeight * 0.5)) / dropHeight;
+
+    float drop = 0.0;
+    float core = 0.0;
+
+    if (t > 0.0 && t < 1.0) {
+        float distX = abs(localX);
+        float w = 0.0;
+
+        if (t > 0.25 && t < 0.75) {
+            w = 0.3;
+
+            core = step(distX, 0.15);
+        } else if (t > 0.1 && t <= 0.9) {
+            w = 0.15;
+        }
+        drop = step(distX, w);
+    }
+
+    // Colors
+    vec3 baseColor = vec3(0.85, 1.0, 0.25);
+    vec3 dripOuter = vec3(0.65, 0.85, 0.15);
+    vec3 dripInner = vec3(0.95, 1.0, 0.25);
+    vec3 dropCol = mix(dripOuter, dripInner, core);
+    vec3 col = mix(baseColor, dropCol, drop);
+    float distSq = dot(centerCoords, centerCoords);
+    float dripAlpha = 0.08 * drop * (1.0 + distSq * 2.0);
+    float finalAlpha = uColor.a * (vignetteAlpha + dripAlpha);
+    FragColor = vec4(col, finalAlpha);
+}
\ No newline at end of file
diff --git a/game/client/src/main/resources/shaders/acid_overlay.vsh b/game/client/src/main/resources/shaders/acid_overlay.vsh
new file mode 100644
index 000000000..775ecf1e5
--- /dev/null
+++ b/game/client/src/main/resources/shaders/acid_overlay.vsh
@@ -0,0 +1,21 @@
+#version 410 core
+layout (location = 0) in vec3 aPos;
+layout (location = 1) in vec4 aColor;
+layout (location = 2) in vec2 aUV;
+
+out vec4 Color;
+out vec2 TexCoord;
+
+uniform mat4 model;
+layout (std140) uniform Matrices {
+    mat4 projection;
+    mat4 projectionInv;
+    mat4 view;
+    mat4 viewInv;
+} matrices;
+
+void main() {
+    gl_Position = matrices.projection * matrices.view * model * vec4(aPos, 1);
+    Color = aColor;
+    TexCoord = aUV;
+}
diff --git a/game/client/src/main/resources/shaders/font.gsh b/game/client/src/main/resources/shaders/font.gsh.disabled
similarity index 100%
rename from game/client/src/main/resources/shaders/font.gsh
rename to game/client/src/main/resources/shaders/font.gsh.disabled
diff --git a/game/client/src/main/resources/shaders/font.vsh b/game/client/src/main/resources/shaders/font.vsh
index 12aa80801..c34eccbe3 100644
--- a/game/client/src/main/resources/shaders/font.vsh
+++ b/game/client/src/main/resources/shaders/font.vsh
@@ -1,18 +1,35 @@
 #version 410 core
-layout (location = 0) in vec2 aPos;
-layout (location = 1) in vec2 aSize;
-layout (location = 2) in vec4 aColor;
-layout (location = 3) in vec2 aUV;
-layout (location = 4) in float aConfig;
-layout (location = 5) in float aLightmap;
-
-out VS_OUT {
-    vec2 size;
-    vec4 color;
-    vec2 uv;
-    vec2 lightmap;
-    float config;
-} vs_out;
+
+#define MASK_ITALIC 0x01u
+#define MASK_UNDERLINE 0x02u
+#define MASK_STRIKETHROUGH 0x04u
+#define MASK_BOLD 0x08u
+#define MASK_OBFUSCATED 0x10u
+#define MASK_OUTLINE 0x20u
+#define MASK_SHADOW 0x80u
+
+layout (location = 0) in vec2 aMesh;
+layout (location = 1) in vec2 aPos;
+layout (location = 2) in vec2 aSize;
+layout (location = 3) in vec4 aColor;
+layout (location = 4) in vec2 aUV;
+layout (location = 5) in float aConfig;
+layout (location = 6) in float aLightmap;
+
+out vec4 Color;
+out vec2 UV;
+out vec2 TexCoord;
+out vec2 Lightmap;
+out vec2 Size;
+out float Config;
+
+uniform mat4 model;
+layout (std140) uniform Matrices {
+    mat4 projection;
+    mat4 projectionInv;
+    mat4 view;
+    mat4 viewInv;
+} matrices;
 
 uniform float uZ;
 
@@ -22,11 +39,29 @@ vec2 unpackLightCoord() {
 }
 
 void main() {
-    gl_Position = vec4(aPos, uZ, 1);
+    vec2 bSize = aSize/2;
+    vec4 bColor = aColor;
+    vec2 bUV = aUV/256.0;
+    vec2 bLightmap = unpackLightCoord();
+    float bConfig = aConfig;
+
+    UV = bUV;
+    Size = bSize;
+    Config = bConfig;
+    Color = bColor;
+    Lightmap = bLightmap;
+
+    // x, y, shiftx, exX, exY, exU, exV
+//    0, 1, -1, -1,  2, -1,  2
+//    1, 1, -1,  2,  2,  2,  2
+//    0, 0,  1, -1, -1, -1, -1
+//    1, 0,  1,  2, -1,  2, -1
+
+    // shiftx, exX, and exY are are derivable from the mesh vertices so are not encoded seperately in the static mesh
+    float shiftX = ((aMesh.y * -2) + 1);
+    vec2 ex = (aMesh * 3) - vec2(1, 1);
 
-    vs_out.size = aSize/2;
-    vs_out.color = aColor;
-    vs_out.uv = aUV/256.0;
-    vs_out.lightmap = unpackLightCoord();
-    vs_out.config = aConfig;
+    vec2 shift = vec2((uint(bConfig) & MASK_ITALIC) * shiftX + 8/256, 0);
+    TexCoord = bUV + ((aMesh * aSize + ex)/128);
+    gl_Position = matrices.projection * matrices.view * model * vec4(aPos + aSize * aMesh + ex + shift, uZ, 1);
 }
diff --git a/game/client/src/main/resources/shaders/particle.gsh b/game/client/src/main/resources/shaders/particle.gsh.disabled
similarity index 100%
rename from game/client/src/main/resources/shaders/particle.gsh
rename to game/client/src/main/resources/shaders/particle.gsh.disabled
diff --git a/game/client/src/main/resources/shaders/particle.vsh b/game/client/src/main/resources/shaders/particle.vsh
index a23f94354..8e00c2704 100644
--- a/game/client/src/main/resources/shaders/particle.vsh
+++ b/game/client/src/main/resources/shaders/particle.vsh
@@ -1,18 +1,23 @@
 #version 330 core
-layout (location = 0) in vec3 aPos;
-layout (location = 1) in float aScale;
-layout (location = 2) in vec4 aColor;
-layout (location = 3) in vec2 aUv;
-layout (location = 4) in vec2 aUvSizes;
-layout (location = 5) in float aLightmap;
+layout (location = 0) in vec2 aMesh;
+layout (location = 1) in vec3 aPos;
+layout (location = 2) in float aScale;
+layout (location = 3) in vec4 aColor;
+layout (location = 4) in vec2 aUv;
+layout (location = 5) in vec2 aUvSizes;
+layout (location = 6) in float aLightmap;
 
-out VS_OUT {
-    float scale;
-    vec4 color;
-    vec2 uv;
-    vec2 uvSizes;
-    vec2 lightmap;
-} vs_out;
+uniform mat4 model;
+layout (std140) uniform Matrices {
+    mat4 projection;
+    mat4 projectionInv;
+    mat4 view;
+    mat4 viewInv;
+} matrices;
+
+out vec4 Color;
+out vec2 TexCoord;
+out vec2 LightMapCoord;
 
 vec2 unpackLightCoord() {
     int asInt = int(aLightmap);
@@ -20,11 +25,9 @@ vec2 unpackLightCoord() {
 }
 
 void main() {
-    gl_Position = vec4(aPos, 1);
+    Color = aColor;
+    LightMapCoord = unpackLightCoord();
 
-    vs_out.scale = aScale;
-    vs_out.color = aColor;
-    vs_out.uv = aUv;
-    vs_out.uvSizes = aUvSizes;
-    vs_out.lightmap = unpackLightCoord();
+    TexCoord = aUv + (((aMesh * vec2(1, -1)) + vec2(1, 1))/2.0) * aUvSizes;
+    gl_Position = matrices.projection * matrices.view * model * (vec4(aPos, 1) + matrices.viewInv * vec4(aMesh.x,  aMesh.y, 0.0, 1.0) * aScale);
 }
diff --git a/game/core/src/main/java/net/minecraft/core/achievement/Achievements.java b/game/core/src/main/java/net/minecraft/core/achievement/Achievements.java
index 1fa32fe63..9e05ec69e 100644
--- a/game/core/src/main/java/net/minecraft/core/achievement/Achievements.java
+++ b/game/core/src/main/java/net/minecraft/core/achievement/Achievements.java
@@ -149,6 +149,13 @@ public abstract class Achievements
         new Achievement(NamespaceID.fromPool("minecraft", "fast_travel"), "fastTravel", Blocks.GRASS, ENTER_NETHER)
             .setType(Achievement.TYPE_NORMAL)
             .registerAchievement();
+    public static final Achievement GET_STEEL_BLAST_FURNACE =
+        new Achievement(NamespaceID.fromPool("minecraft", "blast_furnace"), "blastFurnace", Blocks.FURNACE_BLAST_ACTIVE, Achievements.GET_NETHERCOAL)
+            .setType(Achievement.TYPE_SPECIAL)
+            .registerAchievement();
+    public static final Achievement OBTAIN_STEEL =
+        new Achievement(NamespaceID.fromPool("minecraft", "obtain_steel"), "obtainSteel", Items.INGOT_STEEL, GET_STEEL_BLAST_FURNACE)
+            .registerAchievement();
 
     public static void init(){};
 }
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 bde66452d..62b5b9bcf 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
@@ -25,6 +25,7 @@ import net.minecraft.core.util.phys.BoundingVolume;
 import net.minecraft.core.util.phys.HitResult;
 import net.minecraft.core.world.World;
 import net.minecraft.core.world.WorldSource;
+import net.minecraft.core.world.particle.ParticleEmitterRange;
 import net.minecraft.core.world.pos.TilePosc;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
@@ -191,10 +192,39 @@ public class BlockLogic implements BlockInterface, IItemConvertible {
     }
 
     @Override
-    public void onPlacedByWorld(final @NotNull World world, final @NotNull TilePosc tilePos) { }
+    public void onPlacedByWorld(final @NotNull World world, final @NotNull TilePosc tilePos) {
+        if (world.isClientSide) {
+            ParticleEmitterRange t = this.getParticleEmitterRange();
+            if (t != null) {
+                world.particleEmitters.add(tilePos, t);
+            }
+        }
+    }
+
+    @Override
+    public void onRemoved(final @NotNull World world, final @NotNull TilePosc tilePos, final int data) {
+        if (world.isClientSide) {
+            ParticleEmitterRange t = this.getParticleEmitterRange();
+            if (t != null) {
+                world.particleEmitters.remove(tilePos);
+            }
+        }
+    }
 
     @Override
-    public void onRemoved(final @NotNull World world, final @NotNull TilePosc tilePos, final int data) { }
+    public void onPlacedByMob(final @NotNull World world, final @NotNull TilePosc tilePos, final @NotNull Side side, final @NotNull Mob mob, final double xHit, final double yHit) {
+        this.onPlacedOnSide(world, tilePos, side, xHit, yHit);
+
+        if (world.isClientSide) {
+            ParticleEmitterRange t = this.getParticleEmitterRange();
+            if (t != null) {
+                world.particleEmitters.add(tilePos, t);
+            }
+        }
+    }
+
+    @Override
+    public void onPlacedOnSide(final @NotNull World world, final @NotNull TilePosc tilePos, final @NotNull Side side, final double xHit, final double yHit) { }
 
     @Override
     public void onDestroyedByPlayer(final @NotNull World world, final @NotNull TilePosc tilePos, final @NotNull Side side, final int data, final @NotNull Player player, final @Nullable Item item) { }
@@ -438,14 +468,6 @@ public class BlockLogic implements BlockInterface, IItemConvertible {
         return true;
     }
 
-    @Override
-    public void onPlacedByMob(final @NotNull World world, final @NotNull TilePosc tilePos, final @NotNull Side side, final @NotNull Mob mob, final double xHit, final double yHit) {
-        this.onPlacedOnSide(world, tilePos, side, xHit, yHit);
-    }
-
-    @Override
-    public void onPlacedOnSide(final @NotNull World world, final @NotNull TilePosc tilePos, final @NotNull Side side, final double xHit, final double yHit) { }
-
     @Override
     public @NotNull String getLanguageKey(final int data) {
         return this.block.getKey();
@@ -493,4 +515,8 @@ public class BlockLogic implements BlockInterface, IItemConvertible {
     public @NotNull ItemStack getDefaultStack() {
         return this.block.getDefaultStack();
     }
+
+    public @Nullable ParticleEmitterRange getParticleEmitterRange() {
+        return null; // null by default, 16 block radius
+    }
 }
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicAsh.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicAsh.java
index 8db54e008..054fea3df 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicAsh.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicAsh.java
@@ -17,9 +17,6 @@ public class BlockLogicAsh
 
     @Override
     public ItemStack[] getBreakResult(final @NotNull World world, final @NotNull EnumDropCause dropCause, final int data, final @Nullable TileEntity tileEntity) {
-        return switch (dropCause) {
-            case PICK_BLOCK, SILK_TOUCH -> new ItemStack[] { new ItemStack(this) };
-            default -> null;
-        };
+        return new ItemStack[] { new ItemStack(this) };
     }
 }
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicBonepile.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicBonePile.java
similarity index 80%
rename from game/core/src/main/java/net/minecraft/core/block/BlockLogicBonepile.java
rename to game/core/src/main/java/net/minecraft/core/block/BlockLogicBonePile.java
index 00bc1c4fc..84c3ebcb3 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicBonepile.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicBonePile.java
@@ -10,10 +10,10 @@ import net.minecraft.core.world.pos.TilePosc;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
-public class BlockLogicBonepile
+public class BlockLogicBonePile
     extends BlockLogicFlower {
 
-    public BlockLogicBonepile(final @NotNull Block<?> block) {
+    public BlockLogicBonePile(final @NotNull Block<?> block) {
         super(block);
         final float f = 0.4F;
         this.setBlockBounds(0.5F - f, 0.0F, 0.5F - f, 0.5F + f, 0.8F, 0.5F + f);
@@ -31,13 +31,14 @@ public class BlockLogicBonepile
 
     @Override
     public @NotNull ItemStack @Nullable [] getBreakResult(final @NotNull World world, final @NotNull EnumDropCause dropCause, final int meta, final @Nullable TileEntity tileEntity) {
-        final int quantity = world.rand.nextInt(3) + 1;
-
         return switch (dropCause) {
             case SILK_TOUCH, PICK_BLOCK -> new ItemStack[] { new ItemStack(this) };
             default -> {
-                if (quantity == 0 || world.rand.nextInt(8) != 0) yield null;
-                yield new ItemStack[] { new ItemStack(Items.BONE, quantity) };
+                int dropChance = 5;
+                if (world.rand.nextInt(dropChance) == 0) {
+                    yield new ItemStack[] { new ItemStack(Items.BONE, 1) };
+                }
+                yield null;
             }
         };
     }
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicBoulderMagmatic.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicBoulderMagmatic.java
new file mode 100644
index 000000000..4c551034a
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicBoulderMagmatic.java
@@ -0,0 +1,43 @@
+package net.minecraft.core.block;
+
+import net.minecraft.core.block.entity.TileEntity;
+import net.minecraft.core.entity.Entity;
+import net.minecraft.core.enums.EnumDropCause;
+import net.minecraft.core.item.ItemStack;
+import net.minecraft.core.world.World;
+import net.minecraft.core.world.pos.TilePos;
+import net.minecraft.core.world.pos.TilePosc;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+public class BlockLogicBoulderMagmatic extends BlockLogicFlower {
+	public BlockLogicBoulderMagmatic(@NotNull Block<?> block) {
+		super(block);
+		this.setBlockBounds(0.1F, 0.0F, 0.1F, 0.9F, 0.8F, 0.9F);
+	}
+
+	@Override
+	public void onEntityCollision(@NotNull World world, @NotNull TilePosc tilePos, @NotNull Entity entity) {
+		entity.maxFireTicks = 100;
+		entity.remainingFireTicks = 100;
+		entity.activeFireBlock = Blocks.FIRE;
+	}
+
+	@Override
+	public boolean canStay(@NotNull World world, @NotNull TilePosc tilePos) {
+		return this.mayPlaceOn(world.getBlockType(tilePos.down(new TilePos())));
+	}
+
+	@Override
+	public boolean mayPlaceOn(@NotNull Block<?> block) {
+		return block.isSolidRender();
+	}
+
+	@Override
+	public @NotNull ItemStack @Nullable [] getBreakResult(@NotNull World world, @NotNull EnumDropCause dropCause, int meta, @Nullable TileEntity tileEntity) {
+		return switch (dropCause) {
+			case SILK_TOUCH, PICK_BLOCK -> new ItemStack[]{new ItemStack(this)};
+			default -> null;
+		};
+	}
+}
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicBoulderSulfuric.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicBoulderSulfuric.java
new file mode 100644
index 000000000..5fcbe3029
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicBoulderSulfuric.java
@@ -0,0 +1,20 @@
+package net.minecraft.core.block;
+
+import net.minecraft.core.entity.Entity;
+import net.minecraft.core.world.World;
+import net.minecraft.core.world.pos.TilePosc;
+import org.jetbrains.annotations.NotNull;
+
+public class BlockLogicBoulderSulfuric extends BlockLogicBoulderMagmatic {
+	public BlockLogicBoulderSulfuric(@NotNull Block<?> block) {
+		super(block);
+		this.setBlockBounds(0.1F, 0.0F, 0.1F, 0.9F, 0.8F, 0.9F);
+	}
+
+	@Override
+	public void onEntityCollision(@NotNull World world, @NotNull TilePosc tilePos, @NotNull Entity entity) {
+		entity.maxFireTicks = 100;
+		entity.remainingFireTicks = 100;
+		entity.activeFireBlock = Blocks.FIRE_SULFURIC;
+	}
+}
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicBrimstone.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicBrimstone.java
new file mode 100644
index 000000000..d44d73ab5
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicBrimstone.java
@@ -0,0 +1,20 @@
+package net.minecraft.core.block;
+
+import net.minecraft.core.block.entity.TileEntity;
+import net.minecraft.core.block.material.Material;
+import net.minecraft.core.enums.EnumDropCause;
+import net.minecraft.core.item.ItemStack;
+import net.minecraft.core.world.World;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+public class BlockLogicBrimstone extends BlockLogic {
+	public BlockLogicBrimstone(@NotNull Block<?> block, @NotNull Material material) {
+		super(block, material);
+	}
+
+	@Override
+	public ItemStack @Nullable [] getBreakResult(@NotNull World world, @NotNull EnumDropCause dropCause, int data, @Nullable TileEntity tileEntity) {
+		return dropCause == EnumDropCause.PISTON_CRUSH ? new ItemStack[]{new ItemStack(Blocks.BRIMSAND, world.rand.nextInt(2) + 1)} : super.getBreakResult(world, dropCause, data, tileEntity);
+	}
+}
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicBrimthaw.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicBrimthaw.java
new file mode 100644
index 000000000..a8884933e
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicBrimthaw.java
@@ -0,0 +1,50 @@
+package net.minecraft.core.block;
+
+import net.minecraft.core.block.material.Material;
+import net.minecraft.core.block.material.Materials;
+import net.minecraft.core.entity.Entity;
+import net.minecraft.core.entity.EntityItem;
+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;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Random;
+
+public class BlockLogicBrimthaw extends BlockLogicMagma {
+
+    public BlockLogicBrimthaw(@NotNull Block<?> block) {
+        super(block);
+    }
+
+    @Override
+    public boolean canMelt(@NotNull World world, @NotNull TilePosc tilePos) {
+        boolean canMelt = false;
+        final TilePos queryPos = new TilePos();
+        for (final Direction dir : Direction.all) {
+            final Block<?> block = world.getBlockType(tilePos.add(dir, queryPos));
+            final Material adjacentMaterial = block.getMaterial();
+            if (adjacentMaterial == Materials.WATER) {
+                return false;
+            }
+            canMelt |= adjacentMaterial == Materials.ACID;
+        }
+        return canMelt;
+    }
+
+    @Override
+    public void onEntityCollision(@NotNull World world, @NotNull TilePosc tilePos, @NotNull Entity entity) {
+        if (entity instanceof EntityItem) return; // Don't burn items for this block
+        entity.fireHurt(Blocks.FIRE_SULFURIC);
+    }
+
+    @Override
+    public void updateTick(@NotNull World world, @NotNull TilePosc tilePos, @NotNull Random rand, boolean isRandomTick) {
+        if (canMelt(world, tilePos)) {
+            world.setBlockTypeNotify(tilePos, Blocks.FLUID_ACID_FLOWING);
+        }
+    }
+
+}
+
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicEmber.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicEmber.java
new file mode 100644
index 000000000..31c37986c
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicEmber.java
@@ -0,0 +1,74 @@
+package net.minecraft.core.block;
+
+import net.minecraft.core.block.material.Material;
+import net.minecraft.core.entity.Entity;
+import net.minecraft.core.entity.animal.MobWolf;
+import net.minecraft.core.entity.player.Player;
+import net.minecraft.core.util.helper.DamageType;
+import net.minecraft.core.util.phys.HitResult;
+import net.minecraft.core.world.World;
+import net.minecraft.core.world.pos.TilePos;
+import net.minecraft.core.world.pos.TilePosc;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Random;
+
+public class BlockLogicEmber extends BlockLogic {
+	public BlockLogicEmber(@NotNull Block<?> block, @NotNull Material material) {
+		super(block, material);
+	}
+
+	@Override
+	public void onEntityCollision(@NotNull World world, @NotNull TilePosc tilePos, @NotNull Entity entity) {
+		if(entity instanceof Player || entity instanceof MobWolf){
+			this.ignite(world, tilePos, entity);
+		}
+	}
+
+	@Override
+	public void onEntityWalkedOn(@NotNull World world, @NotNull TilePosc tilePos, @NotNull Entity entity) {
+		if(entity instanceof Player || entity instanceof MobWolf){ // We do a little trolling here...
+			this.ignite(world, tilePos, entity);
+		}
+	}
+
+	public void ignite(@NotNull World world, @NotNull TilePosc tilePos, Entity entity) {
+		if (entity != null) {
+			entity.hurt(null, 8, DamageType.BLAST);
+			entity.remainingFireTicks = 100;
+			entity.fling(1.0f, 1.0f, 1.0f, 0.0f);
+		}
+
+		world.setBlockType(tilePos, Blocks.AIR);
+		world.createExplosion(null, tilePos.x(), tilePos.y(), tilePos.z(), 3.0F, true, false);
+	}
+
+	@Override
+	public void animationTick(@NotNull World world, @NotNull TilePosc tilePos, @NotNull Random rand) {
+		double xPos = (double) tilePos.x() + rand.nextDouble();
+		double zPos = (double) tilePos.z() + rand.nextDouble();
+
+		TilePos blockAboveTilePos = new TilePos(tilePos.x(), tilePos.y() + 1, tilePos.z());
+
+		Block<?> block = world.getBlockType(blockAboveTilePos);
+		double yPos = tilePos.y() + 1.1D;
+
+		boolean isAir = block == Blocks.AIR;
+		boolean isAshLayer = block == Blocks.LAYER_ASH;
+
+		if (isAshLayer) {
+			int data = world.getBlockData(blockAboveTilePos) & BlockLogicLayerBase.MASK_HEIGHT;
+			int layers = data + 1;
+			yPos += layers * 0.125D;
+		}
+
+		if (isAir || isAshLayer) {
+			if (rand.nextInt(3) == 0) {
+				world.spawnParticle("flame", xPos, yPos, zPos, 0.0F, 0.01F, 0.0F, 0, false);
+				world.spawnParticle("largesmoke", xPos, yPos + 0.30, zPos, 0.0F, 0.02F, 0.0F, 0, false);
+				world.spawnParticle("largesmoke", xPos, yPos + 0.30, zPos, 0.0F, 0.01F, 0.0F, 0, false);
+			}
+			world.spawnParticle("largesmoke", xPos, yPos + 0.30, zPos, 0.0F, 0.015F, 0.0F, 0, false);
+		}
+	}
+}
\ No newline at end of file
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 593ab4942..9372320e5 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,6 +6,10 @@ 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.enums.EnumDropCause;
@@ -122,7 +126,15 @@ public class BlockLogicFire
     }
 
     public void setBurnResult(final @NotNull World world, final @NotNull TilePosc tilePos) {
-        world.setBlockTypeNotify(tilePos, this.getBurnResult(world, tilePos));
+        final Block<?> currentBlock = world.getBlockType(tilePos);
+        final int currentData = world.getBlockData(tilePos);
+        final Block<?> resultBlock = this.getBurnResult(world, tilePos);
+
+        if (resultBlock != Blocks.AIR && resultBlock != this.block) {
+            world.setBlockTypeDataNotify(tilePos, resultBlock, currentData);
+        } else {
+            world.setBlockTypeNotify(tilePos, resultBlock);
+        }
     }
 
     @Override
@@ -130,7 +142,7 @@ public class BlockLogicFire
         TilePos queryPos = new TilePos();
 
         final @Nullable Block<?> blockUnder = world.getBlockType(tilePos.down(queryPos));
-        final boolean infiniBurn = blockUnder != null && blockUnder.hasTag(BlockTags.INFINITE_BURN);
+        final boolean infiniBurn = blockUnder.hasTag(this.getInfiniteBurnTag());
 
         // Extinguish fire
         if (!this.isValidFireLocation(world, tilePos)) {
@@ -203,8 +215,7 @@ public class BlockLogicFire
 
                     if (world.getGameRuleValue(GameRules.DO_FIRE_SPREAD) && this.getBurnResult(world, localPos) == Blocks.AIR /* Fix for fire destroying moss blocks */) {
                         //Spread Fire with higher meta
-                        world.setBlockTypeDataNotify(localPos, this.block, Math.min(meta + rand.nextInt(5) / 4, 15));
-                    }
+                        world.setBlockTypeDataNotify(localPos, this.getSpreadFireBlock(), Math.min(meta + rand.nextInt(5) / 4, 15));}
                 }
             }
         }
@@ -218,8 +229,7 @@ public class BlockLogicFire
             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.block, Math.min(meta + random.nextInt(5) / 4, 15));
-                }
+                    world.setBlockTypeDataNotify(tilePos, this.getSpreadFireBlock(), Math.min(meta + random.nextInt(5) / 4, 15));}
             } else {
                 this.setBurnResult(world, tilePos);
             }
@@ -281,12 +291,15 @@ public class BlockLogicFire
     @Override
     public void onNeighborChanged(final @NotNull World world, final @NotNull TilePosc tilePos, final @NotNull Block<?> block) {
         if (!world.isBlockNormalCube(tilePos.down(new TilePos())) && !this.canNeighborCatchFire(world, tilePos)) {
-
-            world.setBlockTypeNotify(tilePos, this.getBurnResult(world, tilePos));
+            this.setBurnResult(world, tilePos);
         }
     }
 
     protected @NotNull Block<?> getBurnResult(final @NotNull World world, final @NotNull TilePosc tilePos) {
+        Block<?> blockAtPos = world.getBlockType(tilePos);
+        int blockDataAtPos = world.getBlockData(tilePos);
+        if (blockAtPos.getLogic() instanceof BlockLogicLog) return Blocks.LOG_SCORCHED;
+
         final @NotNull Block<?> block = world.getBlockType(tilePos);
         final @Nullable Block<?> noMossBlock = BlockLogicMoss.getNoMossBlock(block);
         return Objects.requireNonNullElse(noMossBlock, Blocks.AIR);
@@ -299,7 +312,7 @@ public class BlockLogicFire
             return;
         }
         if (!world.isBlockNormalCube(tilePos.down(queryPos)) && !this.canNeighborCatchFire(world, tilePos)) {
-            world.setBlockTypeNotify(tilePos, this.getBurnResult(world, tilePos));
+            this.setBurnResult(world, tilePos);
         } else {
             world.scheduleBlockUpdate(tilePos, this.block, this.tickDelay());
         }
@@ -364,4 +377,17 @@ public class BlockLogicFire
             }
         }
     }
+
+    protected Tag<Block<?>> getInfiniteBurnTag() {
+        return BlockTags.INFINITE_BURN;
+    }
+
+    protected Block<?> getSpreadFireBlock() {
+        return this.block;
+    }
+
+    @Override
+    public void onEntityCollision(final @NotNull World world, final @NotNull TilePosc tilePos, final @NotNull Entity entity) {
+        entity.activeFireBlock = this.block;
+    }
 }
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicFireCold.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicFireCold.java
index 3b48a1e51..6a8b7b25c 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicFireCold.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicFireCold.java
@@ -1,59 +1,22 @@
 package net.minecraft.core.block;
 
 import net.minecraft.core.block.tag.BlockTags;
-import net.minecraft.core.world.World;
-import net.minecraft.core.world.pos.TilePos;
-import net.minecraft.core.world.pos.TilePosc;
-import net.minecraft.core.world.weather.IPrecipitation;
+import net.minecraft.core.data.tag.Tag;
 import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
 
-import java.util.Random;
+public class BlockLogicFireCold extends BlockLogicFire {
 
-public class BlockLogicFireCold
-    extends BlockLogicFire {
     public BlockLogicFireCold(final @NotNull Block<?> block) {
         super(block);
     }
 
     @Override
-    public void updateTick(final @NotNull World world, final @NotNull TilePosc tilePos, final @NotNull Random rand, final boolean isRandomTick) {
-        TilePos queryPos = new TilePos();
-        final @Nullable Block<?> blockUnder = world.getBlockType(tilePos.down(queryPos));
-        final boolean infiniBurn = blockUnder != null && blockUnder.hasTag(BlockTags.INFINITE_BURN_COLD);
-
-        // Extinguish fire
-        if (!this.isValidFireLocation(world, tilePos)) {
-            this.setBurnResult(world, tilePos);
-        }
-
-        // Extinguish fire (Rain / Snow)
-        if (!infiniBurn && world.getCurrentWeather() != null && world.getCurrentWeather() instanceof IPrecipitation && (
-            world.isBlockBeingRainedOn(tilePos)
-                || world.isBlockBeingRainedOn(tilePos.west(queryPos))
-                || world.isBlockBeingRainedOn(tilePos.east(queryPos))
-                || world.isBlockBeingRainedOn(tilePos.north(queryPos))
-                || world.isBlockBeingRainedOn(tilePos.south(queryPos)))) {
-            this.setBurnResult(world, tilePos);
-            return;
-        }
-
-        final int meta = world.getBlockData(tilePos);
-        if (meta < 15) {
-            world.setBlockData(tilePos, meta + rand.nextInt(3) / 2);
-        }
-        world.scheduleBlockUpdate(tilePos, this.block, this.tickDelay());
-
-
-        if (!infiniBurn && !this.canNeighborCatchFire(world, tilePos)) {
-            if (!world.isBlockNormalCube(tilePos.down(queryPos)) || meta > 3) {
-                this.setBurnResult(world, tilePos);
-            }
-            return;
-        }
+    protected Tag<Block<?>> getInfiniteBurnTag() {
+        return BlockTags.INFINITE_BURN_COLD;
+    }
 
-        if (!infiniBurn && !canBurn(world, tilePos.down(queryPos)) && meta == 15 && rand.nextInt(4) == 0) {
-            this.setBurnResult(world, tilePos);
-        }
+    @Override
+    protected Block<?> getSpreadFireBlock() {
+        return Blocks.FIRE;
     }
-}
+}
\ No newline at end of file
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicFireSulfuric.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicFireSulfuric.java
new file mode 100644
index 000000000..3e8eeb4c3
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicFireSulfuric.java
@@ -0,0 +1,25 @@
+package net.minecraft.core.block;
+
+import net.minecraft.core.block.tag.BlockTags;
+import net.minecraft.core.data.tag.Tag;
+import net.minecraft.core.entity.Entity;
+import net.minecraft.core.world.World;
+import net.minecraft.core.world.pos.TilePosc;
+import org.jetbrains.annotations.NotNull;
+
+public class BlockLogicFireSulfuric extends BlockLogicFire {
+
+    public BlockLogicFireSulfuric(final @NotNull Block<?> block) {
+        super(block);
+    }
+
+    @Override
+    protected Tag<Block<?>> getInfiniteBurnTag() {
+        return BlockTags.INFINITE_BURN_SULFURIC;
+    }
+
+    @Override
+    protected Block<?> getSpreadFireBlock() {
+        return Blocks.FIRE_SULFURIC;
+    }
+}
\ No newline at end of file
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicFlowstone.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicFlowstone.java
deleted file mode 100644
index 860092d4b..000000000
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicFlowstone.java
+++ /dev/null
@@ -1,143 +0,0 @@
-package net.minecraft.core.block;
-
-import net.minecraft.core.block.material.Materials;
-import net.minecraft.core.block.tag.BlockTags;
-import net.minecraft.core.entity.EntityFallingBlock;
-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.WorldSource;
-import net.minecraft.core.world.pos.TilePos;
-import net.minecraft.core.world.pos.TilePosc;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-import org.joml.primitives.AABBd;
-import org.joml.primitives.AABBdc;
-
-import java.util.Random;
-
-public class BlockLogicFlowstone extends BlockLogic {
-    public BlockLogicFlowstone(final @NotNull Block<?> block) {
-        super(block, Materials.STONE);
-    }
-
-    @Override
-    public int getPlacedData(final @Nullable Player player, final @NotNull ItemStack itemStack, final @NotNull World world, final @NotNull TilePosc tilePos, final @NotNull Side side, final double xHit, final double yHit) {
-        return itemStack.getMetadata();
-    }
-
-    @Override
-    public @NotNull AABBdc getBoundsFromState(final @NotNull WorldSource source, final @NotNull TilePosc tilePos) {
-        final int data = source.getBlockData(tilePos);
-
-        if (data == 0 || data > 3) {
-            return new AABBd(0.0, 0.0, 0.0, 1.0, 1.0, 1.0);
-        } else {
-            final double offset = 2.0 * data;
-            return new AABBd(offset / 16.0, 0.0, offset / 16.0, (16.0 - offset) / 16.0, 1.0, (16.0 - offset) / 16.0);
-        }
-    }
-
-    @Override
-    public boolean isSolidRender() {
-        return false;
-    }
-
-    @Override
-    public boolean isCubeShaped() {
-        return false;
-    }
-
-    @Override
-    public void onPlacedByWorld(final @NotNull World world, final @NotNull TilePosc tilePos) {
-        world.scheduleBlockUpdate(tilePos, this.block, this.tickDelay());
-    }
-
-    @Override
-    public void onNeighborChanged(final @NotNull World world, final @NotNull TilePosc tilePos, final @NotNull Block<?> block) {
-        world.scheduleBlockUpdate(tilePos, this.block, this.tickDelay());
-    }
-
-    @Override
-    public void updateTick(final @NotNull World world, final @NotNull TilePosc tilePos, final @NotNull Random rand, final boolean isRandomTick) {
-        this.tryToFall(world, tilePos);
-    }
-
-    private void tryToFall(final @NotNull World world, final @NotNull TilePosc tilePos) {
-        boolean topCanFall = false;
-        boolean bottomCanFall = false;
-        int y1 = -1;
-        int y0 = -1;
-
-        final @NotNull TilePos queryPos = new TilePos();
-
-        // Check if top block of stack is unsupported
-        for (int y = tilePos.y(); y < world.getHeightBlocks(); y++) {
-            queryPos.set(tilePos.x(), y + 1, tilePos.z());
-            if (canFall(world, queryPos)) {
-                topCanFall = true;
-                y1 = y;
-                break;
-            } else if (world.getBlockType(queryPos).getLogic() != this) {
-                return;
-            }
-        }
-
-        if (!topCanFall) {
-            return;
-        }
-
-        // Check if bottom block of stack is unsupported
-        for (int y = tilePos.y(); y > 0; y--) {
-            queryPos.set(tilePos.x(), y - 1, tilePos.z());
-            if (canFall(world, queryPos)) {
-                bottomCanFall = true;
-                y0 = y;
-                break;
-            } else if (world.getBlockType(queryPos).getLogic() != this) {
-                return;
-            }
-        }
-
-        if (!bottomCanFall) {
-            return;
-        }
-
-        // Make stack fall
-        for (int y = y0; y <= y1; y++) {
-            queryPos.set(tilePos.x(), y, tilePos.z());
-            final int data = world.getBlockData(queryPos);
-            final @NotNull EntityFallingBlock entity = new EntityFallingBlock(world, tilePos.x() + 0.5F, y + 0.5F, tilePos.z() + 0.5F, this.block.id(), data, null);
-            world.entityJoinedWorld(entity);
-        }
-    }
-
-    @Override
-    public int tickDelay() {
-        return 3;
-    }
-
-    @Override
-    public void animationTick(final @NotNull World world, final @NotNull TilePosc tilePos, final @NotNull Random rand) {
-        final int data = world.getBlockData(tilePos);
-
-        if (world.getBlockType(tilePos.down(new TilePos())) == Blocks.AIR && rand.nextInt(20) == 0) {
-            final double scale = 1.0 - (2.0 / 16.0) * data;
-            final double cx = (tilePos.x() + 0.5) - scale / 2.0;
-            final double cz = (tilePos.z() + 0.5) - scale / 2.0;
-            final double dx = rand.nextDouble();
-            final double dz = rand.nextDouble();
-
-            world.spawnParticle("dripWater", cx + scale * dx, tilePos.y() - 0.05f, cz + scale * dz, 0.0, 0.0, 0.0, 0, false);
-        }
-    }
-
-    private static boolean canFall(final @NotNull World world, final @NotNull TilePosc tilePos) {
-        final @NotNull Block<?> block = world.getBlockType(tilePos);
-        if (block == Blocks.FLOWSTONE) {
-            return false;
-        }
-        return block.hasTag(BlockTags.PLACE_OVERWRITES);
-    }
-}
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicFluid.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicFluid.java
index 89f9f5c6c..01e71607e 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicFluid.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicFluid.java
@@ -263,4 +263,19 @@ public abstract class BlockLogicFluid extends BlockLogic {
     public boolean isFullBlock(@NotNull World world, TilePos tilePos){
         return getFluidHeight(world, tilePos) == 1;
     }
+
+    public static boolean isAcid(@NotNull World world, TilePos pos) {
+        Block<?> block = world.getBlockType(pos);
+        return block == Blocks.FLUID_ACID_STILL || block == Blocks.FLUID_ACID_FLOWING;
+    }
+
+    public static boolean isWater(@NotNull World world, TilePos pos) {
+        Block<?> block = world.getBlockType(pos);
+        return block == Blocks.FLUID_WATER_STILL || block == Blocks.FLUID_WATER_FLOWING;
+    }
+
+    public static boolean isLava(@NotNull World world, TilePos pos) {
+        Block<?> block = world.getBlockType(pos);
+        return block == Blocks.FLUID_LAVA_STILL || block == Blocks.FLUID_LAVA_FLOWING;
+    }
 }
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicGrowthRubyglass.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicGrowthRubyglass.java
new file mode 100644
index 000000000..1c9369df2
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicGrowthRubyglass.java
@@ -0,0 +1,226 @@
+package net.minecraft.core.block;
+
+import net.minecraft.core.block.entity.TileEntity;
+import net.minecraft.core.block.material.Material;
+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.Side;
+import net.minecraft.core.world.LevelListener;
+import net.minecraft.core.world.World;
+import net.minecraft.core.world.WorldSource;
+import net.minecraft.core.world.pos.TilePos;
+import net.minecraft.core.world.pos.TilePosc;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.joml.primitives.AABBd;
+import org.joml.primitives.AABBdc;
+
+public class BlockLogicGrowthRubyglass extends BlockLogic {
+
+	public static final int DIRECTION_MASK = 0b0000_0111;
+	public static final int SHAPE_MASK = 0b0001_1000;
+
+	public static final int STATE_BUD = 0b0000_0000;
+	public static final int STATE_BOTTOM = 0b0000_1000;
+	public static final int STATE_TOP = 0b0001_0000;
+
+	public BlockLogicGrowthRubyglass(final @NotNull Block<?> block, final @NotNull Material material) {
+		super(block, material);
+	}
+
+	@Override
+	public @Nullable AABBdc getCollisionAABB(final @NotNull WorldSource source, final @NotNull TilePosc tilePos) {
+		int data = source.getBlockData(tilePos);
+		int shape = data & SHAPE_MASK;
+		if(shape == STATE_BUD) return null;
+		else return super.getCollisionAABB(source, tilePos);
+	}
+
+	@Override
+	public boolean isSolidRender() {
+		return false;
+	}
+
+	@Override
+	public boolean isCubeShaped() {
+		return false;
+	}
+
+	@Override
+	public void onNeighborChanged(final @NotNull World world, final @NotNull TilePosc pos, final @NotNull Block<?> neighborBlock) {
+		int data = world.getBlockData(pos);
+		int shape = data & SHAPE_MASK;
+		Direction dir = Direction.fromId(data & DIRECTION_MASK);
+
+		if (!this.canStay(world, pos)) {
+			this.dropWithCause(world, EnumDropCause.IMPROPER_TOOL, pos, data, null, null);
+			world.playBlockEvent(pos, LevelListener.EVENT_BLOCK_BREAK, id());
+			world.setBlockTypeNotify(pos, Blocks.AIR);
+			return;
+		}
+
+		if (shape == STATE_BOTTOM) {
+			TilePos topPos = pos.add(dir, new TilePos());
+			int topMeta = world.getBlockData(topPos);
+			if (world.getBlockType(topPos) != this.block || (topMeta & SHAPE_MASK) != STATE_TOP) {
+				world.setBlockTypeDataNotify(pos, this.block, STATE_BUD | dir.id);
+			}
+		}
+	}
+
+	@Override
+	public boolean canPlaceOnSide(final @NotNull World world, final @NotNull TilePosc tilePos, final @NotNull Side side) {
+		TilePos anchorPos = tilePos.add(side.direction().opposite(), new TilePos());
+		Block<?> anchorBlock = world.getBlockType(anchorPos);
+
+		if (anchorBlock == this.block) {
+			int anchorMeta = world.getBlockData(anchorPos);
+			int anchorShape = anchorMeta & SHAPE_MASK;
+
+			if (anchorShape == STATE_BUD) {
+				Direction budDir = Direction.fromId(anchorMeta & DIRECTION_MASK);
+
+				if (budDir == side.direction()) return true;
+
+				if (world.getBlockType(tilePos) == Blocks.AIR) {
+					TilePos growthPos = anchorPos.add(budDir, new TilePos());
+                    return world.getBlockType(growthPos) == Blocks.AIR;
+				}
+				return false;
+			}
+		}
+
+		return world.isBlockNormalCube(anchorPos);
+	}
+
+	@Override
+	public boolean canPlaceAt(final @NotNull World world, final @NotNull TilePosc tilePos) {
+		if (!super.canPlaceAt(world, tilePos)) return false;
+
+		for (Side side : Side.values()) {
+			TilePos anchorPos = tilePos.add(side.direction().opposite(), new TilePos());
+
+			if (world.isBlockNormalCube(anchorPos)) return true;
+
+			if (world.getBlockType(anchorPos) == this.block) {
+				int meta = world.getBlockData(anchorPos);
+				int shape = meta & SHAPE_MASK;
+				Direction dir = Direction.fromId(meta & DIRECTION_MASK);
+
+				if (shape == STATE_BUD) {
+					if (dir == side.direction()) return true;
+
+					if (world.getBlockType(tilePos) == Blocks.AIR) {
+						TilePos growthPos = anchorPos.add(dir, new TilePos());
+						if (world.getBlockType(growthPos) == Blocks.AIR) {
+							return true;
+						}
+					}
+				}
+			}
+		}
+		return false;
+	}
+
+	@Override
+	public void onPlacedOnSide(@NotNull World world, @NotNull TilePosc tilePos, @NotNull Side side, double xHit, double yHit) {
+		TilePos clickedAnchor = tilePos.add(side.direction().opposite(), new TilePos());
+
+		if (world.getBlockType(clickedAnchor) == this.block) {
+			int data = world.getBlockData(clickedAnchor);
+			if ((data & SHAPE_MASK) == STATE_BUD) {
+				Direction budDir = Direction.fromId(data & DIRECTION_MASK);
+				TilePos growthPos = clickedAnchor.add(budDir, new TilePos());
+
+				if (!tilePos.equals(growthPos) && world.getBlockType(growthPos) == Blocks.AIR) {
+					world.setBlockTypeNotify(tilePos, Blocks.AIR);
+					world.setBlockData(clickedAnchor, STATE_BOTTOM | (data & DIRECTION_MASK));
+					world.markBlockNeedsUpdate(clickedAnchor);
+					world.setBlockTypeDataNotify(growthPos, this.block, STATE_TOP | (data & DIRECTION_MASK));
+					return;
+				}
+				else if (tilePos.equals(growthPos)) {
+					world.setBlockData(clickedAnchor, STATE_BOTTOM | (data & DIRECTION_MASK));
+					world.markBlockNeedsUpdate(clickedAnchor);
+					world.setBlockTypeDataNotify(tilePos, this.block, STATE_TOP | (data & DIRECTION_MASK));
+					return;
+				}
+			}
+		}
+
+		if (world.isBlockNormalCube(clickedAnchor)) {
+			world.setBlockTypeDataNotify(tilePos, this.block, STATE_BUD | side.direction().id);
+			return;
+		}
+
+		for (Side fallback : Side.values()) {
+			TilePos fallbackAnchor = tilePos.add(fallback.direction.opposite(), new TilePos());
+			if (world.isBlockNormalCube(fallbackAnchor)) {
+				world.setBlockTypeDataNotify(tilePos, this.block, STATE_BUD | fallback.direction.id);
+				return;
+			}
+		}
+	}
+
+	@Override
+	public boolean canStay(final @NotNull World world, final @NotNull TilePosc tilePos) {
+		int data = world.getBlockData(tilePos);
+		int shape = data & SHAPE_MASK;
+		Direction dir = Direction.fromId(data & DIRECTION_MASK);
+
+		if (shape == STATE_TOP) {
+			TilePos bottomPos = tilePos.add(dir.opposite(), new TilePos());
+			return world.getBlockType(bottomPos) == this.block &&
+					(world.getBlockData(bottomPos) & SHAPE_MASK) == STATE_BOTTOM;
+		} else {
+			TilePos anchorPos = tilePos.add(dir.opposite(), new TilePos());
+			return world.isBlockNormalCube(anchorPos);
+		}
+	}
+
+	@Override
+	public @NotNull AABBdc getBoundsFromState(final @NotNull WorldSource source, final @NotNull TilePosc tilePos) {
+		int data = source.getBlockData(tilePos);
+		int shape = data & SHAPE_MASK;
+		Direction dir = Direction.fromId(data & DIRECTION_MASK);
+
+		double height;
+		double min = 5.0 / 16.0;
+		double max = 11.0 / 16.0;
+		if (shape == STATE_TOP) {
+			height = 11.0 / 16.0;
+		} else if (shape == STATE_BOTTOM) {
+			min = 3.0 / 16.0;
+			max = 13.0 / 16.0;
+			height = 1.0;
+		} else {
+			height = 12.0 / 16.0;
+		}
+
+        return switch (dir) {
+            case UP -> new AABBd(min, 0.0, min, max, height, max);                // +Y
+            case DOWN -> new AABBd(min, 1.0 - height, min, max, 1.0, max);  // -Y
+            case SOUTH -> new AABBd(min, min, 0.0, max, max, height);             // +Z
+            case NORTH -> new AABBd(min, min, 1.0 - height, max, max, 1.0); // -Z
+            case EAST -> new AABBd(0.0, min, min, height, max, max);              // +X
+            case WEST -> new AABBd(1.0 - height, min, min, 1.0, max, max);  // -X
+            default -> new AABBd(min, 0.0, min, max, height, max);
+        };
+	}
+
+	@Override
+	public ItemStack @Nullable [] getBreakResult(@NotNull World world, @NotNull EnumDropCause dropCause, int data, @Nullable TileEntity tileEntity) {
+		if (dropCause != EnumDropCause.SILK_TOUCH) {
+			return null;
+		}
+
+		int shape = data & SHAPE_MASK;
+
+		if (shape == STATE_BOTTOM) {
+			return new ItemStack[] { new ItemStack(this.block, 2) };
+		}
+
+		return new ItemStack[] { new ItemStack(this.block, 1) };
+	}
+}
\ No newline at end of file
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 64492b0b8..013332665 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
@@ -2,6 +2,9 @@ package net.minecraft.core.block;
 
 import net.minecraft.core.block.entity.TileEntity;
 import net.minecraft.core.block.material.Material;
+import net.minecraft.core.entity.Entity;
+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;
@@ -14,13 +17,27 @@ import org.jetbrains.annotations.Nullable;
 public class BlockLogicLayerAsh extends BlockLogicLayerBase
 {
 
-	public BlockLogicLayerAsh(Block<? extends BlockLogicLayerBase> block, Block<?> fullBlock, Material material)
+    public BlockLogicLayerAsh(Block<? extends BlockLogicLayerBase> block, Block<?> fullBlock, Material material)
     {
         super(block, fullBlock, material);
         setBlockBounds(0.0F, 0.0F, 0.0F, 1.0F, 0.125F, 1.0F);
         block.setTicking(true);
     }
 
+    @Override
+    public void onEntityWalkedOn(@NotNull World world, @NotNull TilePosc tilePos, @NotNull Entity entity) {
+        TilePos emberPos = tilePos.down(new TilePos());
+
+        if (world.getBlockId(emberPos.x(), emberPos.y(), emberPos.z()) == Blocks.EMBER.id()) {
+            BlockLogic logic = Blocks.EMBER.getLogic();
+            if (logic instanceof BlockLogicEmber) {
+                if(entity instanceof Player || entity instanceof MobWolf) {
+                    ((BlockLogicEmber) logic).ignite(world, emberPos, entity);
+                }
+            }
+        }
+    }
+
     public void accumulate(@NotNull World world, @NotNull TilePosc tilePos) {
         int myMetadata = world.getBlockData(tilePos);
         if (myMetadata == 0x7) return;
@@ -35,20 +52,19 @@ public class BlockLogicLayerAsh extends BlockLogicLayerBase
     }
 
     @Override
-	public boolean canPlaceAt(@NotNull World world, @NotNull TilePosc tilePos) {
+    public boolean canPlaceAt(@NotNull World world, @NotNull TilePosc tilePos) {
         TilePos down = tilePos.down(new TilePos());
         Block<?> b = world.getBlockType(down);
-        if(b == null || !b.isSolidRender()) {
-        	return false;
+        if(!b.isSolidRender()) {
+            return false;
         } else {
-        	Material material = world.getBlockMaterial(down);
+            Material material = world.getBlockMaterial(down);
             return material.blocksMotion();
         }
     }
 
     @Override
     public void onNeighborChanged(@NotNull World world, @NotNull TilePosc tilePos, final @NotNull Block<?> block) {
-        // Remove if no longer valid
         if (!canPlaceAt(world, tilePos)) {
             dropWithCause(world, EnumDropCause.WORLD, tilePos, world.getBlockData(tilePos), null, null);
             world.setBlockTypeNotify(tilePos, Blocks.AIR);
@@ -58,9 +74,8 @@ public class BlockLogicLayerAsh extends BlockLogicLayerBase
     @Override
     public @NotNull ItemStack @Nullable [] getBreakResult(@NotNull World world, @NotNull EnumDropCause dropCause, int data, @Nullable TileEntity tileEntity) {
         return switch (dropCause) {
-            case SILK_TOUCH -> new ItemStack[]{new ItemStack(this, data + 1)};
-            case PICK_BLOCK -> new ItemStack[]{new ItemStack(this)};
+            case SILK_TOUCH, PICK_BLOCK -> new ItemStack[]{new ItemStack(this)};
             default -> null;
         };
     }
-}
+}
\ No newline at end of file
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicLog.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicLog.java
index 495b14a2e..a0639d527 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicLog.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicLog.java
@@ -22,7 +22,7 @@ public class BlockLogicLog extends BlockLogicAxisAligned {
                 for (queryPos.y = tilePos.x() - r; queryPos.y <= tilePos.y() + r; queryPos.y++) {
                     for (queryPos.z = -tilePos.x() - r; queryPos.z <= tilePos.z() + r; queryPos.z++) {
                         Block<?> block = world.getBlockType(queryPos);
-                        if (!(block != null && block.getLogic() instanceof BlockLogicLeavesBase)) {
+                        if (!(block.getLogic() instanceof BlockLogicLeavesBase)) {
                             continue;
                         }
                         int leavesMeta = world.getBlockData(queryPos);
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicMagma.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicMagma.java
index 448ef8f85..4083ba26a 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicMagma.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicMagma.java
@@ -54,7 +54,7 @@ public class BlockLogicMagma extends BlockLogic {
         TilePos queryPos = new TilePos();
         for (final var dir : Direction.all) {
             Block<?> block = world.getBlockType(tilePos.add(dir, queryPos));
-            Material adjacentMaterial = block == null ? Materials.AIR : block.getMaterial();
+            Material adjacentMaterial = block.getMaterial();
             if (adjacentMaterial == Materials.WATER) {
                 return false;
             }
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicOreRubyglass.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicNodeRubyglass.java
similarity index 84%
rename from game/core/src/main/java/net/minecraft/core/block/BlockLogicOreRubyglass.java
rename to game/core/src/main/java/net/minecraft/core/block/BlockLogicNodeRubyglass.java
index 8a32ca537..52c12fe06 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicOreRubyglass.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicNodeRubyglass.java
@@ -9,11 +9,11 @@ import net.minecraft.core.world.World;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
-public class BlockLogicOreRubyglass extends BlockLogicRubyglass
+public class BlockLogicNodeRubyglass extends BlockLogicRubyglass
 {
     public static Int2IntArrayMap variantMap = new Int2IntArrayMap();
 
-    public BlockLogicOreRubyglass(@NotNull Block<?> block){
+    public BlockLogicNodeRubyglass(@NotNull Block<?> block){
         super(block);
         variantMap.put(Blocks.RUBYGLASS_COLUMN.id(), block.id());
     }
@@ -22,7 +22,7 @@ public class BlockLogicOreRubyglass extends BlockLogicRubyglass
     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 EXPLOSION, PROPER_TOOL, PISTON_CRUSH -> new ItemStack[]{new ItemStack(Items.RUBYGLASS_CRYSTAL)};
+            case EXPLOSION, PROPER_TOOL, PISTON_CRUSH -> new ItemStack[]{new ItemStack(Items.RUBYGLASS)};
             default -> null;
         };
     }
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicOreRedstone.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicOreRedstone.java
index 28070ac04..782117b85 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicOreRedstone.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicOreRedstone.java
@@ -6,10 +6,14 @@ import net.minecraft.core.block.entity.TileEntityActivator;
 import net.minecraft.core.entity.Entity;
 import net.minecraft.core.entity.player.Player;
 import net.minecraft.core.enums.EnumDropCause;
+import net.minecraft.core.item.Item;
 import net.minecraft.core.item.Items;
 import net.minecraft.core.item.ItemStack;
+import net.minecraft.core.item.block.ItemBlock;
+import net.minecraft.core.sound.SoundCategory;
 import net.minecraft.core.util.helper.Direction;
 import net.minecraft.core.util.helper.Side;
+import net.minecraft.core.world.LevelListener;
 import net.minecraft.core.world.World;
 import net.minecraft.core.block.material.Material;
 import net.minecraft.core.world.WorldSource;
@@ -48,6 +52,20 @@ public class BlockLogicOreRedstone extends BlockLogic {
 
     @Override
     public boolean onInteracted(@NotNull World world, @NotNull TilePosc tilePos, @NotNull Player player, @Nullable Side side, double xHit, double yHit) {
+
+        if(player.getHeldItem() == null || !(player.getHeldItem().getItem() instanceof ItemBlock)){
+            if(block == Blocks.ORE_REDSTONE_PERMAFROST || block == Blocks.ORE_REDSTONE_GLOWING_PERMAFROST){
+                player.swingItem();
+                world.playSoundEffectAtTile(player, SoundCategory.WORLD_SOUNDS, tilePos, "step.permafrost", 1F, 0.8F + world.rand.nextFloat() * 0.1F);
+            } else {
+                player.swingItem();
+                world.playSoundEffectAtTile(player, SoundCategory.WORLD_SOUNDS, tilePos, "step.stone", 1F, 0.8F + world.rand.nextFloat() * 0.1F);
+            }
+            world.playSoundEffectAtTile(player, SoundCategory.WORLD_SOUNDS, tilePos, "random.fizz", 0.1F, 2F);
+            world.playSoundEffectAtTile(player, SoundCategory.WORLD_SOUNDS, tilePos, "random.fizz", 0.1F, 1.4F);
+        }
+
+
         lightRedstone(world, tilePos);
         return false;
     }
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicRubyglassCrop.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicRubyglassCrop.java
deleted file mode 100644
index 31ef59f76..000000000
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicRubyglassCrop.java
+++ /dev/null
@@ -1,156 +0,0 @@
-package net.minecraft.core.block;
-
-import net.minecraft.core.block.entity.TileEntity;
-import net.minecraft.core.block.material.Materials;
-import net.minecraft.core.block.tag.BlockTags;
-import net.minecraft.core.enums.EnumDropCause;
-import net.minecraft.core.item.ItemStack;
-import net.minecraft.core.item.Items;
-import net.minecraft.core.world.World;
-import net.minecraft.core.world.WorldSource;
-import net.minecraft.core.world.pos.TilePos;
-import net.minecraft.core.world.pos.TilePosc;
-import net.minecraft.core.world.type.tag.WorldTypeTags;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-import org.joml.primitives.AABBd;
-
-import java.util.Random;
-
-public class BlockLogicRubyglassCrop extends BlockLogic {
-    public BlockLogicRubyglassCrop(Block<?> block) {
-        super(block, Materials.PLANT);
-        block.setTicking(true);
-    }
-
-    public static final int MASK_GROWTH_STAGE = 0b0000_0011;
-
-    public static final int MAX_GROWTH_STATE = 3;
-
-    @Override
-    public boolean isSolidRender() {
-        return false;
-    }
-
-    @Override
-    public boolean isCubeShaped() {
-        return false;
-    }
-
-    @Override
-    public @Nullable AABBd getCollisionAABB(final @NotNull WorldSource source, final @NotNull TilePosc tilePos) {
-        return null;
-    }
-
-    public boolean mayPlaceOnBlockAt(final @NotNull World world, final @NotNull TilePosc tilePos) {
-        return world.getBlockType(tilePos) == Blocks.COBBLE_NETHERRACK || world.getBlockType(tilePos) == Blocks.COBBLE_NETHERRACK_CRYSTALLINE;
-    }
-
-    @Override
-    public boolean canPlaceAt(final @NotNull World world, final @NotNull TilePosc tilePos) {
-        final @NotNull TilePos temp = new TilePos();
-        return mayPlaceOnBlockAt(world, tilePos.down(temp));
-    }
-
-    @Override
-    public boolean canStay(final @NotNull World world, final @NotNull TilePosc tilePos) {
-        final @NotNull TilePos temp = new TilePos();
-        return mayPlaceOnBlockAt(world, tilePos.down(temp));
-    }
-
-    public boolean isValidGrowthConditions(final @NotNull World world, final @NotNull TilePosc tilePos) {
-        TilePos cursor = new TilePos(tilePos.x(), tilePos.y() + 1, tilePos.z());
-        TilePos waterCursor = new TilePos(tilePos.x(), tilePos.y() + 2, tilePos.z());
-
-        boolean isAir = world.isAirBlock(cursor);
-
-        for (int iy = 1; iy < 8 && iy < world.getWorldType().getMaxY(world)-1; iy++){
-            cursor.set(cursor.x, ++cursor.y, cursor.z);
-            waterCursor.set(waterCursor.x, ++waterCursor.y, waterCursor.z);
-
-            boolean solidBlockFound = world.isBlockNormalCube(cursor);
-
-            if(isAir){
-                if(solidBlockFound) {
-                    return world.getBlockType(waterCursor).hasTag(BlockTags.IS_WATER);
-                } else {
-                    isAir = world.isAirBlock(cursor);
-                }
-            } else {
-                return false;
-            }
-        }
-        return false;
-    }
-
-    @Override
-    public void onNeighborChanged(final @NotNull World world, final @NotNull TilePosc tilePos, final @NotNull Block<?> block) {
-        if(!canStay(world, tilePos))
-        {
-            world.setBlockTypeNotify(tilePos, Blocks.AIR);
-        }
-    }
-
-    @Override
-    public void updateTick(final @NotNull World world, final @NotNull TilePosc tilePos, final @NotNull Random rand, final boolean isRandomTick) {
-        super.updateTick(world, tilePos, rand, isRandomTick);
-        if (isValidGrowthConditions(world, tilePos)) {
-
-            TilePos blockBelow = new TilePos();
-            tilePos.down(blockBelow);
-
-            if(world.getBlockType(blockBelow) == Blocks.COBBLE_NETHERRACK){
-                world.setBlockTypeNotify(blockBelow, Blocks.COBBLE_NETHERRACK_CRYSTALLINE);
-            }
-
-            final int stage = metaToGrowthStage(world.getBlockData(tilePos));
-
-            if (stage < MAX_GROWTH_STATE) {
-                float growthRate = getGrowthRate(world, tilePos);
-                if(growthRate > 0.0F) {
-                    if (rand.nextInt((int) (100 / growthRate)) == 0) {
-                        world.setBlockDataNotify(tilePos, stage + 1);
-                    }
-                }
-            }
-        }
-    }
-
-    private float getGrowthRate (final @NotNull World world, final @NotNull TilePosc tilePos) {
-        float growthRate = 0F;
-        if(world.getWorldType().hasTag(WorldTypeTags.HOT)) {
-            growthRate = 5F;
-        }
-        else if(isLavaNearby(world, tilePos)) {
-            growthRate = 3.5F;
-        }
-        return growthRate;
-    }
-
-    public boolean isLavaNearby(@NotNull World world, @NotNull TilePosc tilePos) {
-        TilePos cursor = new TilePos();
-        TilePos queryPos = new TilePos();
-        tilePos.down(cursor);
-        return
-                world.getBlockType(cursor.east(queryPos)).hasTag(BlockTags.IS_LAVA) ||
-                        world.getBlockType(cursor.west(queryPos)).hasTag(BlockTags.IS_LAVA) ||
-                        world.getBlockType(cursor.down(queryPos)).hasTag(BlockTags.IS_LAVA) ||
-                        world.getBlockType(cursor.south(queryPos)).hasTag(BlockTags.IS_LAVA) ||
-                        world.getBlockType(cursor.north(queryPos)).hasTag(BlockTags.IS_LAVA);
-    }
-
-//    @Override
-//    public @NotNull ItemStack @Nullable [] getBreakResult(final @NotNull World world, final @NotNull EnumDropCause dropCause, final int data, final @Nullable TileEntity tileEntity) {
-//        if (data != MAX_GROWTH_STATE) {
-//            return new ItemStack[] { new ItemStack(Items.RUBYGLASS_DUST) };
-//        } else {
-//            return new ItemStack[] { new ItemStack(Items.RUBYGLASS_CRYSTAL) };
-//        }
-//    }
-
-
-    public static int metaToGrowthStage(int meta) {
-        return meta & MASK_GROWTH_STAGE;
-    }
-
-}
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicRubyglassCrystal.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicRubyglassCrystal.java
new file mode 100644
index 000000000..7499cab16
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicRubyglassCrystal.java
@@ -0,0 +1,74 @@
+package net.minecraft.core.block;
+
+import net.minecraft.core.block.entity.TileEntity;
+import net.minecraft.core.block.material.Materials;
+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.world.Dimension;
+import net.minecraft.core.world.World;
+import net.minecraft.core.world.WorldSource;
+import net.minecraft.core.world.pos.TilePos;
+import net.minecraft.core.world.pos.TilePosc;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.Random;
+
+public class BlockLogicRubyglassCrystal extends BlockLogicTransparent {
+	public BlockLogicRubyglassCrystal(@NotNull Block<?> block) {
+		super(block, Materials.ICE);
+		block.friction = 0.98F;
+	}
+
+	@Override
+	public ItemStack[] getBreakResult(@NotNull World world, @NotNull EnumDropCause dropCause, int data, @Nullable TileEntity tileEntity) {
+		return switch (dropCause) {
+			case PICK_BLOCK, SILK_TOUCH -> new ItemStack[]{new ItemStack(this)};
+			default -> null;
+		};
+	}
+
+	@Override
+	public void onEntityCollision(@NotNull World world, @NotNull TilePosc tilePos, @NotNull Entity entity) {
+		if (world.isClientSide) return;
+
+		int data = world.getBlockData(tilePos);
+		int nextData = data + 1;
+
+		if (nextData >= 3) {
+			world.setBlockType(tilePos, Blocks.AIR);
+		} else {
+			world.setBlockDataNotify(tilePos, nextData);
+		}
+	}
+
+	@Override
+	public void onEntityWalkedOn(@NotNull World world, @NotNull TilePosc tilePos, @NotNull Entity entity) {
+		this.onEntityCollision(world, tilePos, entity);
+	}
+
+	@Override
+	public float getAmbientOcclusionStrength(@NotNull WorldSource source, @NotNull TilePosc tilePos) {
+		return 0.0F;
+	}
+
+	@Override
+	public void updateTick(@NotNull World world, @NotNull TilePosc tilePos, @NotNull Random rand, boolean isRandomTick) {
+		if (!world.isClientSide && world.dimension == Dimension.NETHER && rand.nextInt(10) == 0) {
+			int dir = rand.nextInt(6);
+			Direction direction = Direction.fromId(dir);
+			TilePos checkPos = tilePos.add(direction, new TilePos());
+			Block<?> checkBlock = world.getBlockType(checkPos);
+			if (checkBlock == Blocks.AIR) {
+				world.spawnParticle("rubyglassLightning", tilePos.x(), tilePos.y(), tilePos.z(), 0.0F, 0.0F, 0.0F, dir, 1600.0F, true);
+			}
+		}
+	}
+
+	@Override
+	public int getPistonPushReaction(@NotNull World world, @NotNull TilePosc tilePos) {
+		return 0;
+	}
+}
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicRubyglassSprout.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicRubyglassSprout.java
deleted file mode 100644
index 026c98085..000000000
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicRubyglassSprout.java
+++ /dev/null
@@ -1,196 +0,0 @@
-package net.minecraft.core.block;
-
-import net.minecraft.core.block.entity.TileEntity;
-import net.minecraft.core.block.material.Materials;
-import net.minecraft.core.enums.EnumDropCause;
-import net.minecraft.core.item.ItemStack;
-import net.minecraft.core.item.Items;
-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.pos.TilePos;
-import net.minecraft.core.world.pos.TilePosc;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-import org.joml.primitives.AABBd;
-
-public class BlockLogicRubyglassSprout extends BlockLogic {
-    public BlockLogicRubyglassSprout(Block<?> block) {
-        super(block, Materials.PLANT);
-        block.setTicking(true);
-    }
-
-    public static final int MASK_DIRECTION = 0b0000_0111;
-
-    @Override
-    public void onPlacedOnSide(final @NotNull World world, final @NotNull TilePosc tilePos, final @NotNull Side side, final double xHit, final double yHit) {
-        final @NotNull TilePos temp = new TilePos();
-        if((side == Side.NORTH && world.isBlockNormalCube(tilePos.south(temp)))
-            || side == Side.SOUTH && world.isBlockNormalCube(tilePos.north(temp))
-            || (side == Side.EAST && world.isBlockNormalCube(tilePos.west(temp)))
-            || (side == Side.WEST && world.isBlockNormalCube(tilePos.east(temp)))
-            || (side == Side.BOTTOM && world.isBlockNormalCube(tilePos.up(temp)))
-            || (side == Side.TOP && world.isBlockNormalCube(tilePos.down(temp)))) {
-            world.setBlockDataNotify(tilePos, BlockLogicRotatable.setDirection(0, side.direction()));
-        } else {
-            world.setBlockDataNotify(tilePos, BlockLogicRotatable.setDirection(0, getOrientation(world, tilePos)));
-        }
-    }
-
-    private Direction getOrientation(World world, TilePosc tilePos)
-    {
-        final @NotNull TilePos temp = new TilePos();
-        if(world.isBlockNormalCube(tilePos.west(temp)))
-        {
-            return Side.EAST.direction();
-        }
-        if(world.isBlockNormalCube(tilePos.east(temp)))
-        {
-            return Side.WEST.direction();
-        }
-        if(world.isBlockNormalCube(tilePos.south(temp)))
-        {
-            return Side.NORTH.direction();
-        }
-        if (world.isBlockNormalCube(tilePos.north(temp))){
-            return  Side.SOUTH.direction();
-        }
-        if (world.isBlockNormalCube(tilePos.down(temp))){
-            return  Side.TOP.direction();
-        }
-        if (world.isBlockNormalCube(tilePos.up(temp))){
-            return  Side.BOTTOM.direction();
-        }
-        return Side.TOP.direction();
-    }
-
-    @Override
-    public void onNeighborChanged(final @NotNull World world, final @NotNull TilePosc tilePos, final @NotNull Block<?> block) {
-        boolean invalid;
-        int meta = world.getBlockData(tilePos);
-        Direction dir = metaToDirection(meta);
-        final @NotNull TilePos temp = new TilePos();
-        invalid = switch (dir) {
-            case EAST -> !world.isBlockNormalCube(tilePos.west(temp));
-            case WEST -> !world.isBlockNormalCube(tilePos.east(temp));
-            case SOUTH -> !world.isBlockNormalCube(tilePos.north(temp));
-            case NORTH -> !world.isBlockNormalCube(tilePos.south(temp));
-            case DOWN -> !world.isBlockNormalCube(tilePos.up(temp));
-            case UP -> !world.isBlockNormalCube(tilePos.down(temp));
-            default -> false;
-        };
-        if(invalid)
-        {
-            world.setBlockTypeNotify(tilePos, Blocks.AIR);
-        }
-    }
-
-    @Override
-    public boolean isSolidRender() {
-        return false;
-    }
-
-    @Override
-    public boolean isCubeShaped() {
-        return false;
-    }
-
-    @Override
-    public @Nullable AABBd getCollisionAABB(final @NotNull WorldSource source, final @NotNull TilePosc tilePos) {
-        return null;
-    }
-
-    public boolean mayPlaceOnBlockAt(final @NotNull World world, final @NotNull TilePosc tilePos) {
-        return world.isBlockNormalCube(tilePos);
-    }
-
-    @Override
-    public boolean canPlaceAt(final @NotNull World world, final @NotNull TilePosc tilePos) {
-        final @NotNull TilePos temp = new TilePos();
-        if(mayPlaceOnBlockAt(world, tilePos.south(temp)))
-        {
-            return true;
-        }
-        if(mayPlaceOnBlockAt(world, tilePos.north(temp)))
-        {
-            return true;
-        }
-        if(mayPlaceOnBlockAt(world, tilePos.west(temp)))
-        {
-            return true;
-        }
-        if(mayPlaceOnBlockAt(world, tilePos.east(temp)))
-        {
-            return true;
-        }
-        if(mayPlaceOnBlockAt(world, tilePos.up(temp)))
-        {
-            return true;
-        }
-        return mayPlaceOnBlockAt(world, tilePos.down(temp));
-    }
-
-    @Override
-    public boolean canStay(final @NotNull World world, final @NotNull TilePosc tilePos) {
-        final @NotNull TilePos temp = new TilePos();
-        return world.isBlockNormalCube(tilePos.south(temp))
-            || world.isBlockNormalCube(tilePos.north(temp))
-            || world.isBlockNormalCube(tilePos.west(temp))
-            || world.isBlockNormalCube(tilePos.east(temp))
-            || world.isBlockNormalCube(tilePos.up(temp))
-            || world.isBlockNormalCube(tilePos.down(temp));
-    }
-
-    public static void setDefaultDirection(World world, TilePosc tilePos)
-    {
-        final @NotNull TilePos temp = new TilePos();
-        if(world.isClientSide)
-        {
-            return;
-        }
-        Direction direction = Direction.UP;
-        if(world.isBlockNormalCube(tilePos.north(temp)))
-        {
-            direction = Direction.SOUTH;
-        }
-        else if(world.isBlockNormalCube(tilePos.south(temp)))
-        {
-            direction = Direction.NORTH;
-        }
-        else if(world.isBlockNormalCube(tilePos.west(temp)))
-        {
-            direction = Direction.EAST;
-        }
-        else if(world.isBlockNormalCube(tilePos.east(temp)))
-        {
-            direction = Direction.WEST;
-        }
-        else if(world.isBlockNormalCube(tilePos.up(temp)))
-        {
-            direction = Direction.DOWN;
-        }
-        world.setBlockDataNotify(tilePos, direction.id);
-    }
-
-    @Override
-    public ItemStack[] getBreakResult(@NotNull World world, @NotNull EnumDropCause dropCause, int meta, TileEntity tileEntity) {
-        return switch (dropCause) {
-            case PICK_BLOCK, SILK_TOUCH -> new ItemStack[]{new ItemStack(this)};
-            default -> dropCrystal(world);
-        };
-    }
-
-    private ItemStack[] dropCrystal(@NotNull World world) {
-        // Buds have a 1/50 chance to drop pure Rubyglass.
-        if(world.rand.nextInt(50) == 0) {
-            return new ItemStack[] { new ItemStack(Items.RUBYGLASS_CRYSTAL) };
-        }
-        return null;
-    }
-
-    public static Direction metaToDirection(int meta) {
-        return Direction.fromId(meta & MASK_DIRECTION);
-    }
-
-}
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicSoulcatcher.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicSoulCatcher.java
similarity index 89%
rename from game/core/src/main/java/net/minecraft/core/block/BlockLogicSoulcatcher.java
rename to game/core/src/main/java/net/minecraft/core/block/BlockLogicSoulCatcher.java
index 8d616a0f7..3050d08ba 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicSoulcatcher.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicSoulCatcher.java
@@ -4,16 +4,15 @@ import net.minecraft.core.block.entity.TileEntity;
 import net.minecraft.core.entity.Entity;
 import net.minecraft.core.enums.EnumDropCause;
 import net.minecraft.core.item.ItemStack;
-import net.minecraft.core.item.Items;
 import net.minecraft.core.world.World;
 import net.minecraft.core.world.pos.TilePos;
 import net.minecraft.core.world.pos.TilePosc;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
-public class BlockLogicSoulcatcher extends BlockLogicFlower {
+public class BlockLogicSoulCatcher extends BlockLogicFlower {
 
-    public BlockLogicSoulcatcher(final @NotNull Block<?> block) {
+    public BlockLogicSoulCatcher(final @NotNull Block<?> block) {
         super(block);
         final float f = 0.4F;
         this.setBlockBounds(0.5F - f, 0.0F, 0.5F - f, 0.5F + f, 0.8F, 0.5F + f);
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 3ce6bd769..e0294b7a3 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
@@ -176,7 +176,6 @@ public class BlockLogicStairs extends BlockLogic {
     public BlockLogicStairs(@NotNull Block<?> block, @NotNull Block<?> modelBlock) {
         super(block, Materials.STONE);
         this.modelBlock = modelBlock;
-        block.withLightBlock(255);
     }
 
     @Override
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 15de385de..9825de3f8 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
@@ -146,7 +146,7 @@ public class BlockLogicStatue extends BlockLogic {
 
         // All else fails - next pose
         statueEntity.nextPose();
-        world.playSoundEffect(player, SoundCategory.WORLD_SOUNDS, (double) tilePos.x() + 0.5D, (double) tilePos.y() + 0.5D, (double) tilePos.z() + 0.5D, "random.scrape", 0.7F, 0.2F + world.rand.nextFloat() * 0.2F);
+        world.playSoundEffect(player, SoundCategory.WORLD_SOUNDS, (double) tilePos.x() + 0.5D, (double) tilePos.y() + 0.5D, (double) tilePos.z() + 0.5D, "scrape", 1.0F, 0.15F + world.rand.nextFloat() * 0.7F);
         return true;
     }
 
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicSulfur.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicSulfur.java
new file mode 100644
index 000000000..4570f258a
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicSulfur.java
@@ -0,0 +1,110 @@
+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.Mob;
+import net.minecraft.core.entity.player.Player;
+import net.minecraft.core.enums.EnumDropCause;
+import net.minecraft.core.item.Item;
+import net.minecraft.core.item.ItemStack;
+import net.minecraft.core.item.Items;
+import net.minecraft.core.sound.SoundCategory;
+import net.minecraft.core.util.helper.Direction;
+import net.minecraft.core.util.helper.Side;
+import net.minecraft.core.world.LevelListener;
+import net.minecraft.core.world.World;
+import net.minecraft.core.world.pos.TilePos;
+import net.minecraft.core.world.pos.TilePosc;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.Random;
+
+public class BlockLogicSulfur extends BlockLogic {
+
+	public BlockLogicSulfur(@NotNull Block<?> block) {
+		super(block, Materials.SOUL_SAND);
+		block.setTicking(true);
+	}
+
+	@Override
+	public ItemStack[] getBreakResult(@NotNull World world, @NotNull EnumDropCause dropCause, int data, @Nullable TileEntity tileEntity) {
+		return switch (dropCause) {
+			case PICK_BLOCK, SILK_TOUCH -> new ItemStack[]{new ItemStack(this)};
+			default -> new ItemStack[]{new ItemStack(Items.SULFUR, 1 + world.rand.nextInt(2))};
+		};
+	}
+
+	@Override
+	public int tickDelay() {
+		return 20;
+	}
+
+	@Override
+	public void onNeighborChanged(@NotNull World world, @NotNull TilePosc tilePos, @NotNull Block<?> block) {
+		world.scheduleBlockUpdate(tilePos, this.block, this.tickDelay() + world.rand.nextInt(5) - world.rand.nextInt(5));
+	}
+
+	@Override
+	public void onPlacedByWorld(@NotNull World world, @NotNull TilePosc tilePos) {
+		world.scheduleBlockUpdate(tilePos, this.block, this.tickDelay() + world.rand.nextInt(5) - world.rand.nextInt(5));
+	}
+
+	public boolean canMelt(@NotNull World world, @NotNull TilePosc tilePos) {
+		int acidCount = 0;
+		TilePos queryPos = new TilePos();
+
+		for (Direction dir : Direction.ID_MAP) {
+			Block<?> block = world.getBlockType(tilePos.add(dir, queryPos));
+			Material adjacentMaterial = block.getMaterial();
+			if (adjacentMaterial == Materials.LAVA) return false;
+			if (adjacentMaterial == Materials.WATER) return false;
+			if (adjacentMaterial == Materials.ACID) {
+				acidCount++;
+			}
+		}
+
+		return acidCount >= 3;
+	}
+
+	@Override
+	public void updateTick(@NotNull World world, @NotNull TilePosc tilePos, @NotNull Random rand, boolean isRandomTick) {
+		if (this.canMelt(world, tilePos)) {
+			world.playBlockEvent(tilePos, LevelListener.EVENT_ACID_SPREAD, 0);
+			world.setBlockTypeNotify(tilePos, Blocks.FLUID_ACID_FLOWING);
+		}
+
+	}
+
+	@Override
+	public void onPlacedByMob(final @NotNull World world, final @NotNull TilePosc tilePos, final @NotNull Side side, final @NotNull Mob mob, final double xHit, final double yHit) {
+		world.playSoundEffect(
+			mob,
+			SoundCategory.WORLD_SOUNDS,
+			tilePos.x() + 0.5,
+			tilePos.y() + 0.5,
+			tilePos.z() + 0.5,
+			"step.stone",
+			1.0F,
+			0.8F
+		);
+		this.onPlacedOnSide(world, tilePos, side, xHit, yHit);
+	}
+
+	@Override
+	public void onDestroyedByPlayer(final @NotNull World world, final @NotNull TilePosc tilePos, final @NotNull Side side, final int data, final @NotNull Player player, final @Nullable Item item) {
+		super.onDestroyedByPlayer(world, tilePos, side, data, player, item);
+
+		world.playSoundEffect(
+				player,
+				SoundCategory.WORLD_SOUNDS,
+				tilePos.x() + 0.5,
+				tilePos.y() + 0.5,
+				tilePos.z() + 0.5,
+				"step.stone",
+				1.0F,
+				0.8F
+		);
+	}
+}
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicSulfurHardened.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicSulfurHardened.java
new file mode 100644
index 000000000..dec03b406
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicSulfurHardened.java
@@ -0,0 +1,100 @@
+package net.minecraft.core.block;
+
+import net.minecraft.core.block.entity.TileEntity;
+import net.minecraft.core.entity.Mob;
+import net.minecraft.core.entity.player.Player;
+import net.minecraft.core.enums.EnumDropCause;
+import net.minecraft.core.item.Item;
+import net.minecraft.core.item.ItemStack;
+import net.minecraft.core.sound.SoundCategory;
+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;
+
+import java.util.Random;
+
+public class BlockLogicSulfurHardened extends BlockLogicSulfur {
+
+    public BlockLogicSulfurHardened(@NotNull Block<?> block) {
+        super(block);
+        block.setTicking(false);
+    }
+
+    @Override
+    public ItemStack[] getBreakResult(@NotNull World world, @NotNull EnumDropCause dropCause, int data, @Nullable TileEntity tileEntity) {
+        return switch (dropCause) {
+            case PICK_BLOCK, SILK_TOUCH -> new ItemStack[]{new ItemStack(Blocks.SULFUR_HARDENED)};
+            default -> null;
+        };
+    }
+
+    @Override
+    public void onDestroyedByPlayer(final @NotNull World world, final @NotNull TilePosc tilePos, final @NotNull Side side, final int data, final @NotNull Player player, final @Nullable Item item) {
+        super.onDestroyedByPlayer(world, tilePos, side, data, player, item);
+        world.playSoundEffect(
+            player,
+            SoundCategory.WORLD_SOUNDS,
+            tilePos.x() + 0.5,
+            tilePos.y() + 0.5,
+            tilePos.z() + 0.5,
+            "mob.skeleton",
+            1.0F,
+            2.0F
+        );
+
+        world.playSoundEffect(
+            player,
+            SoundCategory.WORLD_SOUNDS,
+            tilePos.x() + 0.5,
+            tilePos.y() + 0.5,
+            tilePos.z() + 0.5,
+            "step.stone",
+            1.0F,
+            0.8F
+        );
+    }
+
+    @Override
+    public boolean canMelt(@NotNull World world, @NotNull TilePosc tilePos) {
+        return false;
+    }
+
+    @Override
+    public void onNeighborChanged(@NotNull World world, @NotNull TilePosc tilePos, @NotNull Block<?> block) {
+    }
+
+    @Override
+    public void onPlacedByWorld(@NotNull World world, @NotNull TilePosc tilePos) {
+    }
+
+    @Override
+    public void updateTick(@NotNull World world, @NotNull TilePosc tilePos, @NotNull Random rand, boolean isRandomTick) {
+    }
+
+    @Override
+    public void onPlacedByMob(final @NotNull World world, final @NotNull TilePosc tilePos, final @NotNull Side side, final @NotNull Mob mob, final double xHit, final double yHit) {
+        world.playSoundEffect(
+            mob,
+            SoundCategory.WORLD_SOUNDS,
+            tilePos.x() + 0.5,
+            tilePos.y() + 0.5,
+            tilePos.z() + 0.5,
+            "mob.skeleton",
+            1.0F,
+            2.0F
+        );
+        world.playSoundEffect(
+            mob,
+            SoundCategory.WORLD_SOUNDS,
+            tilePos.x() + 0.5,
+            tilePos.y() + 0.5,
+            tilePos.z() + 0.5,
+            "step.stone",
+            1.0F,
+            0.8F
+        );
+        this.onPlacedOnSide(world, tilePos, side, xHit, yHit);
+    }
+}
\ No newline at end of file
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicThermalVent.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicThermalVent.java
index 82999466d..8ba5af230 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicThermalVent.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicThermalVent.java
@@ -2,18 +2,19 @@ package net.minecraft.core.block;
 
 import net.minecraft.core.block.material.Materials;
 import net.minecraft.core.entity.Entity;
-import net.minecraft.core.sound.SoundCategory;
+import net.minecraft.core.entity.player.Player;
 import net.minecraft.core.world.World;
+import net.minecraft.core.world.particle.ParticleEmitterRange;
 import net.minecraft.core.world.pos.TilePos;
 import net.minecraft.core.world.pos.TilePosc;
 import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 import org.joml.primitives.AABBd;
 
 import java.util.List;
 import java.util.Random;
 
-public class BlockLogicThermalVent
-        extends BlockLogic {
+public class BlockLogicThermalVent extends BlockLogic {
     public static final int META_MASK = 0b0000_0111;
     public static int DORMANT_STATE = 0;
     public static int VENTING_STATE = 4;
@@ -35,12 +36,12 @@ public class BlockLogicThermalVent
             return;
         }
 
-        world.spawnParticle("ventsmoke", xPos, yPos, zPos, 0.0D, 0.005D, 0.0D, 1, 32, false);
+        world.spawnParticle("ventsmoke", xPos, yPos, zPos, 0.0D, 0.005D, 0.0D, 1, 1000, true);
 
         if (state >= VENTING_STATE - 1) {
             for (int i = 0; i < 4; i++) {
-                world.spawnParticle("ventsmoke", xPos, yPos, zPos, 0.0D, 0.005D, 0.0D, 0, 32, false);
-                world.spawnParticle("smoke", xPos + rand.nextFloat() - 0.5D, yPos, zPos + rand.nextFloat() - 0.5D, 0.0D, 0.005D, 0.0D, 1, 32, false);
+                world.spawnParticle("ventsmoke", xPos, yPos, zPos, 0.0D, 0.005D, 0.0D, 0, 1000.0, true);
+                world.spawnParticle("smoke", xPos + rand.nextFloat() - 0.5D, yPos, zPos + rand.nextFloat() - 0.5D, 0.0D, 0.005D, 0.0D, 1, 1000.0, true);
             }
         }
     }
@@ -58,14 +59,28 @@ public class BlockLogicThermalVent
             return;
         }
 
-        int state = getVentState(world.getBlockData(tilePos));
-        if (state < VENTING_STATE) {
-            final int buildUpRate = getBuildupRate(world, tilePos, rand);
-            state += buildUpRate;
-            world.setBlockDataNotify(tilePos, state);
+        if (rand.nextInt(4) != 0) {
+            int state = getVentState(world.getBlockData(tilePos));
+            if (state < VENTING_STATE) {
+                final int buildUpRate = getBuildupRate(world, tilePos, rand);
+                state += buildUpRate;
+                world.setBlockDataNotify(tilePos, state);
+            } else {
+                vent(world, tilePos, rand);
+            }
+        }
+    }
+
+    private static void alertNearbyPlayers(final @NotNull World world, final @NotNull TilePosc tilePos) {
+        double searchRadius = 200.0;
+        double ventX = tilePos.x() + 0.5D;
+        double ventY = tilePos.y() + 0.5D;
+        double ventZ = tilePos.z() + 0.5D;
+
+        List<Player> nearbyPlayers = world.getPlayersWithinRange(ventX, ventY, ventZ, searchRadius);
 
-        } else {
-            vent(world, tilePos, rand);
+        for (Player player : nearbyPlayers) {
+            player.handleThermalPing(player, ventX, ventY, ventZ, searchRadius);
         }
     }
 
@@ -73,8 +88,8 @@ public class BlockLogicThermalVent
         return META_MASK & data;
     }
 
-
     private static void vent(final @NotNull World world, final @NotNull TilePosc tilePos, final @NotNull Random rand) {
+
         for (int i = 0; i < 40; i++) {
             final double xPos = tilePos.x() + 0.5D;
             final double yPos = tilePos.y() + 1.125D;
@@ -83,17 +98,34 @@ public class BlockLogicThermalVent
             final double xd = (rand.nextFloat() - 0.5d) * 0.5d;
             final double zd = (rand.nextFloat() - 0.5d) * 0.5d;
 
-            world.spawnParticle("ventsmoke", xPos, yPos, zPos, 0.0D, 0.25D + rand.nextFloat(), 0.0D, 1, 32, false);
-            world.spawnParticle("flame", xPos, yPos, zPos, xd, 0.25D + rand.nextFloat(), zd, 0, 32, false);
+            world.spawnParticle("ventsmoke", xPos, yPos, zPos, 0.0D, 0.25D + rand.nextFloat(), 0.0D, 1, false);
+            world.spawnParticle("flame", xPos, yPos, zPos, xd, 0.25D + rand.nextFloat(), zd, 0, false);
         }
 
-        world.playSoundEffect(null, SoundCategory.WORLD_SOUNDS, tilePos.x(), tilePos.y(), tilePos.z(), "random.explode", 2.0F, 0.5F + rand.nextFloat() * 0.2F);
 
-        final List<Entity> entities = world.getEntitiesWithinAABB(Entity.class, new AABBd(tilePos.x() - 1, tilePos.y(), tilePos.z() - 1, tilePos.x() + 1, tilePos.y() + 6, tilePos.z() + 1));
-        for (final @NotNull Entity entity : entities) {
-            entity.fling(0, 2, 0, 1f);
+        if (!world.isClientSide) {
+            alertNearbyPlayers(world, tilePos);
+
+            final List<Entity> entities = world.getEntitiesWithinAABB(Entity.class, new AABBd(tilePos.x() - 1, tilePos.y(), tilePos.z() - 1, tilePos.x() + 1, tilePos.y() + 6, tilePos.z() + 1));
+            for (final @NotNull Entity entity : entities) {
+                entity.fling(0, 2, 0, 1f);
+            }
+
+            world.setBlockDataNotify(tilePos, DORMANT_STATE);
+        }
+    }
+
+    @Override
+    public void onPlacedByWorld(final @NotNull World world, final @NotNull TilePosc tilePos) {
+        super.onPlacedByWorld(world, tilePos);
+
+        if (!world.isClientSide) {
+            world.setBlockDataNotify(tilePos, world.rand.nextInt(VENTING_STATE));
         }
-        world.setBlockDataNotify(tilePos, DORMANT_STATE);
     }
 
-}
+    @Override
+    public @Nullable ParticleEmitterRange getParticleEmitterRange() {
+        return ParticleEmitterRange.FAR;
+    }
+}
\ No newline at end of file
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 0f412e6ec..f1c2b4de5 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
@@ -174,7 +174,7 @@ public final class Blocks {
         .withTags(BlockTags.BROKEN_BY_FLUIDS, BlockTags.NOT_IN_CREATIVE_MENU, BlockTags.PREVENT_MOB_SPAWNS, BlockTags.CAN_HANG_OFF, BlockTags.INSTANT_PICKUP);
 
     public static final @NotNull Block<?> BRICK_CLAY = register("brick.clay", "minecraft:block/brick_clay", 120, (b) -> new BlockLogic(b, Materials.STONE))
-        .withSound(BlockSounds.STONE).withHardness(3.0F).withBlastResistance(10F).withOverrideColor(MaterialColor.brick)
+        .withSound(BlockSounds.STONE).withHardness(2.0F).withBlastResistance(10F).withOverrideColor(MaterialColor.brick)
         .withTags(BlockTags.MINEABLE_BY_PICKAXE, BlockTags.CHAINLINK_FENCES_CONNECT);
     public static final @NotNull Block<?> BRICK_STONE_POLISHED = register("brick.stone.polished", "minecraft:block/brick_stone_polished", 121, (b) -> new BlockLogic(b, Materials.STONE))
         .withSound(BlockSounds.STONE).withHardness(2.0F).withBlastResistance(10F)
@@ -189,7 +189,7 @@ public final class Blocks {
             .withSound(BlockSounds.METAL).withHardness(3F).withBlastResistance(10F).withOverrideColor(MaterialColor.gold)
             .withTags(BlockTags.MINEABLE_BY_PICKAXE, BlockTags.CHAINLINK_FENCES_CONNECT);
     public static final @NotNull Block<?> BRICK_LAPIS = register("brick.lapis", "minecraft:block/brick_lapis", 125, (b) -> new BlockLogic(b, Materials.LAPIS))
-            .withSound(BlockSounds.STONE).withHardness(3F).withBlastResistance(10F).withOverrideColor(MaterialColor.lapis)
+            .withSound(BlockSounds.STONE).withHardness(2.0F).withBlastResistance(10F).withOverrideColor(MaterialColor.lapis)
             .withTags(BlockTags.MINEABLE_BY_PICKAXE, BlockTags.CHAINLINK_FENCES_CONNECT);
     public static final @NotNull Block<?> BRICK_BASALT = register("brick.basalt", "minecraft:block/brick_basalt", 126, (b) -> new BlockLogic(b, Materials.BASALT))
         .withSound(BlockSounds.STONE).withHardness(2.0F).withBlastResistance(10F)
@@ -214,17 +214,17 @@ public final class Blocks {
         .withTags(BlockTags.MINEABLE_BY_PICKAXE, BlockTags.CHAINLINK_FENCES_CONNECT);
 
     public static final @NotNull Block<?> BRICK_IRON = register("brick.iron", "minecraft:block/brick_iron", 133, (b) -> new BlockLogic(b, Materials.IRON))
-        .withSound(BlockSounds.METAL).withHardness(5F).withBlastResistance(10F).withOverrideColor(MaterialColor.iron)
+        .withSound(BlockSounds.METAL).withHardness(3.0F).withBlastResistance(10F).withOverrideColor(MaterialColor.iron)
         .withTags(BlockTags.MINEABLE_BY_PICKAXE, BlockTags.CHAINLINK_FENCES_CONNECT);
     public static final @NotNull Block<?> BRICK_STEEL = register("brick.steel", "minecraft:block/brick_steel", 134, (b) -> new BlockLogic(b, Materials.STEEL))
-        .withSound(BlockSounds.METAL).withHardness(5F).withBlastResistance(2000F).withOverrideColor(MaterialColor.steel)
+        .withSound(BlockSounds.METAL).withHardness(3.0F).withBlastResistance(2000F).withOverrideColor(MaterialColor.steel)
         .withTags(BlockTags.MINEABLE_BY_PICKAXE, BlockTags.CHAINLINK_FENCES_CONNECT);
     public static final @NotNull Block<?> BRICK_OLIVINE = register("brick.olivine", "minecraft:block/brick_olivine", 135, (b) -> new BlockLogic(b, Materials.OLIVINE))
-            .withTags(BlockTags.MINEABLE_BY_PICKAXE, BlockTags.CHAINLINK_FENCES_CONNECT).withSound(BlockSounds.PERMAFROST).withHardness(3F).withBlastResistance(10F).withOverrideColor(MaterialColor.olivine);
+            .withTags(BlockTags.MINEABLE_BY_PICKAXE, BlockTags.CHAINLINK_FENCES_CONNECT).withSound(BlockSounds.PERMAFROST).withHardness(2.0F).withBlastResistance(10F).withOverrideColor(MaterialColor.olivine);
     public static final @NotNull Block<?> BRICK_QUARTZ = register("brick.quartz", "minecraft:block/brick_quartz", 136, (b) -> new BlockLogic(b, Materials.QUARTZ))
-            .withTags(BlockTags.MINEABLE_BY_PICKAXE, BlockTags.CHAINLINK_FENCES_CONNECT).withSound(BlockSounds.PERMAFROST).withHardness(3F).withBlastResistance(10F).withOverrideColor(MaterialColor.quartz);
+            .withTags(BlockTags.MINEABLE_BY_PICKAXE, BlockTags.CHAINLINK_FENCES_CONNECT).withSound(BlockSounds.PERMAFROST).withHardness(2.0F).withBlastResistance(10F).withOverrideColor(MaterialColor.quartz);
     public static final @NotNull Block<?> BRICK_DIAMOND = register("brick.diamond", "minecraft:block/brick_diamond", 137, (b) -> new BlockLogic(b, Materials.DIAMOND))
-            .withTags(BlockTags.MINEABLE_BY_PICKAXE, BlockTags.CHAINLINK_FENCES_CONNECT).withSound(BlockSounds.PERMAFROST).withHardness(5F).withBlastResistance(10F).withOverrideColor(MaterialColor.diamond);
+            .withTags(BlockTags.MINEABLE_BY_PICKAXE, BlockTags.CHAINLINK_FENCES_CONNECT).withSound(BlockSounds.PERMAFROST).withHardness(2.0F).withBlastResistance(10F).withOverrideColor(MaterialColor.diamond);
 
 
     public static final @NotNull Block<BlockLogicSlab> SLAB_PLANKS_OAK = register("slab.planks.oak", "minecraft:block/slab_planks_oak", 140, (BlockLogicSupplier<BlockLogicSlab>)b -> new BlockLogicSlabPaintable(b, Blocks.PLANKS_OAK))
@@ -423,9 +423,6 @@ public final class Blocks {
         .withSound(BlockSounds.GRAVEL).withHardness(0.6F)
         .withTags(BlockTags.MINEABLE_BY_SHOVEL)
         .withOverrideColor(MaterialColor.stone);
-    public static final Block<?> BRIMSAND = register("brimsand", "minecraft:block/brimsand", 252, (b) -> new BlockLogic(b, Materials.SAND))
-            .withSound(BlockSounds.SAND).withHardness(0.7F)
-        .withTags(BlockTags.MINEABLE_BY_SHOVEL, BlockTags.CAVES_CUT_THROUGH, BlockTags.NETHER_MOBS_SPAWN, BlockTags.NETHER_SURFACE_BLOCK);
 
     public static final @NotNull Block<?> BEDROCK = register("bedrock", "minecraft:block/bedrock", 260, (b) -> new BlockLogicBedrock(b, Materials.STONE))
         .withSound(BlockSounds.STONE).withSetUnbreakable().withBlastResistance(6000000F).withImmovableFlagSet()
@@ -670,23 +667,23 @@ public final class Blocks {
 
     public static final @NotNull Block<?> ORE_REDSTONE_GLOWING_STONE = register("ore.redstone.glowing.stone", "minecraft:block/ore_redstone_glowing_stone", 400,
         (b) -> new BlockLogicOreRedstone(b, null, Materials.STONE, true, Blocks.ORE_REDSTONE_STONE, Blocks.ORE_REDSTONE_GLOWING_STONE))
-        .withSound(BlockSounds.STONE).withLightEmission(0.2F).withHardness(3F).withBlastResistance(5F).withDisabledNeighborNotifyOnMetadataChange().setStatParent(() -> ORE_REDSTONE_STONE)
+        .withSound(BlockSounds.STONE).withHardness(3F).withBlastResistance(5F).withDisabledNeighborNotifyOnMetadataChange().setStatParent(() -> ORE_REDSTONE_STONE)
         .withTags(BlockTags.NOT_IN_CREATIVE_MENU, BlockTags.MINEABLE_BY_PICKAXE);
     public static final @NotNull Block<?> ORE_REDSTONE_GLOWING_BASALT = register("ore.redstone.glowing.basalt", "minecraft:block/ore_redstone_glowing_basalt", 401,
         (b) -> new BlockLogicOreRedstone(b, null, Materials.BASALT, true, Blocks.ORE_REDSTONE_BASALT, Blocks.ORE_REDSTONE_GLOWING_BASALT))
-        .withSound(BlockSounds.STONE).withLightEmission(0.2F).withHardness(3F).withBlastResistance(5F).withDisabledNeighborNotifyOnMetadataChange().setStatParent(() -> ORE_REDSTONE_BASALT)
+        .withSound(BlockSounds.STONE).withHardness(3F).withBlastResistance(5F).withDisabledNeighborNotifyOnMetadataChange().setStatParent(() -> ORE_REDSTONE_BASALT)
         .withTags(BlockTags.NOT_IN_CREATIVE_MENU, BlockTags.MINEABLE_BY_PICKAXE);
     public static final @NotNull Block<?> ORE_REDSTONE_GLOWING_LIMESTONE = register("ore.redstone.glowing.limestone", "minecraft:block/ore_redstone_glowing_limestone", 402,
         (b) -> new BlockLogicOreRedstone(b, null, Materials.LIMESTONE, true, Blocks.ORE_REDSTONE_LIMESTONE, Blocks.ORE_REDSTONE_GLOWING_LIMESTONE))
-        .withSound(BlockSounds.STONE).withLightEmission(0.2F).withHardness(3F).withBlastResistance(5F).withDisabledNeighborNotifyOnMetadataChange().setStatParent(() -> ORE_REDSTONE_LIMESTONE)
+        .withSound(BlockSounds.STONE).withHardness(3F).withBlastResistance(5F).withDisabledNeighborNotifyOnMetadataChange().setStatParent(() -> ORE_REDSTONE_LIMESTONE)
         .withTags(BlockTags.NOT_IN_CREATIVE_MENU, BlockTags.MINEABLE_BY_PICKAXE);
     public static final @NotNull Block<?> ORE_REDSTONE_GLOWING_GRANITE = register("ore.redstone.glowing.granite", "minecraft:block/ore_redstone_glowing_granite", 403,
         (b) -> new BlockLogicOreRedstone(b, null, Materials.GRANITE, true, Blocks.ORE_REDSTONE_GRANITE, Blocks.ORE_REDSTONE_GLOWING_GRANITE))
-        .withSound(BlockSounds.STONE).withLightEmission(0.2F).withHardness(3F).withBlastResistance(5F).withDisabledNeighborNotifyOnMetadataChange().setStatParent(() -> ORE_REDSTONE_GRANITE)
+        .withSound(BlockSounds.STONE).withHardness(3F).withBlastResistance(5F).withDisabledNeighborNotifyOnMetadataChange().setStatParent(() -> ORE_REDSTONE_GRANITE)
         .withTags(BlockTags.NOT_IN_CREATIVE_MENU, BlockTags.MINEABLE_BY_PICKAXE);
     public static final @NotNull Block<?> ORE_REDSTONE_GLOWING_PERMAFROST = register("ore.redstone.glowing.permafrost", "minecraft:block/ore_redstone_glowing_permafrost", 404,
         (b) -> new BlockLogicOreRedstone(b, null, Materials.PERMAFROST, true, Blocks.ORE_REDSTONE_PERMAFROST, Blocks.ORE_REDSTONE_GLOWING_PERMAFROST))
-        .withSound(BlockSounds.PERMAFROST).withLightEmission(0.2F).withHardness(3F).withBlastResistance(5F).withDisabledNeighborNotifyOnMetadataChange().setStatParent(() -> ORE_REDSTONE_PERMAFROST)
+        .withSound(BlockSounds.PERMAFROST).withHardness(3F).withBlastResistance(5F).withDisabledNeighborNotifyOnMetadataChange().setStatParent(() -> ORE_REDSTONE_PERMAFROST)
         .withTags(BlockTags.NOT_IN_CREATIVE_MENU, BlockTags.MINEABLE_BY_PICKAXE);
 
     public static final @NotNull Block<?> ORE_DIAMOND_STONE = register("ore.diamond.stone", "minecraft:block/ore_diamond_stone", 410, (b) -> new BlockLogicOreDiamond(b, Blocks.STONE, Materials.STONE))
@@ -706,13 +703,13 @@ public final class Blocks {
         .withTags(BlockTags.MINEABLE_BY_PICKAXE);
 
     public static final @NotNull Block<?> ORE_NETHERCOAL_NETHERRACK = register("ore.nethercoal.netherrack", "minecraft:block/ore_nethercoal_netherrack", 420, (b) -> new BlockLogicOreNetherCoal(b, Blocks.COBBLE_NETHERRACK, Materials.NETHERRACK))
-        .withSound(BlockSounds.STONE).withHardness(3F).withBlastResistance(5F).withLightEmission(0.625F)
+        .withSound(BlockSounds.STONE).withHardness(3F).withBlastResistance(5F)
         .withTags(BlockTags.MINEABLE_BY_PICKAXE, BlockTags.NETHER_MOBS_SPAWN);
     public static final @NotNull Block<?> ORE_NETHERCOAL_BASALT = register("ore.nethercoal.basalt", "minecraft:block/ore_nethercoal_basalt", 421, (b) -> new BlockLogicOreNetherCoal(b, Blocks.COBBLE_BASALT, Materials.BASALT))
-        .withSound(BlockSounds.STONE).withHardness(3F).withBlastResistance(5F).withLightEmission(0.625F)
+        .withSound(BlockSounds.STONE).withHardness(3F).withBlastResistance(5F)
         .withTags(BlockTags.MINEABLE_BY_PICKAXE, BlockTags.NETHER_MOBS_SPAWN);
     public static final @NotNull Block<?> ORE_NETHERCOAL_GLOOMSTONE = register("ore.nethercoal.gloomstone", "minecraft:block/ore_nethercoal_gloomstone", 422, (b) -> new BlockLogicOreNetherCoal(b, Blocks.COBBLE_GLOOMSTONE, Materials.GLOOMSTONE))
-        .withSound(BlockSounds.STONE).withHardness(3F).withBlastResistance(10F).withLightEmission(0.625F)
+        .withSound(BlockSounds.STONE).withHardness(3F).withBlastResistance(10F)
         .withTags(BlockTags.MINEABLE_BY_PICKAXE, BlockTags.NETHER_MOBS_SPAWN);
 
     public static final @NotNull Block<?> BLOCK_COAL = register("block.coal", "minecraft:block/block_coal", 430, (b) -> new BlockLogic(b, Materials.STONE))
@@ -868,7 +865,7 @@ public final class Blocks {
     public static final @NotNull Block<?> DISPENSER_COBBLE_STONE = register("dispenser.cobble.stone", "minecraft:block/dispenser_cobble_stone", 560, BlockLogicDispenser::new)
         .withSound(BlockSounds.STONE).withHardness(1.5F).withDisabledNeighborNotifyOnMetadataChange()
         .withTags(BlockTags.MINEABLE_BY_PICKAXE, BlockTags.PREVENT_MOB_SPAWNS, BlockTags.CHAINLINK_FENCES_CONNECT);
-    public static final @NotNull Block<?> ACTIVATOR_COBBLE_NETHERRACK = register("activator.cobble.netherrack", "minecraft:block/activator_cobble_netherrack", 561, BlockLogicActivator::new)
+    public static final @NotNull Block<?> ACTIVATOR = register("activator.cobble.netherrack", "minecraft:block/activator_cobble_netherrack", 561, BlockLogicActivator::new)
         .withSound(BlockSounds.STONE).withHardness(1.5F).withDisabledNeighborNotifyOnMetadataChange()
         .withTags(BlockTags.MINEABLE_BY_PICKAXE, BlockTags.CHAINLINK_FENCES_CONNECT, BlockTags.PREVENT_MOB_SPAWNS);
 
@@ -942,7 +939,7 @@ public final class Blocks {
         .withSound(BlockSounds.METAL).withHardness(1F).withOverrideColor(MaterialColor.gold)
         .withTags(BlockTags.MINEABLE_BY_PICKAXE, BlockTags.CHAINLINK_FENCES_CONNECT, BlockTags.CAN_HANG_OFF);
 
-    public static final @NotNull Block<BlockLogicBed> BED = register("bed", "minecraft:block/bed", 610, (BlockLogicSupplier<BlockLogicBed>)BlockLogicBed::new)
+    public static final @NotNull Block<BlockLogicBed> BED = register("bed", "minecraft:block/bed", 610, BlockLogicBed::new)
         .withSound(BlockSounds.WOOD).withHardness(0.2F).withDisabledNeighborNotifyOnMetadataChange().withLitInteriorSurface(true).setStatParent(() -> Items.BED)
         .withTags(BlockTags.NOT_IN_CREATIVE_MENU, BlockTags.MINEABLE_BY_AXE);
     public static final @NotNull Block<?> SEAT = register("seat", "minecraft:block/seat", 611, BlockLogicSeat::new)
@@ -965,6 +962,9 @@ public final class Blocks {
     public static final @NotNull Block<BlockLogicFireCold> FIRE_COLD = register("fire_cold", "minecraft:block/fire_cold", 633, BlockLogicFireCold::new)
         .withSound(BlockSounds.FIRE).withHardness(0.0F).withLightEmission(1.0F).withDisabledStats().withDisabledNeighborNotifyOnMetadataChange()
         .withTags(BlockTags.BROKEN_BY_FLUIDS, BlockTags.PLACE_OVERWRITES, BlockTags.NOT_IN_CREATIVE_MENU);
+    public static final @NotNull Block<BlockLogicFireSulfuric> FIRE_SULFURIC = register("fire_sulfuric", "minecraft:block/fire_sulfuric", 634, BlockLogicFireSulfuric::new)
+        .withSound(BlockSounds.FIRE).withHardness(0.0F).withLightEmission(1.0F).withDisabledStats().withDisabledNeighborNotifyOnMetadataChange()
+        .withTags(BlockTags.BROKEN_BY_FLUIDS, BlockTags.PLACE_OVERWRITES, BlockTags.NOT_IN_CREATIVE_MENU, BlockTags.SULFURIC_FIRE_OVERLAY_SOURCE);
 
     public static final @NotNull Block<?> MOBSPAWNER = register("mobspawner", "minecraft:block/mobspawner", 640, BlockLogicMobSpawner::new)
         .withSound(BlockSounds.METAL).withHardness(5F).withImmovableFlagSet()
@@ -1146,35 +1146,38 @@ public final class Blocks {
 
     public static final @NotNull Block<?> GLOWSTONE = register("glowstone", "minecraft:block/glowstone", 820, (b) -> new BlockLogicGlowStone(b, Materials.STONE))
         .withSound(BlockSounds.GLASS).withHardness(0.3F).withLightEmission(1.0F).withOverrideColor(MaterialColor.paintedYellow)
+        .withTags(BlockTags.MINEABLE_BY_PICKAXE, BlockTags.INFINITE_BURN_SULFURIC);
+
+    public static final @NotNull Block<?> RUBYGLASS_COLUMN = register("rubyglass.column", "minecraft:block/rubyglass_column", 821, (b) -> new BlockLogicRubyglass(b))
+        .withSound(BlockSounds.CRYSTAL).withOverrideColor(MaterialColor.rubyglass).withHardness(2F)
         .withTags(BlockTags.MINEABLE_BY_PICKAXE);
 
-    public static final Block<?> RUBYGLASS_COLUMN = register("rubyglass.column", "minecraft:block/rubyglass_column", 821, (b) -> new BlockLogicRubyglass(b))
-        .withSound(BlockSounds.GLASS).withHardness(3F).withLightEmission(0.1F).withLightBlock(0)
-        .withTags(BlockTags.MINEABLE_BY_PICKAXE)
-        .withOverrideColor(MaterialColor.rubyglass);
-    public static final Block<?> BLOCK_RUBYGLASS = register("block.rubyglass", "minecraft:block/block_rubyglass", 822, (b) -> new BlockLogicTransparent(b, Materials.STONE))
-        .withSound(BlockSounds.GLASS).withHardness(3F).withLightEmission(0.5F)
+    public static final @NotNull Block<?> BLOCK_RUBYGLASS = register("block.rubyglass", "minecraft:block/rubyglass_block", 822, (b) -> new BlockLogic(b, Materials.STONE))
+        .withSound(BlockSounds.CRYSTAL).withOverrideColor(MaterialColor.rubyglass).withHardness(2F)
+        .withTags(BlockTags.MINEABLE_BY_PICKAXE);
+    public static final @NotNull Block<?> RUBYGLASS_CRYSTAL = register("rubyglass.crystal", "minecraft:block/rubyglass_crystal", 823, BlockLogicRubyglassCrystal::new)
+        .withSound(BlockSounds.CRYSTAL).withHardness(1.5F).withOverrideColor(MaterialColor.rubyglass)
+        .withTags(BlockTags.MINEABLE_BY_PICKAXE, BlockTags.SKATEABLE, BlockTags.NETHER_MOBS_SPAWN, BlockTags.NETHER_SURFACE_BLOCK, BlockTags.CAVES_CUT_THROUGH);
+    public static final @NotNull Block<BlockLogicConduit> CONDUIT = register("rubyglass.conduit", "minecraft:block/conduit", 824, (BlockLogicSupplier<BlockLogicConduit>) b -> new BlockLogicConduit(b, Materials.STONE))
+        .withSound(BlockSounds.STONE).withHardness(1.5F).withOverrideColor(MaterialColor.rubyglass).withDisabledNeighborNotifyOnMetadataChange()
+        .withTags(BlockTags.MINEABLE_BY_PICKAXE, BlockTags.CHAINLINK_FENCES_CONNECT, BlockTags.PREVENT_MOB_SPAWNS);
+    public static final @NotNull Block<?> RUBYGLASS_GROWTH = register("rubyglass.growth", "minecraft:block/rubyglass_growth", 825, block -> new BlockLogicGrowthRubyglass(block, Materials.PLANT))
+        .withSound(BlockSounds.CRYSTAL).withOverrideColor(MaterialColor.rubyglass)
+        .withTags(BlockTags.MINEABLE_BY_PICKAXE, BlockTags.SHEARS_DO_SILK_TOUCH);
+    public static final @NotNull Block<?> RUBYGLASS_NODE = register("rubyglass.node", "minecraft:block/rubyglass_node", 826, (b) -> new BlockLogicNodeRubyglass(b))
+        .withSound(BlockSounds.CRYSTAL).withHardness(3F)
         .withTags(BlockTags.MINEABLE_BY_PICKAXE)
         .withOverrideColor(MaterialColor.rubyglass);
-    public static final Block<?> RUBYGLASS_SPROUT = register("rubyglass.sprout", "minecraft:block/rubyglass_sprout", 823, (b) -> new BlockLogicRubyglassSprout(b))
-        .withSound(BlockSounds.GLASS).withDisabledNeighborNotifyOnMetadataChange().withLightEmission(0.5F)
+    public static final @NotNull Block<?> BRICK_RUBYGLASS = register("brick.rubyglass", "minecraft:block/rubyglass_brick", 827, (b) -> new BlockLogicRubyglass(b))
+        .withSound(BlockSounds.CRYSTAL).withHardness(2F)
         .withTags(BlockTags.MINEABLE_BY_PICKAXE)
         .withOverrideColor(MaterialColor.rubyglass);
-    public static final Block<BlockLogicConduit> CONDUIT = register("rubyglass.conduit", "minecraft:block/conduit", 824, (BlockLogicSupplier<BlockLogicConduit>) b -> new BlockLogicConduit(b, Materials.STONE))
-        .withSound(BlockSounds.STONE).withHardness(1.5F).withDisabledNeighborNotifyOnMetadataChange()
-        .withTags(BlockTags.MINEABLE_BY_PICKAXE, BlockTags.CHAINLINK_FENCES_CONNECT, BlockTags.PREVENT_MOB_SPAWNS)
-        .withOverrideColor(MaterialColor.rubyglass);
-
-    public static final Block<?> RUBYGLASS_NODE = register("rubyglass.node", "minecraft:block/rubyglass_node", 826, (b) -> new BlockLogicOreRubyglass(b))
-            .withSound(BlockSounds.GLASS).withHardness(3F).withLightEmission(1.0F)
-            .withTags(BlockTags.MINEABLE_BY_PICKAXE)
-            .withOverrideColor(MaterialColor.rubyglass);
 
     public static final @NotNull Block<BlockLogicPortal> PORTAL_NETHER = register("portal.nether", "minecraft:block/portal_nether", 830, (BlockLogicSupplier<BlockLogicPortal>) b -> new BlockLogicPortal(b, Dimension.NETHER, Blocks.OBSIDIAN, Blocks.FIRE))
         .withSound(BlockSounds.GLASS).withHardness(-1F).withLightEmission(0.75F).withOverrideColor(MaterialColor.paintedPurple).withDisabledStats()
         .withTags(BlockTags.BROKEN_BY_FLUIDS, BlockTags.NOT_IN_CREATIVE_MENU);
     public static final @NotNull Block<BlockLogicPortal> PORTAL_DRIFT = register("portal.drift", "minecraft:block/portal_drift", 831, (BlockLogicSupplier<BlockLogicPortal>) b -> new BlockLogicPortal(b, Dimension.DRIFT, Blocks.GLOWSTONE, Blocks.BEDROCK))
-        .withSound(BlockSounds.GLASS).withHardness(-1F).withLightEmission(0.75f).withOverrideColor(MaterialColor.paintedPurple).withDisabledStats()
+        .withSound(BlockSounds.GLASS).withHardness(-1F).withLightEmission(0.75f).withOverrideColor(MaterialColor.paintedSilver).withDisabledStats()
         .withTags(BlockTags.BROKEN_BY_FLUIDS, BlockTags.NOT_IN_CREATIVE_MENU);
 
     public static final @NotNull Block<?> CAKE = register("cake", "minecraft:block/cake", 840, BlockLogicCake::new)
@@ -1280,7 +1283,12 @@ public final class Blocks {
         .withSound(BlockSounds.GLASS).withHardness(0.1F).withDisabledNeighborNotifyOnMetadataChange().setStatParent(() -> Items.JAR_BUTTERFLY_SILVER)
         .withTags(BlockTags.BROKEN_BY_FLUIDS, BlockTags.MINEABLE_BY_PICKAXE, BlockTags.NOT_IN_CREATIVE_MENU);
 
-    //ID 999 taken by SLAB_BRICK_DIAMOND, placed near other ore brick slabs for organization. Ran out of IDs on that side                                             - id:999 -
+    public static final Block<?> SLAB_BRICK_RUBYGLASS = register("slab.brick.rubyglass", "minecraft:block/slab_brick_rubyglass", 998, (BlockLogicSupplier<BlockLogicSlab>) b -> new BlockLogicSlab(b, Blocks.BRICK_RUBYGLASS))
+            .withSound(BlockSounds.CRYSTAL).withOverrideColor(MaterialColor.rubyglass).withLitInteriorSurface(true)
+            .withTags(BlockTags.MINEABLE_BY_PICKAXE);
+    public static final @NotNull Block<BlockLogicSlab> SLAB_BRICK_DIAMOND = register("slab.brick.diamond", "minecraft:block/slab_brick_diamond", 999, (BlockLogicSupplier<BlockLogicSlab>)b -> new BlockLogicSlab(b, Blocks.BRICK_DIAMOND))
+            .withSound(BlockSounds.PERMAFROST).withDisabledNeighborNotifyOnMetadataChange().withLitInteriorSurface(true)
+            .withTags(BlockTags.MINEABLE_BY_PICKAXE);
 
     public static final @NotNull Block<BlockLogicSlab> SLAB_BRICK_SANDSTONE = register("slab.brick.sandstone", "minecraft:block/slab_brick_sandstone", 1000, (BlockLogicSupplier<BlockLogicSlab>) b -> new BlockLogicSlab(b, Blocks.BRICK_SANDSTONE))
         .withSound(BlockSounds.STONE).withDisabledNeighborNotifyOnMetadataChange().withLitInteriorSurface(true)
@@ -1295,9 +1303,6 @@ public final class Blocks {
         .withSound(BlockSounds.PERMAFROST).withDisabledNeighborNotifyOnMetadataChange().withLitInteriorSurface(true)
         .withTags(BlockTags.MINEABLE_BY_PICKAXE);
 
-    public static final @NotNull Block<BlockLogicSlab> SLAB_BRICK_DIAMOND = register("slab.brick.diamond", "minecraft:block/slab_brick_diamond", 999, (BlockLogicSupplier<BlockLogicSlab>)b -> new BlockLogicSlab(b, Blocks.BRICK_DIAMOND))
-            .withSound(BlockSounds.PERMAFROST).withDisabledNeighborNotifyOnMetadataChange().withLitInteriorSurface(true)
-            .withTags(BlockTags.MINEABLE_BY_PICKAXE);
     public static final @NotNull Block<BlockLogicSlab> SLAB_BRICK_IRON = register("slab.brick.iron", "minecraft:block/slab_brick_iron", 1004, (BlockLogicSupplier<BlockLogicSlab>)b -> new BlockLogicSlab(b, Blocks.BRICK_IRON))
         .withSound(BlockSounds.METAL).withDisabledNeighborNotifyOnMetadataChange().withLitInteriorSurface(true)
         .withTags(BlockTags.MINEABLE_BY_PICKAXE);
@@ -1355,34 +1360,36 @@ public final class Blocks {
         .withTags(BlockTags.MINEABLE_BY_PICKAXE, BlockTags.NOT_IN_CREATIVE_MENU);
 
     public static final Block<BlockLogicThermalVent> THERMAL_VENT = register("vent", "minecraft:block/thermal_vent", 1025, BlockLogicThermalVent::new)
-        .withSound(BlockSounds.STONE).withHardness(1.5F).withTags(BlockTags.MINEABLE_BY_PICKAXE);
+        .withSound(BlockSounds.STONE).withHardness(1.5F)
+        .withTags(BlockTags.MINEABLE_BY_PICKAXE);
 
-    public static final Block<?> BONE_PILE = register("bone_pile", "minecraft:block/bone_pile", 1030, BlockLogicBonepile::new)
+    public static final Block<?> BONE_PILE = register("bone_pile", "minecraft:block/bone_pile", 1030, BlockLogicBonePile::new)
         .withSound(BlockSounds.STONE).withHardness(1.0F).withOverrideColor(MaterialColor.marble)
         .withTags(BlockTags.MINEABLE_BY_PICKAXE, BlockTags.BROKEN_BY_FLUIDS, BlockTags.PLACE_OVERWRITES, BlockTags.SHEARS_DO_SILK_TOUCH, BlockTags.PLANTABLE_IN_JAR);
-    public static final Block<?> SOULCATCHER = register("soul_catcher", "minecraft:block/soul_catcher", 1031, BlockLogicSoulcatcher::new)
+    public static final Block<?> SOUL_CATCHER = register("soul_catcher", "minecraft:block/soul_catcher", 1031, BlockLogicSoulCatcher::new)
         .withSound(BlockSounds.GRAVEL).withHardness(1.0F).withOverrideColor(MaterialColor.sand)
         .withTags(BlockTags.MINEABLE_BY_SHOVEL, BlockTags.BROKEN_BY_FLUIDS, BlockTags.PLACE_OVERWRITES, BlockTags.SHEARS_DO_SILK_TOUCH, BlockTags.PLANTABLE_IN_JAR)
         .withOverrideColor(MaterialColor.soulsand);
 
     public static final Block<?> GLOOMSTONE = register("gloomstone", "minecraft:block/gloomstone", 1032, (b) -> new BlockLogicStone(b, Blocks.COBBLE_GLOOMSTONE, Materials.GLOOMSTONE))
-        .withSound(BlockSounds.STONE).withHardness(1.5F).withBlastResistance(10F)
-        .withTags(BlockTags.CAVES_CUT_THROUGH, BlockTags.NETHER_MOBS_SPAWN, BlockTags.NETHER_SURFACE_BLOCK, BlockTags.CHAINLINK_FENCES_CONNECT, BlockTags.MINEABLE_BY_PICKAXE);
+        .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))
         .withSound(BlockSounds.STONE).withHardness(2.0F).withBlastResistance(10F)
-        .withTags(BlockTags.MINEABLE_BY_PICKAXE, BlockTags.CHAINLINK_FENCES_CONNECT);
+        .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))
-        .withSound(BlockSounds.STONE).withHardness(1.5F).withBlastResistance(10F)
+        .withSound(BlockSounds.STONE).withHardness(2.0F).withBlastResistance(10F)
         .withTags(BlockTags.MINEABLE_BY_PICKAXE, BlockTags.CHAINLINK_FENCES_CONNECT);
     public static final @NotNull Block<BlockLogicSlab> SLAB_GLOOMSTONE_POLISHED = register("slab.gloomstone.carved", "minecraft:block/slab_gloomstone_carved", 1035, (BlockLogicSupplier<BlockLogicSlab>)b -> new BlockLogicSlab(b, Blocks.GLOOMSTONE_CARVED))
-        .withSound(BlockSounds.STONE).withHardness(1.5F).withBlastResistance(10F)
+        .withSound(BlockSounds.STONE).withHardness(2.0F).withBlastResistance(10F)
         .withTags(BlockTags.MINEABLE_BY_PICKAXE);
     public static final @NotNull Block<BlockLogicSlab> SLAB_COBBLE_GLOOMSTONE = register("slab.cobble.gloomstone", "minecraft:block/slab_cobble_gloomstone", 1036, (BlockLogicSupplier<BlockLogicSlab>)b -> new BlockLogicSlab(b, Blocks.COBBLE_GLOOMSTONE))
-        .withSound(BlockSounds.STONE).withHardness(1.5F).withBlastResistance(10F)
+        .withSound(BlockSounds.STONE).withHardness(2.0F).withBlastResistance(10F)
         .withTags(BlockTags.MINEABLE_BY_PICKAXE);
     public static final @NotNull Block<BlockLogicSlab> SLAB_BRICK_GLOOMSTONE = register("slab.brick.gloomstone", "minecraft:block/slab_brick_gloomstone", 1037, (BlockLogicSupplier<BlockLogicSlab>)b -> new BlockLogicSlab(b, Blocks.BRICK_GLOOMSTONE))
-        .withSound(BlockSounds.STONE).withHardness(1.5F).withBlastResistance(10F)
+        .withSound(BlockSounds.STONE).withHardness(2.0F).withBlastResistance(10F)
         .withTags(BlockTags.MINEABLE_BY_PICKAXE);
+
     public static final @NotNull Block<BlockLogicStairs> STAIRS_COBBLE_GLOOMSTONE = register("stairs.cobble.gloomstone", "minecraft:block/stairs_cobble_gloomstone", 1038, (BlockLogicSupplier<BlockLogicStairs>)b -> new BlockLogicStairs(b, Blocks.COBBLE_GLOOMSTONE))
         .withSound(BlockSounds.STONE).withHardness(1.5F).withBlastResistance(10F).withLitInteriorSurface(true)
         .withTags(BlockTags.MINEABLE_BY_PICKAXE);
@@ -1397,6 +1404,10 @@ public final class Blocks {
         .withSound(BlockSounds.STONE).withHardness(1.5F).withDisabledNeighborNotifyOnMetadataChange()
         .withTags(BlockTags.MINEABLE_BY_PICKAXE, BlockTags.NOT_IN_CREATIVE_MENU, BlockTags.CHAINLINK_FENCES_CONNECT, BlockTags.PREVENT_MOB_SPAWNS);
 
+    public static final @NotNull Block<?> STAIRS_BRICK_RUBYGLASS = register("stairs.brick.rubyglass", "minecraft:block/stairs_brick_rubyglass", 1048, (BlockLogicSupplier<BlockLogicStairs>) b -> new BlockLogicStairs(b, Blocks.BRICK_RUBYGLASS))
+        .withSound(BlockSounds.CRYSTAL).withOverrideColor(MaterialColor.rubyglass).withLitInteriorSurface(true)
+        .withTags(BlockTags.MINEABLE_BY_PICKAXE);
+
     public static final @NotNull Block<BlockLogicStairs> STAIRS_BRICK_DIAMOND = register("stairs.brick.diamond", "minecraft:block/stairs_brick_diamond", 1049, (BlockLogicSupplier<BlockLogicStairs>)b -> new BlockLogicStairs(b, Blocks.BRICK_DIAMOND))
         .withSound(BlockSounds.PERMAFROST).withDisabledNeighborNotifyOnMetadataChange().withLitInteriorSurface(true)
         .withTags(BlockTags.MINEABLE_BY_PICKAXE);
@@ -1419,10 +1430,92 @@ public final class Blocks {
         .withSound(BlockSounds.PERMAFROST).withDisabledNeighborNotifyOnMetadataChange().withLitInteriorSurface(true)
         .withTags(BlockTags.MINEABLE_BY_PICKAXE);
 
-    public static final @NotNull Block<BlockLogicFlowstone> FLOWSTONE = register("flowstone", "minecraft:block/flowstone", 1060, BlockLogicFlowstone::new)
-        .withSound(BlockSounds.STONE).withHardness(1.5F).withDisabledNeighborNotifyOnMetadataChange()
+    public static final @NotNull Block<?> BOULDER_MAGMATIC = register("boulder.magmatic", "minecraft:block/boulder_magmatic", 1060, BlockLogicBoulderMagmatic::new)
+        .withHardness(1.0F).withLitInteriorSurface(true).setTicking(true)
+        .withTags(BlockTags.BROKEN_BY_FLUIDS, BlockTags.MINEABLE_BY_PICKAXE);
+    public static final @NotNull Block<?> BOULDER_SULFURIC = register("boulder.sulfuric", "minecraft:block/boulder_sulfuric", 1061, BlockLogicBoulderSulfuric::new)
+        .withHardness(1.0F).withLitInteriorSurface(true).setTicking(true)
+        .withTags(BlockTags.BROKEN_BY_FLUIDS, BlockTags.MINEABLE_BY_PICKAXE, BlockTags.SULFURIC_FIRE_OVERLAY_SOURCE);
+
+    public static final @NotNull Block<BlockLogicFluid> FLUID_ACID_FLOWING = register("fluid.acid.flowing", "minecraft:block/fluid_acid_flowing", 1062, (BlockLogicSupplier<BlockLogicFluid>) b -> new BlockLogicFluidFlowing(b, Materials.ACID, new FluidAcid(), Blocks.FLUID_ACID_STILL))
+        .withHardness(100.0F).withLitInteriorSurface(true).withDisabledStats().withDisabledNeighborNotifyOnMetadataChange()
+        .withTags(BlockTags.PLACE_OVERWRITES, BlockTags.NOT_IN_CREATIVE_MENU, BlockTags.IS_ACID);
+    public static final @NotNull Block<BlockLogicFluid> FLUID_ACID_STILL = register("fluid.acid.still", "minecraft:block/fluid_acid_still", 1063, (BlockLogicSupplier<BlockLogicFluid>) b -> new BlockLogicFluidStill(b, Materials.ACID, new FluidAcid(), Blocks.FLUID_ACID_FLOWING))
+        .withHardness(100.0F).withLitInteriorSurface(true).withDisabledStats().withDisabledNeighborNotifyOnMetadataChange().setStatParent(() -> FLUID_ACID_FLOWING)
+        .withTags(BlockTags.PLACE_OVERWRITES, BlockTags.NOT_IN_CREATIVE_MENU, BlockTags.IS_ACID);
+
+    public static final Block<?> EMBER = register("ember", "minecraft:block/ember", 1065, block -> new BlockLogicEmber(block, Materials.STONE))
+        .withHardness(1.0f).withOverrideColor(MaterialColor.paintedBlack).withLightEmission(5)
+        .withTags(BlockTags.MINEABLE_BY_PICKAXE, BlockTags.NETHER_SURFACE_BLOCK, BlockTags.NETHER_MOBS_SPAWN, BlockTags.CAVES_CUT_THROUGH);
+
+    public static final Block<?> SLATE_CARVED = register("slate.carved", "minecraft:block/slate_carved", 1070, block -> new BlockLogic(block, Materials.SLATE))
+        .withSound(BlockSounds.STONE).withHardness(1.0F).withBlastResistance(10.0F).withDisabledStats()
+        .withTags(BlockTags.NOT_IN_CREATIVE_MENU, BlockTags.MINEABLE_BY_PICKAXE, BlockTags.CHAINLINK_FENCES_CONNECT, BlockTags.NETHER_MOBS_SPAWN);
+    public static final Block<?> SLAB_SLATE_POLISHED = register("slab.slate.carved", "minecraft:block/slab_slate_carved", 1071, block -> new BlockLogicSlab(block, SLATE_CARVED))
+        .withSound(BlockSounds.STONE)
         .withTags(BlockTags.MINEABLE_BY_PICKAXE);
 
+    public static final Block<?> LOG_SCORCHED = register("log.scorched", "minecraft:block/log_scorched", 1072, BlockLogicLog::new)
+        .withSound(BlockSounds.WOOD).withHardness(1.5F)
+        .withTags(BlockTags.FENCES_CONNECT, BlockTags.MINEABLE_BY_AXE);
+
+    public static final Block<BlockLogicStatue> STATUE_SLATE_LOWER = register("statue.slate.lower", "minecraft:block/statue_slate_lower", 1075, (BlockLogicSupplier<BlockLogicStatue>) block -> new BlockLogicStatue(block, Materials.SLATE, true, () -> Items.STATUE_SLATE))
+        .withSound(BlockSounds.STONE).withHardness(1.5F).setStatParent(() -> Items.STATUE_SLATE)
+        .withTags(BlockTags.MINEABLE_BY_PICKAXE, BlockTags.NOT_IN_CREATIVE_MENU);
+    public static final Block<BlockLogicStatue> STATUE_SLATE_UPPER = register("statue.slate.upper", "minecraft:block/statue_slate_upper", 1076, (BlockLogicSupplier<BlockLogicStatue>) block -> new BlockLogicStatue(block, Materials.SLATE, false, () -> Items.STATUE_SLATE))
+        .withSound(BlockSounds.STONE).withHardness(1.5F).setStatParent(() -> Items.STATUE_SLATE)
+        .withTags(BlockTags.MINEABLE_BY_PICKAXE, BlockTags.NOT_IN_CREATIVE_MENU);
+    public static final Block<BlockLogicStatue> STATUE_PERMAFROST_LOWER = register("statue.permafrost.lower", "minecraft:block/statue_permafrost_lower", 1077, (BlockLogicSupplier<BlockLogicStatue>) block -> new BlockLogicStatue(block, Materials.PERMAFROST, true, () -> Items.STATUE_PERMAFROST))
+        .withSound(BlockSounds.PERMAFROST).withHardness(1.5F).setStatParent(() -> Items.STATUE_PERMAFROST)
+        .withTags(BlockTags.MINEABLE_BY_PICKAXE, BlockTags.NOT_IN_CREATIVE_MENU);
+    public static final Block<BlockLogicStatue> STATUE_PERMAFROST_UPPER = register("statue.permafrost.upper", "minecraft:block/statue_permafrost_upper", 1078, (BlockLogicSupplier<BlockLogicStatue>) block -> new BlockLogicStatue(block, Materials.PERMAFROST, false, () -> Items.STATUE_PERMAFROST))
+        .withSound(BlockSounds.PERMAFROST).withHardness(1.5F).setStatParent(() -> Items.STATUE_PERMAFROST)
+        .withTags(BlockTags.MINEABLE_BY_PICKAXE, BlockTags.NOT_IN_CREATIVE_MENU);
+    public static final Block<BlockLogicStatue> STATUE_NETHERRACK_LOWER = register("statue.netherrack.lower", "minecraft:block/statue_netherrack_lower", 1079, (BlockLogicSupplier<BlockLogicStatue>) block -> new BlockLogicStatue(block, Materials.NETHERRACK, true, () -> Items.STATUE_NETHERRACK))
+        .withSound(BlockSounds.STONE).withHardness(1.5F).setStatParent(() -> Items.STATUE_NETHERRACK)
+        .withTags(BlockTags.MINEABLE_BY_PICKAXE, BlockTags.NOT_IN_CREATIVE_MENU);
+    public static final Block<BlockLogicStatue> STATUE_NETHERRACK_UPPER = register("statue.netherrack.upper", "minecraft:block/statue_netherrack_upper", 1080, (BlockLogicSupplier<BlockLogicStatue>) block -> new BlockLogicStatue(block, Materials.NETHERRACK, false, () -> Items.STATUE_NETHERRACK))
+        .withSound(BlockSounds.STONE).withHardness(1.5F).setStatParent(() -> Items.STATUE_NETHERRACK)
+        .withTags(BlockTags.MINEABLE_BY_PICKAXE, BlockTags.NOT_IN_CREATIVE_MENU);
+    public static final Block<BlockLogicStatue> STATUE_GLOOMSTONE_LOWER = register("statue.gloomstone.lower", "minecraft:block/statue_gloomstone_lower", 1081, (BlockLogicSupplier<BlockLogicStatue>) block -> new BlockLogicStatue(block, Materials.GLOOMSTONE, true, () -> Items.STATUE_GLOOMSTONE))
+        .withSound(BlockSounds.STONE).withHardness(1.5F).setStatParent(() -> Items.STATUE_GLOOMSTONE)
+        .withTags(BlockTags.MINEABLE_BY_PICKAXE, BlockTags.NOT_IN_CREATIVE_MENU);
+    public static final Block<BlockLogicStatue> STATUE_GLOOMSTONE_UPPER = register("statue.gloomstone.upper", "minecraft:block/statue_gloomstone_upper", 1082, (BlockLogicSupplier<BlockLogicStatue>) block -> new BlockLogicStatue(block, Materials.GLOOMSTONE, false, () -> Items.STATUE_GLOOMSTONE))
+        .withSound(BlockSounds.STONE).withHardness(1.5F).setStatParent(() -> Items.STATUE_GLOOMSTONE)
+        .withTags(BlockTags.MINEABLE_BY_PICKAXE, BlockTags.NOT_IN_CREATIVE_MENU);
+
+    public static final Block<?> BRIMSAND = register("brimsand", "minecraft:block/brimsand", 1085, (b) -> new BlockLogic(b, Materials.SAND))
+        .withSound(BlockSounds.SAND).withHardness(0.7F)
+        .withTags(BlockTags.MINEABLE_BY_SHOVEL, BlockTags.CAVES_CUT_THROUGH, BlockTags.NETHER_MOBS_SPAWN, BlockTags.NETHER_SURFACE_BLOCK, BlockTags.INFINITE_BURN_SULFURIC);
+    public static final Block<?> BRIMSTONE = register("brimstone", "minecraft:block/brimstone", 1086, block -> new BlockLogicBrimstone(block, Materials.STONE))
+        .withSound(BlockSounds.STONE).withHardness(1.8F).withOverrideColor(MaterialColor.paintedBlack)
+        .withTags(BlockTags.CAVES_CUT_THROUGH, BlockTags.MINEABLE_BY_PICKAXE, BlockTags.CHAINLINK_FENCES_CONNECT, BlockTags.NETHER_MOBS_SPAWN, BlockTags.NETHER_SURFACE_BLOCK, BlockTags.INFINITE_BURN_SULFURIC);
+    public static final Block<?> SLAB_BRIMSTONE = register("slab.brimstone", "minecraft:block/slab_brimstone", 1087, block -> new BlockLogicSlab(block, BRIMSTONE))
+        .withSound(BlockSounds.STONE).withDisabledNeighborNotifyOnMetadataChange().withLitInteriorSurface(true).withOverrideColor(MaterialColor.paintedBlack)
+        .withTags(BlockTags.MINEABLE_BY_PICKAXE, BlockTags.INFINITE_BURN_SULFURIC);
+    public static final Block<?> STAIRS_BRIMSTONE = register("stairs.brimstone", "minecraft:block/stairs_brimstone", 1088, block -> new BlockLogicStairs(block, BRIMSTONE))
+        .withSound(BlockSounds.STONE).withDisabledNeighborNotifyOnMetadataChange().withLitInteriorSurface(true).withOverrideColor(MaterialColor.paintedBlack)
+        .withTags(BlockTags.MINEABLE_BY_PICKAXE, BlockTags.INFINITE_BURN_SULFURIC);
+    public static final Block<?> BRICK_BRIMSTONE = register("brick.brimstone", "minecraft:block/brick_brimstone", 1089, block -> new BlockLogicBrimstone(block, Materials.STONE))
+        .withSound(BlockSounds.STONE).withHardness(1.8F).withOverrideColor(MaterialColor.paintedBlack)
+        .withTags(BlockTags.CAVES_CUT_THROUGH, BlockTags.MINEABLE_BY_PICKAXE, BlockTags.CHAINLINK_FENCES_CONNECT, BlockTags.NETHER_MOBS_SPAWN, BlockTags.NETHER_SURFACE_BLOCK, BlockTags.INFINITE_BURN_SULFURIC);
+    public static final Block<?> SLAB_BRICK_BRIMSTONE = register("slab.brick.brimstone", "minecraft:block/slab_brick_brimstone", 1090, block -> new BlockLogicSlab(block, BRICK_BRIMSTONE))
+        .withSound(BlockSounds.STONE).withDisabledNeighborNotifyOnMetadataChange().withLitInteriorSurface(true).withOverrideColor(MaterialColor.paintedBlack)
+        .withTags(BlockTags.MINEABLE_BY_PICKAXE, BlockTags.INFINITE_BURN_SULFURIC);
+    public static final Block<?> STAIRS_BRICK_BRIMSTONE = register("stairs.brick.brimstone", "minecraft:block/stairs_brick_brimstone", 1091, block -> new BlockLogicStairs(block, BRICK_BRIMSTONE))
+        .withSound(BlockSounds.STONE).withDisabledNeighborNotifyOnMetadataChange().withLitInteriorSurface(true).withOverrideColor(MaterialColor.paintedBlack)
+        .withTags(BlockTags.MINEABLE_BY_PICKAXE, BlockTags.INFINITE_BURN_SULFURIC);
+    public static final @NotNull Block<?> BRIMTHAW = register("brimthaw", "minecraft:block/brimthaw", 1092, BlockLogicBrimthaw::new)
+        .withSound(BlockSounds.STONE).withHardness(1.25F).withBlastResistance(7F).withLightEmission(10).withOverrideColor(MaterialColor.paintedYellow)
+        .withTags(BlockTags.MINEABLE_BY_PICKAXE, BlockTags.INFINITE_BURN_SULFURIC, BlockTags.SULFURIC_FIRE_OVERLAY_SOURCE);
+
+    public static final Block<?> SULFUR = register("sulfur", "minecraft:block/sulfur", 1098, BlockLogicSulfur::new)
+        .withSound(BlockSounds.SAND).withHardness(0.5f).withOverrideColor(MaterialColor.paintedYellow)
+        .withTags(BlockTags.MINEABLE_BY_SHOVEL, BlockTags.NETHER_SURFACE_BLOCK, BlockTags.NETHER_MOBS_SPAWN, BlockTags.CAVES_CUT_THROUGH, BlockTags.INFINITE_BURN_SULFURIC);
+    public static final Block<?> SULFUR_HARDENED = register("sulfur_hardened", "minecraft:block/sulfur_hardened", 1099, BlockLogicSulfurHardened::new)
+        .withSound(BlockSounds.SAND).withHardness(0.5f).withOverrideColor(MaterialColor.paintedYellow)
+        .withTags(BlockTags.MINEABLE_BY_PICKAXE, BlockTags.NETHER_SURFACE_BLOCK, BlockTags.NETHER_MOBS_SPAWN, BlockTags.CAVES_CUT_THROUGH, BlockTags.INFINITE_BURN_SULFURIC);
+
     public static <T extends BlockLogic> Block<T> register(String key, String namespaceId, int id, BlockLogicSupplier<T> logicSupplier) {
         Block<T> container = new Block<>(id, key, namespaceId, logicSupplier);
 
diff --git a/game/core/src/main/java/net/minecraft/core/block/FluidAcid.java b/game/core/src/main/java/net/minecraft/core/block/FluidAcid.java
new file mode 100644
index 000000000..dc9189408
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/block/FluidAcid.java
@@ -0,0 +1,107 @@
+package net.minecraft.core.block;
+
+import net.minecraft.core.block.material.Material;
+import net.minecraft.core.block.material.Materials;
+import net.minecraft.core.enums.EnumDropCause;
+import net.minecraft.core.sound.SoundCategory;
+import net.minecraft.core.world.LevelListener;
+import net.minecraft.core.world.World;
+import net.minecraft.core.world.type.tag.WorldTypeTags;
+import net.minecraft.core.world.pos.TilePos;
+import net.minecraft.core.world.pos.TilePosc;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Random;
+
+public class FluidAcid implements Fluid {
+	public FluidAcid() {
+	}
+
+	public int tickDelay() {
+		return 40;
+	}
+
+	@Override
+	public void animationTick(@NotNull BlockLogicFluid logicFluid, @NotNull World world, @NotNull TilePosc tilePos, @NotNull Random rand) {
+		int data = world.getBlockData(tilePos) & 15;
+		TilePos queryPos = new TilePos();
+
+		boolean isBottom = world.isBlockOpaqueCube(tilePos.down(queryPos));
+		if (rand.nextInt(64) == 0) {
+			world.playSoundEffect(null, SoundCategory.WORLD_SOUNDS, (float) tilePos.x() + 0.5F, (float) tilePos.y() + 0.5F, (float) tilePos.z() + 0.5F,
+				"random.fizz", rand.nextFloat() * 0.125F, rand.nextFloat() + 0.5F);
+		}
+
+		if (rand.nextInt(64) == 0) {
+			world.playSoundEffect(null, SoundCategory.WORLD_SOUNDS, (float) tilePos.x() + 0.5F, (float) tilePos.y() + 0.5F, (float) tilePos.z() + 0.5F,
+				"tile.acidbubble", rand.nextFloat() * 0.25F + 0.25F, rand.nextFloat() - 1.0F);
+		}
+
+		if ((isBottom || rand.nextInt(16) == 0) && data == 0 && rand.nextInt(2) == 0) {
+			world.spawnParticle("acidBoiling", (double) tilePos.x() + Math.random(), (float) tilePos.y() + 0.05F, (double) tilePos.z() + Math.random(), 0.0F, 0.0F, 0.0F, 0, false);
+		}
+
+		if (rand.nextInt(4) == 0 && world.getBlockType(tilePos.down(new TilePos())).solid() && !world.getBlockType(tilePos.down(new TilePos()).down()).solid()) {
+			float off1 = rand.nextFloat() * 0.9F + 0.05F;
+			float off2 = rand.nextFloat() * 0.9F + 0.05F;
+			float lOff = 0.01F;
+			world.spawnParticle("acidDrip", (float) tilePos.x() + off1, (float) (tilePos.y() - 1) - lOff, (float) tilePos.z() + off2, 0.0F, 0.0F, 0.0F, 0, false);
+		}
+
+	}
+
+	@Override
+	public boolean checkForHarden(@NotNull BlockLogicFluid logicFluid, @NotNull World world, @NotNull TilePosc tilePos, @NotNull Material encountered) {
+		if (encountered == Materials.LAVA || encountered == Materials.WATER) {
+			final int data = world.getBlockData(tilePos) & 15;
+			final boolean netherWorld = world.getWorldType().hasTag(WorldTypeTags.NETHER);
+			if (netherWorld) {
+				if (data == 0) {
+					world.setBlockTypeNotify(tilePos, Blocks.BRIMTHAW);
+				} else {
+					world.setBlockTypeNotify(tilePos, Blocks.BRIMSTONE);
+				}
+			} else {
+				if (data == 0) {
+					world.setBlockTypeNotify(tilePos, Blocks.SULFUR);
+				} else {
+					world.setBlockTypeNotify(tilePos, Blocks.SULFUR_HARDENED);
+				}
+			}
+
+			world.playSoundEffect(null, SoundCategory.WORLD_SOUNDS, (float) tilePos.x() + 0.5F, (float) tilePos.y() + 0.5F, (float) tilePos.z() + 0.5F, "random.fizz", 0.5F, 2.6F + (world.rand.nextFloat() - world.rand.nextFloat()) * 0.8F);
+
+			for (int i = 0; i < 8; ++i) {
+				world.spawnParticle("largesmoke", (double) tilePos.x() + Math.random(), (double) tilePos.y() + 1.01, (double) tilePos.z() + Math.random(), 0.0F, 0.0F, 0.0F, 0, false);
+			}
+			return true;
+		} else {
+			return false;
+		}
+	}
+
+	public boolean shouldTick() {
+		return false;
+	}
+
+	public void updateTickStill(@NotNull BlockLogicFluid logicFluid, @NotNull World world, @NotNull TilePosc tilePos, @NotNull Random rand) {
+	}
+
+	public byte getFlowDecayMod(@NotNull BlockLogicFluid logicFluid, @NotNull World world, @NotNull TilePosc tilePos) {
+		return 2;
+	}
+
+	public boolean canBecomeSource(@NotNull BlockLogicFluid logicFluid, @NotNull World world, @NotNull TilePosc tilePos, @NotNull Random rand) {
+		return false;
+	}
+
+	public void onFlowIntoBlock(@NotNull BlockLogicFluid logicFluid, @NotNull World world, @NotNull TilePos tilePos, int meta) {
+		world.getBlockType(tilePos).dropWithCause(world, EnumDropCause.WORLD, tilePos, world.getBlockData(tilePos), null, null);
+		world.playBlockEvent(tilePos, LevelListener.EVENT_ACID_SPREAD, 0);
+	}
+
+	public boolean canSpreadTo(@NotNull BlockLogicFluid logicFluid, @NotNull World world, @NotNull TilePos tilePos, @NotNull Material material) {
+		return material != Materials.LAVA && material != Materials.WATER;
+	}
+}
+
diff --git a/game/core/src/main/java/net/minecraft/core/block/FluidLava.java b/game/core/src/main/java/net/minecraft/core/block/FluidLava.java
index d307bf7c6..fc8367a19 100644
--- a/game/core/src/main/java/net/minecraft/core/block/FluidLava.java
+++ b/game/core/src/main/java/net/minecraft/core/block/FluidLava.java
@@ -59,6 +59,10 @@ public class FluidLava
                 }
             }
 
+            BlockLogicFluid.fizz(world, tilePos);
+            return true;
+        } else if (encountered == Blocks.FLUID_ACID_STILL.getMaterial()) {
+            world.setBlockTypeNotify(tilePos, Blocks.SULFUR_HARDENED);
             BlockLogicFluid.fizz(world, tilePos);
             return true;
         }
@@ -125,6 +129,6 @@ public class FluidLava
 
     @Override
     public boolean canSpreadTo(final @NotNull BlockLogicFluid logicFluid, final @NotNull World world, final @NotNull TilePos tilePos, final @NotNull Material material) {
-        return material != Materials.LAVA;
+        return material != Materials.WATER && material != Blocks.FLUID_ACID_STILL.getMaterial();
     }
 }
diff --git a/game/core/src/main/java/net/minecraft/core/block/FluidWater.java b/game/core/src/main/java/net/minecraft/core/block/FluidWater.java
index 4460b1a87..ccbd936ef 100644
--- a/game/core/src/main/java/net/minecraft/core/block/FluidWater.java
+++ b/game/core/src/main/java/net/minecraft/core/block/FluidWater.java
@@ -61,6 +61,10 @@ public class FluidWater
             }
             BlockLogicFluid.fizz(world, tilePos);
             return true;
+        } else if (encountered == Blocks.FLUID_ACID_STILL.getMaterial()) {
+            world.setBlockTypeNotify(tilePos, Blocks.SULFUR_HARDENED);
+            BlockLogicFluid.fizz(world, tilePos);
+            return true;
         }
         return false;
     }
@@ -92,6 +96,6 @@ public class FluidWater
 
     @Override
     public boolean canSpreadTo(final @NotNull BlockLogicFluid logicFluid, final @NotNull World world, final @NotNull TilePos tilePos, final @NotNull Material material) {
-        return material != Materials.LAVA;
+        return material != Materials.LAVA && material != Blocks.FLUID_ACID_STILL.getMaterial();
     }
 }
diff --git a/game/core/src/main/java/net/minecraft/core/block/material/Materials.java b/game/core/src/main/java/net/minecraft/core/block/material/Materials.java
index bdd7e38ad..7b24e0757 100644
--- a/game/core/src/main/java/net/minecraft/core/block/material/Materials.java
+++ b/game/core/src/main/java/net/minecraft/core/block/material/Materials.java
@@ -51,6 +51,6 @@ public final class Materials {
     public static final @NotNull Material PISTON = new Material(MaterialColor.stone);
     public static final @NotNull Material MOSS = new Material(MaterialColor.grass).flammable().notAlwaysDestroyable();
     public static final @NotNull Material WOOD_WET = (new Material(MaterialColor.grass).setConductivity(Material.LIGHTNING_LIGHT_CONDUCTOR));
-
+    public static final @NotNull Material ACID = new MaterialLiquid(MaterialColor.paintedYellow).setConductivity(5).destroyOnPush();
     private Materials() { }
 }
diff --git a/game/core/src/main/java/net/minecraft/core/block/piston/BlockLogicPistonHead.java b/game/core/src/main/java/net/minecraft/core/block/piston/BlockLogicPistonHead.java
index 95d6d5b68..cb836acbe 100644
--- a/game/core/src/main/java/net/minecraft/core/block/piston/BlockLogicPistonHead.java
+++ b/game/core/src/main/java/net/minecraft/core/block/piston/BlockLogicPistonHead.java
@@ -36,6 +36,7 @@ import static net.minecraft.core.block.piston.PistonCommon.*;
 public class BlockLogicPistonHead extends BlockLogic implements ICarrySource {
 
     // "Good lord. This really is our most NSFW update" -- Jonk
+    // "Daaayyymmnn boy! He thick boy! That's a thick ass boy, DAYMN!" -- Melon
     final double headThickness, shaftThickness;
 
     public BlockLogicPistonHead(final Block<?> block, final double headThickness, final double shaftThickness) {
diff --git a/game/core/src/main/java/net/minecraft/core/block/tag/BlockTags.java b/game/core/src/main/java/net/minecraft/core/block/tag/BlockTags.java
index 5f4b11937..ea1db34c2 100644
--- a/game/core/src/main/java/net/minecraft/core/block/tag/BlockTags.java
+++ b/game/core/src/main/java/net/minecraft/core/block/tag/BlockTags.java
@@ -68,6 +68,10 @@ public abstract class BlockTags
      * derived block has the properties of lava.
      */
     public static final Tag<Block<?>> IS_LAVA = Tag.of("is_lava");
+    /**
+     * derived block has the properties of acid.
+     */
+    public static final Tag<Block<?>> IS_ACID = Tag.of("is_acid");
     /**
      * {@link BlockLogicFlower BlockFlower}-derived blocks can be placed on this block.
      */
@@ -150,9 +154,17 @@ public abstract class BlockTags
      */
     public static final Tag<Block<?>> INFINITE_BURN = Tag.of("infinite_burn");
     /**
-     * Block allows fire to burn forever on it {@link BlockLogicFire BlockFire}
+     * Block allows fire to burn forever on it {@link net.minecraft.core.block.BlockLogicFireCold BlockFireCold}
      */
     public static final Tag<Block<?>> INFINITE_BURN_COLD = Tag.of("infinite_burn_cold");
+    /**
+     * Block allows fire to burn forever on it {@link net.minecraft.core.block.BlockLogicFireSulfuric BlockFireSulfuric}
+     */
+    public static final Tag<Block<?>> INFINITE_BURN_SULFURIC = Tag.of("infinite_burn_sulfuric");
+    /**
+     * Set entity on sulfuric fire instead of Normal Fire
+     */
+    public static final Tag<Block<?>> SULFURIC_FIRE_OVERLAY_SOURCE = Tag.of("sulfuric_fire_overlay_source");
     /**
      * Block is broken on contact with an {@link EntityBoat EntityBoat}
      */
diff --git a/game/core/src/main/java/net/minecraft/core/data/gamerule/GameRules.java b/game/core/src/main/java/net/minecraft/core/data/gamerule/GameRules.java
index d6199334e..509fb9c7f 100644
--- a/game/core/src/main/java/net/minecraft/core/data/gamerule/GameRules.java
+++ b/game/core/src/main/java/net/minecraft/core/data/gamerule/GameRules.java
@@ -1,6 +1,7 @@
 package net.minecraft.core.data.gamerule;
 
 import net.minecraft.core.data.registry.Registries;
+import net.minecraft.core.entity.Entity;
 
 public abstract class GameRules
 {
@@ -16,6 +17,7 @@ public abstract class GameRules
     public static GameRuleBoolean DO_WEATHER_CYCLE = register(new GameRuleBoolean("doWeatherCycle", "gamerule.do_weather_cycle", true));
     public static GameRuleBoolean DO_FIRE_SPREAD = register(new GameRuleBoolean("doFireSpread", "gamerule.do_fire_spread", true));
     public static GameRuleBoolean INSTANT_HEALING = register(new GameRuleBoolean("instantHealing", "gamerule.instant_healing", false));
+    public static GameRuleInteger ACID_MELT_RATE = register(new GameRuleInteger("acidMeltRate", "gamerule.acid_melt_rate", Entity.FIRE_DAMAGE_INTERVAL_TICKS));
     public static GameRuleInteger RANDOM_TICK_SPEED = register(new GameRuleInteger("randomTickSpeed", "gamerule.random_tick_speed", 4));
     public static GameRuleInteger ITEM_DESPAWN_DELAY = register(new GameRuleInteger("itemDespawnDelay", "gamerule.item_despawn_delay", 12000));
     public static GameRuleInteger DROPPED_ITEM_DESPAWN_DELAY = register(new GameRuleInteger("droppedItemDespawnDelay", "gamerule.dropped_item_despawn_delay", 12000));
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 0f22991d0..8e7cf0baa 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
@@ -19,6 +19,7 @@ import net.minecraft.core.enums.EnumBlockSoundEffectType;
 import net.minecraft.core.item.ItemStack;
 import net.minecraft.core.lang.I18n;
 import net.minecraft.core.util.collection.NamespaceID;
+import net.minecraft.core.data.tag.Tag;
 import net.minecraft.core.util.helper.*;
 import net.minecraft.core.world.ICarriable;
 import net.minecraft.core.world.IVehicle;
@@ -37,19 +38,30 @@ import java.util.List;
 import java.util.Random;
 
 public abstract class Entity
-    implements IVehicle {
+        implements IVehicle {
+
+    // Util
+    protected @NotNull Random random = new Random();
+    public @NotNull World world;
+
     public static final int DATA_SHARED_FLAGS_ID = 0;
     public static final int FLAG_ONFIRE = 0;
     public static final int FLAG_SNEAKING = 1;
     public static final int FLAG_RIDING = 2;
     public static final int FLAG_SPRINTING = 3;
+
+    // Misc/IDK
+    public boolean slide = true;
+    public boolean removed = false;
+    public boolean hadNicknameSet;
+    public boolean blocksBuilding = false;
+    public double viewScale = 1.0D;
     private static int entityCounter = 0;
     public int id = entityCounter++;
-    public double viewScale = 1.0D;
-    public boolean blocksBuilding = false;
     public @Nullable Entity passenger;
-    public @Nullable IVehicle vehicle;
-    public @NotNull World world;
+    private int soundCooldown;
+
+    // Position
     public double xo;
     public double yo;
     public double zo;
@@ -63,60 +75,89 @@ public abstract class Entity
     public float xRot;
     public float yRotO;
     public float xRotO;
-    public float pushTime;
-    public int pushesThisTick;
+    public int serverPosX;
+    public int serverPosY;
+    public int serverPosZ;
+    private final @NotNull TilePos workingPos1 = new TilePos();
+    private final @NotNull TilePos workingPos2 = new TilePos();
+
+    // Collision/Hitbox
     public final @NotNull AABBd bb = new AABBd();
-    public boolean onGround = false;
-    public boolean horizontalCollision;
-    public boolean verticalCollision;
-    public boolean collision = false;
-    public boolean hurtMarked = false;
-    public boolean hadNicknameSet;
-    public boolean stuckInCobweb;
-    public boolean stuckInSpikes;
-    public boolean slide = true;
-    public boolean removed = false;
     public float heightOffset = 0.0F;
     public float bbWidth = 0.6F;
     public float bbHeight = 1.825F;
+    public boolean horizontalCollision;
+    public boolean verticalCollision;
+    public boolean collision = false;
+    public boolean onGround = false;
+    private final @NotNull AABBd workingBox1 = new AABBd();
+    private final @NotNull AABBd workingBox2 = new AABBd();
+    private final @NotNull AABBd workingBox3 = new AABBd();
+    private final @NotNull AABBd workingBox4 = new AABBd();
+
+    // Movement/Physics
+    public boolean noPhysics = false;
+    public float pushthrough = 0.0F;
     public float walkDistO = 0.0F;
     public float walkDist = 0.0F;
     public float fallDistance = 0.0F;
     private int nextStep = 1;
     public float ySlideOffset = 0.0F;
-    public float footSize = 0.0F;
-    public boolean noPhysics = false;
-    public float pushthrough = 0.0F;
-    protected @NotNull Random random = new Random();
-    public int tickCount;
+    public boolean stuckInCobweb;
+    public boolean stuckInSpikes;
+    public float pushTime;
+    public int pushesThisTick;
+    public int acidJumpCooldown = 0;
+
+    // Vehicle
+    public @Nullable IVehicle vehicle;
+    private double entityRiderPitchDelta;
+    private double entityRiderYawDelta;
+    public boolean turnWithVehicle = true;
+
+    // Damage
+    public int heartsFlashTime = 0;
+    public boolean hurtMarked = false;
+
+    // Water
+    public int airSupply = 300;
+    protected int airMaxSupply = 300;
+    protected boolean wasInWater = false;
+
+    // Fire
+    public static final int FIRE_DAMAGE_INTERVAL_TICKS = 20;
     public int fireImmuneTicks = 1;
     public int remainingFireTicks = 0;
     public int maxFireTicks;
-    private int soundCooldown;
-    protected int airMaxSupply = 300;
-    protected boolean wasInWater = false;
-    public int heartsFlashTime = 0;
-    public int airSupply = 300;
-    private boolean firstTick = true;
     protected boolean fireImmune;
-    protected final @NotNull SynchedEntityData entityData = new SynchedEntityData();
+    public Block<?> activeFireBlock = Blocks.FIRE;
+
+    // Visual
     public float entityBrightness = 0.0F;
-    private double entityRiderPitchDelta;
-    private double entityRiderYawDelta;
+    public boolean ignoreFrustumCheck;
+
+    // Chunk
     public boolean addedToChunk = false;
     public int chunkCoordX;
     public int chunkCoordY;
     public int chunkCoordZ;
-    public int serverPosX;
-    public int serverPosY;
-    public int serverPosZ;
-    public boolean ignoreFrustumCheck;
-    public boolean isWalking = false;
-    protected boolean muteStepSounds = false;
+
+    // Speed
     public float speed;
     public float flySpeed;
+
+    // Walk
+    public boolean isWalking = false;
+    protected boolean muteStepSounds = false;
+    public float footSize = 0.0F;
+
+    // Tick
+    private boolean firstTick = true;
+    public int tickCount;
+
+    // Data
     public boolean sendAdditionalData = false;
-    public boolean turnWithVehicle = true;
+    protected final @NotNull SynchedEntityData entityData = new SynchedEntityData();
 
     public Entity(final @NotNull World world) {
         this.world = world;
@@ -270,7 +311,7 @@ public abstract class Entity
                     this.remainingFireTicks = 0;
                 }
             } else {
-                if (this.remainingFireTicks % 20 == 0) {
+                if (this.remainingFireTicks % FIRE_DAMAGE_INTERVAL_TICKS == 0) {
                     hurt(null, 1, DamageType.FIRE);
                 }
                 this.remainingFireTicks--;
@@ -348,17 +389,22 @@ public abstract class Entity
             hurt(null, 4, DamageType.FIRE);
             this.remainingFireTicks = 600;
             this.maxFireTicks = this.remainingFireTicks;
+            this.activeFireBlock = Blocks.FIRE;
         }
     }
 
-    public void fireHurt() {
+    public void fireHurt(Block<?> fireSource) {
         if (!this.fireImmune) {
             hurt(null, 1, DamageType.FIRE);
             this.remainingFireTicks = 300;
             this.maxFireTicks = this.remainingFireTicks;
+            this.activeFireBlock = fireSource;
         }
     }
 
+    public void fireHurt() {
+        this.fireHurt(Blocks.FIRE);
+    }
     public void outOfWorld() {
         remove();
     }
@@ -411,17 +457,19 @@ public abstract class Entity
 
             this.stuckInSpikes = false;
         }
+
         double oldXd = xd;
         double oldYd = yd;
         double oldZd = zd;
-        AABBd bbCopy = new AABBd(this.bb);
+        workingBox3.set(this.bb);
 
         double bbWidth = this.bb.maxX - this.bb.minX - 0.05;
         if (isSneaking()) { // Edge safety behavior
-            if (xd != 0.0) {
-                AABBd floor = new AABBd(this.bb.minX, this.bb.minY - 1.0, this.bb.minZ, this.bb.maxX, this.bb.minY, this.bb.maxZ);
-                MathHelper.aabbExpand(floor, xd, 0.0, 0.0, floor);
-                List<AABBdc> collidingBoxes = this.world.getCubes(this, floor);
+            if (zd != 0.0) {
+                workingBox1.setMin(this.bb.minX, this.bb.minY - 1.0, this.bb.minZ);
+                workingBox1.setMax(this.bb.maxX, this.bb.minY, this.bb.maxZ);
+                MathHelper.aabbExpand(workingBox1, 0.0, 0.0, zd, workingBox1);
+                List<AABBdc> collidingBoxes = this.world.getCubes(this, workingBox1);
                 if (!collidingBoxes.isEmpty()) {
                     double limitX = 0.0;
                     for (int i = 0; i < collidingBoxes.size(); i++) {
@@ -462,7 +510,18 @@ public abstract class Entity
             }
         }
 
-        List<AABBdc> cubes = this.world.getCubes(this, MathHelper.aabbExpand(this.bb, xd, yd, zd, new AABBd()));
+        List<AABBdc> cubes = this.world.getCubes(this, MathHelper.aabbExpand(this.bb, xd, yd, zd, workingBox2));
+        List<Entity> solidEntities = this.world.getEntitiesWithinAABBExcludingEntity(this, MathHelper.aabbExpand(this.bb, xd, yd, zd, workingBox2));
+
+        for (int i = 0; i < solidEntities.size(); i++) {
+            Entity solidEnt = solidEntities.get(i);
+
+            if (solidEnt.hasCollision() && solidEnt.getCollisionAABB() != null) {
+                if (this.collidesWith(solidEnt) && solidEnt.collidesWith(this)) {
+                    cubes.add(solidEnt.getCollisionAABB());
+                }
+            }
+        }
 
         // Check collision on Y Axis
         for (int i = 0; i < cubes.size(); i++) {
@@ -504,9 +563,11 @@ public abstract class Entity
             xd = oldXd;
             yd = this.footSize;
             zd = oldZd;
-            AABBd aabbCopy = new AABBd(this.bb);
-            this.bb.set(bbCopy);
-            cubes = this.world.getCubes(this, MathHelper.aabbExpand(this.bb, xd, 0.0, zd, new AABBd()));
+
+            workingBox4.set(this.bb);
+
+            this.bb.set(workingBox3);
+            cubes = this.world.getCubes(this, MathHelper.aabbExpand(this.bb, xd, 0.0, zd, workingBox2));
             for (int i = 0; i < cubes.size(); i++) {
                 yd = MathHelper.aabbClipYCollide(cubes.get(i), this.bb, yd);
             }
@@ -546,7 +607,7 @@ public abstract class Entity
                 xd = oldX;
                 yd = oldY;
                 zd = oldZ;
-                this.bb.set(aabbCopy);
+                this.bb.set(workingBox4);
             }
         }
         //fix for entity jitter when blasted by cannonballs (can cause some client-side jitter when entity is under a ceiling. unsure exactly why)
@@ -559,6 +620,7 @@ public abstract class Entity
         this.verticalCollision = oldYd != yd;
         this.onGround = oldYd != yd && oldYd < 0.0D;
         this.collision = this.horizontalCollision || this.verticalCollision;
+
         checkOnWater(false);
         checkFallDamage(yd, this.onGround);
         if (oldXd != xd) {
@@ -573,13 +635,13 @@ public abstract class Entity
         double d10 = this.x - d3;
         double d12 = this.z - d4;
         if (makeStepSound() && !isSneaking() && this.vehicle == null) {
-            this.walkDist += (double) MathHelper.sqrt(d10 * d10 + d12 * d12) * 0.6;
+            this.walkDist += (float) ((double) MathHelper.sqrt(d10 * d10 + d12 * d12) * 0.6);
 
-            // Tiny bounding box
-            AABBdc boundingBox = new AABBd(this.x - 0.01, this.bb.minY - 0.01, this.z - 0.01, this.x + 0.01, this.bb.minY + 0.01, this.z + 0.01);
+            workingBox1.setMin(this.x - 0.01, this.bb.minY - 0.01, this.z - 0.01);
+            workingBox1.setMax(this.x + 0.01, this.bb.minY + 0.01, this.z + 0.01);
 
             int blockX = MathHelper.floor(this.x);
-            int blockY = MathHelper.floor(boundingBox.minY());
+            int blockY = MathHelper.floor(workingBox1.minY());
             int blockZ = MathHelper.floor(this.z);
 
             Block<?> blockWalkedOn = null;
@@ -589,12 +651,9 @@ public abstract class Entity
                 for (int z = blockZ - 1; z <= blockZ + 1; z++) {
                     for (int y = blockY - 1; y < blockY + 1; y++) {
                         Block<?> block = this.world.getBlock(x, y, z);
-                        if (block == null) {
-                            continue;
-                        }
 
                         this.world.collidingBoundingBoxes.clear();
-                        block.getCollidingBoundingBoxes(this.world, x, y, z, boundingBox, this.world.collidingBoundingBoxes);
+                        block.getCollidingBoundingBoxes(this.world, x, y, z, workingBox1, this.world.collidingBoundingBoxes);
 
                         if (!this.world.collidingBoundingBoxes.isEmpty()) {
                             blockWalkedOn = block;
@@ -608,7 +667,7 @@ public abstract class Entity
             }
 
             // Check for snow layers
-            int blockYUp = MathHelper.floor(boundingBox.maxY());
+            int blockYUp = MathHelper.floor(workingBox1.maxY());
             Block<?> blockAbove = this.world.getBlock(blockX, blockYUp, blockZ);
             if (blockAbove != null && blockAbove.hasTag(BlockTags.OVERRIDE_STEPSOUND)) {
                 blockWalkedOn = blockAbove;
@@ -650,7 +709,7 @@ public abstract class Entity
 
         }
         boolean inWaterOrRain = isInWaterOrRain();
-        if (this.world.isBoundingBoxBurning(MathHelper.aabbInsetBoundingBox(this.bb, 0.001D, 0.001D, 0.001D, new AABBd()))) {
+        if (this.world.isBoundingBoxBurning(MathHelper.aabbInsetBoundingBox(this.bb, 0.001D, 0.001D, 0.001D, workingBox1))) {
             burn(1);
             if (!inWaterOrRain) {
                 this.remainingFireTicks++;
@@ -705,12 +764,17 @@ public abstract class Entity
         return null;
     }
 
-    protected void burn(int damage) {
+    protected void burn(int damage, Block<?> fireSource) {
         if (!this.fireImmune) {
             hurt(null, damage, DamageType.FIRE);
+            this.activeFireBlock = fireSource;
         }
     }
 
+    protected void burn(int damage) {
+        this.burn(damage, this.activeFireBlock != null ? this.activeFireBlock : Blocks.FIRE);
+    }
+
     protected void causeFallDamage(float distance) {
         if (this.passenger != null) {
             this.passenger.causeFallDamage(distance);
@@ -725,17 +789,78 @@ public abstract class Entity
         return this.wasInWater;
     }
 
+    public boolean isInAcid() {
+        return this.world.isMaterialInBB(this.bb, Materials.ACID);
+    }
+
+
+    public boolean isBoundingBoxIntersectingBlock(final @NotNull Block<?> target) {
+        return isAabbIntersectingBlock(this.bb, target);
+    }
+
+    public boolean isBoundingBoxIntersectingBlockTag(final @NotNull Tag<Block<?>> tag) {
+        return isAabbIntersectingBlockTag(this.bb, tag);
+    }
+
+    private boolean isAabbIntersectingBlock(final @NotNull AABBdc queryBb, final @NotNull Block<?> target) {
+        final double inset = 0.001D;
+        final int minX = MathHelper.floor(queryBb.minX() + inset);
+        final int minY = MathHelper.floor(queryBb.minY() + inset);
+        final int minZ = MathHelper.floor(queryBb.minZ() + inset);
+        final int maxX = MathHelper.floor(queryBb.maxX() - inset);
+        final int maxY = MathHelper.floor(queryBb.maxY() - inset);
+        final int maxZ = MathHelper.floor(queryBb.maxZ() - inset);
+        if (!this.world.areBlocksLoaded(minX, minY, minZ, maxX, maxY, maxZ)) {
+            return false;
+        }
+        final TilePos p = new TilePos();
+        for (p.x = minX; p.x <= maxX; p.x++) {
+            for (p.y = minY; p.y <= maxY; p.y++) {
+                for (p.z = minZ; p.z <= maxZ; p.z++) {
+                    if (this.world.getBlockType(p) == target) {
+                        return true;
+                    }
+                }
+            }
+        }
+        return false;
+    }
+
+    private boolean isAabbIntersectingBlockTag(final @NotNull AABBdc queryBb, final @NotNull Tag<Block<?>> tag) {
+        final double inset = 0.001D;
+        final int minX = MathHelper.floor(queryBb.minX() + inset);
+        final int minY = MathHelper.floor(queryBb.minY() + inset);
+        final int minZ = MathHelper.floor(queryBb.minZ() + inset);
+        final int maxX = MathHelper.floor(queryBb.maxX() - inset);
+        final int maxY = MathHelper.floor(queryBb.maxY() - inset);
+        final int maxZ = MathHelper.floor(queryBb.maxZ() - inset);
+        if (!this.world.areBlocksLoaded(minX, minY, minZ, maxX, maxY, maxZ)) {
+            return false;
+        }
+        final TilePos p = new TilePos();
+        for (p.x = minX; p.x <= maxX; p.x++) {
+            for (p.y = minY; p.y <= maxY; p.y++) {
+                for (p.z = minZ; p.z <= maxZ; p.z++) {
+                    if (Blocks.hasTag(this.world.getBlockType(p), tag)) {
+                        return true;
+                    }
+                }
+            }
+        }
+        return false;
+    }
+
     public boolean checkAndHandleWater(boolean addVelocity) {
         return this.world.handleMaterialAcceleration(MathHelper.aabbInsetBoundingBox(this.bb/*.expand(0.0D, -0.4D, 0.0D)*/, 0.001D, 0.001D, 0.001D, new AABBd()), Materials.WATER, this, addVelocity);
     }
 
     public boolean isUnderLiquid(@NotNull Material material) {
         double headY = this.y + (double) getHeadHeight();
-        TilePos tilePos = new TilePos(this.x, headY, this.z);
-        Block<?> block = this.world.getBlockType(tilePos);
-        if (block != null && block.getMaterial() == material) {
-            float f = BlockLogicFluid.getHeight(this.world.getBlockData(tilePos)) - 0.2F;
-            float f1 = (float) (tilePos.y + 1) - f;
+        workingPos1.set(this.x, headY, this.z);
+        Block<?> block = this.world.getBlockType(workingPos1);
+        if (block.getMaterial() == material) {
+            float f = BlockLogicFluid.getHeight(this.world.getBlockData(workingPos1)) - 0.2F;
+            float f1 = (float) (workingPos1.y + 1) - f;
             return headY < (double) f1;
         } else {
             return false;
@@ -887,18 +1012,23 @@ public abstract class Entity
         return !(this.pushTime > 0.5f) || !((this.xd * this.xd + this.yd * this.yd + this.zd * this.zd) > 1) || !Block.hasLogicClass(block, BlockLogicPistonHead.class); // TODO horrible hardcoded exception to getting stuck in piston heads during flings
     }
 
-    public void push(Entity entity) {
+    public void push(@NotNull Entity entity) {
         if (entity.passenger == this || entity.vehicle == this || entity.noPhysics || !entity.isPickable() || !this.collidesWith(entity) || !entity.collidesWith(this)) {
             return;
         }
+
+        if (this.bb.minY >= entity.bb.maxY - 0.25D || entity.bb.minY >= this.bb.maxY - 0.25D) {
+            return;
+        }
+
         double d = entity.x - this.x;
         double d1 = entity.z - this.z;
-        double d2 = MathHelper.abs_max(d, d1);
-        if (d2 >= 0.01D) {
-            d2 = MathHelper.sqrt(d2);
-            d /= d2;
-            d1 /= d2;
-            double d3 = 1.0D / d2;
+        double distSq = d * d + d1 * d1;
+        if (distSq >= 0.0001D) {
+            double dist = MathHelper.sqrt(distSq);
+            d /= dist;
+            d1 /= dist;
+            double d3 = 1.0D / dist;
             if (d3 > 1.0D) {
                 d3 = 1.0D;
             }
@@ -940,6 +1070,10 @@ public abstract class Entity
         return false;
     }
 
+    public boolean hasCollision() {
+        return false;
+    }
+
     public void awardKillScore(Entity entity, int i) {
     }
 
@@ -952,8 +1086,8 @@ public abstract class Entity
     }
 
     public boolean shouldRenderAtSqrDistance(final double distance, final double viewDistanceModifier) {
-        double d1 = MathHelper.getAABBSize(this.bb);
-        d1 *= 64D * this.viewScale * viewDistanceModifier;
+        double d1 = (this.bbWidth * 2.0F + this.bbHeight) / 3.0F;
+        d1 *= 64.0D * this.viewScale * viewDistanceModifier;
         return distance < d1 * d1;
     }
 
@@ -1070,10 +1204,13 @@ public abstract class Entity
     }
 
     public boolean isInWall() {
+        float widthOffset = this.bbWidth * 0.45F;
+        float heightOffset = 0.05F;
+
         for (int i = 0; i < 8; i++) {
-            float f = ((float) ((i >> 0) % 2) - 0.5F) * this.bbWidth * 0.9F;
-            float f1 = ((float) ((i >> 1) % 2) - 0.5F) * 0.1F;
-            float f2 = ((float) ((i >> 2) % 2) - 0.5F) * this.bbWidth * 0.9F;
+            float f = (i & 1) == 0 ? -widthOffset : widthOffset;
+            float f1 = (i & 2) == 0 ? -heightOffset : heightOffset;
+            float f2 = (i & 4) == 0 ? -widthOffset : widthOffset;
             int x = MathHelper.floor(this.x + (double) f);
             int y = MathHelper.floor(this.y + (double) getHeadHeight() + (double) f1);
             int z = MathHelper.floor(this.z + (double) f2);
@@ -1181,7 +1318,7 @@ public abstract class Entity
                        float xRot, int i) {
         setPos(x, y, z);
         setRot(yRot, xRot);
-        List<AABBdc> cubes = this.world.getCubes(this, MathHelper.aabbInsetBoundingBox(this.bb, 0.03125D, 0.0D, 0.03125D, new AABBd()));
+        List<AABBdc> cubes = this.world.getCubes(this, MathHelper.aabbInsetBoundingBox(this.bb, 0.03125D, 0.0D, 0.03125D, workingBox1));
         if (!cubes.isEmpty()) {
             double d3 = 0.0D;
             for (AABBdc cube : cubes) {
@@ -1276,35 +1413,35 @@ public abstract class Entity
     }
 
     protected boolean checkAndPushInTile(double x, double y, double z) {
-        TilePosc tilePos = new TilePos(x, y, z);
-        TilePos queryPos = new TilePos();
-        double depthX = x - (double) tilePos.x();
-        double depthY = y - (double) tilePos.y();
-        double depthZ = z - (double) tilePos.z();
-        if (this.world.isBlockNormalCube(tilePos)) {
+        workingPos1.set(x, y, z);
+        double depthX = x - (double) workingPos1.x();
+        double depthY = y - (double) workingPos1.y();
+        double depthZ = z - (double) workingPos1.z();
+
+        if (this.world.isBlockNormalCube(workingPos1)) {
             Direction direction = Direction.NONE;
             double minDepth = Double.MAX_VALUE;
-            if (depthX < minDepth && !this.world.isBlockNormalCube(tilePos.west(queryPos))) {
+            if (depthX < minDepth && !this.world.isBlockNormalCube(workingPos1.west(workingPos2))) {
                 minDepth = depthX;
                 direction = Direction.WEST;
             }
-            if (1.0D - depthX < minDepth && !this.world.isBlockNormalCube(tilePos.east(queryPos))) {
+            if (1.0D - depthX < minDepth && !this.world.isBlockNormalCube(workingPos1.east(workingPos2))) {
                 minDepth = 1.0D - depthX;
                 direction = Direction.EAST;
             }
-            if (depthY < minDepth && !this.world.isBlockNormalCube(tilePos.down(queryPos))) {
+            if (depthY < minDepth && !this.world.isBlockNormalCube(workingPos1.down(workingPos2))) {
                 minDepth = depthY;
                 direction = Direction.DOWN;
             }
-            if (1.0D - depthY < minDepth && !this.world.isBlockNormalCube(tilePos.up(queryPos))) {
+            if (1.0D - depthY < minDepth && !this.world.isBlockNormalCube(workingPos1.up(workingPos2))) {
                 minDepth = 1.0D - depthY;
                 direction = Direction.UP;
             }
-            if (depthZ < minDepth && !this.world.isBlockNormalCube(tilePos.north(queryPos))) {
+            if (depthZ < minDepth && !this.world.isBlockNormalCube(workingPos1.north(workingPos2))) {
                 minDepth = depthZ;
                 direction = Direction.NORTH;
             }
-            if (1.0D - depthZ < minDepth && !this.world.isBlockNormalCube(tilePos.south(queryPos))) {
+            if (1.0D - depthZ < minDepth && !this.world.isBlockNormalCube(workingPos1.south(workingPos2))) {
 //                minDepth = 1.0D - depthZ;
                 direction = Direction.SOUTH;
             }
diff --git a/game/core/src/main/java/net/minecraft/core/entity/EntityDispatcher.java b/game/core/src/main/java/net/minecraft/core/entity/EntityDispatcher.java
index ea20d7f3c..3eed062cb 100644
--- a/game/core/src/main/java/net/minecraft/core/entity/EntityDispatcher.java
+++ b/game/core/src/main/java/net/minecraft/core/entity/EntityDispatcher.java
@@ -391,6 +391,7 @@ public final class EntityDispatcher
             addMapping(MobChicken.class, "Chicken", 93);
             addMapping(MobSquid.class, "Squid", 94);
             addMapping(MobWolf.class, "Wolf", 95);
+            addMapping(ProjectileArrowFlaming.class, "FlamingArrow", 96);
         }
     }
 }
diff --git a/game/core/src/main/java/net/minecraft/core/entity/EntityItemHoming.java b/game/core/src/main/java/net/minecraft/core/entity/EntityItemHoming.java
new file mode 100644
index 000000000..4cd2339ae
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/entity/EntityItemHoming.java
@@ -0,0 +1,59 @@
+package net.minecraft.core.entity;
+
+import net.minecraft.core.item.ItemStack;
+import net.minecraft.core.world.World;
+import net.minecraft.core.util.helper.MathHelper;
+
+public class EntityItemHoming extends EntityItem {
+    private Entity target;
+    private int homingTicks;
+
+    public EntityItemHoming(World world, double x, double y, double z, ItemStack itemStack, Entity target) {
+        super(world, x, y, z, itemStack);
+        this.target = target;
+        this.homingTicks = 0;
+        this.pickupDelay = 20;
+    }
+
+    @Override
+    public void tick() {
+        super.tick();
+
+        this.homingTicks++;
+
+        if (this.target != null && !this.target.removed) {
+            double dX = this.target.x - this.x;
+            double dY = (this.target.y + (this.target.bbHeight / 2.0D)) - this.y;
+            double dZ = this.target.z - this.z;
+            double distance = MathHelper.sqrt(dX * dX + dY * dY + dZ * dZ);
+
+            if (distance < 64.0D && distance > 0.1D) {
+                double acceleration = Math.min(0.5D, 0.02D + (this.homingTicks * 0.01D));
+
+                double noiseX = (this.random.nextFloat() - 0.5D) * 0.2D;
+                double noiseY = (this.random.nextFloat() - 0.5D) * 0.2D;
+                double noiseZ = (this.random.nextFloat() - 0.5D) * 0.2D;
+
+                this.xd += ((dX / distance) + noiseX) * acceleration;
+                this.yd += ((dY / distance) + noiseY) * acceleration;
+                this.zd += ((dZ / distance) + noiseZ) * acceleration;
+
+                double antiGravity = Math.min(0.02D, this.homingTicks * 0.002D);
+                this.yd += antiGravity;
+
+                double currentSpeed = MathHelper.sqrt(this.xd * this.xd + this.yd * this.yd + this.zd * this.zd);
+                double maxSpeed = Math.min(1.2D, 0.2D + (this.homingTicks * 0.05D));
+
+                if (currentSpeed > maxSpeed) {
+                    this.xd = (this.xd / currentSpeed) * maxSpeed;
+                    this.yd = (this.yd / currentSpeed) * maxSpeed;
+                    this.zd = (this.zd / currentSpeed) * maxSpeed;
+                }
+
+                if (this.homingTicks > 10) {
+                    this.noPhysics = true;
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/game/core/src/main/java/net/minecraft/core/entity/IArmorWearing.java b/game/core/src/main/java/net/minecraft/core/entity/IArmorWearing.java
index 2964241b9..31e196982 100644
--- a/game/core/src/main/java/net/minecraft/core/entity/IArmorWearing.java
+++ b/game/core/src/main/java/net/minecraft/core/entity/IArmorWearing.java
@@ -24,8 +24,7 @@ public interface IArmorWearing<T extends IArmorShape> {
         for (int i = 0; i < getNumArmorSlots(); i++) {
             final @NotNull T armorSlot = Objects.requireNonNull(getArmorSlotByIndex(i));
             final @Nullable ItemStack armorItem = getItemInArmorSlot(armorSlot);
-            if (armorItem != null && armorItem.getItem() instanceof IArmorItem<?>) {
-                final @NotNull IArmorItem<?> armor = (IArmorItem<?>) armorItem.getItem();
+            if (armorItem != null && armorItem.getItem() instanceof @NotNull IArmorItem<?> armor) {
                 final @Nullable ArmorMaterial material = armor.getArmorMaterial();
                 if (material != null) {
                     protectionPercentage += material.getProtection(damageType) * armor.getArmorPieceProtectionPercentage();
@@ -38,8 +37,7 @@ public interface IArmorWearing<T extends IArmorShape> {
 
     default void damageArmor(final int damage, final @NotNull T armorSlot) {
         final @Nullable ItemStack armorItem = getItemInArmorSlot(armorSlot);
-        if (armorItem != null && armorItem.getItem() instanceof IArmorItem<?>) {
-            final @NotNull IArmorItem<?> armor = (IArmorItem<?>) armorItem.getItem();
+        if (armorItem != null && armorItem.getItem() instanceof @NotNull IArmorItem<?> armor) {
             final @Nullable ArmorMaterial material = armor.getArmorMaterial();
             if (material != null) {
                 if (this instanceof Entity) {
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 6f73ebaa1..1295c8448 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
@@ -757,6 +757,29 @@ public abstract class Mob extends Entity {
             if (this.horizontalCollision && isFree(this.xd, ((this.yd + 0.6D) - this.y) + d1, this.zd)) {
                 this.yd = 0.3D;
             }
+        } else if (isInAcid()) {
+            double d = this.y;
+
+            moveRelative(moveStrafing, moveForward, 0.01F);
+            move(this.xd, this.yd, this.zd);
+
+            this.xd *= 0.5D;
+            this.yd *= 0.5D;
+            this.zd *= 0.5D;
+
+            this.yd -= 0.01D;
+
+            int blockX = MathHelper.floor(this.x);
+            int blockY = MathHelper.floor(this.y - this.heightOffset);
+            int blockZ = MathHelper.floor(this.z);
+            Block<?> b = this.world.getBlock(blockX, blockY, blockZ);
+
+            float depth;
+            depth = 1 - (float) b.getBlockBoundsFromState(this.world, blockX, blockY, blockZ).maxY();
+
+            if (this.horizontalCollision && isFree(this.xd, ((this.yd + depth + 0.2) - this.y) + d, this.zd)) {
+                this.yd = 0.2D;
+            }
         } else if (canSkate() && BlockTags.SKATEABLE.appliesTo(floorBlock)) {
             this.muteStepSounds = true;
             this.isSkating = true;
@@ -794,7 +817,7 @@ public abstract class Mob extends Entity {
                 movementScale = 0.5460001F;
                 if (BlockTags.OVERRIDE_FRICTION.appliesTo(floorAboveBlock)) {
                     movementScale = floorAboveBlock.friction * 0.91F;
-                } else if (floorBlock != null) {
+                } else {
                     movementScale = floorBlock.friction * 0.91F;
                 }
             }
@@ -940,11 +963,14 @@ public abstract class Mob extends Entity {
         }
         boolean inWater = isInWater();
         boolean inLava = isInLava();
+        boolean inAcid = isInAcid();
         if (this.isJumping) {
             if (inWater) {
                 this.yd += 0.04D;
             } else if (inLava) {
                 this.yd += 0.04D;
+            } else if (inAcid) {
+                this.yd += 0.02D;
             } else if (this.onGround) {
                 jump();
             }
@@ -1034,7 +1060,8 @@ public abstract class Mob extends Entity {
         }
         boolean inWater = isInWater();
         boolean inLava = isInLava();
-        if (inWater || inLava) {
+        boolean inAcid = isInAcid();
+        if (inWater || inLava || inAcid) {
             this.isJumping = this.random.nextFloat() < 0.8F;
         }
     }
@@ -1067,7 +1094,7 @@ public abstract class Mob extends Entity {
         return this.currentTarget;
     }
 
-    private float rotationLerp(float f, float f1, float f2) // Was called rotLerp in the b1.2 leak
+    protected float rotationLerp(float f, float f1, float f2) // Was called rotLerp in the b1.2 leak
     {
         float f3;
         for (f3 = f1 - f; f3 < -180F; f3 += 360F) {
diff --git a/game/core/src/main/java/net/minecraft/core/entity/animal/MobButterfly.java b/game/core/src/main/java/net/minecraft/core/entity/animal/MobButterfly.java
index a38227e25..98a7d3ff3 100644
--- a/game/core/src/main/java/net/minecraft/core/entity/animal/MobButterfly.java
+++ b/game/core/src/main/java/net/minecraft/core/entity/animal/MobButterfly.java
@@ -109,7 +109,7 @@ public class MobButterfly extends MobFlying implements AmbientCreature {
     }
 
     @Override
-    public void push(Entity entity) {
+    public void push(@NotNull Entity entity) {
     }
 
     @Override
diff --git a/game/core/src/main/java/net/minecraft/core/entity/animal/MobFireflyCluster.java b/game/core/src/main/java/net/minecraft/core/entity/animal/MobFireflyCluster.java
index 649d9c0c2..6b05f57ac 100644
--- a/game/core/src/main/java/net/minecraft/core/entity/animal/MobFireflyCluster.java
+++ b/game/core/src/main/java/net/minecraft/core/entity/animal/MobFireflyCluster.java
@@ -249,7 +249,7 @@ public class MobFireflyCluster extends MobFlying
     }
 
     @Override
-    public void push(Entity entity) {
+    public void push(@NotNull Entity entity) {
     }
 
     @Override
diff --git a/game/core/src/main/java/net/minecraft/core/entity/monster/MobGhast.java b/game/core/src/main/java/net/minecraft/core/entity/monster/MobGhast.java
index 89fdb342f..c62b4cf45 100644
--- a/game/core/src/main/java/net/minecraft/core/entity/monster/MobGhast.java
+++ b/game/core/src/main/java/net/minecraft/core/entity/monster/MobGhast.java
@@ -2,24 +2,22 @@ package net.minecraft.core.entity.monster;
 
 import net.minecraft.core.WeightedRandomLootObject;
 import net.minecraft.core.block.tag.BlockTags;
+import net.minecraft.core.entity.EntityItemHoming;
 import net.minecraft.core.entity.MobFlying;
 import net.minecraft.core.entity.player.Player;
-import net.minecraft.core.entity.projectile.ProjectileFireball;
 import net.minecraft.core.entity.Entity;
-import net.minecraft.core.entity.vehicle.EntityBoat;
+import net.minecraft.core.entity.projectile.ProjectileFireball;
+import net.minecraft.core.item.ItemStack;
 import net.minecraft.core.item.Items;
-import net.minecraft.core.util.helper.LightIndexHelper;
+import net.minecraft.core.player.gamemode.Gamemodes;
 import net.minecraft.core.util.helper.MathHelper;
-import net.minecraft.core.world.Dimension;
 import net.minecraft.core.world.World;
 import net.minecraft.core.util.helper.DamageType;
 import net.minecraft.core.world.pos.TilePos;
 import org.jetbrains.annotations.NotNull;
-import org.joml.Vector3dc;
 import org.joml.primitives.AABBd;
 
-public class MobGhast extends MobFlying
-    implements Enemy {
+public class MobGhast extends MobFlying implements Enemy {
     public static final int DATA_CHARGING = 16;
     public int courseChangeCooldown;
     public double waypointX;
@@ -30,7 +28,6 @@ public class MobGhast extends MobFlying
     public int attackChargeO;
     public int attackCharge;
 
-
     public MobGhast(World world) {
         super(world);
         this.courseChangeCooldown = 0;
@@ -39,10 +36,9 @@ public class MobGhast extends MobFlying
         this.attackChargeO = 0;
         this.attackCharge = 0;
         setTextureIdentifier("minecraft", "ghast");
-        setSize(4F, 7F);
+        setSize(4F, 4.5F);
         this.fireImmune = true;
         this.scoreValue = 1000;
-        MathHelper.aabbGrow(this.bb, 2f, 3f, 2f, this.bb);
         this.mobDrops.add(new WeightedRandomLootObject(Items.GUNPOWDER.getDefaultStack(), 0, 2));
     }
 
@@ -70,8 +66,8 @@ public class MobGhast extends MobFlying
         }
     }
 
+    @Override
     public void tick() {
-        //prevAttackCounter = attackCounter; -uncomment if it still doesn't work
         if (this.world.isClientSide) {
             byte i = this.entityData.getByte(DATA_CHARGING);
             if (i > 0 && this.attackCharge == 0) {
@@ -84,11 +80,12 @@ public class MobGhast extends MobFlying
             if (this.attackCharge >= 20) {
                 this.attackCharge = 20;
             }
-            if (this.attackCharge >= 20 && i == 0) {
+            if (this.attackCharge == 20 && i == 0) {
                 this.world.playSoundAtEntity(null, this, "mob.ghast.fireball", getSoundVolume(), (this.random.nextFloat() - this.random.nextFloat()) * 0.2F + 1.0F);
                 this.attackCharge = -40;
             }
         }
+
         super.tick();
     }
 
@@ -96,86 +93,99 @@ public class MobGhast extends MobFlying
     protected void updateAI() {
         if (!this.world.isClientSide && !this.world.getDifficulty().canHostileMobsSpawn()) {
             remove();
+            return;
         }
         tryToDespawn();
-        this.attackChargeO = this.attackCharge;
+
         double d = this.waypointX - this.x;
         double d1 = this.waypointY - this.y;
         double d2 = this.waypointZ - this.z;
-        double d3 = MathHelper.sqrt(d * d + d1 * d1 + d2 * d2);
-        if (d3 < 1.0D || d3 > 60D) {
-            this.waypointX = this.x + (double) ((this.random.nextFloat() * 2.0F - 1.0F) * 16F);
-            this.waypointY = this.y + (double) ((this.random.nextFloat() * 2.0F - 1.0F) * 16F);
-            this.waypointZ = this.z + (double) ((this.random.nextFloat() * 2.0F - 1.0F) * 16F);
+        double d3 = d * d + d1 * d1 + d2 * d2;
+
+        if (d3 < 1.0D || d3 > 3600.0D) {
+            this.waypointX = this.x + (double)((this.random.nextFloat() * 2.0F - 1.0F) * 16.0F);
+            this.waypointY = this.y + (double)((this.random.nextFloat() * 2.0F - 1.0F) * 16.0F);
+            this.waypointZ = this.z + (double)((this.random.nextFloat() * 2.0F - 1.0F) * 16.0F);
         }
+
         if (this.courseChangeCooldown-- <= 0) {
             this.courseChangeCooldown += this.random.nextInt(5) + 2;
-            if (isCourseTraversable(this.waypointX, this.waypointY, this.waypointZ, d3)) {
-                this.xd += (d / d3) * 0.1D;
-                this.yd += (d1 / d3) * 0.1D;
-                this.zd += (d2 / d3) * 0.1D;
+            d3 = MathHelper.sqrt(d3);
+            if (this.isCourseTraversable(this.waypointX, this.waypointY, this.waypointZ, d3)) {
+                this.xd += d / d3 * 0.1D;
+                this.yd += d1 / d3 * 0.1D;
+                this.zd += d2 / d3 * 0.1D;
             } else {
                 this.waypointX = this.x;
                 this.waypointY = this.y;
                 this.waypointZ = this.z;
             }
         }
-        if (this.targetedEntity != null && this.targetedEntity.removed) {
-            this.targetedEntity = null;
-        }
+
         if (this.targetedEntity == null || this.aggroCooldown-- <= 0) {
-            this.targetedEntity = this.world.getClosestPlayerToEntity(this, 100D);
-            if (this.targetedEntity != null && !((Player) this.targetedEntity).getGamemode().hasHostileMobs()) {
-                this.targetedEntity = null;
+            Entity potentialTarget = this.world.getClosestPlayerToEntity(this, 100.0D);
+
+            if (potentialTarget instanceof Player p && p.gamemode != Gamemodes.CREATIVE && p.gamemode != Gamemodes.SPECTATOR) {
+                this.targetedEntity = potentialTarget;
             }
+
             if (this.targetedEntity != null) {
                 this.aggroCooldown = 20;
             }
         }
-        double d4 = 64D;
-        if (this.targetedEntity != null && this.targetedEntity.distanceToSqr(this) < d4 * d4) {
-            double d8 = 4D;
-            Vector3dc viewVec = getViewVector(1.0F);
-            assert viewVec != null;
-            double dX = this.targetedEntity.x - this.x;
-            double dY = this.targetedEntity.y - this.y;
-            double dZ = this.targetedEntity.z - this.z;
-            double dist = MathHelper.sqrt(dX * dX + dY * dY + dZ * dZ);
-            double vX = dX + (this.targetedEntity.xd * dist / 7.5d) - viewVec.x() * d8;
-            double vY = dY + (this.targetedEntity.yd * dist / 7.5d) - ((double) (this.bbHeight / 2.0F) + 0.5D);
-            double vZ = dZ + (this.targetedEntity.zd * dist / 7.5d) - viewVec.z() * d8;
-            this.yBodyRot = this.yRot = (-(float) java.lang.Math.atan2(vX, vZ) * 180F) / MathHelper.PI;
-            if (canEntityBeSeen(this.targetedEntity)) {
+
+        double maxAttackDist = 64.0D;
+        if (this.targetedEntity != null && this.targetedEntity.distanceToSqr(this) < maxAttackDist * maxAttackDist) {
+            double tx = this.targetedEntity.x - this.x;
+            double ty = (this.targetedEntity.bb.minY + (double)(this.targetedEntity.bbHeight / 2.0F)) - (this.y + (double)(this.bbHeight / 2.0F));
+            double tz = this.targetedEntity.z - this.z;
+
+            // Calculate where we WANT to look
+            float targetYaw = (-(float)java.lang.Math.atan2(tx, tz) * 180F) / MathHelper.PI;
+
+            // Safely calculate the shortest path to that angle
+            float deltaYaw = targetYaw - this.yRot;
+            while (deltaYaw < -180.0F) deltaYaw += 360.0F;
+            while (deltaYaw >= 180.0F) deltaYaw -= 360.0F;
+
+            this.yRot += deltaYaw * 0.1F;
+            this.yBodyRot = this.yRot;
+
+            if (this.canEntityBeSeen(this.targetedEntity)) {
                 if (this.attackCharge == 10) {
                     this.world.playSoundAtEntity(null, this, "mob.ghast.charge", getSoundVolume(), (this.random.nextFloat() - this.random.nextFloat()) * 0.2F + 1.0F);
                 }
                 this.attackCharge++;
                 if (this.attackCharge == 20) {
                     this.world.playSoundAtEntity(null, this, "mob.ghast.fireball", getSoundVolume(), (this.random.nextFloat() - this.random.nextFloat()) * 0.2F + 1.0F);
-                    ProjectileFireball fireball = new ProjectileFireball(this.world, this, vX, vY, vZ);
-
-                    fireball.x = this.x + viewVec.x() * d8;
-                    fireball.y = this.y + (double) (this.bbHeight / 2.0F) + 0.5D;
-                    fireball.z = this.z + viewVec.z() * d8;
+                    ProjectileFireball fireball = new ProjectileFireball(this.world, this, tx, ty, tz);
                     this.world.entityJoinedWorld(fireball);
                     this.attackCharge = -40;
                 }
             } else if (this.attackCharge > 0) {
                 this.attackCharge--;
-            } else {
-                this.targetedEntity = null;
             }
         } else {
-            this.yBodyRot = this.yRot = (-(float) java.lang.Math.atan2(this.xd, this.zd) * 180F) / MathHelper.PI;
+            if (this.xd * this.xd + this.zd * this.zd > 0.001D) {
+                float targetYaw = (-(float) java.lang.Math.atan2(this.xd, this.zd) * 180F) / MathHelper.PI;
+
+                float deltaYaw = targetYaw - this.yRot;
+                while (deltaYaw < -180.0F) deltaYaw += 360.0F;
+                while (deltaYaw >= 180.0F) deltaYaw -= 360.0F;
+
+                this.yRot += deltaYaw * 0.1F;
+                this.yBodyRot = this.yRot;
+            }
+
             if (this.attackCharge > 0) {
                 this.attackCharge--;
             }
         }
+
         if (!this.world.isClientSide) {
-            byte chargeData = this.entityData.getByte(DATA_CHARGING);
-            byte chargeState = (byte) (this.attackCharge <= 10 ? 0 : 1);
-            if (chargeData != chargeState) {
-                this.entityData.set(DATA_CHARGING, chargeState);
+            byte isCharging = (byte)(this.attackCharge > 10 ? 1 : 0);
+            if (this.entityData.getByte(DATA_CHARGING) != isCharging) {
+                this.entityData.set(DATA_CHARGING, isCharging);
             }
         }
     }
@@ -191,7 +201,6 @@ public class MobGhast extends MobFlying
                 return false;
             }
         }
-
         return true;
     }
 
@@ -245,8 +254,10 @@ public class MobGhast extends MobFlying
     public boolean canSpawnHere() {
         return
             this.world.getDifficulty().canHostileMobsSpawn() &&
-                this.random.nextInt(20) == 0 &&
+                this.random.nextInt(30) == 0 &&
                 BlockTags.NETHER_MOBS_SPAWN.appliesTo(this.world.getBlockType(new TilePos(this.x, this.y - this.heightOffset - 1, this.z))) &&
+                this.world.areBlocksLoaded(this.bb) &&
+                this.world.getCubes(this, this.bb).isEmpty() &&
                 super.canSpawnHere();
     }
 
@@ -261,12 +272,34 @@ public class MobGhast extends MobFlying
     }
 
     @Override
-    public float calcBrightness(float partialTick) {
-        return 1;
-    }
+    public void onDeath(Entity killer) {
+        java.util.List<WeightedRandomLootObject> storedDrops = new java.util.ArrayList<>(this.mobDrops);
+        this.mobDrops.clear();
 
-    @Override
-    public byte calcLightIndex(float partialTick) {
-        return LightIndexHelper.lightIndex2i(15, 15);
+        super.onDeath(killer);
+
+        this.mobDrops.addAll(storedDrops);
+
+        if (!this.world.isClientSide) {
+            for (WeightedRandomLootObject lootObject : storedDrops) {
+                ItemStack dropStack = lootObject.getItemStack(this.random);
+
+                if (dropStack != null && dropStack.stackSize > 0) {
+                    if (killer instanceof Player) {
+                        for (int i = 0; i < dropStack.stackSize; i++) {
+                            ItemStack singleDrop = new ItemStack(dropStack.itemID, 1, dropStack.getMetadata());
+
+                            EntityItemHoming homingItem = new EntityItemHoming(this.world, this.x, this.y, this.z, singleDrop, killer);
+                            homingItem.xd = (this.random.nextFloat() - 0.5F) * 0.8F;
+                            homingItem.yd = this.random.nextFloat() * 0.8F;
+                            homingItem.zd = (this.random.nextFloat() - 0.5F) * 0.8F;
+                            this.world.entityJoinedWorld(homingItem);
+                        }
+                    } else {
+                        this.world.dropItem(new TilePos(this.x, this.y, this.z), dropStack);
+                    }
+                }
+            }
+        }
     }
-}
+}
\ No newline at end of file
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 b5ba3bd35..bb0386cfb 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
@@ -35,9 +35,11 @@ import net.minecraft.core.enums.ArtType;
 import net.minecraft.core.enums.EnumDropCause;
 import net.minecraft.core.enums.EnumSleepStatus;
 import net.minecraft.core.item.Item;
-import net.minecraft.core.item.ItemBucket;
+import net.minecraft.core.item.ItemArmor;
 import net.minecraft.core.item.ItemStack;
 import net.minecraft.core.item.Items;
+import net.minecraft.core.item.tool.ItemTool;
+import net.minecraft.core.item.tool.ItemToolShears;
 import net.minecraft.core.item.tool.ItemToolSword;
 import net.minecraft.core.lang.I18n;
 import net.minecraft.core.net.command.TextFormatting;
@@ -47,6 +49,7 @@ import net.minecraft.core.player.inventory.container.Container;
 import net.minecraft.core.player.inventory.container.ContainerInventory;
 import net.minecraft.core.player.inventory.menu.MenuAbstract;
 import net.minecraft.core.player.inventory.menu.MenuInventory;
+import net.minecraft.core.sound.SoundCategory;
 import net.minecraft.core.util.helper.DamageType;
 import net.minecraft.core.util.helper.DyeColor;
 import net.minecraft.core.util.helper.MathHelper;
@@ -58,7 +61,6 @@ import net.minecraft.core.world.chunk.ChunkCoordinates;
 import net.minecraft.core.world.chunk.provider.ChunkProvider;
 import net.minecraft.core.world.pos.TilePos;
 import net.minecraft.core.world.pos.TilePosc;
-import net.minecraft.core.world.type.tag.WorldTypeTags;
 import net.minecraft.core.world.weather.Weathers;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
@@ -131,6 +133,15 @@ public abstract class Player
     public int lastRenderTick = 0;
     public float wobbleTimer = 0;
 
+    public static final int ACID_CYCLE_MAX = 10;
+    private static final int ACID_POST_EXIT_DRAIN_TICKS = 20;
+    private static final int ACID_EXIT_BUFFER = 2;
+
+    private int acidMeltTicks;
+    private int acidExitDelayLeft;
+    private int acidPostExitDrainTicksLeft;
+    private int acidPostExitPeak;
+
     public final PlayerConfig playerConfig = new PlayerConfig();
 
     public Player(World world) {
@@ -252,6 +263,13 @@ public abstract class Player
     public void tick() {
         if (this.world.getDimension() == Dimension.NETHER) {
             addStat(Achievements.ENTER_NETHER, 1);
+            if (random.nextInt(20) == 0) {
+                int distance = 50;
+                world.playSoundEffect(this, SoundCategory.CAVE_SOUNDS, this.x + distance, this.y, this.z + distance, "ambient.cave.cave", 1.0F, 0.01F);
+                world.playSoundEffect(this, SoundCategory.CAVE_SOUNDS, this.x - distance, this.y, this.z - distance, "ambient.cave.cave", 1.0F, 0.01F);
+                world.playSoundEffect(this, SoundCategory.CAVE_SOUNDS, this.x + distance, this.y, this.z - distance, "ambient.cave.cave", 1.0F, 0.01F);
+                world.playSoundEffect(this, SoundCategory.CAVE_SOUNDS, this.x - distance, this.y, this.z + distance, "ambient.cave.cave", 1.0F, 0.01F);
+            }
         }
 
         this.xdO = this.xd;
@@ -374,6 +392,8 @@ public abstract class Player
         this.cameraVelocityOld = this.cameraVelocity;
         super.onLivingUpdate();
 
+        acidTick();
+
         this.speed = this.baseSpeed;
         this.flySpeed = this.baseFlySpeed;
         if (isSprinting()) {
@@ -411,6 +431,202 @@ public abstract class Player
                 }
             }
         }
+
+        AABBd feetBox = new AABBd(this.bb.minX, this.bb.minY - 0.1D, this.bb.minZ, this.bb.maxX, this.bb.minY + 0.1D, this.bb.maxZ);
+        List<Entity> platforms = this.world.getEntitiesWithinAABBExcludingEntity(this, feetBox);
+
+        for (int i = 0; i < platforms.size(); i++) {
+            Entity platform = platforms.get(i);
+
+            if (platform instanceof MobGhast) continue;
+
+            if (platform.hasCollision() && this.bb.minY >= platform.bb.maxY - 0.25D) {
+
+                if (this.yd < 0.0D) {
+                    this.yd = 0.0D;
+                }
+                this.fallDistance = 0.0F;
+
+                double deltaX = platform.x - platform.xo;
+                double deltaZ = platform.z - platform.zo;
+                double snapY = platform.bb.maxY - this.bb.minY;
+
+                this.move(deltaX, snapY, deltaZ);
+
+                this.onGround = true;
+                break;
+            }
+        }
+    }
+
+    public void acidTick() {
+        if (!getGamemode().hasToolDurability()) {
+            this.acidMeltTicks = 0;
+            this.acidExitDelayLeft = 0;
+            this.acidPostExitPeak = 0;
+            this.acidPostExitDrainTicksLeft = 0;
+            return;
+        }
+
+        final boolean physicallyInAcid = isInAcid();
+        final int meltRate = Math.max(1, this.world.getGameRuleValue(GameRules.ACID_MELT_RATE));
+        final boolean shouldTickRate = (this.tickCount % meltRate == 0);
+
+        if (physicallyInAcid) {
+            this.acidExitDelayLeft = ACID_EXIT_BUFFER;
+            this.acidPostExitDrainTicksLeft = ACID_POST_EXIT_DRAIN_TICKS;
+            this.acidPostExitPeak = 0;
+        }
+
+        final boolean applyAcidMeltAndDamage = physicallyInAcid || this.acidExitDelayLeft > 0;
+
+        if (applyAcidMeltAndDamage) {
+            if (!physicallyInAcid && this.acidExitDelayLeft > 0) {
+                this.acidExitDelayLeft--;
+                if (this.acidExitDelayLeft == 0) {
+                    this.acidPostExitPeak = this.acidMeltTicks;
+                }
+            }
+
+            if (shouldTickRate) {
+                this.acidMeltTicks = Math.min(this.acidMeltTicks + 1, ACID_CYCLE_MAX);
+
+                if (!this.world.isClientSide && isAlive()) {
+                    processAcidDamageAndParticles();
+                }
+            }
+        } else if (this.acidMeltTicks > 0) {
+            if (this.acidPostExitDrainTicksLeft > 0) {
+                this.acidPostExitDrainTicksLeft--;
+                if (this.acidPostExitDrainTicksLeft <= 0) {
+                    this.acidMeltTicks = 0;
+                    this.acidPostExitPeak = 0;
+                }
+            }
+        }
+    }
+
+    /** True while physically in acid or during {@link #ACID_EXIT_BUFFER} post-exit melt/damage ticks. */
+    public boolean isEffectiveInAcid() {
+        return isInAcid() || this.acidExitDelayLeft > 0;
+    }
+
+    private void processAcidDamageAndParticles() {
+        final int acidIntensity = this.acidMeltTicks;
+        boolean meltedAny = false;
+
+        for (int i = 0; i < this.inventory.mainInventory.length; i++) {
+            final @Nullable ItemStack stack = this.inventory.mainInventory[i];
+            if (stack == null || !isMeltableHeldTool(stack)) {
+                continue;
+            }
+
+            stack.damageItem(getAcidMeltDamage(stack, acidIntensity), this);
+            if (stack.stackSize <= 0) {
+                this.inventory.mainInventory[i] = null;
+            }
+            meltedAny = true;
+        }
+
+        for (int i = 0; i < getNumArmorSlots(); i++) {
+            final @Nullable HumanArmorShape armorSlot = getArmorSlotByIndex(i);
+            if (armorSlot == null) {
+                continue;
+            }
+
+            final @Nullable ItemStack armorItem = getItemInArmorSlot(armorSlot);
+            if (armorItem == null) {
+                continue;
+            }
+
+            damageArmor(getAcidMeltDamage(armorItem, acidIntensity), armorSlot);
+            meltedAny = true;
+        }
+
+        int acidExposureDamage = getAcidExposureArmorSlotCount();
+        if (acidExposureDamage > 0) {
+            hurt(null, acidExposureDamage, DamageType.GENERIC);
+        }
+
+        if (meltedAny || acidExposureDamage > 0) {
+            float fizzPitch = 0.6F + this.random.nextFloat() * 0.25F;
+            float bubblePitch = 0.7F + this.random.nextFloat() * 0.2F;
+            this.world.playSoundAtEntity(null, this, "random.fizz", 0.35F, fizzPitch);
+            this.world.playSoundAtEntity(null, this, "tile.acidbubble", 0.4F, bubblePitch);
+
+            for (int i = 0; i < 6; i++) {
+                double offX = (this.random.nextDouble() - 0.5D) * (double) this.bbWidth;
+                double offY = this.random.nextDouble() * (double) this.bbHeight;
+                double offZ = (this.random.nextDouble() - 0.5D) * (double) this.bbWidth;
+                this.world.spawnParticle("acidBoiling", this.x + offX, this.bb.minY + offY, this.z + offZ, 0.0F, 0.02F, 0.0F, 0, false);
+            }
+        }
+    }
+
+    public boolean shouldShowAcidVisualEffects() {
+        return isEffectiveInAcid() || this.acidMeltTicks > 0;
+    }
+
+    public int getAcidHudBarFillTicks() {
+        if (isEffectiveInAcid() || this.acidPostExitPeak == 0) {
+            return this.acidMeltTicks;
+        }
+        if (this.acidPostExitDrainTicksLeft <= 0) {
+            return 0;
+        }
+
+        return (this.acidPostExitPeak * this.acidPostExitDrainTicksLeft) / ACID_POST_EXIT_DRAIN_TICKS;
+    }
+
+    private int getAcidExposureArmorSlotCount() {
+        int count = 0;
+        for (int i = 0; i < getNumArmorSlots(); i++) {
+            final @Nullable HumanArmorShape armorSlot = getArmorSlotByIndex(i);
+            if (armorSlot == null) {
+                continue;
+            }
+            final @Nullable ItemStack stack = getItemInArmorSlot(armorSlot);
+            if (!isHumanArmorItemCoveringSlot(stack)) {
+                count++;
+            }
+        }
+        return Math.max(0, count - 1);
+    }
+
+    private static boolean isHumanArmorItemCoveringSlot(final @Nullable ItemStack stack) {
+        if (stack == null) {
+            return false;
+        }
+        if (!(stack.getItem() instanceof ItemArmor<?> armor)) {
+            return false;
+        }
+        return armor.getArmorShape() instanceof HumanArmorShape;
+    }
+
+    private boolean isMeltableHeldTool(final @NotNull ItemStack stack) {
+        final @NotNull Item item = stack.getItem();
+        return item instanceof ItemTool || item instanceof ItemToolSword || item instanceof ItemToolShears;
+    }
+
+    private int getAcidMeltDamage(final @NotNull ItemStack stack, final int acidIntensity) {
+        if (!stack.isItemStackDamageable()) {
+            return 1;
+        }
+
+        final int base = Math.max(1, (int) Math.ceil(Math.sqrt(stack.getMaxDamage()) / 10.0D));
+
+        final int tier = Math.min(ACID_CYCLE_MAX, Math.max(1, acidIntensity));
+        if (tier < 2 || ACID_CYCLE_MAX < 2) {
+            return base;
+        }
+
+        float progress = (float) (tier - 1) / (float) (ACID_CYCLE_MAX - 1);
+
+        float curve = (float) Math.sqrt(progress);
+
+        final float multiplier = 1.0F + (curve * 0.45F);
+
+        return Math.max(1, (int) Math.ceil(base * multiplier));
     }
 
     @Override
@@ -467,8 +683,8 @@ public abstract class Player
         } else if (UUID_SHIGN.equals(this.uuid) || this.username.equals("ShignBright")) {
             dropItem(new ItemStack(Items.SIGN_PAINTED, 1, DyeColor.PINK.itemMeta), true);
         } else if ((UUID_MELON.equals(this.uuid) || this.username.equals("MelonMojito")) || (UUID_WATERMELON.equals(this.uuid) || this.username.equals("WatermelonMojito"))) {
-            ItemStack melon = new ItemStack(Blocks.PUMPKIN, 1, this.random.nextInt(Short.MAX_VALUE / 2) + 1);
-            melon.setCustomName("Melon");
+            ItemStack melon = new ItemStack(Blocks.PUMPKIN, 1);
+            melon.setCustomName("Melon Block?");
             melon.setCustomColor((byte) TextFormatting.LIME.id);
             dropItem(melon, true);
         }
@@ -659,7 +875,7 @@ public abstract class Player
     }
 
     @Override
-    public void push(Entity entity) {
+    public void push(@NotNull Entity entity) {
         if (this.noPhysics || !getGamemode().canInteract()) return;
         super.push(entity);
     }
@@ -1421,6 +1637,45 @@ public abstract class Player
         }
     }
 
+
+    //TODO Need to implement a native solution for distance/player oriented functionality
+    /**
+     * Handles the incoming ping from a nearby Thermal Vent.
+     * Categorizes the player into Close, Medium, or Far range based on maxRadius,
+     * playing a different sound for each tier.
+     */
+    public void handleThermalPing(Player player, double ventCoordinateX, double ventCoordinateY, double ventCoordinateZ, double maxRadius) {
+        double distanceSquared = this.distanceToSqr(ventCoordinateX, ventCoordinateY, ventCoordinateZ);
+        double distance = Math.sqrt(distanceSquared);
+
+        double oneFifthOfRadius = maxRadius * 0.2;
+        double threeFifthsOfRadius = maxRadius * 0.6;
+
+        float randomizedPitch = 0.8F + (this.world.rand.nextFloat() - this.world.rand.nextFloat()) * 0.45F;
+
+        if (distance <= oneFifthOfRadius) {
+            this.world.playSoundEffect(player, SoundCategory.WORLD_SOUNDS,
+                ventCoordinateX, ventCoordinateY, ventCoordinateZ,
+            "tile.crumble", 20.0F, randomizedPitch);
+            this.world.playSoundEffect(player, SoundCategory.WORLD_SOUNDS,
+                ventCoordinateX, ventCoordinateY, ventCoordinateZ,
+            "tile.deepboom", 20.0F, randomizedPitch);
+        }
+        else if (distance <= threeFifthsOfRadius) {
+            this.world.playSoundEffect(player, SoundCategory.WORLD_SOUNDS,
+                ventCoordinateX, ventCoordinateY, ventCoordinateZ,
+            "tile.deepcrumble", 20.0F, randomizedPitch);
+            this.world.playSoundEffect(player, SoundCategory.WORLD_SOUNDS,
+                ventCoordinateX, ventCoordinateY, ventCoordinateZ,
+            "tile.deepquietboom", 20.0F, randomizedPitch);
+        }
+        else {
+            this.world.playSoundEffect(player, SoundCategory.WORLD_SOUNDS,
+                ventCoordinateX, ventCoordinateY, ventCoordinateZ,
+            "tile.deepquietboom", 20.0F, randomizedPitch);
+        }
+    }
+
     @Override
     public boolean canBreatheUnderwater() {
         return this.gamemode.hasInvulnerablePlayer();
diff --git a/game/core/src/main/java/net/minecraft/core/entity/projectile/ProjectileArrowFlaming.java b/game/core/src/main/java/net/minecraft/core/entity/projectile/ProjectileArrowFlaming.java
new file mode 100644
index 000000000..1cedf70ab
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/entity/projectile/ProjectileArrowFlaming.java
@@ -0,0 +1,130 @@
+package net.minecraft.core.entity.projectile;
+
+import net.minecraft.core.block.BlockLogicEmber;
+import net.minecraft.core.block.Blocks;
+import net.minecraft.core.block.Block;
+import net.minecraft.core.block.BlockLogicTNT;
+import net.minecraft.core.entity.Mob;
+import net.minecraft.core.entity.monster.MobCreeper;
+import net.minecraft.core.entity.monster.MobZombiePig;
+import net.minecraft.core.item.ItemStack;
+import net.minecraft.core.item.Items;
+import net.minecraft.core.util.helper.DamageType;
+import net.minecraft.core.util.helper.LightIndexHelper;
+import net.minecraft.core.util.phys.HitResult;
+import net.minecraft.core.world.World;
+import org.jetbrains.annotations.NotNull;
+
+
+public class ProjectileArrowFlaming extends ProjectileArrow {
+	public ProjectileArrowFlaming(World world) {
+		super(world, 4);
+		this.stack = new ItemStack(Items.AMMO_ARROW_FLAMING);
+		this.entityBrightness = 1.0F;
+	}
+
+	public ProjectileArrowFlaming(World world, double x, double y, double z) {
+		super(world, x, y, z, 4);
+		this.stack = new ItemStack(Items.AMMO_ARROW_FLAMING);
+		this.entityBrightness = 1.0F;
+	}
+
+	public ProjectileArrowFlaming(World world, Mob owner, boolean doesArrowBelongToPlayer) {
+		super(world, owner, doesArrowBelongToPlayer, 4);
+		this.stack = new ItemStack(Items.AMMO_ARROW_FLAMING);
+		this.entityBrightness = 1.0F;
+	}
+
+	@Override
+	protected void initProjectile() {
+		super.initProjectile();
+		this.damage = 6;
+		this.entityBrightness = 1.0F;
+	}
+
+	@Override
+	public float getBrightness(float partialTick) {
+		return 10.0F;
+	}
+
+	@Override
+	public float calcBrightness(float partialTick) {
+		return 1.0F;
+	}
+
+	@Override
+	public byte calcLightIndex(float partialTick) {
+		return LightIndexHelper.setBlockLight(super.calcLightIndex(partialTick), 15);
+	}
+
+	@Override
+	public void tick() {
+		world.spawnParticle("flame", this.x, this.y, this.z, this.xd * 0.05, this.yd * 0.05 - 0.1, this.zd * 0.05, 0, false);
+		world.spawnParticle("flame", this.x, this.y, this.z, this.xd * 0.05, this.yd * 0.05 - 0.1, this.zd * 0.05, 0, false);
+		world.spawnParticle("smoke", this.x, this.y, this.z, this.xd * 0.05, this.yd * 0.05 - 0.1, this.zd * 0.05, 0, false);
+		world.spawnParticle("smoke", this.x, this.y, this.z, this.xd * 0.05, this.yd * 0.05 - 0.1, this.zd * 0.05, 0, false);
+		super.tick();
+	}
+
+	@Override
+	public void onHit(@NotNull HitResult hitResult) {
+		if (hitResult instanceof HitResult.Entity hitEntity) {
+			if (hitEntity.entity instanceof MobZombiePig) {
+				hitEntity.entity.hurt(this.owner, this.damage, DamageType.COMBAT);
+			} else if (hitEntity.entity.hurt(this.owner, this.damage, DamageType.FIRE)) {
+				if (hitEntity.entity instanceof MobCreeper entityCreeper) {
+					entityCreeper.setTarget(entityCreeper);
+				}
+				hitEntity.entity.fireHurt();
+
+				if (!this.world.isClientSide) {
+					this.world.playSoundAtEntity(null, this, "random.drr", 1.0F, 1.2F / (this.random.nextFloat() * 0.2F + 0.9F));
+				}
+			}
+			this.remove();
+		} else if (hitResult instanceof HitResult.Tile hitTile && !this.world.isClientSide) {
+			int x = hitTile.tilePos.x();
+			int y = hitTile.tilePos.y();
+			int z = hitTile.tilePos.z();
+			Block<?> hitBlock = world.getBlockType(hitTile.tilePos);
+
+			x += hitTile.side.offsetX();
+			y += hitTile.side.offsetY();
+			z += hitTile.side.offsetZ();
+
+			if (world.isAirBlock(x, y, z)) {
+				world.setBlockWithNotify(x, y, z, Blocks.FIRE.id());
+			}
+
+			if (hitBlock == Blocks.BRAZIER_INACTIVE) {
+				world.setBlockType(hitTile.tilePos, Blocks.BRAZIER_ACTIVE);
+			}
+
+			if (hitBlock == Blocks.TNT) {
+				world.getBlockLogic(hitTile.tilePos, BlockLogicTNT.class).ignite(world, hitTile.tilePos, true);
+			}
+
+			if (hitBlock == Blocks.EMBER) {
+				world.getBlockLogic(hitTile.tilePos, BlockLogicEmber.class).ignite(world, hitTile.tilePos, null);
+			}
+			if (hitBlock == Blocks.ICE) {
+				world.setBlockTypeNotify(hitTile.tilePos, Blocks.FLUID_WATER_STILL);
+			}
+		}
+
+		this.remove();
+
+		super.onHit(hitResult);
+	}
+
+	@Override
+	protected void inGroundAction() {
+		world.spawnParticle("explode", this.x, this.y, this.z, this.xd * 0.05, this.yd * 0.05 - 0.1, this.zd * 0.05, 0, false);
+		world.spawnParticle("explode", this.x, this.y, this.z, this.xd * 0.05, this.yd * 0.05 - 0.1, this.zd * 0.05, 0, false);
+		world.spawnParticle("ventsmoke", this.x, this.y, this.z, this.xd * 0.05, this.yd * 0.05 - 0.1, this.zd * 0.05, 0, false);
+		world.spawnParticle("ventsmoke", this.x, this.y, this.z, this.xd * 0.05, this.yd * 0.05 - 0.1, this.zd * 0.05, 0, false);
+		world.playSoundAtEntity(null, this, "random.fizz", 0.1f, (random.nextFloat() * 1.4F + 0.8F));
+
+		super.inGroundAction();
+	}
+}
diff --git a/game/core/src/main/java/net/minecraft/core/entity/projectile/ProjectileFireball.java b/game/core/src/main/java/net/minecraft/core/entity/projectile/ProjectileFireball.java
index 1746edc10..7f9171650 100644
--- a/game/core/src/main/java/net/minecraft/core/entity/projectile/ProjectileFireball.java
+++ b/game/core/src/main/java/net/minecraft/core/entity/projectile/ProjectileFireball.java
@@ -4,6 +4,7 @@ import net.minecraft.core.achievement.Achievements;
 import net.minecraft.core.entity.Mob;
 import net.minecraft.core.entity.monster.MobGhast;
 import net.minecraft.core.entity.player.Player;
+import net.minecraft.core.sound.SoundCategory;
 import net.minecraft.core.util.phys.HitResult;
 import net.minecraft.core.entity.Entity;
 import net.minecraft.core.util.helper.MathHelper;
@@ -14,6 +15,7 @@ import org.joml.Vector3dc;
 
 public class ProjectileFireball extends Projectile {
     public static final double FIREBALL_SPEED = 1d;
+    public static final double DEFLECTED_SPEED = 2d;
 
     public ProjectileFireball(World world) {
         super(world);
@@ -113,7 +115,9 @@ public class ProjectileFireball extends Projectile {
                 this.owner = (Mob) entity;
             }
             if (lookAngle != null) {
-                setVelocity(lookAngle.x(), lookAngle.y(), lookAngle.z(), FIREBALL_SPEED);
+                world.playSoundEffect(entity, SoundCategory.WORLD_SOUNDS, this.x, this.y, this.z, "random.drr", 1.0F, 1.3F + this.random.nextFloat() * 0.4F);
+                world.playSoundEffect(entity, SoundCategory.WORLD_SOUNDS, this.x, this.y, this.z, "random.bow", 1.0F, 1.3F + this.random.nextFloat() * 0.4F);
+                setVelocity(lookAngle.x(), lookAngle.y(), lookAngle.z(), DEFLECTED_SPEED);
             }
             return true;
         } else {
diff --git a/game/core/src/main/java/net/minecraft/core/entity/vehicle/EntityBoat.java b/game/core/src/main/java/net/minecraft/core/entity/vehicle/EntityBoat.java
index f3a3b1df5..b5694f797 100644
--- a/game/core/src/main/java/net/minecraft/core/entity/vehicle/EntityBoat.java
+++ b/game/core/src/main/java/net/minecraft/core/entity/vehicle/EntityBoat.java
@@ -321,7 +321,7 @@ public class EntityBoat extends Entity {
             int blockY = MathHelper.floor(this.y);
             int blockZ = MathHelper.floor(this.z + ((double) (i / 2) - 0.5D) * 0.8D);
             Block<?> block = this.world.getBlock(blockX, blockY, blockZ);
-            if (block != null && block.hasTag(BlockTags.BOAT_BREAKS)) {
+            if (block.hasTag(BlockTags.BOAT_BREAKS)) {
                 this.world.setBlockWithNotify(blockX, blockY, blockZ, 0);
             }
         }
diff --git a/game/core/src/main/java/net/minecraft/core/entity/vehicle/EntityMinecart.java b/game/core/src/main/java/net/minecraft/core/entity/vehicle/EntityMinecart.java
index fabb99dc7..620ebf182 100644
--- a/game/core/src/main/java/net/minecraft/core/entity/vehicle/EntityMinecart.java
+++ b/game/core/src/main/java/net/minecraft/core/entity/vehicle/EntityMinecart.java
@@ -677,7 +677,7 @@ public class EntityMinecart extends Entity implements Container {
     }
 
     @Override
-    public void push(Entity entity) {
+    public void push(@NotNull Entity entity) {
         if (this.world.isClientSide) {
             return;
         }
diff --git a/game/core/src/main/java/net/minecraft/core/item/ItemBucket.java b/game/core/src/main/java/net/minecraft/core/item/ItemBucket.java
index 201bb20eb..2d2785f6e 100644
--- a/game/core/src/main/java/net/minecraft/core/item/ItemBucket.java
+++ b/game/core/src/main/java/net/minecraft/core/item/ItemBucket.java
@@ -16,6 +16,7 @@ import net.minecraft.core.util.collection.NamespaceID;
 import net.minecraft.core.util.helper.Direction;
 import net.minecraft.core.util.helper.Side;
 import net.minecraft.core.util.phys.HitResult;
+import net.minecraft.core.world.LevelListener;
 import net.minecraft.core.world.World;
 import net.minecraft.core.world.pos.TilePos;
 import net.minecraft.core.world.pos.TilePosc;
@@ -77,6 +78,7 @@ public abstract class ItemBucket extends ItemFood implements IItemContainer {
     public static final NamespaceID STATE_EMPTY = new NamespaceID("minecraft", "empty");
     public static final NamespaceID STATE_WATER = new NamespaceID("minecraft", "water");
     public static final NamespaceID STATE_LAVA  = new NamespaceID("minecraft", "lava");
+    public static final NamespaceID STATE_ACID = new NamespaceID("minecraft", "acid");
     public static final NamespaceID STATE_MILK  = new NamespaceID("minecraft", "milk");
     public static final NamespaceID STATE_ICECREAM = new NamespaceID("minecraft", "icecream");
 
@@ -100,6 +102,13 @@ public abstract class ItemBucket extends ItemFood implements IItemContainer {
                 BlockLogicFluid::fizz
         ));
 
+        registerState(STATE_ACID, new BucketState(
+                "acid",
+                Blocks.FLUID_ACID_FLOWING,
+                BlockTags.IS_ACID,
+                "tile.acidbubble",
+                (world, tilePos) -> world.playBlockEvent(tilePos, LevelListener.EVENT_ACID_SPREAD, 0)));
+
         registerState(STATE_MILK, new BucketState(
                 "milk",
                 0,
@@ -210,6 +219,11 @@ public abstract class ItemBucket extends ItemFood implements IItemContainer {
             }
 
             if (currentState.canPlace()) {
+                NamespaceID aimedFluid = getFluidStateAt(world, tilePos);
+                if (currentStateId.equals(aimedFluid) && getCharges(itemStack) < this.maxCharges) {
+                    return itemStack;
+                }
+
                 HitResult hitResultNoFluid = player.rayCast(reachDistance, 1f, false, false, false);
                 if (hitResultNoFluid instanceof HitResult.Tile hitTileNoFluid) {
                     return tryPlaceFluid(itemStack, world, player, hitTileNoFluid);
diff --git a/game/core/src/main/java/net/minecraft/core/item/ItemFireStriker.java b/game/core/src/main/java/net/minecraft/core/item/ItemFireStriker.java
index da56e8899..d68dc74dc 100644
--- a/game/core/src/main/java/net/minecraft/core/item/ItemFireStriker.java
+++ b/game/core/src/main/java/net/minecraft/core/item/ItemFireStriker.java
@@ -5,6 +5,7 @@ import net.minecraft.core.block.BlockLogicBrazier;
 import net.minecraft.core.block.BlockLogicFluid;
 import net.minecraft.core.block.BlockLogicTNT;
 import net.minecraft.core.block.entity.TileEntityActivator;
+import net.minecraft.core.block.tag.BlockTags;
 import net.minecraft.core.entity.player.Player;
 import net.minecraft.core.item.material.ToolMaterial;
 import net.minecraft.core.sound.SoundCategory;
@@ -29,10 +30,18 @@ public class ItemFireStriker extends Item {
 
     @Override
     public boolean onUseOnBlock(@NotNull ItemStack selfStack, @NotNull World world, @Nullable Player player, @NotNull TilePosc blockPos, @NotNull Side side, double xHit, double yHit) {
-        blockPos = blockPos.add(side.direction(), new TilePos());
-        if (world.isAirBlock(blockPos)) {
-            if (world.setBlockTypeNotify(blockPos, Blocks.FIRE)) {
-                world.playSoundEffect(player, SoundCategory.WORLD_SOUNDS, (double) blockPos.x() + 0.5D, (double) blockPos.y() + 0.5D, (double) blockPos.z() + 0.5D, "fire.ignite", 1.0F, itemRand.nextFloat() * 0.4F + 0.8F);
+        Block<?> clickedBlock = world.getBlockType(blockPos);
+
+        TilePos firePos = blockPos.add(side.direction(), new TilePos());
+
+        if (world.isAirBlock(firePos)) {
+            Block<?> fireToPlace = Blocks.FIRE;
+            if (clickedBlock.hasTag(BlockTags.INFINITE_BURN_SULFURIC)) {
+                fireToPlace = Blocks.FIRE_SULFURIC;
+            }
+
+            if (world.setBlockTypeNotify(firePos, fireToPlace)) {
+                world.playSoundEffect(player, SoundCategory.WORLD_SOUNDS, (double) firePos.x() + 0.5D, (double) firePos.y() + 0.5D, (double) firePos.z() + 0.5D, "fire.ignite", 1.0F, itemRand.nextFloat() * 0.4F + 0.8F);
                 selfStack.damageItem(1, player);
                 return true;
             }
diff --git a/game/core/src/main/java/net/minecraft/core/item/ItemPaintBrush.java b/game/core/src/main/java/net/minecraft/core/item/ItemPaintBrush.java
index 4fbc6bc9b..9f20a442d 100644
--- a/game/core/src/main/java/net/minecraft/core/item/ItemPaintBrush.java
+++ b/game/core/src/main/java/net/minecraft/core/item/ItemPaintBrush.java
@@ -169,4 +169,11 @@ public class ItemPaintBrush extends Item {
             tag.putInt("Color", color.itemMeta);
         }
     }
+
+    @Override
+    public @NotNull String getLanguageKey(@NotNull ItemStack selfStack) {
+        DyeColor color = getColor(selfStack);
+        if (color != null) return super.getKey() + "." + color.colorID;
+        return super.getKey();
+    }
 }
diff --git a/game/core/src/main/java/net/minecraft/core/item/ItemRubyglassGrowth.java b/game/core/src/main/java/net/minecraft/core/item/ItemRubyglassGrowth.java
new file mode 100644
index 000000000..010c6701e
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/item/ItemRubyglassGrowth.java
@@ -0,0 +1,66 @@
+package net.minecraft.core.item;
+
+import net.minecraft.core.block.Block;
+import net.minecraft.core.block.Blocks;
+import net.minecraft.core.block.entity.TileEntityActivator;
+import net.minecraft.core.entity.player.Player;
+import net.minecraft.core.enums.EnumBlockSoundEffectType;
+import net.minecraft.core.util.helper.Direction;
+import net.minecraft.core.util.helper.Side;
+import net.minecraft.core.world.World;
+import net.minecraft.core.world.pos.TilePos;
+import net.minecraft.core.world.pos.TilePosc;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.Random;
+
+import static net.minecraft.core.block.BlockLogicGrowthRubyglass.STATE_BUD;
+
+public class ItemRubyglassGrowth extends Item {
+
+	public ItemRubyglassGrowth(@NotNull String translationKey, @NotNull String namespaceId, int id, Block<?> block) {
+		super(translationKey, namespaceId, id);
+		this.maxStackSize = 64;
+	}
+
+	@Override
+	public boolean onUseOnBlock(@NotNull ItemStack selfStack, @NotNull World world, @Nullable Player player, @NotNull TilePosc blockPos, @NotNull Side side, double xHit, double yHit) {
+		if (player == null) return false;
+
+		TilePos placePos = new TilePos(blockPos);
+
+		if (!world.canPlaceInsideBlock(placePos)) {
+			placePos = placePos.add(side.direction(), new TilePos());
+		}
+
+		if (!Blocks.RUBYGLASS_GROWTH.getLogic().canPlaceAt(world, placePos)) return false;
+
+		if (world.setBlockTypeNotify(placePos, Blocks.RUBYGLASS_GROWTH)) {
+			Blocks.RUBYGLASS_GROWTH.getLogic().onPlacedByMob(world, placePos, side, player, xHit, yHit);
+
+			world.playBlockSoundEffect(player, (float) placePos.x() + 0.5F, (float) placePos.y() + 0.5F, (float) placePos.z() + 0.5F, Blocks.RUBYGLASS_GROWTH, EnumBlockSoundEffectType.PLACE);
+			Blocks.RUBYGLASS_GROWTH.onPlacedOnSide(world, placePos, side, 0.5, 0.5);
+
+			selfStack.consumeItem(player);
+			return true;
+		}
+
+		return false;
+	}
+
+	@Override
+	public void onUseByActivator(@NotNull ItemStack selfStack, @NotNull World world, @NotNull TileEntityActivator activator, @NotNull Random random, @NotNull TilePosc blockPos, @NotNull Direction direction, double offX, double offY, double offZ) {
+		TilePos placePos = new TilePos(blockPos);
+
+		if (!world.canPlaceInsideBlock(placePos)) {
+			placePos = placePos.add(direction, new TilePos());
+		}
+
+		if (Blocks.RUBYGLASS_GROWTH.getLogic().canPlaceAt(world, placePos)) {
+			world.setBlockTypeDataNotify(placePos, Blocks.RUBYGLASS_GROWTH, STATE_BUD);
+			world.playBlockSoundEffect(null, (float) placePos.x() + 0.5F, (float) placePos.y() + 0.5F, (float) placePos.z() + 0.5F, Blocks.RUBYGLASS_GROWTH, EnumBlockSoundEffectType.PLACE);
+			selfStack.consumeItem(null);
+		}
+	}
+}
\ No newline at end of file
diff --git a/game/core/src/main/java/net/minecraft/core/item/Items.java b/game/core/src/main/java/net/minecraft/core/item/Items.java
index 3141a025f..d99e64d24 100644
--- a/game/core/src/main/java/net/minecraft/core/item/Items.java
+++ b/game/core/src/main/java/net/minecraft/core/item/Items.java
@@ -210,7 +210,7 @@ public final class Items {
     public static Item DOUGH;
     public static Item WAND_NBT;
 //    public static Item RUBYGLASS_DUST;
-    public static Item RUBYGLASS_CRYSTAL;
+    public static Item RUBYGLASS;
     public static Item TIMER;
 
     public static Item ARMOR_WOLF_LEATHER;
@@ -220,6 +220,14 @@ public final class Items {
     public static Item ARMOR_WOLF_GOLD;
     public static Item ARMOR_WOLF_STEEL;
 
+    public static Item SULFUR;
+    public static Item AMMO_ARROW_FLAMING;
+
+    public static Item STATUE_SLATE;
+    public static Item STATUE_PERMAFROST;
+    public static Item STATUE_NETHERRACK;
+    public static Item STATUE_GLOOMSTONE;
+
     private static boolean hasInit = false;
     public static void init(){
         if (hasInit) return;
@@ -444,8 +452,8 @@ public final class Items {
         FOOD_VENISON_COOKED = (new ItemFood("food.venison.cooked", "minecraft:item/food_venison_cooked", 16551, 12, 8, true, 4));
         DOUGH = (new Item("dough", "minecraft:item/dough", 16552));
         WAND_NBT = new ItemWandNBT("wand.nbt", "minecraft:item/wand_nbt", 16553);
-//        RUBYGLASS_DUST = (new Item("rubyglass.crystal.cracked", "minecraft:item/rubyglass_crystal_cracked", 16554));
-        RUBYGLASS_CRYSTAL = (new Item("rubyglass.crystal.pure", "minecraft:item/rubyglass_crystal_pure", 16555));
+
+        RUBYGLASS = (new Item("rubyglass", "minecraft:item/rubyglass_crystal", 16555));
 
 
         ARMOR_WOLF_LEATHER = new ItemArmor<>("armor.wolf.leather", "minecraft:item/armor_wolf_leather", 16556, ArmorMaterial.LEATHER, WolfArmorShape.BODY);
@@ -457,5 +465,14 @@ public final class Items {
 
         BUCKET_IRON = (new ItemBucketIron("bucket.iron", "minecraft:item/bucket_iron", 16562));
         BUCKET_STEEL = (new ItemBucketSteel("bucket.steel", "minecraft:item/bucket_steel", 16563));
+
+        SULFUR = new Item("sulfur", "minecraft:item/sulfur", 16567);
+        AMMO_ARROW_FLAMING = new Item("ammo.arrow.flaming", "minecraft:item/ammo_arrow_flaming", 16568);
+
+        STATUE_SLATE = new ItemStatue("statue.slate", "minecraft:item/statue_slate", 16571, Blocks.STATUE_SLATE_LOWER, Blocks.STATUE_SLATE_UPPER);
+        STATUE_PERMAFROST = new ItemStatue("statue.permafrost", "minecraft:item/statue_permafrost", 16572, Blocks.STATUE_PERMAFROST_LOWER, Blocks.STATUE_PERMAFROST_UPPER);
+        STATUE_NETHERRACK = new ItemStatue("statue.netherrack", "minecraft:item/statue_netherrack", 16573, Blocks.STATUE_NETHERRACK_LOWER, Blocks.STATUE_NETHERRACK_UPPER);
+        STATUE_GLOOMSTONE = new ItemStatue("statue.gloomstone", "minecraft:item/statue_gloomstone", 16574, Blocks.STATUE_GLOOMSTONE_LOWER, Blocks.STATUE_GLOOMSTONE_UPPER);
+
     }
 }
diff --git a/game/core/src/main/java/net/minecraft/core/item/material/ToolMaterial.java b/game/core/src/main/java/net/minecraft/core/item/material/ToolMaterial.java
index 32919fec2..95768cb9f 100644
--- a/game/core/src/main/java/net/minecraft/core/item/material/ToolMaterial.java
+++ b/game/core/src/main/java/net/minecraft/core/item/material/ToolMaterial.java
@@ -7,7 +7,7 @@ public class ToolMaterial {
 	public static final ToolMaterial iron = new ToolMaterial().setDurability(384).setEfficiency(6.0f, 8.0f).setMiningLevel(2);
 	public static final ToolMaterial steel = new ToolMaterial().setDurability(4608).setEfficiency(7.0f, 14.0f).setMiningLevel(3);
 	public static final ToolMaterial diamond = new ToolMaterial().setDurability(1536).setEfficiency(14.0f, 45.0f).setMiningLevel(3).setDamage(4).setBlockHitDelay(4);
-	public static final ToolMaterial gold = new ToolMaterial().setDurability(256).setEfficiency(7.0f, 12f).setMiningLevel(2).setSilkTouch(true);
+	public static final ToolMaterial gold = new ToolMaterial().setDurability(384).setEfficiency(7.0f, 12f).setMiningLevel(2).setSilkTouch(true).setBlockHitDelay(4);
 	
 	private int miningLevel;
 	private int durability;
diff --git a/game/core/src/main/java/net/minecraft/core/net/command/arguments/ArgumentTypeParticleId.java b/game/core/src/main/java/net/minecraft/core/net/command/arguments/ArgumentTypeParticleId.java
index 38e49f8fb..359de42bf 100644
--- a/game/core/src/main/java/net/minecraft/core/net/command/arguments/ArgumentTypeParticleId.java
+++ b/game/core/src/main/java/net/minecraft/core/net/command/arguments/ArgumentTypeParticleId.java
@@ -51,6 +51,7 @@ public class ArgumentTypeParticleId implements ArgumentType<String> {
         particleIds.add("ashmote");
         particleIds.add("ventsmoke");
         particleIds.add("rubyglassLightning");
+        particleIds.add("acidBoiling");
     }
 
     private static final List<String> EXAMPLES = Arrays.asList("fireflyGreen", "explode", "soulflame");
diff --git a/game/core/src/main/java/net/minecraft/core/net/command/util/CommandHelper.java b/game/core/src/main/java/net/minecraft/core/net/command/util/CommandHelper.java
index f2cec404d..00c9503b1 100644
--- a/game/core/src/main/java/net/minecraft/core/net/command/util/CommandHelper.java
+++ b/game/core/src/main/java/net/minecraft/core/net/command/util/CommandHelper.java
@@ -15,6 +15,7 @@ import net.minecraft.core.util.collection.NamespaceID;
 import net.minecraft.core.util.helper.MathHelper;
 import net.minecraft.core.world.WorldSource;
 import net.minecraft.core.world.generate.feature.*;
+import net.minecraft.core.world.generate.feature.hanging.WorldFeatureHangingDungeon;
 import net.minecraft.core.world.generate.feature.tree.*;
 import net.minecraft.core.world.generate.feature.tree.spooner.*;
 import org.jetbrains.annotations.NotNull;
@@ -211,6 +212,11 @@ public class CommandHelper {
         registerWorldFeatureClass(WorldFeatureSpoonerTreeRainforest.class);
         registerWorldFeatureClass(WorldFeatureSpoonerTreeRound.class);
         registerWorldFeatureClass(WorldFeatureSpoonerTreeStick.class);
+
+        registerWorldFeatureClass(WorldFeatureHangingDungeon.Gloomstone.class, "HangingDungeonGloomstone");
+        registerWorldFeatureClass(WorldFeatureHangingDungeon.Basalt.class, "HangingDungeonBasalt");
+        registerWorldFeatureClass(WorldFeatureHangingDungeon.Netherrack.class, "HangingDungeonNetherrack");
+        registerWorldFeatureClass(WorldFeatureHangingDungeon.Slate.class, "HangingDungeonSlate");
     }
 
     public static <T extends WorldFeatureInterface> void registerWorldFeatureClass(@NotNull final Class<T> tClass) {
diff --git a/game/core/src/main/java/net/minecraft/core/net/entity/NetEntityHandler.java b/game/core/src/main/java/net/minecraft/core/net/entity/NetEntityHandler.java
index 18413628b..99a71308e 100644
--- a/game/core/src/main/java/net/minecraft/core/net/entity/NetEntityHandler.java
+++ b/game/core/src/main/java/net/minecraft/core/net/entity/NetEntityHandler.java
@@ -4,23 +4,7 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap;
 import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
 import it.unimi.dsi.fastutil.objects.Object2IntArrayMap;
 import it.unimi.dsi.fastutil.objects.Object2IntMap;
-import net.minecraft.core.net.entity.entries.NetEntryAnimal;
-import net.minecraft.core.net.entity.entries.NetEntryArrow;
-import net.minecraft.core.net.entity.entries.NetEntryBoat;
-import net.minecraft.core.net.entity.entries.NetEntryBobber;
-import net.minecraft.core.net.entity.entries.NetEntryCannonball;
-import net.minecraft.core.net.entity.entries.NetEntryEgg;
-import net.minecraft.core.net.entity.entries.NetEntryFallingBlock;
-import net.minecraft.core.net.entity.entries.NetEntryFireball;
-import net.minecraft.core.net.entity.entries.NetEntryFirefly;
-import net.minecraft.core.net.entity.entries.NetEntryItem;
-import net.minecraft.core.net.entity.entries.NetEntryMinecart;
-import net.minecraft.core.net.entity.entries.NetEntryPainting;
-import net.minecraft.core.net.entity.entries.NetEntryPebble;
-import net.minecraft.core.net.entity.entries.NetEntryPlayerServer;
-import net.minecraft.core.net.entity.entries.NetEntrySnowball;
-import net.minecraft.core.net.entity.entries.NetEntrySquid;
-import net.minecraft.core.net.entity.entries.NetEntryPrimedTNT;
+import net.minecraft.core.net.entity.entries.*;
 import net.minecraft.core.net.packet.Packet;
 import org.jetbrains.annotations.NotNull;
 
@@ -150,5 +134,7 @@ public class NetEntityHandler {
         registerNetworkEntry(new NetEntryPrimedTNT(), 21);
 
         registerNetworkEntry(new NetEntryBobber(), 30);
+
+        registerNetworkEntry(new NetEntryArrowFlaming(), 32);
     }
 }
diff --git a/game/core/src/main/java/net/minecraft/core/net/entity/entries/NetEntryArrowFlaming.java b/game/core/src/main/java/net/minecraft/core/net/entity/entries/NetEntryArrowFlaming.java
new file mode 100644
index 000000000..03f87625c
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/net/entity/entries/NetEntryArrowFlaming.java
@@ -0,0 +1,55 @@
+package net.minecraft.core.net.entity.entries;
+
+import com.mojang.nbt.tags.CompoundTag;
+import net.minecraft.core.entity.Entity;
+import net.minecraft.core.entity.Mob;
+import net.minecraft.core.entity.projectile.ProjectileArrowFlaming;
+import net.minecraft.core.net.entity.EntityTracker;
+import net.minecraft.core.net.entity.EntityTrackerEntry;
+import net.minecraft.core.net.entity.ITrackedEntry;
+import net.minecraft.core.net.entity.IVehicleEntry;
+import net.minecraft.core.net.packet.PacketAddEntity;
+import net.minecraft.core.world.World;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+public class NetEntryArrowFlaming implements IVehicleEntry<ProjectileArrowFlaming>, ITrackedEntry<ProjectileArrowFlaming> {
+
+	public NetEntryArrowFlaming() {
+	}
+
+	@Override
+	public @NotNull Class<ProjectileArrowFlaming> getAppliedClass() {
+		return ProjectileArrowFlaming.class;
+	}
+
+	@Override
+	public int getTrackingDistance() {
+		return 64;
+	}
+
+	@Override
+	public int getMovementPacketDelay() {
+		return 20;
+	}
+
+	@Override
+	public boolean sendMotionUpdates() {
+		return false;
+	}
+
+	@Override
+	public void onEntityTracked(EntityTracker tracker, EntityTrackerEntry trackerEntry, ProjectileArrowFlaming trackedObject) {
+	}
+
+	@Override
+	public Entity getEntity(World world, double x, double y, double z, int metadata, boolean hasVelocity, double xd, double yd, double zd, Entity owner, @Nullable CompoundTag tag) {
+		return new ProjectileArrowFlaming(world, x, y, z);
+	}
+
+	@Override
+	public PacketAddEntity getSpawnPacket(EntityTrackerEntry tracker, ProjectileArrowFlaming trackedObject) {
+		Mob entityliving = trackedObject.owner;
+		return new PacketAddEntity(trackedObject, 0, entityliving == null ? -1 : entityliving.id, trackedObject.xd, trackedObject.yd, trackedObject.zd);
+	}
+}
diff --git a/game/core/src/main/java/net/minecraft/core/player/inventory/CreativeBlocks.java b/game/core/src/main/java/net/minecraft/core/player/inventory/CreativeBlocks.java
new file mode 100644
index 000000000..3382db5d9
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/player/inventory/CreativeBlocks.java
@@ -0,0 +1,591 @@
+package net.minecraft.core.player.inventory;
+
+import net.minecraft.core.block.Block;
+import net.minecraft.core.block.Blocks;
+import net.minecraft.core.block.IPainted;
+import net.minecraft.core.item.ItemStack;
+import net.minecraft.core.util.helper.DyeColor;
+
+import java.util.List;
+
+public final class CreativeBlocks {
+
+	private static final DyeColor[] RAINBOW_ORDER = {
+		DyeColor.RED,
+		DyeColor.ORANGE,
+		DyeColor.YELLOW,
+		DyeColor.LIME,
+		DyeColor.GREEN,
+		DyeColor.CYAN,
+		DyeColor.LIGHT_BLUE,
+		DyeColor.BLUE,
+		DyeColor.PURPLE,
+		DyeColor.MAGENTA,
+		DyeColor.PINK,
+		DyeColor.BROWN,
+		DyeColor.WHITE,
+		DyeColor.SILVER,
+		DyeColor.GRAY,
+		DyeColor.BLACK
+	};
+
+	public static void populate(List<ItemStack> list) {
+
+		addStoneTypes(list);
+
+		addWoodTypes(list);
+
+		//WOOL
+		addPainted(list, Blocks.WOOL, painted(Blocks.WOOL));
+
+		addNaturalTypes(list);
+
+		addOrganicTypes(list);
+
+
+		//WORKSTATIONS
+		add(list, Blocks.WORKBENCH);
+
+		add(list, Blocks.FURNACE_STONE_IDLE);
+		add(list, Blocks.FURNACE_BLAST_IDLE);
+
+		add(list, Blocks.TROMMEL_IDLE);
+
+		add(list, Blocks.CHEST_PLANKS_OAK);
+		addPainted(list, Blocks.CHEST_PLANKS_OAK_PAINTED, painted(Blocks.CHEST_PLANKS_OAK_PAINTED));
+
+		add(list, Blocks.BOOKSHELF_PLANKS_OAK);
+		add(list, Blocks.LADDER_OAK);
+		add(list, Blocks.TORCH_COAL);
+
+		add(list, Blocks.GLASS);
+		add(list, Blocks.TRAPDOOR_GLASS);
+
+		add(list, Blocks.GLASS_TINTED);
+
+		add(list, Blocks.GLASS_STEEL);
+
+
+		addRedstoneTypes(list);
+
+		addOreTypes(list);
+
+		addStorageTypes(list);
+
+		add(list, Blocks.PAPER_WALL);
+		add(list, Blocks.FENCE_PAPER_WALL);
+
+		add(list, Blocks.FENCE_CHAINLINK);
+		add(list, Blocks.FENCE_STEEL);
+
+	}
+
+	public static void addStoneTypes(List<ItemStack> list) {
+		// STONE
+		add(list, Blocks.STONE);
+		add(list, Blocks.MOSS_STONE);
+
+		add(list, Blocks.COBBLE_STONE);
+		add(list, Blocks.STAIRS_COBBLE_STONE);
+		add(list, Blocks.SLAB_COBBLE_STONE);
+		add(list, Blocks.COBBLE_STONE_MOSSY);
+
+		add(list, Blocks.BRICK_STONE);
+		add(list, Blocks.STAIRS_BRICK_STONE);
+		add(list, Blocks.SLAB_BRICK_STONE);
+
+		add(list, Blocks.STONE_POLISHED);
+		add(list, Blocks.SLAB_STONE_POLISHED);
+		add(list, Blocks.STONE_CARVED);
+
+		add(list, Blocks.BRICK_STONE_POLISHED);
+		add(list, Blocks.STAIRS_BRICK_STONE_POLISHED);
+		add(list, Blocks.SLAB_BRICK_STONE_POLISHED);
+		add(list, Blocks.BRICK_STONE_POLISHED_MOSSY);
+
+
+		//BASALT
+		add(list, Blocks.BASALT);
+		add(list, Blocks.MOSS_BASALT);
+
+		add(list, Blocks.COBBLE_BASALT);
+		add(list, Blocks.STAIRS_COBBLE_BASALT);
+		add(list, Blocks.SLAB_COBBLE_BASALT);
+		add(list, Blocks.COBBLE_BASALT_MOSSY);
+
+		add(list, Blocks.BRICK_BASALT);
+		add(list, Blocks.STAIRS_BRICK_BASALT);
+		add(list, Blocks.SLAB_BRICK_BASALT);
+
+		add(list, Blocks.BASALT_POLISHED);
+		add(list, Blocks.SLAB_BASALT_POLISHED);
+		add(list, Blocks.BASALT_CARVED);
+
+
+		//LIMESTONE
+		add(list, Blocks.LIMESTONE);
+		add(list, Blocks.MOSS_LIMESTONE);
+
+		add(list, Blocks.COBBLE_LIMESTONE);
+		add(list, Blocks.STAIRS_COBBLE_LIMESTONE);
+		add(list, Blocks.SLAB_COBBLE_LIMESTONE);
+		add(list, Blocks.COBBLE_LIMESTONE_MOSSY);
+
+		add(list, Blocks.BRICK_LIMESTONE);
+		add(list, Blocks.STAIRS_BRICK_LIMESTONE);
+		add(list, Blocks.SLAB_BRICK_LIMESTONE);
+
+		add(list, Blocks.LIMESTONE_POLISHED);
+		add(list, Blocks.SLAB_LIMESTONE_POLISHED);
+		add(list, Blocks.LIMESTONE_CARVED);
+
+
+		//GRANITE
+		add(list, Blocks.GRANITE);
+		add(list, Blocks.MOSS_GRANITE);
+
+		add(list, Blocks.COBBLE_GRANITE);
+		add(list, Blocks.STAIRS_COBBLE_GRANITE);
+		add(list, Blocks.SLAB_COBBLE_GRANITE);
+		add(list, Blocks.COBBLE_GRANITE_MOSSY);
+
+		add(list, Blocks.BRICK_GRANITE);
+		add(list, Blocks.STAIRS_BRICK_GRANITE);
+		add(list, Blocks.SLAB_BRICK_GRANITE);
+
+		add(list, Blocks.GRANITE_POLISHED);
+		add(list, Blocks.SLAB_GRANITE_POLISHED);
+		add(list, Blocks.GRANITE_CARVED);
+
+
+		//PERMAFROST
+		add(list, Blocks.PERMAFROST);
+
+		add(list, Blocks.COBBLE_PERMAFROST);
+		add(list, Blocks.STAIRS_COBBLE_PERMAFROST);
+		add(list, Blocks.SLAB_COBBLE_PERMAFROST);
+
+		add(list, Blocks.BRICK_PERMAFROST);
+		add(list, Blocks.STAIRS_BRICK_PERMAFROST);
+		add(list, Blocks.SLAB_BRICK_PERMAFROST);
+
+		add(list, Blocks.PERMAFROST_POLISHED);
+		add(list, Blocks.SLAB_PERMAFROST_POLISHED);
+		add(list, Blocks.PERMAFROST_CARVED);
+
+
+		//NETHERRACK
+		add(list, Blocks.NETHERRACK);
+
+		add(list, Blocks.COBBLE_NETHERRACK);
+		add(list, Blocks.STAIRS_COBBLE_NETHERRACK);
+		add(list, Blocks.SLAB_COBBLE_NETHERRACK);
+		add(list, Blocks.COBBLE_NETHERRACK_CRYSTALLINE);
+
+		add(list, Blocks.BRICK_NETHERRACK);
+		add(list, Blocks.STAIRS_BRICK_NETHERRACK);
+		add(list, Blocks.SLAB_BRICK_NETHERRACK);
+
+		add(list, Blocks.NETHERRACK_POLISHED);
+		add(list, Blocks.SLAB_NETHERRACK_POLISHED);
+		add(list, Blocks.NETHERRACK_CARVED);
+
+
+		//GLOOMSTONE
+		add(list, Blocks.GLOOMSTONE);
+
+		add(list, Blocks.COBBLE_GLOOMSTONE);
+		add(list, Blocks.STAIRS_COBBLE_GLOOMSTONE);
+		add(list, Blocks.SLAB_COBBLE_GLOOMSTONE);
+
+		add(list, Blocks.BRICK_GLOOMSTONE);
+		add(list, Blocks.STAIRS_BRICK_GLOOMSTONE);
+		add(list, Blocks.SLAB_BRICK_GLOOMSTONE);
+
+		add(list, Blocks.GLOOMSTONE_POLISHED);
+		add(list, Blocks.SLAB_GLOOMSTONE_POLISHED);
+		add(list, Blocks.GLOOMSTONE_CARVED);
+
+
+		//MARBLE
+		add(list, Blocks.MARBLE);
+
+		add(list, Blocks.BRICK_MARBLE);
+		add(list, Blocks.STAIRS_BRICK_MARBLE);
+		add(list, Blocks.SLAB_BRICK_MARBLE);
+
+		add(list, Blocks.PILLAR_MARBLE);
+		add(list, Blocks.CAPSTONE_MARBLE);
+		add(list, Blocks.SLAB_CAPSTONE_MARBLE);
+
+
+		//SLATE
+		add(list, Blocks.SLATE);
+		add(list, Blocks.LAYER_SLATE);
+
+		add(list, Blocks.BRICK_SLATE);
+		add(list, Blocks.STAIRS_BRICK_SLATE);
+		add(list, Blocks.SLAB_BRICK_SLATE);
+
+		add(list, Blocks.SLATE_POLISHED);
+		add(list, Blocks.SLAB_SLATE_POLISHED);
+		add(list, Blocks.SLATE_CARVED);
+	}
+
+	public static void addWoodTypes(List<ItemStack> list) {
+		add(list, Blocks.PLANKS_OAK);
+		addPainted(list, Blocks.PLANKS_OAK_PAINTED, painted(Blocks.PLANKS_OAK_PAINTED));
+
+		add(list, Blocks.STAIRS_PLANKS_OAK);
+		addPainted(list, Blocks.STAIRS_PLANKS_PAINTED, painted(Blocks.STAIRS_PLANKS_PAINTED));
+		add(list, Blocks.SLAB_PLANKS_OAK);
+		addPainted(list, Blocks.SLAB_PLANKS_PAINTED, painted(Blocks.SLAB_PLANKS_PAINTED));
+
+		add(list, Blocks.FENCE_PLANKS_OAK);
+		addPainted(list, Blocks.FENCE_PLANKS_OAK_PAINTED, painted(Blocks.FENCE_PLANKS_OAK_PAINTED));
+
+		add(list, Blocks.FENCE_GATE_PLANKS_OAK);
+		addPainted(list, Blocks.FENCE_GATE_PLANKS_OAK_PAINTED, painted(Blocks.FENCE_GATE_PLANKS_OAK_PAINTED));
+
+		add(list, Blocks.TRAPDOOR_PLANKS_OAK);
+		addPainted(list, Blocks.TRAPDOOR_PLANKS_PAINTED, painted(Blocks.TRAPDOOR_PLANKS_PAINTED));
+	}
+
+	public static void addOrganicTypes(List<ItemStack> list) {
+		//LOGS
+		add(list, Blocks.LOG_OAK);
+		add(list, Blocks.LOG_OAK_MOSSY);
+		add(list, Blocks.LOG_PINE);
+		add(list, Blocks.LOG_BIRCH);
+		add(list, Blocks.LOG_CHERRY);
+		add(list, Blocks.LOG_EUCALYPTUS);
+		add(list, Blocks.LOG_THORN);
+		add(list, Blocks.LOG_PALM);
+		add(list, Blocks.LOG_SCORCHED);
+
+
+		//LEAVES
+		add(list, Blocks.LEAVES_OAK);
+		add(list, Blocks.LAYER_LEAVES_OAK);
+		add(list, Blocks.LEAVES_OAK_RETRO);
+		add(list, Blocks.LEAVES_PINE);
+		add(list, Blocks.LEAVES_BIRCH);
+		add(list, Blocks.LEAVES_CHERRY);
+		add(list, Blocks.LEAVES_CHERRY_FLOWERING);
+		add(list, Blocks.LEAVES_EUCALYPTUS);
+		add(list, Blocks.LEAVES_THORN);
+		add(list, Blocks.LEAVES_PALM);
+		add(list, Blocks.LEAVES_SHRUB);
+		add(list, Blocks.LEAVES_CACAO);
+
+
+		//SAPLINGS
+		add(list, Blocks.SAPLING_OAK);
+		add(list, Blocks.SAPLING_OAK_RETRO);
+		add(list, Blocks.SAPLING_PINE);
+		add(list, Blocks.SAPLING_BIRCH);
+		add(list, Blocks.SAPLING_CHERRY);
+		add(list, Blocks.SAPLING_EUCALYPTUS);
+		add(list, Blocks.SAPLING_THORN);
+		add(list, Blocks.SAPLING_PALM);
+		add(list, Blocks.SAPLING_SHRUB);
+		add(list, Blocks.SAPLING_CACAO);
+
+
+		//FLOWERS
+		list.add(new ItemStack(Blocks.FLOWER_YELLOW, 1, 0));
+//		list.add(new ItemStack(Blocks.FLOWER_YELLOW, 1, 32);
+//		list.add(new ItemStack(Blocks.FLOWER_YELLOW, 1, 64);
+//		list.add(new ItemStack(Blocks.FLOWER_YELLOW, 1, 96);
+
+		list.add(new ItemStack(Blocks.FLOWER_RED, 1, 0));
+//		list.add(new ItemStack(Blocks.FLOWER_RED, 1, 32);
+//		list.add(new ItemStack(Blocks.FLOWER_RED, 1, 64);
+//		list.add(new ItemStack(Blocks.FLOWER_RED, 1, 96);
+
+		list.add(new ItemStack(Blocks.FLOWER_PINK, 1, 0));
+//		list.add(new ItemStack(Blocks.FLOWER_PINK, 1, 32);
+//		list.add(new ItemStack(Blocks.FLOWER_PINK, 1, 64);
+//		list.add(new ItemStack(Blocks.FLOWER_PINK, 1, 96);
+
+		list.add(new ItemStack(Blocks.FLOWER_PURPLE, 1, 0));
+//		list.add(new ItemStack(Blocks.FLOWER_PURPLE, 1, 32);
+//		list.add(new ItemStack(Blocks.FLOWER_PURPLE, 1, 64);
+//		list.add(new ItemStack(Blocks.FLOWER_PURPLE, 1, 96);
+
+		list.add(new ItemStack(Blocks.FLOWER_LIGHT_BLUE, 1, 0));
+//		list.add(new ItemStack(Blocks.FLOWER_LIGHT_BLUE, 1, 32);
+//		list.add(new ItemStack(Blocks.FLOWER_LIGHT_BLUE, 1, 64);
+//		list.add(new ItemStack(Blocks.FLOWER_LIGHT_BLUE, 1, 96);
+
+		list.add(new ItemStack(Blocks.FLOWER_ORANGE, 1, 0));
+//		list.add(new ItemStack(Blocks.FLOWER_ORANGE, 1, 32);
+//		list.add(new ItemStack(Blocks.FLOWER_ORANGE, 1, 64);
+//		list.add(new ItemStack(Blocks.FLOWER_ORANGE, 1, 96);
+
+		add(list, Blocks.MUSHROOM_BROWN);
+		add(list, Blocks.MUSHROOM_RED);
+
+		//FOLIAGE
+		add(list, Blocks.TALLGRASS);
+		add(list, Blocks.TALLGRASS_FERN);
+		add(list, Blocks.DEADBUSH);
+		add(list, Blocks.SPINIFEX);
+		add(list, Blocks.ALGAE);
+		add(list, Blocks.CACTUS);
+
+		add(list, Blocks.PUMPKIN);
+		add(list, Blocks.PUMPKIN_CARVED_IDLE);
+		add(list, Blocks.PUMPKIN_CARVED_ACTIVE);
+
+		add(list, Blocks.BLOCK_SUGARCANE);
+		add(list, Blocks.BLOCK_SUGARCANE_BAKED);
+
+		add(list, Blocks.SPONGE_DRY);
+		add(list, Blocks.SPONGE_WET);
+
+		add(list, Blocks.PUMICE_DRY);
+		add(list, Blocks.PUMICE_WET);
+
+		add(list, Blocks.COBWEB);
+
+		add(list, Blocks.BONE_PILE);
+		add(list, Blocks.SOUL_CATCHER);
+		add(list, Blocks.BOULDER_MAGMATIC);
+		add(list, Blocks.BOULDER_SULFURIC);
+	}
+
+	public static void addNaturalTypes(List<ItemStack> list) {
+		add(list, Blocks.GRASS);
+		add(list, Blocks.GRASS_SCORCHED);
+		add(list, Blocks.GRASS_RETRO);
+
+		add(list, Blocks.DIRT);
+		add(list, Blocks.PATH_DIRT);
+		add(list, Blocks.FARMLAND_DIRT);
+
+		add(list, Blocks.DIRT_SCORCHED);
+		add(list, Blocks.DIRT_SCORCHED_RICH);
+
+		add(list, Blocks.MUD);
+		add(list, Blocks.MUD_BAKED);
+
+		add(list, Blocks.SAND);
+
+		add(list, Blocks.SANDSTONE);
+		add(list, Blocks.STAIRS_SANDSTONE);
+		add(list, Blocks.SLAB_SANDSTONE);
+
+		add(list, Blocks.BRICK_SANDSTONE);
+		add(list, Blocks.STAIRS_BRICK_SANDSTONE);
+		add(list, Blocks.SLAB_BRICK_SANDSTONE);
+
+		add(list, Blocks.GRAVEL);
+
+		add(list, Blocks.BRIMSAND);
+
+		add(list, Blocks.BRIMSTONE);
+		add(list, Blocks.BRIMTHAW);
+		add(list, Blocks.STAIRS_BRIMSTONE);
+		add(list, Blocks.SLAB_BRIMSTONE);
+
+		add(list, Blocks.BRICK_BRIMSTONE);
+		add(list, Blocks.STAIRS_BRICK_BRIMSTONE);
+		add(list, Blocks.SLAB_BRICK_BRIMSTONE);
+
+		add(list, Blocks.SULFUR);
+		add(list, Blocks.SULFUR_HARDENED);
+
+		add(list, Blocks.BLOCK_SNOW);
+		add(list, Blocks.LAYER_SNOW);
+
+		add(list, Blocks.BLOCK_ASH);
+		add(list, Blocks.LAYER_ASH);
+
+		add(list, Blocks.ICE);
+		add(list, Blocks.PERMAICE);
+
+		add(list, Blocks.BLOCK_CLAY);
+
+		add(list, Blocks.BRICK_CLAY);
+		add(list, Blocks.STAIRS_BRICK_CLAY);
+		add(list, Blocks.SLAB_BRICK_CLAY);
+
+		add(list, Blocks.OBSIDIAN);
+		add(list, Blocks.BONESHALE);
+		add(list, Blocks.BEDROCK);
+
+		add(list, Blocks.MAGMA);
+		add(list, Blocks.SOULSAND);
+		add(list, Blocks.SOULSCHIST);
+
+		add(list, Blocks.GLOWSTONE);
+
+		add(list, Blocks.THERMAL_VENT);
+
+		add(list, Blocks.EMBER);
+	}
+
+	public static void addRedstoneTypes(List<ItemStack> list) {
+		add(list, Blocks.TORCH_REDSTONE_ACTIVE);
+		add(list, Blocks.LEVER_COBBLE_STONE);
+
+		add(list, Blocks.BUTTON_STONE);
+		add(list, Blocks.BUTTON_PLANKS);
+		addPainted(list, Blocks.BUTTON_PLANKS_PAINTED, painted(Blocks.BUTTON_PLANKS_PAINTED));
+
+		add(list, Blocks.PRESSURE_PLATE_STONE);
+		add(list, Blocks.PRESSURE_PLATE_COBBLE_STONE);
+		add(list, Blocks.PRESSURE_PLATE_PLANKS_OAK);
+		addPainted(list, Blocks.PRESSURE_PLATE_PLANKS_OAK_PAINTED, painted(Blocks.PRESSURE_PLATE_PLANKS_OAK_PAINTED));
+
+		add(list, Blocks.PISTON_BASE);
+		add(list, Blocks.PISTON_BASE_STICKY);
+		add(list, Blocks.PISTON_BASE_STEEL);
+
+		add(list, Blocks.DISPENSER_COBBLE_STONE);
+		add(list, Blocks.MOTION_SENSOR_IDLE);
+		add(list, Blocks.ACTIVATOR);
+		add(list, Blocks.MATCHER);
+
+		add(list, Blocks.PUMPKIN_REDSTONE);
+
+		add(list, Blocks.RUBYGLASS_COLUMN);
+		add(list, Blocks.RUBYGLASS_NODE);
+		add(list, Blocks.BLOCK_RUBYGLASS);
+		add(list, Blocks.CONDUIT);
+		add(list, Blocks.RUBYGLASS_GROWTH);
+		add(list, Blocks.RUBYGLASS_CRYSTAL);
+
+		add(list, Blocks.BRAZIER_INACTIVE);
+
+		add(list, Blocks.MESH);
+		add(list, Blocks.MESH_GOLD);
+
+		add(list, Blocks.MOBSPAWNER);
+		add(list, Blocks.MOBSPAWNER_DEACTIVATED);
+
+		add(list, Blocks.NOTEBLOCK);
+		add(list, Blocks.JUKEBOX);
+
+		add(list, Blocks.RAIL);
+		add(list, Blocks.RAIL_POWERED);
+		add(list, Blocks.RAIL_DETECTOR);
+
+		add(list, Blocks.TNT);
+		add(list, Blocks.SPIKES);
+
+		add(list, Blocks.TRAPDOOR_IRON);
+		add(list, Blocks.TRAPDOOR_STEEL);
+
+		addPainted(list, Blocks.LAMP_IDLE, painted(Blocks.LAMP_IDLE));
+	}
+
+	public static void addOreTypes(List<ItemStack> list) {
+		add(list, Blocks.ORE_COAL_STONE);
+		add(list, Blocks.ORE_COAL_BASALT);
+		add(list, Blocks.ORE_COAL_LIMESTONE);
+		add(list, Blocks.ORE_COAL_GRANITE);
+		add(list, Blocks.ORE_COAL_PERMAFROST);
+
+		add(list, Blocks.ORE_IRON_STONE);
+		add(list, Blocks.ORE_IRON_BASALT);
+		add(list, Blocks.ORE_IRON_LIMESTONE);
+		add(list, Blocks.ORE_IRON_GRANITE);
+		add(list, Blocks.ORE_IRON_PERMAFROST);
+
+		add(list, Blocks.ORE_GOLD_STONE);
+		add(list, Blocks.ORE_GOLD_BASALT);
+		add(list, Blocks.ORE_GOLD_LIMESTONE);
+		add(list, Blocks.ORE_GOLD_GRANITE);
+		add(list, Blocks.ORE_GOLD_PERMAFROST);
+
+		add(list, Blocks.ORE_LAPIS_STONE);
+		add(list, Blocks.ORE_LAPIS_BASALT);
+		add(list, Blocks.ORE_LAPIS_LIMESTONE);
+		add(list, Blocks.ORE_LAPIS_GRANITE);
+		add(list, Blocks.ORE_LAPIS_PERMAFROST);
+
+		add(list, Blocks.ORE_REDSTONE_STONE);
+		add(list, Blocks.ORE_REDSTONE_BASALT);
+		add(list, Blocks.ORE_REDSTONE_LIMESTONE);
+		add(list, Blocks.ORE_REDSTONE_GRANITE);
+		add(list, Blocks.ORE_REDSTONE_PERMAFROST);
+
+		add(list, Blocks.ORE_DIAMOND_STONE);
+		add(list, Blocks.ORE_DIAMOND_BASALT);
+		add(list, Blocks.ORE_DIAMOND_LIMESTONE);
+		add(list, Blocks.ORE_DIAMOND_GRANITE);
+		add(list, Blocks.ORE_DIAMOND_PERMAFROST);
+
+		add(list, Blocks.ORE_NETHERCOAL_BASALT);
+		add(list, Blocks.ORE_NETHERCOAL_NETHERRACK);
+		add(list, Blocks.ORE_NETHERCOAL_GLOOMSTONE);
+	}
+
+	public static void addStorageTypes(List<ItemStack> list) {
+		add(list, Blocks.BLOCK_IRON);
+		add(list, Blocks.BLOCK_STEEL);
+		add(list, Blocks.BLOCK_GOLD);
+		add(list, Blocks.BLOCK_LAPIS);
+		add(list, Blocks.BLOCK_REDSTONE);
+		add(list, Blocks.BLOCK_DIAMOND);
+		add(list, Blocks.BLOCK_QUARTZ);
+
+		add(list, Blocks.BLOCK_COAL);
+		add(list, Blocks.BLOCK_CHARCOAL);
+		add(list, Blocks.BLOCK_NETHER_COAL);
+		add(list, Blocks.BLOCK_OLIVINE);
+
+		add(list, Blocks.BRICK_IRON);
+		add(list, Blocks.STAIRS_BRICK_IRON);
+		add(list, Blocks.SLAB_BRICK_IRON);
+
+		add(list, Blocks.BRICK_STEEL);
+		add(list, Blocks.STAIRS_BRICK_STEEL);
+		add(list, Blocks.SLAB_BRICK_STEEL);
+
+		add(list, Blocks.BRICK_GOLD);
+		add(list, Blocks.STAIRS_BRICK_GOLD);
+		add(list, Blocks.SLAB_BRICK_GOLD);
+
+		add(list, Blocks.BRICK_LAPIS);
+		add(list, Blocks.STAIRS_BRICK_LAPIS);
+		add(list, Blocks.SLAB_BRICK_LAPIS);
+
+		add(list, Blocks.BRICK_DIAMOND);
+		add(list, Blocks.STAIRS_BRICK_DIAMOND);
+		add(list, Blocks.SLAB_BRICK_DIAMOND);
+
+		add(list, Blocks.BRICK_QUARTZ);
+		add(list, Blocks.STAIRS_BRICK_QUARTZ);
+		add(list, Blocks.SLAB_BRICK_QUARTZ);
+
+		add(list, Blocks.BRICK_OLIVINE);
+		add(list, Blocks.STAIRS_BRICK_OLIVINE);
+		add(list, Blocks.SLAB_BRICK_OLIVINE);
+
+		add(list, Blocks.BRICK_RUBYGLASS);
+		add(list, Blocks.STAIRS_BRICK_RUBYGLASS);
+		add(list, Blocks.SLAB_BRICK_RUBYGLASS);
+
+	}
+
+	private static void addPainted(List<ItemStack> list, Block<?> block, IPainted painted) {
+		for (DyeColor color : RAINBOW_ORDER) {
+			list.add(new ItemStack(block, 1, painted.toMetadata(color)));
+		}
+	}
+
+	private static void add(List<ItemStack> list, Block<?>... blocks) {
+		for (Block<?> block : blocks) {
+			list.add(new ItemStack(block));
+		}
+	}
+
+	private static IPainted painted(Block<?> block) {
+		return (IPainted) block.getLogic();
+	}
+
+}
+
diff --git a/game/core/src/main/java/net/minecraft/core/player/inventory/CreativeItems.java b/game/core/src/main/java/net/minecraft/core/player/inventory/CreativeItems.java
new file mode 100644
index 000000000..959f19131
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/player/inventory/CreativeItems.java
@@ -0,0 +1,331 @@
+package net.minecraft.core.player.inventory;
+
+import net.minecraft.core.item.Item;
+import net.minecraft.core.item.ItemBucket;
+import net.minecraft.core.item.ItemStack;
+import net.minecraft.core.item.Items;
+import net.minecraft.core.util.collection.NamespaceID;
+import net.minecraft.core.util.helper.DyeColor;
+
+import java.util.List;
+
+public final class CreativeItems {
+
+	private static final DyeColor[] RAINBOW_ORDER = {
+		DyeColor.RED,
+		DyeColor.ORANGE,
+		DyeColor.YELLOW,
+		DyeColor.LIME,
+		DyeColor.GREEN,
+		DyeColor.CYAN,
+		DyeColor.LIGHT_BLUE,
+		DyeColor.BLUE,
+		DyeColor.PURPLE,
+		DyeColor.MAGENTA,
+		DyeColor.PINK,
+		DyeColor.BROWN,
+		DyeColor.WHITE,
+		DyeColor.SILVER,
+		DyeColor.GRAY,
+		DyeColor.BLACK
+	};
+
+	private static void addBucketVariants(List<ItemStack> out, Item item) {
+		if (item instanceof ItemBucket itemBucket) {
+			out.add(new ItemStack(item));
+			for (NamespaceID stateId : ItemBucket.getRegisteredStateIds()) {
+				if (!ItemBucket.STATE_EMPTY.equals(stateId)) {
+					ItemStack itemStack = new ItemStack(item, 1);
+					ItemBucket.setState(itemStack, stateId);
+					ItemBucket.setCharges(itemStack, itemBucket.maxCharges);
+					out.add(itemStack);
+				}
+			}
+		}
+	}
+
+	public static void populate(List<ItemStack> out) {
+
+		//TOOLS
+		out.add(new ItemStack(Items.TOOL_SHOVEL_WOOD));
+		out.add(new ItemStack(Items.TOOL_PICKAXE_WOOD));
+		out.add(new ItemStack(Items.TOOL_AXE_WOOD));
+		out.add(new ItemStack(Items.TOOL_HOE_WOOD));
+		out.add(new ItemStack(Items.TOOL_SWORD_WOOD));
+
+		out.add(new ItemStack(Items.TOOL_SHOVEL_STONE));
+		out.add(new ItemStack(Items.TOOL_PICKAXE_STONE));
+		out.add(new ItemStack(Items.TOOL_AXE_STONE));
+		out.add(new ItemStack(Items.TOOL_HOE_STONE));
+		out.add(new ItemStack(Items.TOOL_SWORD_STONE));
+
+		out.add(new ItemStack(Items.TOOL_SHOVEL_IRON));
+		out.add(new ItemStack(Items.TOOL_PICKAXE_IRON));
+		out.add(new ItemStack(Items.TOOL_AXE_IRON));
+		out.add(new ItemStack(Items.TOOL_HOE_IRON));
+		out.add(new ItemStack(Items.TOOL_SWORD_IRON));
+
+		out.add(new ItemStack(Items.TOOL_SHOVEL_GOLD));
+		out.add(new ItemStack(Items.TOOL_PICKAXE_GOLD));
+		out.add(new ItemStack(Items.TOOL_AXE_GOLD));
+		out.add(new ItemStack(Items.TOOL_HOE_GOLD));
+		out.add(new ItemStack(Items.TOOL_SWORD_GOLD));
+
+		out.add(new ItemStack(Items.TOOL_SHOVEL_DIAMOND));
+		out.add(new ItemStack(Items.TOOL_PICKAXE_DIAMOND));
+		out.add(new ItemStack(Items.TOOL_AXE_DIAMOND));
+		out.add(new ItemStack(Items.TOOL_HOE_DIAMOND));
+		out.add(new ItemStack(Items.TOOL_SWORD_DIAMOND));
+
+		out.add(new ItemStack(Items.TOOL_SHOVEL_STEEL));
+		out.add(new ItemStack(Items.TOOL_PICKAXE_STEEL));
+		out.add(new ItemStack(Items.TOOL_AXE_STEEL));
+		out.add(new ItemStack(Items.TOOL_HOE_STEEL));
+		out.add(new ItemStack(Items.TOOL_SWORD_STEEL));
+
+
+		//MISC TOOLS
+		out.add(new ItemStack(Items.TOOL_FIRESTRIKER_IRON));
+		out.add(new ItemStack(Items.TOOL_FIRESTRIKER_STEEL));
+
+		out.add(new ItemStack(Items.TOOL_SHEARS));
+		out.add(new ItemStack(Items.TOOL_SHEARS_STEEL));
+
+		addBucketVariants(out, Items.BUCKET_IRON);
+		addBucketVariants(out, Items.BUCKET_STEEL);
+
+		out.add(new ItemStack(Items.PAINTBRUSH));
+
+		out.add(new ItemStack(Items.TOOL_FISHINGROD));
+
+		out.add(new ItemStack(Items.TOOL_BOW));
+		out.add(new ItemStack(Items.AMMO_ARROW));
+		out.add(new ItemStack(Items.AMMO_ARROW_GOLD));
+		out.add(new ItemStack(Items.AMMO_ARROW_PURPLE));
+		out.add(new ItemStack(Items.AMMO_ARROW_FLAMING));
+
+		out.add(new ItemStack(Items.HANDCANNON_UNLOADED));
+		out.add(new ItemStack(Items.AMMO_CHARGE_EXPLOSIVE));
+
+		out.add(new ItemStack(Items.AMMO_FIREBALL));
+
+		out.add(new ItemStack(Items.TOOL_COMPASS));
+		out.add(new ItemStack(Items.TOOL_CLOCK));
+		out.add(new ItemStack(Items.TOOL_CALENDAR));
+		out.add(new ItemStack(Items.MAP));
+
+		out.add(new ItemStack(Items.ROPE));
+
+		out.add(new ItemStack(Items.LABEL));
+
+		out.add(new ItemStack(Items.WAND_MONSTER_SPAWNER));
+		out.add(new ItemStack(Items.WAND_NBT));
+
+
+		//ARMOR
+		out.add(new ItemStack(Items.ARMOR_HELMET_LEATHER));
+		out.add(new ItemStack(Items.ARMOR_CHESTPLATE_LEATHER));
+		out.add(new ItemStack(Items.ARMOR_LEGGINGS_LEATHER));
+		out.add(new ItemStack(Items.ARMOR_BOOTS_LEATHER));
+
+		out.add(new ItemStack(Items.ARMOR_HELMET_CHAINMAIL));
+		out.add(new ItemStack(Items.ARMOR_CHESTPLATE_CHAINMAIL));
+		out.add(new ItemStack(Items.ARMOR_LEGGINGS_CHAINMAIL));
+		out.add(new ItemStack(Items.ARMOR_BOOTS_CHAINMAIL));
+
+		out.add(new ItemStack(Items.ARMOR_HELMET_IRON));
+		out.add(new ItemStack(Items.ARMOR_CHESTPLATE_IRON));
+		out.add(new ItemStack(Items.ARMOR_LEGGINGS_IRON));
+		out.add(new ItemStack(Items.ARMOR_BOOTS_IRON));
+
+		out.add(new ItemStack(Items.ARMOR_HELMET_GOLD));
+		out.add(new ItemStack(Items.ARMOR_CHESTPLATE_GOLD));
+		out.add(new ItemStack(Items.ARMOR_LEGGINGS_GOLD));
+		out.add(new ItemStack(Items.ARMOR_BOOTS_GOLD));
+
+		out.add(new ItemStack(Items.ARMOR_HELMET_DIAMOND));
+		out.add(new ItemStack(Items.ARMOR_CHESTPLATE_DIAMOND));
+		out.add(new ItemStack(Items.ARMOR_LEGGINGS_DIAMOND));
+		out.add(new ItemStack(Items.ARMOR_BOOTS_DIAMOND));
+
+		out.add(new ItemStack(Items.ARMOR_HELMET_STEEL));
+		out.add(new ItemStack(Items.ARMOR_CHESTPLATE_STEEL));
+		out.add(new ItemStack(Items.ARMOR_LEGGINGS_STEEL));
+		out.add(new ItemStack(Items.ARMOR_BOOTS_STEEL));
+
+		out.add(new ItemStack(Items.ARMOR_QUIVER));
+		out.add(new ItemStack(Items.ARMOR_QUIVER_GOLD));
+		out.add(new ItemStack(Items.ARMOR_BOOTS_ICESKATES));
+
+		out.add(new ItemStack(Items.ARMOR_WOLF_LEATHER));
+		out.add(new ItemStack(Items.ARMOR_WOLF_CHAINMAIL));
+		out.add(new ItemStack(Items.ARMOR_WOLF_IRON));
+		out.add(new ItemStack(Items.ARMOR_WOLF_GOLD));
+		out.add(new ItemStack(Items.ARMOR_WOLF_DIAMOND));
+		out.add(new ItemStack(Items.ARMOR_WOLF_STEEL));
+
+
+		//FOOD
+		out.add(new ItemStack(Items.FOOD_APPLE));
+		out.add(new ItemStack(Items.FOOD_APPLE_GOLD));
+
+		out.add(new ItemStack(Items.FOOD_CHERRY));
+
+		out.add(new ItemStack(Items.FOOD_PORKCHOP_RAW));
+		out.add(new ItemStack(Items.FOOD_PORKCHOP_COOKED));
+
+		out.add(new ItemStack(Items.FOOD_VENISON_RAW));
+		out.add(new ItemStack(Items.FOOD_VENISON_COOKED));
+
+		out.add(new ItemStack(Items.FOOD_FISH_RAW));
+		out.add(new ItemStack(Items.FOOD_FISH_COOKED));
+
+		out.add(new ItemStack(Items.DUST_SUGAR));
+		out.add(new ItemStack(Items.EGG_CHICKEN));
+
+		out.add(new ItemStack(Items.SEEDS_WHEAT));
+		out.add(new ItemStack(Items.SEEDS_PUMPKIN));
+
+		out.add(new ItemStack(Items.WHEAT));
+		out.add(new ItemStack(Items.DOUGH));
+		out.add(new ItemStack(Items.FOOD_BREAD));
+
+		out.add(new ItemStack(Items.FOOD_COOKIE));
+		out.add(new ItemStack(Items.FOOD_CAKE));
+		out.add(new ItemStack(Items.FOOD_PUMPKIN_PIE));
+
+		out.add(new ItemStack(Items.BOWL));
+		out.add(new ItemStack(Items.FOOD_STEW_MUSHROOM));
+
+
+		//ORE
+		out.add(new ItemStack(Items.AMMO_PEBBLE));
+
+		out.add(new ItemStack(Items.COAL));
+		out.add(new ItemStack(Items.COAL, 1, 1));
+		out.add(new ItemStack(Items.NETHERCOAL));
+		out.add(new ItemStack(Items.OLIVINE));
+
+		out.add(new ItemStack(Items.ORE_RAW_IRON));
+		out.add(new ItemStack(Items.INGOT_IRON));
+
+		out.add(new ItemStack(Items.DIAMOND));
+
+		out.add(new ItemStack(Items.INGOT_STEEL_CRUDE));
+		out.add(new ItemStack(Items.INGOT_STEEL));
+
+		out.add(new ItemStack(Items.ORE_RAW_GOLD));
+		out.add(new ItemStack(Items.INGOT_GOLD));
+
+		out.add(new ItemStack(Items.DUST_REDSTONE));
+
+		out.add(new ItemStack(Items.REPEATER));
+
+		out.add(new ItemStack(Items.TIMER));
+
+		out.add(new ItemStack(Items.QUARTZ));
+
+		out.add(new ItemStack(Items.FLINT));
+		out.add(new ItemStack(Items.CLAY));
+		out.add(new ItemStack(Items.BRICK_CLAY));
+
+		out.add(new ItemStack(Items.DUST_GLOWSTONE));
+
+		out.add(new ItemStack(Items.RUBYGLASS));
+
+		out.add(new ItemStack(Items.SULFUR));
+
+
+		//Materials
+		out.add(new ItemStack(Items.STICK));
+		out.add(new ItemStack(Items.AMMO_SNOWBALL));
+		out.add(new ItemStack(Items.SUGARCANE));
+		out.add(new ItemStack(Items.PAPER));
+		out.add(new ItemStack(Items.BOOK));
+		for (DyeColor color : RAINBOW_ORDER) {
+			int meta = color.itemMeta;
+			out.add(new ItemStack(Items.DYE, 1, meta));
+		}
+
+		//Mob Drops
+		out.add(new ItemStack(Items.CLOTH));
+		out.add(new ItemStack(Items.STRING));
+		out.add(new ItemStack(Items.FEATHER_CHICKEN));
+		out.add(new ItemStack(Items.GUNPOWDER));
+		out.add(new ItemStack(Items.BONE));
+		out.add(new ItemStack(Items.CHAINLINK));
+		out.add(new ItemStack(Items.SLIMEBALL));
+		out.add(new ItemStack(Items.LEATHER));
+
+		//Placeables
+		out.add(new ItemStack(Items.PAINTING));
+
+		out.add(new ItemStack(Items.DOOR_OAK));
+		for (DyeColor color : RAINBOW_ORDER) {
+			int meta = color.itemMeta;
+			out.add(new ItemStack(Items.DOOR_OAK_PAINTED, 1, meta));
+		}
+
+		out.add(new ItemStack(Items.DOOR_GLASS));
+		out.add(new ItemStack(Items.DOOR_IRON));
+		out.add(new ItemStack(Items.DOOR_STEEL));
+
+		out.add(new ItemStack(Items.SIGN));
+		for (DyeColor color : RAINBOW_ORDER) {
+			int meta = color.itemMeta;
+			out.add(new ItemStack(Items.SIGN_PAINTED, 1, meta));
+		}
+
+		out.add(new ItemStack(Items.FLAG));
+		out.add(new ItemStack(Items.BED));
+		out.add(new ItemStack(Items.SEAT));
+
+		out.add(new ItemStack(Items.BASKET));
+
+		out.add(new ItemStack(Items.JAR));
+
+		out.add(new ItemStack(Items.LANTERN_FIREFLY_GREEN));
+		out.add(new ItemStack(Items.LANTERN_FIREFLY_BLUE));
+		out.add(new ItemStack(Items.LANTERN_FIREFLY_ORANGE));
+		out.add(new ItemStack(Items.LANTERN_FIREFLY_RED));
+
+		out.add(new ItemStack(Items.JAR_BUTTERFLY_BLUE));
+		out.add(new ItemStack(Items.JAR_BUTTERFLY_ORANGE));
+		out.add(new ItemStack(Items.JAR_BUTTERFLY_PINK));
+		out.add(new ItemStack(Items.JAR_BUTTERFLY_SILVER));
+
+		out.add(new ItemStack(Items.STATUE_STONE));
+		out.add(new ItemStack(Items.STATUE_BASALT));
+		out.add(new ItemStack(Items.STATUE_LIMESTONE));
+		out.add(new ItemStack(Items.STATUE_GRANITE));
+		out.add(new ItemStack(Items.STATUE_PERMAFROST));
+		out.add(new ItemStack(Items.STATUE_MARBLE));
+		out.add(new ItemStack(Items.STATUE_SLATE));
+		out.add(new ItemStack(Items.STATUE_NETHERRACK));
+		out.add(new ItemStack(Items.STATUE_GLOOMSTONE));
+		out.add(new ItemStack(Items.STATUE_PIGMAN));
+
+		out.add(new ItemStack(Items.SADDLE));
+
+		out.add(new ItemStack(Items.BOAT));
+
+		out.add(new ItemStack(Items.MINECART));
+		out.add(new ItemStack(Items.MINECART_CHEST));
+		out.add(new ItemStack(Items.MINECART_FURNACE));
+
+		// Records
+		out.add(new ItemStack(Items.RECORD_13));
+		out.add(new ItemStack(Items.RECORD_CAT));
+
+		out.add(new ItemStack(Items.RECORD_BLOCKS));
+		out.add(new ItemStack(Items.RECORD_CHIRP));
+		out.add(new ItemStack(Items.RECORD_FAR));
+		out.add(new ItemStack(Items.RECORD_MALL));
+		out.add(new ItemStack(Items.RECORD_MELLOHI));
+		out.add(new ItemStack(Items.RECORD_STAL));
+		out.add(new ItemStack(Items.RECORD_STRAD));
+		out.add(new ItemStack(Items.RECORD_WARD));
+		out.add(new ItemStack(Items.RECORD_WAIT));
+		out.add(new ItemStack(Items.RECORD_DOG));
+	}
+}
diff --git a/game/core/src/main/java/net/minecraft/core/player/inventory/menu/MenuInventoryCreative.java b/game/core/src/main/java/net/minecraft/core/player/inventory/menu/MenuInventoryCreative.java
index a721eea58..75b22c5cf 100644
--- a/game/core/src/main/java/net/minecraft/core/player/inventory/menu/MenuInventoryCreative.java
+++ b/game/core/src/main/java/net/minecraft/core/player/inventory/menu/MenuInventoryCreative.java
@@ -7,6 +7,8 @@ import net.minecraft.core.item.*;
 import net.minecraft.core.item.tag.ItemTags;
 import net.minecraft.core.lang.I18n;
 import net.minecraft.core.block.Blocks;
+import net.minecraft.core.player.inventory.CreativeBlocks;
+import net.minecraft.core.player.inventory.CreativeItems;
 import net.minecraft.core.player.inventory.container.ContainerInventory;
 import net.minecraft.core.player.inventory.slot.SlotCreative;
 import net.minecraft.core.util.collection.NamespaceID;
@@ -124,7 +126,13 @@ public class MenuInventoryCreative extends MenuInventory
 
 	static
 	{
-		for (Block<?> block : Blocks.blocksList) {
+		CreativeBlocks.populate(creativeItems);
+		CreativeItems.populate(creativeItems);
+	}
+
+	// PREVIOUS IMPLEMENTATION:
+
+		/*for (Block<?> block : Blocks.blocksList) {
 			if (block == null || block.hasTag(BlockTags.NOT_IN_CREATIVE_MENU)) continue;
 
 			if (Block.hasLogicClass(block, IPainted.class)) {
@@ -168,5 +176,5 @@ public class MenuInventoryCreative extends MenuInventory
 				creativeItems.add(new ItemStack(item));
 			}
 		}
-	}
+	}*/
 }
diff --git a/game/core/src/main/java/net/minecraft/core/sound/BlockSounds.java b/game/core/src/main/java/net/minecraft/core/sound/BlockSounds.java
index f7e38683b..a4938201a 100644
--- a/game/core/src/main/java/net/minecraft/core/sound/BlockSounds.java
+++ b/game/core/src/main/java/net/minecraft/core/sound/BlockSounds.java
@@ -7,11 +7,18 @@ public abstract class BlockSounds
     public static final BlockSound GRAVEL = new BlockSound("step.gravel", "step.gravel", 1.0F, 1.0F);
     public static final BlockSound GRASS = new BlockSound("step.grass", "step.grass", 1.0F, 1.0F);
     public static final BlockSound STONE = new BlockSound("step.stone", "step.stone", 1.0F, 1.0F);
-    public static final BlockSound PERMAFROST = new BlockSound("step.permafrost", "step.permafrost", 1.0F, 1.0F);
     public static final BlockSound METAL = new BlockSound("step.stone", "step.stone", 1.0F, 1.5F);
     public static final BlockSound GLASS = new BlockSound("step.stone", "random.glass", 1.0F, 1.0F);
     public static final BlockSound CLOTH = new BlockSound("step.cloth", "step.cloth", 1.0F, 1.0F);
     public static final BlockSound SAND = new BlockSound("step.sand", "step.sand", 1.0F, 1.0F);
     public static final BlockSound FIRE = new BlockSound("", "random.fizz", 0.5F, 2.6F);
 
+    public static final BlockSound PERMAFROST = new BlockSound("step.permafrost", "step.permafrost", 1.0F, 1.0F);
+    public static final BlockSound ASH = new BlockSound("block.ash", "block.ash", 1.0F, 1.0F);
+    public static final BlockSound SOULSAND = new BlockSound("step.soulsand", "step.soulsand", 1.0F, 1.0F);
+    public static final BlockSound GLOOMSTONE = new BlockSound("step.gloomstone", "step.gloomstone", 1.0F, 1.0F);
+    public static final BlockSound BRIMSTONE = new BlockSound("step.brimstone", "step.brimstone", 1.0F, 1.0F);
+    public static final BlockSound CRYSTAL = new BlockSound("step.crystal", "step.crystal", 1.0F, 1.0F);
+
+
 }
diff --git a/game/core/src/main/java/net/minecraft/core/util/helper/MathHelper.java b/game/core/src/main/java/net/minecraft/core/util/helper/MathHelper.java
index f706e07a4..7abca18e0 100644
--- a/game/core/src/main/java/net/minecraft/core/util/helper/MathHelper.java
+++ b/game/core/src/main/java/net/minecraft/core/util/helper/MathHelper.java
@@ -4,6 +4,7 @@ package net.minecraft.core.util.helper;
 import net.minecraft.core.util.phys.HitResult;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
+import org.joml.Matrix2d;
 import org.joml.Vector3d;
 import org.joml.Vector3dc;
 import org.joml.primitives.AABBd;
@@ -528,10 +529,48 @@ public class MathHelper {
         }
     }
 
+    public static @NotNull Matrix2d rotateVec2(double radians) {
+        return new Matrix2d(Math.cos(radians), -Math.sin(radians), Math.sin(radians), Math.cos(radians));
+    }
+
     static {
         SIN_TABLE = new float[0x10000];
         for (int i = 0; i < 0x10000; i++) {
             SIN_TABLE[i] = (float) Math.sin(((double) i * Math.PI * 2D) / 65536D);
         }
     }
+
+    /**
+     * Normalizes a degree angle to the range [-180, 180).
+     */
+    public static float wrapDegrees(float value) {
+        value %= 360.0F;
+
+        if (value >= 180.0F) {
+            value -= 360.0F;
+        }
+
+        if (value < -180.0F) {
+            value += 360.0F;
+        }
+
+        return value;
+    }
+
+    /**
+     * Double version of wrapDegrees.
+     */
+    public static double wrapDegrees(double value) {
+        value %= 360.0D;
+
+        if (value >= 180.0D) {
+            value -= 360.0D;
+        }
+
+        if (value < -180.0D) {
+            value += 360.0D;
+        }
+
+        return value;
+    }
 }
diff --git a/game/core/src/main/java/net/minecraft/core/world/LevelListener.java b/game/core/src/main/java/net/minecraft/core/world/LevelListener.java
index 9312da9e5..96a6e15e7 100644
--- a/game/core/src/main/java/net/minecraft/core/world/LevelListener.java
+++ b/game/core/src/main/java/net/minecraft/core/world/LevelListener.java
@@ -62,6 +62,10 @@ public interface LevelListener
      * Bit 7: Unused (Reserved) <div></div>
      */
     int EVENT_CONDUIT_SURFACE_PARTICLES = 1009;
+    /**
+     * data: unused
+     */
+    int EVENT_ACID_SPREAD = 1010;
 
 	/**
 	 * Redraw block in singleplayer and send block change packet in multiplayer. <br>
diff --git a/game/core/src/main/java/net/minecraft/core/world/World.java b/game/core/src/main/java/net/minecraft/core/world/World.java
index 5f83f8b14..c3968a005 100644
--- a/game/core/src/main/java/net/minecraft/core/world/World.java
+++ b/game/core/src/main/java/net/minecraft/core/world/World.java
@@ -1,6 +1,7 @@
 package net.minecraft.core.world;
 
 import com.mojang.logging.LogUtils;
+import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
 import it.unimi.dsi.fastutil.objects.ObjectArrayList;
 import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet;
 import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
@@ -47,6 +48,7 @@ import net.minecraft.core.world.chunk.provider.ChunkProvider;
 import net.minecraft.core.world.config.spawning.SpawnerConfig;
 import net.minecraft.core.world.lighting.LightingEngine;
 import net.minecraft.core.world.lighting.LightingEngineLegacy;
+import net.minecraft.core.world.particle.ParticleEmitterRegistry;
 import net.minecraft.core.world.pathfinder.Path;
 import net.minecraft.core.world.pathfinder.PathFinder;
 import net.minecraft.core.world.pos.*;
@@ -113,11 +115,14 @@ public abstract class World implements MutableWorldSource {
     public final @NotNull List<@NotNull AABBdc> collidingBoundingBoxes = new ObjectArrayList<>();
 
     private final @NotNull List<@NotNull LevelListener> listeners = new ObjectArrayList<>();
-    private final @NotNull Set<@NotNull ChunkPosc> positionsToUpdate = new ObjectOpenHashSet<>();
+    private final @NotNull LongOpenHashSet positionsToUpdate = new LongOpenHashSet();
     private final @NotNull List<@NotNull Entity> entityBuffer = new ObjectArrayList<>();
     private final @NotNull Set<@NotNull NextTickListEntry> immediatelyUpdatedPositions = new ObjectOpenHashSet<>();
 
+    public final ParticleEmitterRegistry particleEmitters = new ParticleEmitterRegistry();
+
     public final @NotNull Random rand = new Random();
+    private final AABBd workingBox = new AABBd();
     public final @NotNull Dimension dimension;
     public final boolean isNewWorld;
 
@@ -638,7 +643,7 @@ public abstract class World implements MutableWorldSource {
 
     public boolean setBlockTypeDataEntity(final @NotNull TilePosc tilePos, final @NotNull Block<?> block, final int data, final @NotNull TileEntity entity) {
         if (!tilePos.inBounds(this)) return false;
-        
+
         final var c = this.getChunk(tilePos);
         final var ctp = new ChunkTilePos(tilePos);
         final var prevBlock = c.getBlock(ctp);
@@ -742,6 +747,11 @@ public abstract class World implements MutableWorldSource {
     public boolean setBlockTypeNotify(final @NotNull TilePosc tilePos, final @NotNull Block<?> block) {
         if (this.setBlockType(tilePos, block)) {
             this.notifyBlockChange(tilePos, block);
+
+            TilePos pos = new TilePos(tilePos);
+            this.scheduleLightingUpdate(LightLayer.Block, pos);
+            this.scheduleLightingUpdate(LightLayer.Sky, pos);
+
             return true;
         }
 
@@ -765,6 +775,11 @@ public abstract class World implements MutableWorldSource {
     public boolean setBlockTypeDataNotify(final @NotNull TilePosc tilePos, final @NotNull Block<?> block, final int data) {
         if (this.setBlockTypeData(tilePos, block, data)) {
             this.notifyBlockChange(tilePos, block);
+
+            TilePos pos = new TilePos(tilePos);
+            this.scheduleLightingUpdate(LightLayer.Block, pos);
+            this.scheduleLightingUpdate(LightLayer.Sky, pos);
+
             return true;
         }
 
@@ -846,7 +861,7 @@ public abstract class World implements MutableWorldSource {
      * or the block at the given position if null.
      */
     public void notifyBlocksInRadiusOfNeighborChange(
-        final int radius, 
+        final int radius,
         final @NotNull TilePosc tilePos, final @Nullable Block<?> block
     ) {
         if (this.noNeighborUpdate || this.isClientSide) return;
@@ -1122,6 +1137,7 @@ public abstract class World implements MutableWorldSource {
                 return hitResult;
             }
         }
+        final Vector3d startCopy = new Vector3d(start2);
         for (int l1 = 200; l1-- >= 0; ) {
             if (!start2.isFinite()) {
                 return null;
@@ -1200,7 +1216,6 @@ public abstract class World implements MutableWorldSource {
                 start2.y += d7 * d5;
                 start2.z = d2;
             }
-            final Vector3d startCopy = new Vector3d(start2);
             blockStart.x = (int) (startCopy.x = MathHelper.floor(start2.x()));
             if (byte0 == 5) {
                 blockStart.x--;
@@ -1249,6 +1264,13 @@ public abstract class World implements MutableWorldSource {
         }
     }
 
+    public void playSoundEffectAtTile(@Nullable final Entity player, final SoundCategory category, TilePosc tilePosc, final String soundPath,
+                                      final float volume, final float pitch) {
+        for (final LevelListener listener : this.listeners) {
+            listener.playSound(player, soundPath, category, tilePosc.x(), tilePosc.y(), tilePosc.z(), volume, pitch);
+        }
+    }
+
     public void playBlockSoundEffect(@Nullable final Entity player, final double x, final double y, final double z, final @NotNull Block<?> block, final EnumBlockSoundEffectType soundType) {
         final BlockSound sound = block.getSound();
         if (sound == null) return;
@@ -1415,7 +1437,7 @@ public abstract class World implements MutableWorldSource {
         }
 
         final double radius = 0.25D;
-        final List<Entity> entities = this.getEntitiesWithinAABBExcludingEntity(entity, MathHelper.aabbGrow(aabb, radius, radius, radius, new AABBd()));
+        final List<Entity> entities = this.getEntitiesWithinAABBExcludingEntity(entity, MathHelper.aabbGrow(aabb, radius, radius, radius, workingBox));
         for (final Entity e : entities) {
             final AABBdc entityBB = e.getCollisionAABB();
             if (entityBB != null && entityBB.intersectsAABB((AABBd) aabb)) {
@@ -1454,7 +1476,7 @@ public abstract class World implements MutableWorldSource {
         }
 
         final double d = 0.25D;
-        final List<Entity> list = this.getEntitiesWithinAABBExcludingEntity(entity, MathHelper.aabbGrow(aabb, d, d, d, new AABBd()));
+        final List<Entity> list = this.getEntitiesWithinAABBExcludingEntity(entity, MathHelper.aabbGrow(aabb, d, d, d, workingBox));
         for (int j2 = 0; j2 < list.size(); j2++) {
             final AABBdc collisionBB = list.get(j2).getCollisionAABB();
             if (collisionBB != null && collisionBB.intersectsAABB((AABBd) aabb)) {
@@ -2199,8 +2221,8 @@ public abstract class World implements MutableWorldSource {
             final byte radius = 9;
             for (int x = -radius; x <= radius; x++) {
                 for (int z = -radius; z <= radius; z++) {
-                    this.positionsToUpdate.add(new ChunkPos(x + playerChunkX, z + playerChunkZ));
-                }
+                    long packedChunkPos = ((long) (x + playerChunkX) & 0xFFFFFFFFL) | (((long) (z + playerChunkZ) & 0xFFFFFFFFL) << 32);
+                    this.positionsToUpdate.add(packedChunkPos);}
             }
         }
 
@@ -2210,13 +2232,19 @@ public abstract class World implements MutableWorldSource {
         ChunkTilePos chunkTileQueryPos = new ChunkTilePos();
         TilePos tileQueryPos = new TilePos();
         final int randomTicks = this.getGameRuleValue(GameRules.RANDOM_TICK_SPEED);
-        for (final ChunkPosc chunkPos : this.positionsToUpdate) {
-            final int chunkBlockX = chunkPos.x() * Chunk.CHUNK_SIZE_X;
-            final int chunkBlockZ = chunkPos.z() * Chunk.CHUNK_SIZE_Z;
-            if (!this.isChunkLoaded(chunkPos)) {
+        it.unimi.dsi.fastutil.longs.LongIterator iterator = this.positionsToUpdate.iterator();
+        while (iterator.hasNext()) {
+            long packedPos = iterator.nextLong();
+            int cX = (int) packedPos;          // Unpack X
+            int cZ = (int) (packedPos >> 32);  // Unpack Z
+
+            final int chunkBlockX = cX * Chunk.CHUNK_SIZE_X;
+            final int chunkBlockZ = cZ * Chunk.CHUNK_SIZE_Z;
+
+            if (!this.isChunkLoaded(cX, cZ)) {
                 continue;
             }
-            final Chunk chunk = this.getChunk(chunkPos);
+            final Chunk chunk = this.getChunkFromChunkCoords(cX, cZ);
             this.updateLCG = this.updateLCG * 3 + 0x3c6ef35f;
             int randVal = this.updateLCG >> 2;
             int blockX = randVal & (Chunk.CHUNK_SIZE_X - 1);
@@ -2292,18 +2320,18 @@ public abstract class World implements MutableWorldSource {
 
     public void randomDisplayUpdates(final @NotNull TilePos tilePos) {
         final int radius = 16;
-        final @NotNull Random random = new Random();
-
+        final @NotNull Random random = this.rand;
         final TilePos randomPos = new TilePos();
         for (int i = 0; i < ANIMATION_TICKS_PER_TICK; i++) {
             tilePos.add(
-                this.rand.nextInt(radius) - this.rand.nextInt(radius),
-                this.rand.nextInt(radius) - this.rand.nextInt(radius),
-                this.rand.nextInt(radius) - this.rand.nextInt(radius),
-                randomPos);
+                random.nextInt(radius) - random.nextInt(radius),
+                random.nextInt(radius) - random.nextInt(radius),
+                random.nextInt(radius) - random.nextInt(radius),
+                randomPos
+            );
 
-            final @Nullable Block<?> block = this.getBlockType(randomPos);
-            if (block != null) {
+            if (this.isBlockLoaded(randomPos)) {
+                final @Nullable Block<?> block = this.getBlockType(randomPos);
                 block.animationTick(this, randomPos, random);
             }
         }
@@ -2539,12 +2567,12 @@ public abstract class World implements MutableWorldSource {
     }
 
     public boolean hasNeighborSignal(final @NotNull TilePosc tilePos) {
+        TilePos queryPos = new TilePos();
         for (final @NotNull Side side : Side.sides) {
-            if (this.hasSignal(tilePos.add(side.direction(), new TilePos()), side)) {
+            if (this.hasSignal(tilePos.add(side.direction(), queryPos), side)) {
                 return true;
             }
         }
-
         return false;
     }
 
@@ -2585,6 +2613,19 @@ public abstract class World implements MutableWorldSource {
         return entityplayer;
     }
 
+    public List<Player> getPlayersWithinRange(final double x, final double y, final double z, final double radius) {
+        List<Player> playersInRange = new ArrayList<>();
+        final double rSquared = radius * radius;
+
+        for (final Player player : this.players) {
+            if (player.distanceToSqr(x, y, z) <= rSquared) {
+                playersInRange.add(player);
+            }
+        }
+
+        return playersInRange;
+    }
+
 
     public Player getPlayerEntityByName(final String s) {
         for (final Player player : this.players) {
@@ -2968,4 +3009,8 @@ public abstract class World implements MutableWorldSource {
     public int renderDistance() {
         return 12;
     }
+
+    public int getParticleRenderDistance() {
+        return 1; //chunks
+    }
 }
diff --git a/game/core/src/main/java/net/minecraft/core/world/biome/Biome.java b/game/core/src/main/java/net/minecraft/core/world/biome/Biome.java
index 28ade554b..3464bea6f 100644
--- a/game/core/src/main/java/net/minecraft/core/world/biome/Biome.java
+++ b/game/core/src/main/java/net/minecraft/core/world/biome/Biome.java
@@ -50,6 +50,7 @@ public class Biome
     public float defaultHumidity = 0.5f;
     public float defaultVariety = 0.5f;
     private @NotNull SurfaceProperties surfaceProperties;
+    private @Nullable String registryKey;
 
     public Biome(final @NotNull String key) {
         this.translationKey = "biome." + key;
@@ -181,6 +182,14 @@ public class Biome
         }
     }
 
+    void bindRegistryKey(final @NotNull String key) {
+        this.registryKey = key;
+    }
+
+    public @Nullable String getRegistryKey() {
+        return this.registryKey;
+    }
+
     public boolean hasTag(final @NotNull Tag<Biome> tag) {
         return tag.appliesTo(this);
     }
diff --git a/game/core/src/main/java/net/minecraft/core/world/biome/Biomes.java b/game/core/src/main/java/net/minecraft/core/world/biome/Biomes.java
index 6553f0b42..3389965b3 100644
--- a/game/core/src/main/java/net/minecraft/core/world/biome/Biomes.java
+++ b/game/core/src/main/java/net/minecraft/core/world/biome/Biomes.java
@@ -216,7 +216,7 @@ public class Biomes {
             .withSurfaceProperties(
                 new SurfaceProperties.Builder()
                     .withTopBlock(Blocks.BRIMSAND)
-                    .withFillerBlock(Blocks.BRIMSAND)
+                    .withFillerBlock(Blocks.BRIMSTONE)
                     .build()
             )
     );
@@ -227,8 +227,8 @@ public class Biomes {
             .withBlockedWeathers(Weathers.OVERWORLD_RAIN, Weathers.OVERWORLD_SNOW, Weathers.OVERWORLD_STORM, Weathers.OVERWORLD_FOG)
             .withSurfaceProperties(
                 new SurfaceProperties.Builder()
-                    .withTopBlock(Blocks.BRIMSAND)
-                    .withFillerBlock(Blocks.BRIMSAND)
+                    .withTopBlock(Blocks.BRIMSTONE)
+                    .withFillerBlock(Blocks.BRIMSTONE)
                     .build()
             )
             .withTags(BiomeTags.HAS_SULFUR_POOLS)
@@ -298,6 +298,8 @@ public class Biomes {
             )
     );
 
+
+
     public static final @NotNull Biome DRIFT_DRIFT = register(
         "minecraft:drift.drift",
         new BiomeDrift("drift.drift")
@@ -379,6 +381,7 @@ public class Biomes {
     );
 
     public static @NotNull Biome register(final @NotNull String key, final @NotNull Biome biome) {
+        biome.bindRegistryKey(key);
         Registries.BIOMES.register(key, biome);
         return biome;
     }
diff --git a/game/core/src/main/java/net/minecraft/core/world/biome/provider/BiomeProviderNether.java b/game/core/src/main/java/net/minecraft/core/world/biome/provider/BiomeProviderNether.java
index c17d7b859..199476ffc 100644
--- a/game/core/src/main/java/net/minecraft/core/world/biome/provider/BiomeProviderNether.java
+++ b/game/core/src/main/java/net/minecraft/core/world/biome/provider/BiomeProviderNether.java
@@ -3,46 +3,44 @@ package net.minecraft.core.world.biome.provider;
 import net.minecraft.core.world.World;
 import net.minecraft.core.world.biome.Biome;
 import net.minecraft.core.world.biome.Biomes;
+import net.minecraft.core.world.biome.data.BiomeRange;
+import net.minecraft.core.world.biome.data.BiomeRangeMap;
 import net.minecraft.core.world.noise.FractalNoise2D;
 import net.minecraft.core.world.noise.FractalNoise3D;
 import net.minecraft.core.world.noise.SimplexNoise;
-import net.minecraft.core.world.type.WorldTypes;
 import org.jetbrains.annotations.NotNull;
 
 import java.util.Arrays;
+import java.util.Set;
+
+public final class BiomeProviderNether extends BiomeProvider {
+    public static final BiomeRangeMap brm = new BiomeRangeMap();
+    private static Biome[] biomes;
+
+    private static final double NETHER_BIOME_PATCH_SIZE_FRACTION = 0.75;
+    private static final double BIOME_STRETCH_FACTOR = 2.0 * NETHER_BIOME_PATCH_SIZE_FRACTION;
+
+    private static final double TEMP_SCALE = 0.025 / BIOME_STRETCH_FACTOR;
+    private static final double DOWNFALL_SCALE = 0.05 / BIOME_STRETCH_FACTOR;
+    private static final double NOISE_SCALE = 0.25F / BIOME_STRETCH_FACTOR;
 
-public final class BiomeProviderNether
-    extends BiomeProvider
-{
-    public static final Biome[] BIOMES_ORDER = {
-            Biomes.NETHER_OLD_WORLD,
-            Biomes.NETHER_CRYSTAL_PLAINS,
-            Biomes.NETHER_CRYSTAL_FOREST,
-            Biomes.NETHER_SHELF,
-            Biomes.NETHER_CRAG,
-            Biomes.NETHER_SULFUR_POOLS,
-            Biomes.NETHER_VOLCANIC_ISLANDS,
-    };
-
-    private static final double TEMP_SCALE = 0.025;
-    private static final double DOWNFALL_SCALE = 0.05;
-    private static final double NOISE_SCALE = 0.25;
     private final FractalNoise2D<?> temperatureNoise;
     private final FractalNoise2D<?> downfallNoise;
     private final FractalNoise2D<?> fuzzinessNoise;
 
-    public BiomeProviderNether(final @NotNull World world)
-    {
-        super(world);
+    static {
+        init();
+    }
 
-        final long seed = world.getRandomSeed();
-        this.temperatureNoise = new FractalNoise3D<>(SimplexNoise.genOctaves(seed * 9871, 4));
-        this.downfallNoise = new FractalNoise3D<>(SimplexNoise.genOctaves(seed * 39811, 4));
-        this.fuzzinessNoise = new FractalNoise2D<>(SimplexNoise.genOctaves(seed * 543321, 4));
+    public BiomeProviderNether(@NotNull World world) {
+        super(world);
+        long seed = world.getRandomSeed();
+        this.temperatureNoise = new FractalNoise3D<>(SimplexNoise.genOctaves(seed * 9871L, 4));
+        this.downfallNoise = new FractalNoise3D<>(SimplexNoise.genOctaves(seed * 39811L, 4));
+        this.fuzzinessNoise = new FractalNoise2D<>(SimplexNoise.genOctaves(seed * 543321L, 4));
     }
 
-    @Override
-    public Biome[] getBiomes(Biome[] biomes, double[] temperatures, double[] humidities, double[] varieties, final int x, final int y, final int z, final int xSize, final int ySize, final int zSize) {
+    public Biome[] getBiomes(Biome[] biomes, double[] temperatures, double[] humidities, double[] varieties, int x, int y, int z, int xSize, int ySize, int zSize) {
         if (biomes == null || biomes.length < xSize * ySize * zSize) {
             biomes = new Biome[xSize * ySize * zSize];
         }
@@ -93,102 +91,131 @@ public final class BiomeProviderNether
 
                 temperatures[xx * zSize + zz] = temperature;
                 humidities[xx * zSize + zz] = downfall;
+                double variety = varieties[xx * zSize + zz];
 
-                for (int yy = 0; yy < ySize; yy++) {
-                    biomes[yy * xSize * zSize + zz * xSize + xx] = this.lookupBiome(temperature, downfall, 0.0, 0.0);
+                for (int yy = 0; yy < ySize; ++yy) {
+                    double actualY = (double) y + yy;
+                    double altitude = actualY / 128.0;
+                    altitude = Math.max(0.0, Math.min(1.0, altitude));
+
+                    biomes[yy * xSize * zSize + zz * xSize + xx] = this.lookupBiome(temperature, downfall, altitude, variety);
                 }
             }
         }
-
         return biomes;
     }
 
-    @Override
-    public double[] getTemperatures(double[] temperatures, final int x, final int z, final int xSize, final int zSize) {
+    public double[] getTemperatures(double[] temperatures, int x, int z, int xSize, int zSize) {
         if (temperatures == null || temperatures.length < xSize * zSize) {
             temperatures = new double[xSize * zSize];
         }
-        this.temperatureNoise.setLacunarity(0.25).getRegion(temperatures, x, z, xSize, xSize, TEMP_SCALE, TEMP_SCALE);
 
-        final double @NotNull [] noises = this.fuzzinessNoise.setLacunarity(10.0 / 17.0).getRegion(null, x, z, xSize, zSize, NOISE_SCALE, NOISE_SCALE);
+        this.temperatureNoise.setLacunarity(0.25F).getRegion(temperatures, x, z, xSize, xSize, TEMP_SCALE, TEMP_SCALE);
+        double[] noises = this.fuzzinessNoise.setLacunarity(0.5882352941176471).getRegion(null, x, z, xSize, zSize, NOISE_SCALE, NOISE_SCALE);
 
         int i = 0;
-        for (int xx = 0; xx < xSize; xx++) {
-            for (int zz = 0; zz < zSize; zz++) {
-                final double a = noises[i] * 1.1 + 0.5;
-                final double b = 0.01;
-                final double c = 1.0 - b;
+        for (int xx = 0; xx < xSize; ++xx) {
+            for (int zz = 0; zz < zSize; ++zz) {
+                double a = noises[i] * 1.1 + 0.5;
+                double b = 0.01;
+                double c = 0.99;
                 double temperature = (temperatures[i] * 0.15 + 0.7) * c + a * b;
                 temperature = 1.0 - (1.0 - temperature) * (1.0 - temperature);
-
-                if (temperature < 0.0) {
-                    temperature = 0.0;
-                }
-                if (temperature > 1.0) {
-                    temperature = 1.0;
-                }
-
-                temperatures[i++] = temperature;
+                temperatures[i++] = Math.max(0.0, Math.min(1.0, temperature));
             }
         }
-
         return temperatures;
     }
 
-    @Override
-    public double[] getHumidities(double[] humidities, final int x, final int z, final int xSize, final int zSize) {
+    public double[] getHumidities(double[] humidities, int x, int z, int xSize, int zSize) {
         if (humidities == null || humidities.length < xSize * zSize) {
             humidities = new double[xSize * zSize];
         }
-        return this.downfallNoise.setLacunarity(0.5).getRegion(humidities, x, z, xSize, zSize, DOWNFALL_SCALE, DOWNFALL_SCALE);
+        return this.downfallNoise.setLacunarity(0.5F).getRegion(humidities, x, z, xSize, zSize, DOWNFALL_SCALE, DOWNFALL_SCALE);
     }
 
-    @Override
-    public double[] getVarieties(double[] varieties, final int x, final int z, final int xSize, final int zSize) {
+    public double[] getVarieties(double[] varieties, int x, int z, int xSize, int zSize) {
         if (varieties == null || varieties.length < xSize * zSize) {
             varieties = new double[xSize * zSize];
         }
-        Arrays.fill(varieties, 0.0);
 
+        Arrays.fill(varieties, 0.0F);
         return varieties;
     }
 
-    @Override
     public double[] getBiomenesses(double[] biomenesses, int x, int y, int z, int xSize, int ySize, int zSize) {
-        if (biomenesses == null || biomenesses.length < xSize * ySize * zSize)
-        {
+        if (biomenesses == null || biomenesses.length < xSize * ySize * zSize) {
             biomenesses = new double[xSize * ySize * zSize];
         }
 
-        Arrays.fill(biomenesses, 1.0f);
-
+        Arrays.fill(biomenesses, 1.0F);
         return biomenesses;
     }
 
-    @Override
     public Biome lookupBiome(double temperature, double humidity, double altitude, double variety) {
+        return brm.lookupBiome(temperature, humidity, altitude, variety);
+    }
 
-        if (temperature <= 0.2) {
-            return Biomes.NETHER_OLD_WORLD;
-        }
-        if (temperature < 0.65) {
-            return humidity < 0.1 ? Biomes.NETHER_CRYSTAL_PLAINS : Biomes.NETHER_CRYSTAL_FOREST;
-        }
-
-        if (temperature <= 0.75) {
-            return Biomes.NETHER_SHELF;
-        }
-
-        if(humidity > 0.85) {
-            return Biomes.NETHER_SULFUR_POOLS;
-        }
-        if(temperature >= 0.95 && humidity < 0.1) {
-            return Biomes.NETHER_VOLCANIC_ISLANDS;
-        }
-        return Biomes.NETHER_CRAG;
+    public static void init() {
+        brm.clear();
+
+        // Hot Biomes
+        brm.addRange(Biomes.NETHER_VOLCANIC_ISLANDS, new BiomeRange(
+                0.95, 1.0,
+                0.0, 1.0,
+                0.5, 1.0,
+                0.0, 1.0));
+        brm.addRange(Biomes.NETHER_CRAG, new BiomeRange(
+                0.75, 0.95,
+                0.0, 0.85,
+                0.5, 1.0,
+                0.0, 1.0));
+        brm.addRange(Biomes.NETHER_SULFUR_POOLS, new BiomeRange(
+                0.75, 0.95,
+                0.85, 1.0,
+                0.5, 1.0,
+                0.0, 1.0));
+
+        // Mid/Warm Biome
+        brm.addRange(Biomes.NETHER_SHELF, new BiomeRange(
+                0.70, 0.75,
+                0.0, 1.0,
+                0.5, 1.0,
+                0.0, 1.0));
+
+        // Mid/Cool Biomes
+        brm.addRange(Biomes.NETHER_CRYSTAL_FOREST, new BiomeRange(
+                0.45, 0.70,
+                0.1, 1.0,
+                0.5, 1.0,
+                0.0, 1.0));
+        brm.addRange(Biomes.NETHER_CRYSTAL_PLAINS, new BiomeRange(
+                0.45, 0.70,
+                0.0, 0.1,
+                0.5, 1.0,
+                0.0, 1.0));
+
+        // Coldest Biome
+        brm.addRange(Biomes.NETHER_OLD_WORLD, new BiomeRange(
+                0.0, 0.45,
+                0.0, 1.0,
+                0.50, 1.0,
+                0.0, 1.0));
+
+        // Underground Desert
+        brm.addRange(Biomes.NETHER_OLD_WORLD_DESERT, new BiomeRange(
+                0.0, 1.0,
+                0.0, 1.0,
+                0.0, 0.5,
+                0.0, 1.0));
+
+        brm.lock();
+
+        Set<Biome> allBiomes = brm.allBiomes();
+        biomes = allBiomes.toArray(new Biome[0]);
     }
 
     public static Biome[] allBiomes() {
-        return BIOMES_ORDER;
+        return biomes;
     }
-}
+}
\ No newline at end of file
diff --git a/game/core/src/main/java/net/minecraft/core/world/chunk/Chunk.java b/game/core/src/main/java/net/minecraft/core/world/chunk/Chunk.java
index 467d406c1..8f2d70dfe 100644
--- a/game/core/src/main/java/net/minecraft/core/world/chunk/Chunk.java
+++ b/game/core/src/main/java/net/minecraft/core/world/chunk/Chunk.java
@@ -569,6 +569,7 @@ public class Chunk {
     }
 
     public void removeEntityAtIndex(@NotNull Entity entity, int index) {
+        this.isModified = true;
         index = MathHelper.clamp(index, 0, CHUNK_SECTIONS - 1);
         getSection(index).removeEntity(entity);
     }
diff --git a/game/core/src/main/java/net/minecraft/core/world/generate/RubyglassFloeLargeFeature.java b/game/core/src/main/java/net/minecraft/core/world/generate/RubyglassFloeLargeFeature.java
new file mode 100644
index 000000000..39cb00389
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/world/generate/RubyglassFloeLargeFeature.java
@@ -0,0 +1,85 @@
+package net.minecraft.core.world.generate;
+
+import net.minecraft.core.block.Blocks;
+import net.minecraft.core.util.helper.MathHelper;
+import net.minecraft.core.world.World;
+import net.minecraft.core.world.biome.Biome;
+import net.minecraft.core.world.biome.Biomes;
+import net.minecraft.core.world.generate.chunk.ChunkGeneratorResult;
+import net.minecraft.core.world.noise.FractalNoise2D;
+import net.minecraft.core.world.noise.ImprovedPerlinNoise;
+import net.minecraft.core.world.noise.Noise2D;
+import net.minecraft.core.world.noise.WorleyNoise;
+import net.minecraft.core.world.noise.operator.Normalize2D;
+import net.minecraft.core.world.pos.TilePos;
+import net.minecraft.core.world.type.WorldType;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.Random;
+
+public class RubyglassFloeLargeFeature extends LargeFeature {
+	private @Nullable WorleyNoise chunkNoise = null;
+	private @Nullable Noise2D gapNoise = null;
+	private @Nullable Noise2D scaleNoise = null;
+	private @Nullable World lastWorld = null;
+
+	public RubyglassFloeLargeFeature() {
+	}
+
+	protected void doGeneration(@NotNull World world, @NotNull Random random, int chunkX, int chunkZ, int baseChunkX, int baseChunkZ, @NotNull ChunkGeneratorResult result) {
+		if (this.chunkNoise == null || this.gapNoise == null || this.scaleNoise == null || this.lastWorld != world) {
+			this.chunkNoise = new WorleyNoise(new Random(world.getRandomSeed()));
+			this.gapNoise = new FractalNoise2D<>(ImprovedPerlinNoise.genOctaves(world.getRandomSeed(), 4));
+			this.scaleNoise = new Normalize2D<>(new FractalNoise2D<>(ImprovedPerlinNoise.genOctaves(world.getRandomSeed(), 2)));
+			this.lastWorld = world;
+		}
+
+		WorldType worldType = world.getWorldType();
+		int oceanY = worldType.getMinY(world) + worldType.getOceanY();
+		TilePos tilePos = new TilePos();
+		WorleyNoise.Result worleyResult = new WorleyNoise.Result();
+
+		for (int x = 0; x < 16; ++x) {
+			for (int z = 0; z < 16; ++z) {
+				tilePos.set(16 * baseChunkX + x, oceanY, 16 * baseChunkZ + z);
+				Biome biome = world.getBlockBiome(tilePos);
+				if (biome == Biomes.NETHER_CRYSTAL_FOREST || biome == Biomes.NETHER_CRYSTAL_PLAINS) {
+
+					double sampleX = (double) baseChunkX + (double) x / 16.0;
+					double sampleZ = (double) baseChunkZ + (double) z / 16.0;
+					this.chunkNoise.getValue(sampleX, sampleZ, worleyResult);
+
+					double lineScale = 15.0;
+					double lineWidth = this.gapNoise.getValue((double) (baseChunkX * 16 + x) / lineScale, (double) (baseChunkZ * 16 + z) / lineScale) / 2.0 + 0.5;
+					double edgeThreshold = 0.8 + lineWidth * 0.05;
+
+					if (MathHelper.unsignedMod(worleyResult.index, 15.0F) != 0.0 && worleyResult.distance <= edgeThreshold) {
+
+						double thicknessVariation = this.scaleNoise.getValue((double) x / 10.0, (double) z / 10.0);
+						int depth = (int) (thicknessVariation * 6.0);
+						for (int y = oceanY - (depth + 2); y < oceanY; ++y) {
+							this.trySetBlock(result, x, y, z, Blocks.COBBLE_NETHERRACK_CRYSTALLINE.id());
+						}
+
+						for (int y = oceanY; y < oceanY + depth; ++y) {
+							this.trySetBlock(result, x, y, z, Blocks.COBBLE_NETHERRACK.id());
+						}
+					}
+				}
+			}
+		}
+	}
+
+	private void trySetBlock(@NotNull ChunkGeneratorResult result, int x, int y, int z, int id) {
+		int current = result.getBlock(x, y, z);
+		if (current == Blocks.RUBYGLASS_CRYSTAL.id() || current == Blocks.FLUID_WATER_STILL.id() || current == Blocks.AIR.id()) {
+			result.setBlock(x, y, z, id);
+		}
+	}
+
+	@Override
+	public int getRadiusChunk() {
+		return 0;
+	}
+}
diff --git a/game/core/src/main/java/net/minecraft/core/world/generate/chunk/ChunkDecorationBuilder.java b/game/core/src/main/java/net/minecraft/core/world/generate/chunk/ChunkDecorationBuilder.java
index ad4d89526..7259cfe1b 100644
--- a/game/core/src/main/java/net/minecraft/core/world/generate/chunk/ChunkDecorationBuilder.java
+++ b/game/core/src/main/java/net/minecraft/core/world/generate/chunk/ChunkDecorationBuilder.java
@@ -6,8 +6,8 @@ import net.minecraft.core.world.chunk.Chunk;
 import net.minecraft.core.world.generate.feature.WorldFeatureInterface;
 import net.minecraft.core.world.pos.TilePos;
 import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 
-import java.util.Arrays;
 import java.util.Random;
 
 public class ChunkDecorationBuilder implements ChunkDecoration
@@ -16,6 +16,13 @@ public class ChunkDecorationBuilder implements ChunkDecoration
     private @NotNull PlacementMethod placementMethod = PlacementMethods.OncePerChunk;
     private @NotNull Biome[] biomeMask = new Biome[]{};
     private @NotNull PositionSelector positionSelector = PositionSelectors.SurfaceBlock;
+
+    private final TilePos scratchPos = new TilePos();
+    private @Nullable PlaceableFeature cachedPlaceable;
+    private int cachedMinY;
+    private int cachedMaxY;
+    private int cachedRangeY;
+
     public ChunkDecorationBuilder(final @NotNull WorldFeatureInterface worldFeature){
         this.worldFeature = worldFeature;
     }
@@ -38,13 +45,34 @@ public class ChunkDecorationBuilder implements ChunkDecoration
 
     @Override
     public void placeDecoration(@NotNull World world, @NotNull Chunk chunk, int worldX, int worldZ, int minY, int maxY, int rangeY, @NotNull Random rand) {
+        if (this.biomeMask.length != 0) {
+            int checkY = minY + (maxY - minY) / 2;
+            checkY = Math.max(world.getWorldType().getMinY(world), Math.min(world.getWorldType().getMaxY(world) - 1, checkY));
 
-        if(biomeMask.length > 0){
-            if(!Arrays.asList(biomeMask).contains(world.getBlockBiome(new TilePos(worldX, minY, worldZ)))){
+            this.scratchPos.set(worldX, checkY, worldZ);
+            final Biome here = world.getBlockBiome(this.scratchPos);
+            if (!this.biomeMaskContains(here)) {
                 return;
             }
         }
 
-        placementMethod.placeFeature(new PlaceableFeature(this.worldFeature, this.positionSelector, minY, maxY, rangeY), world, chunk, rand);
+        if (this.cachedPlaceable == null || minY != this.cachedMinY || maxY != this.cachedMaxY || rangeY != this.cachedRangeY) {
+            this.cachedPlaceable = new PlaceableFeature(this.worldFeature, this.positionSelector, minY, maxY, rangeY);
+            this.cachedMinY = minY;
+            this.cachedMaxY = maxY;
+            this.cachedRangeY = rangeY;
+        }
+
+        this.placementMethod.placeFeature(this.cachedPlaceable, world, chunk, rand);
+    }
+
+    private boolean biomeMaskContains(@NotNull Biome biome) {
+        final Biome[] mask = this.biomeMask;
+        for (int i = 0, n = mask.length; i < n; i++) {
+            if (mask[i] == biome) {
+                return true;
+            }
+        }
+        return false;
     }
 }
diff --git a/game/core/src/main/java/net/minecraft/core/world/generate/chunk/ChunkFeatureDecorator.java b/game/core/src/main/java/net/minecraft/core/world/generate/chunk/ChunkFeatureDecorator.java
index ba47c0510..ba54249c7 100644
--- a/game/core/src/main/java/net/minecraft/core/world/generate/chunk/ChunkFeatureDecorator.java
+++ b/game/core/src/main/java/net/minecraft/core/world/generate/chunk/ChunkFeatureDecorator.java
@@ -1,21 +1,36 @@
 package net.minecraft.core.world.generate.chunk;
 
+import com.mojang.logging.LogUtils;
+import it.unimi.dsi.fastutil.objects.Object2IntLinkedOpenHashMap;
 import it.unimi.dsi.fastutil.objects.Object2IntMap;
-import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
 import net.minecraft.core.block.BlockLogicFallingBlock;
 import net.minecraft.core.util.HardIllegalArgumentException;
 import net.minecraft.core.util.collection.NamespaceID;
 import net.minecraft.core.world.World;
 import net.minecraft.core.world.chunk.Chunk;
 import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.slf4j.Logger;
 
 import java.util.Random;
 
 public abstract class ChunkFeatureDecorator
-    implements ChunkDecorator {
+        implements ChunkDecorator {
+    private static final @NotNull Logger LOGGER = LogUtils.getLogger();
+
     private boolean initialized = false;
     private final @NotNull World world;
-    private final @NotNull Object2IntMap<ChunkDecoration> decorations = new Object2IntOpenHashMap<>();
+
+    private final @NotNull Object2IntMap<ChunkDecoration> decorations = new Object2IntLinkedOpenHashMap<>();
+
+    private ChunkDecoration @Nullable [] decorationArray;
+    private int @Nullable [] decorationSeedArray;
+
+    private boolean seedConstantsInitialized = false;
+    private long seedMultiplierA;
+    private long seedMultiplierB;
+
+    private final @NotNull Random sharedRand = new Random();
 
     public ChunkFeatureDecorator(final @NotNull World world) {
         this.world = world;
@@ -41,39 +56,82 @@ public abstract class ChunkFeatureDecorator
 
     public final @NotNull ChunkFeatureDecorator register(final @NotNull NamespaceID id, final @NotNull ChunkDecoration decoration) {
         this.decorations.put(decoration, id.toString().hashCode());
-
+        this.decorationArray = null;
+        this.decorationSeedArray = null;
         return this;
     }
 
+    private void bakeDecorations() {
+        final int n = this.decorations.size();
+        final ChunkDecoration[] decoArr = new ChunkDecoration[n];
+        final int[] seedArr = new int[n];
+
+        int i = 0;
+        for (final Object2IntMap.Entry<ChunkDecoration> entry : this.decorations.object2IntEntrySet()) {
+            decoArr[i] = entry.getKey();
+            seedArr[i] = entry.getIntValue();
+            i++;
+        }
+
+        this.decorationArray = decoArr;
+        this.decorationSeedArray = seedArr;
+    }
+
     @Override
     public final void decorate(final @NotNull Chunk chunk) {
         this.world.scheduledUpdatesAreImmediate = true;
         BlockLogicFallingBlock.fallInstantly = true;
 
-        final int chunkX = chunk.pos.x;
-        final int chunkZ = chunk.pos.z;
-        final int worldX = chunkX * 16;
-        final int worldZ = chunkZ * 16;
-
-        final int minY = this.world.getWorldType().getMinY(world);
-        final int maxY = this.world.getWorldType().getMaxY(world);
-        final int rangeY = (maxY + 1) - minY;
-
-        final @NotNull Random rand = new Random(this.world.getRandomSeed());
-        final long a = (rand.nextLong() / 2L) * 2L + 1L;
-        final long b = (rand.nextLong() / 2L) * 2L + 1L;
-
-        for (final @NotNull ChunkDecoration decoration : this.decorations.keySet()) {
-            final int decorationSeed = this.decorations.getInt(decoration);
-            rand.setSeed((long) chunkX * a + (long) chunkZ * b ^ this.world.getRandomSeed() ^ decorationSeed);
-            decoration.placeDecoration(this.world, chunk, worldX, worldZ, minY, maxY, rangeY, rand);
+        try {
+            final int chunkX = chunk.pos.x;
+            final int chunkZ = chunk.pos.z;
+            final int worldX = chunkX * 16;
+            final int worldZ = chunkZ * 16;
+
+            final int minY = this.world.getWorldType().getMinY(world);
+            final int maxY = this.world.getWorldType().getMaxY(world);
+            final int rangeY = (maxY + 1) - minY;
+
+            final long worldSeed = this.world.getRandomSeed();
+            if (!this.seedConstantsInitialized) {
+                final Random seedRand = new Random(worldSeed);
+                this.seedMultiplierA = (seedRand.nextLong() / 2L) * 2L + 1L;
+                this.seedMultiplierB = (seedRand.nextLong() / 2L) * 2L + 1L;
+                this.seedConstantsInitialized = true;
+            }
+
+            if (this.decorationArray == null || this.decorationSeedArray == null) {
+                this.bakeDecorations();
+            }
+
+            final ChunkDecoration[] decoArr = this.decorationArray;
+            final int[] seedArr = this.decorationSeedArray;
+            final long a = this.seedMultiplierA;
+            final long b = this.seedMultiplierB;
+            final Random rand = this.sharedRand;
+
+            for (int i = 0, n = decoArr.length; i < n; i++) {
+                assert seedArr != null;
+                rand.setSeed((long) chunkX * a + (long) chunkZ * b ^ worldSeed ^ seedArr[i]);
+                try {
+                    decoArr[i].placeDecoration(this.world, chunk, worldX, worldZ, minY, maxY, rangeY, rand);
+                } catch (final Throwable t) {
+                    LOGGER.error("Decoration {} failed at chunk ({}, {}); continuing with remaining decorations.",
+                            i, chunkX, chunkZ, t);
+                }
+            }
+
+            try {
+                this.postDecorate(this.world, chunk);
+            } catch (final Throwable t) {
+                LOGGER.error("postDecorate failed at chunk ({}, {}).", chunkX, chunkZ, t);
+            }
+
+        } finally {
+            BlockLogicFallingBlock.fallInstantly = false;
+            this.world.scheduledUpdatesAreImmediate = false;
         }
-
-        this.postDecorate(this.world, chunk);
-
-        BlockLogicFallingBlock.fallInstantly = false;
-        this.world.scheduledUpdatesAreImmediate = false;
     }
 
     public abstract void postDecorate(final @NotNull World world, final @NotNull Chunk chunk);
-}
+}
\ No newline at end of file
diff --git a/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/nether/ChunkDecoratorNether.java b/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/nether/ChunkDecoratorNether.java
index 7a9f1af6c..bc07f6874 100644
--- a/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/nether/ChunkDecoratorNether.java
+++ b/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/nether/ChunkDecoratorNether.java
@@ -1,7 +1,8 @@
 package net.minecraft.core.world.generate.chunk.perlin.nether;
 
+import net.minecraft.core.block.Block;
 import net.minecraft.core.block.BlockLogicOreNetherCoal;
-import net.minecraft.core.block.BlockLogicOreRubyglass;
+import net.minecraft.core.block.BlockLogicNodeRubyglass;
 import net.minecraft.core.block.Blocks;
 import net.minecraft.core.world.World;
 import net.minecraft.core.world.biome.Biome;
@@ -10,7 +11,6 @@ import net.minecraft.core.world.biome.Biomes;
 import net.minecraft.core.world.chunk.Chunk;
 import net.minecraft.core.world.generate.chunk.*;
 import net.minecraft.core.world.generate.feature.*;
-import net.minecraft.core.world.generate.feature.tree.WorldFeatureBoneshaleTree;
 import net.minecraft.core.world.noise.FractalNoise2D;
 import net.minecraft.core.world.noise.ImprovedPerlinNoise;
 import net.minecraft.core.world.noise.WorleyNoise;
@@ -23,279 +23,299 @@ public class ChunkDecoratorNether
         extends ChunkFeatureDecorator {
     private final @NotNull FractalNoise2D<ImprovedPerlinNoise> crystalDensityNoise;
     private final @NotNull WorleyNoise pillarNoise;
-    private final @NotNull WorleyNoise.Result worleyResult;
+
     public ChunkDecoratorNether(@NotNull World world) {
         super(world);
         this.pillarNoise = new WorleyNoise(new Random(world.getRandomSeed()));
         this.crystalDensityNoise = new FractalNoise2D<>(ImprovedPerlinNoise.genOctaves(world.getRandomSeed(), 8, 74));
-        this.worleyResult = new WorleyNoise.Result();
+    }
+
+    private TilePos selectPillarPosition(@NotNull Chunk chunk, @NotNull Random random) {
+        int x = chunk.pos.x() * 16 + random.nextInt(16);
+        int z = chunk.pos.z() * 16 + random.nextInt(16);
+        int y = 200;
+
+        WorleyNoise.Result result = new WorleyNoise.Result();
+        this.pillarNoise.getValue(x >> 5, z >> 5, result);
+
+        int xPillar = (int) (result.center.x * 32.0D);
+        int zPillar = (int) (result.center.y * 32.0D);
+
+        return new TilePos(xPillar, y, zPillar);
     }
 
     @Override
     public void registerDecorations() {
-        this.register("minecraft:decoration/nether/default/speleothems",
-            new ChunkDecorationBuilder(new WorldFeatureSpeleothems(12, true, 12, true))
+        // Rubyglass Features
+        this.register("minecraft:decoration/nether/default/rubyglass_crystal_ceiling", (new ChunkDecorationBuilder(new WorldFeatureRubyglassCrystalline(true, 20, 15)))
+            .withBiomeMask(new Biome[]{Biomes.NETHER_CRYSTAL_FOREST})
+            .withPositionSelector(PositionSelectors.UpperHeightRangeUniform)
+            .withPlacementMethod((feature, world, chunk, random) -> {
+                int x = chunk.pos.x * 16;
+                int z = chunk.pos.z * 16;
+                int density = (int) ((this.crystalDensityNoise.getValue((double) x * (double) 0.5F, (double) z * (double) 0.5F) - (double) 6.0F + (double) 30.0F) / (double) 6.0F);
+
+                for (int i = 0; i < density + 4; ++i) {
+                    feature.placeFeature(world, chunk, random);
+                }
+            }));
+
+        this.register("minecraft:decoration/nether/default/rubyglass_crystal_floor", (new ChunkDecorationBuilder(new WorldFeatureRubyglassCrystalline(false, 20, 30)))
+                .withBiomeMask(new Biome[]{Biomes.NETHER_CRYSTAL_FOREST})
+                .withPositionSelector(PositionSelectors.HeightRangeUniformFromOcean)
+                .withPlacementMethod((feature, world, chunk, random) -> {
+                    int x = chunk.pos.x * 16;
+                    int z = chunk.pos.z * 16;
+                    int density = (int) ((this.crystalDensityNoise.getValue((double) x * (double) 0.5F, (double) z * (double) 0.5F) - (double) 6.0F + (double) 30.0F) / (double) 6.0F);
+
+                    for (int i = 0; i < density + 4; ++i) {
+                        feature.placeFeature(world, chunk, random);
+                    }
+                }));
+
+        this.register("minecraft:decoration/nether/default/rubyglass_crystal_floor_2", (new ChunkDecorationBuilder(new WorldFeatureRubyglassCrystalline(false, 15, 25)))
+                .withBiomeMask(new Biome[]{Biomes.NETHER_CRYSTAL_FOREST, Biomes.NETHER_CRYSTAL_PLAINS})
+                .withPositionSelector(PositionSelectors.HeightRangeUniformFromOcean)
+                .withPlacementMethod(new PlacementMethod.TriesPerChunk(4)));
+
+        this.register("minecraft:decoration/nether/default/rubyglass_node", (new ChunkDecorationBuilder(new WorldFeatureOre(BlockLogicNodeRubyglass.variantMap, 5)))
+                .withBiomeMask(new Biome[]{Biomes.NETHER_CRYSTAL_FOREST, Biomes.NETHER_CRYSTAL_PLAINS})
+                .withPositionSelector(PositionSelectors.HeightRangeUniform)
+                .withPlacementMethod(new PlacementMethod.TriesPerChunk(10)));
+
+        this.register("minecraft:decoration/nether/default/rubyglass_bud_patch", (new ChunkDecorationBuilder(new WorldFeatureRubyglassGrowthPatch(0.0F)))
+                .withBiomeMask(new Biome[]{Biomes.NETHER_CRYSTAL_FOREST, Biomes.NETHER_CRYSTAL_PLAINS})
+                .withPositionSelector(PositionSelectors.HeightRangeUniformFromOcean)
+                .withPlacementMethod(new PlacementMethod.TriesPerChunk(100)));
+
+        this.register("minecraft:decoration/nether/default/rubyglass_growth_patch", (new ChunkDecorationBuilder(new WorldFeatureRubyglassGrowthPatch()))
+                .withBiomeMask(new Biome[]{Biomes.NETHER_CRYSTAL_FOREST, Biomes.NETHER_CRYSTAL_PLAINS})
+                .withPositionSelector(PositionSelectors.HeightRangeUniformFromOcean)
+                .withPlacementMethod(new PlacementMethod.TriesPerChunk(100)));
+
+        // Wall Scatter Features
+        this.register("minecraft:decoration/nether/default/scatter_crystalline", (new ChunkDecorationBuilder(new WorldFeaturePerlinVein(Blocks.COBBLE_NETHERRACK_CRYSTALLINE, Blocks.COBBLE_NETHERRACK, 0.06, 0.045)))
+                .withBiomeMask(new Biome[]{Biomes.NETHER_CRYSTAL_PLAINS, Biomes.NETHER_CRYSTAL_FOREST})
+                .withPositionSelector(PositionSelectors.MinY)
+                .withPlacementMethod(PlaceableFeature::placeFeature));
+
+        this.register("minecraft:decoration/nether/default/clump_crystalline", (new ChunkDecorationBuilder(new WorldFeatureWallClump(Blocks.COBBLE_NETHERRACK_CRYSTALLINE, Blocks.COBBLE_NETHERRACK, 1)))
+                .withBiomeMask(new Biome[]{Biomes.NETHER_CRYSTAL_PLAINS, Biomes.NETHER_CRYSTAL_FOREST})
+                .withPositionSelector(PositionSelectors.HeightRangeUniform)
+                .withPlacementMethod(new PlacementMethod.TriesPerChunk(500)));
+
+        this.register("minecraft:decoration/nether/default/clump_brimthaw", (new ChunkDecorationBuilder(new WorldFeatureWallClump(Blocks.BRIMTHAW, Blocks.BRIMSTONE, 1)))
+                .withBiomeMask(new Biome[]{Biomes.NETHER_SULFUR_POOLS})
+                .withPositionSelector(PositionSelectors.HeightRangeUniform)
+                .withPlacementMethod(new PlacementMethod.TriesPerChunk(1000)));
+
+        // Water Features
+        this.register("minecraft:decoration/nether/default/water_spring", (new ChunkDecorationBuilder(new WorldFeatureNetherSpring(Blocks.FLUID_WATER_FLOWING.id())))
+                .withBiomeMask(new Biome[]{Biomes.NETHER_CRYSTAL_FOREST, Biomes.NETHER_CRYSTAL_PLAINS})
+                .withPositionSelector(PositionSelectors.HeightRangeBiasedTop)
+                .withPlacementMethod(new PlacementMethod.TriesPerChunk(16)));
+
+        // Lava Features
+        this.register("minecraft:decoration/nether/default/lava_spring", (new ChunkDecorationBuilder(new WorldFeatureNetherSpring(Blocks.FLUID_LAVA_FLOWING.id())))
+                .withBiomeMask(new Biome[]{Biomes.NETHER_VOLCANIC_ISLANDS, Biomes.NETHER_CRAG, Biomes.NETHER_SHELF})
+                .withPositionSelector(PositionSelectors.HeightRangeUniform)
+                .withPlacementMethod(new PlacementMethod.TriesPerChunk(16)));
+
+        this.register("minecraft:decoration/nether/default/lava_spring_2", (new ChunkDecorationBuilder(new WorldFeatureNetherSpring(Blocks.FLUID_LAVA_FLOWING.id())))
+                .withBiomeMask(new Biome[]{Biomes.NETHER_VOLCANIC_ISLANDS})
+                .withPositionSelector(PositionSelectors.HeightRangeUniform)
+                .withPlacementMethod(new PlacementMethod.TriesPerChunk(16)));
+
+        this.register("minecraft:decoration/nether/default/lava_lake", (new ChunkDecorationBuilder(new WorldFeatureLake(Blocks.FLUID_LAVA_STILL.id())))
+                .withBiomeMask(new Biome[]{Biomes.NETHER_VOLCANIC_ISLANDS, Biomes.NETHER_CRAG, Biomes.NETHER_CRYSTAL_PLAINS, Biomes.NETHER_CRYSTAL_FOREST, Biomes.NETHER_SHELF, Biomes.NETHER_SULFUR_POOLS})
                 .withPositionSelector(PositionSelectors.UpperHeightRangeUniform)
-                .withPlacementMethod(new PlacementMethod.TriesPerChunk(4))
-        );
-        this.register("minecraft:decoration/nether/default/lava_lake",
-                new ChunkDecorationBuilder(new WorldFeatureLake(Blocks.FLUID_LAVA_STILL.id()))
-                        .withPositionSelector(PositionSelectors.UpperHeightRangeUniform)
-                        .withPlacementMethod(new PlacementMethod.ChanceToPlace(4))
-        );
-        this.register("minecraft:decoration/nether/default/rubyglass_crystal_ceiling",
-                new ChunkDecorationBuilder(new WorldFeatureRubyglassCrystal(true))
-                        .withBiomeMask(new Biome[]{ Biomes.NETHER_CRYSTAL_FOREST})
-                        .withPositionSelector(PositionSelectors.UpperHeightRangeUniform)
-                        .withPlacementMethod(((feature, world, chunk, random) -> {
-                            int x = chunk.pos.x * 16;
-                            int z = chunk.pos.z * 16;
-                            int density = (int) ((this.crystalDensityNoise.getValue(x * 0.5, z * 0.5) - 6D + 30D) / 6D);
-
-                            for (int i = 0; i < density + 4; i++) {
-                                feature.placeFeature(world, chunk, random);
-                            }
-                        }))
-        );
-
-        this.register("minecraft:decoration/nether/default/rubyglass_crystal_floor",
-                new ChunkDecorationBuilder(new WorldFeatureRubyglassCrystal(false, 25, 50))
-                        .withBiomeMask(new Biome[]{ Biomes.NETHER_CRYSTAL_FOREST})
-                        .withPositionSelector(PositionSelectors.UpperHeightRangeUniform)
-                        .withPlacementMethod(((feature, world, chunk, random) -> {
-                            int x = chunk.pos.x * 16;
-                            int z = chunk.pos.z * 16;
-                            int density = (int) ((this.crystalDensityNoise.getValue(x * 0.5, z * 0.5) - 6D + 30D) / 6D);
-
-                            for (int i = 0; i < density + 4; i++) {
-                                feature.placeFeature(world, chunk, random);
-                            }
-                        }))
-        );
-
-        this.register("minecraft:decoration/nether/default/rubyglass_crystal_floor_2",
-                new ChunkDecorationBuilder(new WorldFeatureRubyglassCrystal(false,  20, 30))
-                        .withBiomeMask(new Biome[]{ Biomes.NETHER_CRYSTAL_FOREST, Biomes.NETHER_CRYSTAL_PLAINS})
-                        .withPositionSelector(PositionSelectors.UpperHeightRangeUniform)
-                        .withPlacementMethod(new PlacementMethod.TriesPerChunk(4))
-        );
-
-        this.register("minecraft:decoration/nether/default/rubyglass_node",
-                new ChunkDecorationBuilder(new WorldFeatureOre(BlockLogicOreRubyglass.variantMap, 5))
-                        .withBiomeMask(new Biome[]{ Biomes.NETHER_CRYSTAL_FOREST, Biomes.NETHER_CRYSTAL_PLAINS})
-                        .withPositionSelector(PositionSelectors.HeightRangeUniform)
-                        .withPlacementMethod(new PlacementMethod.TriesPerChunk(10))
-        );
-
-        this.register("minecraft:decoration/nether/default/rubyglass_sprout_patch",
-                new ChunkDecorationBuilder(new WorldFeatureRubyglassSproutPatch())
-                        .withBiomeMask(new Biome[]{ Biomes.NETHER_CRYSTAL_FOREST, Biomes.NETHER_CRYSTAL_PLAINS})
-                        .withPositionSelector(PositionSelectors.HeightRangeUniformFromOcean)
-                        .withPlacementMethod(new PlacementMethod.TriesPerChunk(20))
-        );
-
-        this.register("minecraft:decoration/nether/default/nether_water_spring",
-                new ChunkDecorationBuilder(new WorldFeatureNetherSpring(Blocks.FLUID_WATER_FLOWING.id()))
-                        .withBiomeMask(new Biome[]{ Biomes.NETHER_CRYSTAL_FOREST, Biomes.NETHER_CRYSTAL_PLAINS, Biomes.NETHER_OLD_WORLD })
-                        .withPositionSelector(PositionSelectors.HeightRangeBiasedTop)
-                        .withPlacementMethod(new PlacementMethod.TriesPerChunk(30))
-        );
-
-        this.register("minecraft:decoration/nether/default/nether_lava_spring",
-                new ChunkDecorationBuilder(new WorldFeatureNetherSpring(Blocks.FLUID_LAVA_FLOWING.id()))
-                        .withBiomeMask(new Biome[]{ Biomes.NETHER_VOLCANIC_ISLANDS, Biomes.NETHER_SULFUR_POOLS, Biomes.NETHER_CRAG, Biomes.NETHER_SHELF })
-                        .withPositionSelector(PositionSelectors.HeightRangeBiasedTop)
-                        .withPlacementMethod(new PlacementMethod.TriesPerChunk(30))
-        );
-
-        this.register("minecraft:decoration/nether/default/nether_lava_spring_2",
-                new ChunkDecorationBuilder(new WorldFeatureNetherSpring(Blocks.FLUID_LAVA_FLOWING.id()))
-                        .withBiomeMask(new Biome[]{ Biomes.NETHER_VOLCANIC_ISLANDS })
-                        .withPositionSelector(PositionSelectors.HeightRangeBiasedTop)
-                        .withPlacementMethod(new PlacementMethod.TriesPerChunk(20))
-        );
-
-        this.register("minecraft:decoration/nether/default/pillar_netherrack",
-                new ChunkDecorationBuilder(new WorldFeaturePillar(Blocks.NETHERRACK.id(), false))
-                        .withBiomeMask(new Biome[]{ Biomes.NETHER_CRYSTAL_PLAINS, Biomes.NETHER_CRYSTAL_FOREST, Biomes.NETHER_CRAG, Biomes.NETHER_SHELF })
-                        .withPositionSelector(((world, chunk, random, minY, maxY, rangeY) -> {
-                            int x = (chunk.pos.x() * 16) + random.nextInt(16);
-                            int z = (chunk.pos.z() * 16) + random.nextInt(16);
-                            int y = 200;
-
-                            double scale = 0.5;
-
-                            this.pillarNoise.getValue(
-                                    Math.floor(x / 32F),
-                                    Math.floor(z / 32F),
-                                this.worleyResult
-                            );
-
-                            int xPillar = (int) (this.worleyResult.center.x * 32);
-                            int zPillar = (int) (this.worleyResult.center.y * 32);
-
-                            return new TilePos(xPillar, y, zPillar);
-                        }))
-                        .withPlacementMethod(new PlacementMethod.ChanceToPlace(4))
-        );
-
-        this.register("minecraft:decoration/nether/default/pillar_cobble_basalt",
-                new ChunkDecorationBuilder(new WorldFeaturePillar(Blocks.COBBLE_BASALT.id(), true))
-                        .withBiomeMask(new Biome[]{ Biomes.NETHER_SULFUR_POOLS, Biomes.NETHER_VOLCANIC_ISLANDS })
-                        .withPositionSelector(((world, chunk, random, minY, maxY, rangeY) -> {
-                            int x = (chunk.pos.x() * 16) + random.nextInt(16);
-                            int z = (chunk.pos.z() * 16) + random.nextInt(16);
-                            int y = 200;
-
-                            double scale = 0.5;
-
-                            this.pillarNoise.getValue(
-                                    Math.floor(x / 32F),
-                                    Math.floor(z / 32F),
-                                this.worleyResult
-                            );
-
-                            int xPillar = (int) (this.worleyResult.center.x * 32);
-                            int zPillar = (int) (this.worleyResult.center.y * 32);
-
-                            return new TilePos(xPillar, y, zPillar);
-                        }))
-                        .withPlacementMethod(new PlacementMethod.TriesPerChunk(4))
-        );
-
-        this.register("minecraft:decoration/nether/default/boulder",
-                new ChunkDecorationBuilder(new WorldFeatureMagmaBoulder())
-                        .withBiomeMask(new Biome[]{ Biomes.NETHER_SULFUR_POOLS, Biomes.NETHER_VOLCANIC_ISLANDS })
-                        .withPositionSelector(PositionSelectors.HeightRangeUniformFromOcean)
-                        .withPlacementMethod(new PlacementMethod.TriesPerChunk(2))
-        );
-
-        this.register("minecraft:decoration/nether/default/sulfur_pool_terrace",
-                new ChunkDecorationBuilder(new WorldFeatureTerrace(Blocks.FLUID_WATER_STILL, Blocks.BRIMSAND))
-                        .withBiomeMask(new Biome[]{ Biomes.NETHER_SULFUR_POOLS })
-                        .withPositionSelector(PositionSelectors.MinY)
-                        .withPlacementMethod(PlacementMethods.OncePerChunk)
-        );
-
-        this.register("minecraft:decoration/nether/default/lava_pool_terrace",
-                new ChunkDecorationBuilder(new WorldFeatureTerrace(Blocks.FLUID_LAVA_STILL, Blocks.COBBLE_BASALT, 4, 4))
-                        .withBiomeMask(new Biome[]{ Biomes.NETHER_VOLCANIC_ISLANDS })
-                        .withPositionSelector(PositionSelectors.MinY)
-                        .withPlacementMethod(new PlacementMethod.ChanceToPlace(3))
-        );
-
-        this.register("minecraft:decoration/nether/default/sulfur_pool_floor_vent",
-                new ChunkDecorationBuilder(new WorldFeatureFloorVent())
-                        .withBiomeMask(new Biome[]{ Biomes.NETHER_SULFUR_POOLS })
-                        .withPositionSelector(PositionSelectors.HeightRangeUniformFromOcean)
-                        .withPlacementMethod(new PlacementMethod.TriesPerChunk(12))
-        );
-
-        this.register("minecraft:decoration/nether/default/sulfur_pool_spire",
-                new ChunkDecorationBuilder(new WorldFeatureThermalSpire())
-                        .withBiomeMask(new Biome[]{ Biomes.NETHER_SULFUR_POOLS })
-                        .withPositionSelector(PositionSelectors.HeightRangeUniformFromOcean)
-                        .withPlacementMethod(new PlacementMethod.TriesPerChunk(8))
-        );
-
-        this.register("minecraft:decoration/nether/default/nether_coal_ore",
-                new ChunkDecorationBuilder(new WorldFeatureOre(BlockLogicOreNetherCoal.variantMap, 12))
-                        .withPositionSelector(PositionSelectors.HeightRangeUniform)
-                        .withPlacementMethod(new PlacementMethod.TriesPerChunk(10))
-        );
-
-        this.register("minecraft:decoration/nether/default/nether_coal_ore_2",
-                new ChunkDecorationBuilder(new WorldFeatureOre(BlockLogicOreNetherCoal.variantMap, 12))
-                        .withBiomeMask(new Biome[]{ Biomes.NETHER_VOLCANIC_ISLANDS })
-                        .withPositionSelector(PositionSelectors.HeightRangeUniform)
-                        .withPlacementMethod(new PlacementMethod.TriesPerChunk(20))
-        );
-
-        this.register("minecraft:decoration/nether/default/glowstone_1",
-                new ChunkDecorationBuilder(new WorldFeatureGlowstone())
-                        .withPositionSelector(PositionSelectors.HeightRangeUniformFromOcean)
-                        .withPlacementMethod(((feature, world, chunk, random) -> {
-                            int max = random.nextInt(random.nextInt(10) + 1);
-                            for (int i = 0; i < max; i++) {
-                                feature.placeFeature(world, chunk, random);
-                            }
-                        }))
-        );
-
-        this.register("minecraft:decoration/nether/default/glowstone_2",
-                new ChunkDecorationBuilder(new WorldFeatureGlowstone())
-                        .withPositionSelector(PositionSelectors.HeightRangeUniformFromOcean)
-                        .withPlacementMethod(new PlacementMethod.TriesPerChunk(10))
-        );
-
-        this.register("minecraft:decoration/nether/default/patch_fire_1",
-                new ChunkDecorationBuilder(new WorldFeatureFire())
-                        .withPositionSelector(PositionSelectors.HeightRangeUniformFromOcean)
-                        .withPlacementMethod(new PlacementMethod.TriesPerChunk(4))
-        );
-
-        this.register("minecraft:decoration/nether/default/patch_fire_2",
-                new ChunkDecorationBuilder(new WorldFeatureFire())
-                        .withBiomeMask(new Biome[]{ Biomes.NETHER_VOLCANIC_ISLANDS })
-                        .withPositionSelector(PositionSelectors.HeightRangeUniformFromOcean)
-                        .withPlacementMethod(new PlacementMethod.TriesPerChunk(4))
-        );
-
-        this.register("minecraft:decoration/nether/default/old_world_patch_bone_pile",
-                new ChunkDecorationBuilder(new WorldFeatureNetherPatch(Blocks.BONE_PILE.id()))
-                        .withBiomeMask(new Biome[]{ Biomes.NETHER_OLD_WORLD, Biomes.NETHER_OLD_WORLD_DESERT })
-                        .withPositionSelector(PositionSelectors.HeightRangeUniformFromOcean)
-                        .withPlacementMethod(new PlacementMethod.TriesPerChunk(4))
-        );
-
-        this.register("minecraft:decoration/nether/default/patch_bone_pile",
-                new ChunkDecorationBuilder(new WorldFeatureNetherPatch(Blocks.BONE_PILE.id()))
-                        .withPositionSelector(PositionSelectors.HeightRangeUniformFromOcean)
-                        .withPlacementMethod(new PlacementMethod.TriesPerChunk(1))
-        );
-
-        this.register("minecraft:decoration/nether/default/patch_soul_catcher",
-                new ChunkDecorationBuilder(new WorldFeatureNetherPatch(Blocks.SOULCATCHER.id()))
-                        .withPositionSelector(PositionSelectors.HeightRangeUniformFromOcean)
-                        .withPlacementMethod(new PlacementMethod.TriesPerChunk(2))
-        );
-
-        this.register("minecraft:decoration/nether/default/boneshale_tree",
-                new ChunkDecorationBuilder(((world, random, tilePos) -> {
-                    //todo refactor how spooner trees are instantiated
-                    WorldFeature wf = new WorldFeatureBoneshaleTree(0, Blocks.LOG_PETRIFIED.id());
-                    wf.init(1.0D, 1.0D, 1.0D);
-                    return wf.place(world, random, tilePos);
-                }))
-                        .withBiomeMask(new Biome[]{ Biomes.NETHER_OLD_WORLD })
-                        .withPositionSelector(PositionSelectors.HeightRangeUniformFromOcean)
-                        .withPlacementMethod(new PlacementMethod.TriesPerChunk(4))
-        );
+                .withPlacementMethod(new PlacementMethod.ChanceToPlace(4)));
+
+        this.register("minecraft:decoration/nether/default/lava_pool_terrace", (new ChunkDecorationBuilder(new WorldFeatureTerrace(Blocks.FLUID_LAVA_STILL, Blocks.BRIMSTONE, 4, 4)))
+                .withBiomeMask(new Biome[]{Biomes.NETHER_VOLCANIC_ISLANDS})
+                .withPositionSelector(PositionSelectors.MinY)
+                .withPlacementMethod(new PlacementMethod.ChanceToPlace(3)));
+
+        this.register("minecraft:decoration/nether/default/boulder_magma", (new ChunkDecorationBuilder(new WorldFeatureTerrace(Blocks.FLUID_LAVA_STILL, Blocks.BRIMSTONE, 4, 4)))
+                .withBiomeMask(new Biome[]{Biomes.NETHER_VOLCANIC_ISLANDS})
+                .withPositionSelector(PositionSelectors.MinY)
+                .withPlacementMethod(new PlacementMethod.ChanceToPlace(3)));
+
+        this.register("minecraft:decoration/nether/default/acid_spring", (new ChunkDecorationBuilder(new WorldFeatureNetherSpring(Blocks.FLUID_ACID_FLOWING.id())))
+                .withBiomeMask(new Biome[]{Biomes.NETHER_SULFUR_POOLS})
+                .withPositionSelector(PositionSelectors.HeightRangeUniform)
+                .withPlacementMethod(new PlacementMethod.TriesPerChunk(16)));
+
+        // Pillar Features
+        this.register("minecraft:decoration/nether/default/pillar_netherrack", (new ChunkDecorationBuilder(new WorldFeaturePillar(Blocks.RUBYGLASS_COLUMN.id(), false)))
+                .withBiomeMask(new Biome[]{Biomes.NETHER_CRYSTAL_PLAINS, Biomes.NETHER_CRYSTAL_FOREST})
+                .withPositionSelector((world, chunk, random, minY, maxY, rangeY) -> selectPillarPosition(chunk, random))
+                .withPlacementMethod(new PlacementMethod.ChanceToPlace(8)));
+
+        this.register("minecraft:decoration/nether/default/pillar_netherrack_2", (new ChunkDecorationBuilder(new WorldFeaturePillar(Blocks.COBBLE_NETHERRACK.id(), false)))
+                .withBiomeMask(new Biome[]{Biomes.NETHER_CRYSTAL_PLAINS, Biomes.NETHER_CRYSTAL_FOREST, Biomes.NETHER_CRAG, Biomes.NETHER_SHELF})
+                .withPositionSelector((world, chunk, random, minY, maxY, rangeY) -> selectPillarPosition(chunk, random))
+                .withPlacementMethod(new PlacementMethod.ChanceToPlace(4)));
+
+        this.register("minecraft:decoration/nether/default/pillar_cobble_basalt", (new ChunkDecorationBuilder(new WorldFeaturePillar(Blocks.BRIMSTONE.id(), true)))
+                .withBiomeMask(new Biome[]{Biomes.NETHER_VOLCANIC_ISLANDS, Biomes.NETHER_SULFUR_POOLS})
+                .withPositionSelector((world, chunk, random, minY, maxY, rangeY) -> selectPillarPosition(chunk, random))
+                .withPlacementMethod(new PlacementMethod.TriesPerChunk(4)));
+
+        this.register("minecraft:decoration/nether/default/pillar_slate", (new ChunkDecorationBuilder(new WorldFeaturePillar(Blocks.SLATE.id(), true)))
+                .withBiomeMask(new Biome[]{Biomes.NETHER_OLD_WORLD})
+                .withPositionSelector((world, chunk, random, minY, maxY, rangeY) -> selectPillarPosition(chunk, random))
+                .withPlacementMethod(new PlacementMethod.TriesPerChunk(4)));
+
+        // Nether biome features
+        this.register("minecraft:decoration/nether/default/boulder_magma", new ChunkDecorationBuilder(new WorldFeatureBoulderMagma())
+                .withBiomeMask(new Biome[]{Biomes.NETHER_VOLCANIC_ISLANDS})
+                .withPositionSelector(PositionSelectors.HeightRangeUniformFromOcean)
+                .withPlacementMethod(new PlacementMethod.TriesPerChunk(2)));
+
+        this.register("minecraft:decoration/nether/default/sulfur_pool_terrace", new ChunkDecorationBuilder(new WorldFeatureTerrace(Blocks.FLUID_ACID_FLOWING, Blocks.BRIMSAND))
+                .withBiomeMask(new Biome[]{Biomes.NETHER_SULFUR_POOLS})
+                .withPositionSelector(PositionSelectors.MinY)
+                .withPlacementMethod(new PlacementMethod.TriesPerChunk(3)));
+
+        this.register("minecraft:decoration/nether/default/floor_vent", new ChunkDecorationBuilder(new WorldFeatureFloorVent())
+                .withBiomeMask(new Biome[]{Biomes.NETHER_VOLCANIC_ISLANDS, Biomes.NETHER_SULFUR_POOLS})
+                .withPositionSelector(PositionSelectors.HeightRangeUniformFromOcean)
+                .withPlacementMethod(new PlacementMethod.TriesPerChunk(12)));
+
+        this.register("minecraft:decoration/nether/default/volcanic_islands_spire", new ChunkDecorationBuilder(new WorldFeatureSpireVent())
+                .withBiomeMask(new Biome[]{Biomes.NETHER_VOLCANIC_ISLANDS})
+                .withPositionSelector(PositionSelectors.HeightRangeUniformFromOcean)
+                .withPlacementMethod(new PlacementMethod.TriesPerChunk(8)));
+
+        this.register("minecraft:decoration/nether/default/sulfur_pool_boulder", new ChunkDecorationBuilder(new WorldFeatureBoulderGlowstone(Blocks.GLOWSTONE, Blocks.FIRE_SULFURIC, 1))
+                .withBiomeMask(new Biome[]{Biomes.NETHER_SULFUR_POOLS})
+                .withPositionSelector(PositionSelectors.HeightRangeUniformFromOcean)
+                .withPlacementMethod(new PlacementMethod.TriesPerChunk(12)));
+
+        this.register("minecraft:decoration/nether/default/nether_roof_spire", new ChunkDecorationBuilder(new WorldFeatureRoofSpire(Blocks.COBBLE_NETHERRACK))
+                .withBiomeMask(new Biome[]{Biomes.NETHER_CRAG, Biomes.NETHER_SHELF})
+                .withPositionSelector(PositionSelectors.HeightRangeUniformFromOcean)
+                .withPlacementMethod(new PlacementMethod.TriesPerChunk(2)));
+
+        this.register("minecraft:decoration/nether/default/volcano_roof_spire", new ChunkDecorationBuilder(new WorldFeatureRoofSpire(Blocks.BRIMSTONE))
+                .withBiomeMask(new Biome[]{Biomes.NETHER_VOLCANIC_ISLANDS})
+                .withPositionSelector(PositionSelectors.HeightRangeUniformFromOcean)
+                .withPlacementMethod(new PlacementMethod.TriesPerChunk(2)));
+
+        this.register("minecraft:decoration/nether/default/sulfur_roof_spire", new ChunkDecorationBuilder(new WorldFeatureRoofSpire(Blocks.SULFUR))
+                .withBiomeMask(new Biome[]{Biomes.NETHER_SULFUR_POOLS})
+                .withPositionSelector(PositionSelectors.HeightRangeUniformFromOcean)
+                .withPlacementMethod(new PlacementMethod.TriesPerChunk(2)));
+
+        this.register("minecraft:decoration/nether/default/oldworld_roof_spire", new ChunkDecorationBuilder(new WorldFeatureRoofSpire(Blocks.SLATE))
+                .withBiomeMask(new Biome[]{Biomes.NETHER_OLD_WORLD})
+                .withPositionSelector(PositionSelectors.HeightRangeUniformFromOcean)
+                .withPlacementMethod(new PlacementMethod.TriesPerChunk(2)));
+
+        // Ore Features
+        this.register("minecraft:decoration/nether/default/nether_coal_ore", (new ChunkDecorationBuilder(new WorldFeatureOre(BlockLogicOreNetherCoal.variantMap, 12)))
+                .withPositionSelector(PositionSelectors.HeightRangeUniform)
+                .withPlacementMethod(new PlacementMethod.TriesPerChunk(10)));
+
+        this.register("minecraft:decoration/nether/default/nether_coal_ore_2", (new ChunkDecorationBuilder(new WorldFeatureOre(BlockLogicOreNetherCoal.variantMap, 12)))
+                .withBiomeMask(new Biome[]{Biomes.NETHER_VOLCANIC_ISLANDS})
+                .withPositionSelector(PositionSelectors.HeightRangeUniform)
+                .withPlacementMethod(new PlacementMethod.TriesPerChunk(20)));
+
+        this.register("minecraft:decoration/nether/default/nether_coal_ore_3", (new ChunkDecorationBuilder(new WorldFeatureOre(BlockLogicOreNetherCoal.variantMap, 16)))
+                .withBiomeMask(new Biome[]{Biomes.NETHER_CRAG, Biomes.NETHER_SHELF})
+                .withPositionSelector(PositionSelectors.HeightRangeUniform)
+                .withPlacementMethod(new PlacementMethod.TriesPerChunk(10)));
+
+        // Glowstone Features
+        this.register("minecraft:decoration/nether/default/glowstone_1", (new ChunkDecorationBuilder(new WorldFeatureGlowstone()))
+                .withBiomeMask(new Biome[]{Biomes.NETHER_VOLCANIC_ISLANDS, Biomes.NETHER_CRAG, Biomes.NETHER_CRYSTAL_PLAINS, Biomes.NETHER_CRYSTAL_FOREST, Biomes.NETHER_SHELF, Biomes.NETHER_SULFUR_POOLS})
+                .withPositionSelector(PositionSelectors.HeightRangeUniformFromOcean)
+                .withPlacementMethod((feature, world, chunk, random) -> {
+                    int max = random.nextInt(random.nextInt(10) + 1);
+
+                    for (int i = 0; i < max; ++i) {
+                        feature.placeFeature(world, chunk, random);
+                    }
+                }));
+
+        this.register("minecraft:decoration/nether/default/glowstone_2", (new ChunkDecorationBuilder(new WorldFeatureGlowstone()))
+                .withBiomeMask(new Biome[]{Biomes.NETHER_VOLCANIC_ISLANDS, Biomes.NETHER_CRAG, Biomes.NETHER_CRYSTAL_PLAINS, Biomes.NETHER_CRYSTAL_FOREST, Biomes.NETHER_SHELF, Biomes.NETHER_SULFUR_POOLS})
+                .withPositionSelector(PositionSelectors.HeightRangeUniformFromOcean)
+                .withPlacementMethod(new PlacementMethod.TriesPerChunk(10)));
+
+        this.register("minecraft:decoration/nether/default/glowstone_sulfur", (new ChunkDecorationBuilder(new WorldFeatureGlowstone()))
+                .withBiomeMask(new Biome[]{Biomes.NETHER_SULFUR_POOLS})
+                .withPositionSelector(PositionSelectors.HeightRangeUniformFromOcean)
+                .withPlacementMethod(new PlacementMethod.TriesPerChunk(20)));
+
+        // Fire Features
+        this.register("minecraft:decoration/nether/default/patch_fire", (new ChunkDecorationBuilder(new WorldFeatureFire()))
+                .withBiomeMask(new Biome[]{Biomes.NETHER_CRAG, Biomes.NETHER_SHELF})
+                .withPositionSelector(PositionSelectors.HeightRangeUniformFromOcean)
+                .withPlacementMethod(new PlacementMethod.TriesPerChunk(16)));
+
+        this.register("minecraft:decoration/nether/default/patch_fire_volcanic", (new ChunkDecorationBuilder(new WorldFeatureFire(Blocks.FIRE, Blocks.NETHERRACK)))
+                .withBiomeMask(new Biome[]{Biomes.NETHER_VOLCANIC_ISLANDS})
+                .withPositionSelector(PositionSelectors.HeightRangeUniformFromOcean)
+                .withPlacementMethod(new PlacementMethod.TriesPerChunk(24)));
+
+        this.register("minecraft:decoration/nether/default/patch_fire_sulfuric", (new ChunkDecorationBuilder(new WorldFeatureFire()))
+                .withBiomeMask(new Biome[]{Biomes.NETHER_SULFUR_POOLS})
+                .withPositionSelector(PositionSelectors.HeightRangeUniformFromOcean)
+                .withPlacementMethod(new PlacementMethod.TriesPerChunk(32)));
+
+        this.register("minecraft:decoration/nether/default/scatter", (new ChunkDecorationBuilder(new WorldFeatureNetherScatter()))
+                .withPositionSelector(PositionSelectors.HeightRangeUniformFromOcean)
+                .withPlacementMethod(new PlacementMethod.TriesPerChunk(32)));
+
+        this.register("minecraft:decoration/nether/default/patch_soul_catcher", (new ChunkDecorationBuilder(new WorldFeatureSoulCatcherPatch()))
+                .withPositionSelector(PositionSelectors.HeightRangeUniformFromOcean)
+                .withPlacementMethod(new PlacementMethod.TriesPerChunk(2)));
+
+        this.register("minecraft:decoration/nether/default/pebbles", (world, chunk, worldX, worldZ, minY, maxY, rangeY, rand) -> {
+            for (int i = 0; i < 16; ++i) {
+                int xx = worldX + rand.nextInt(16) + 8;
+                int yy = 64 + rand.nextInt(128);
+                int zz = worldZ + rand.nextInt(16) + 8;
+                (new WorldFeaturePebbles(Blocks.OVERLAY_PEBBLES, 16, true)).place(world, rand, xx, yy, zz);
+            }
+        });
+    }
+
+    private static boolean isRubyglassAshSupportForbidden(final @NotNull Block<?> block) {
+        return block == Blocks.RUBYGLASS_NODE
+            || block == Blocks.RUBYGLASS_COLUMN
+            || block == Blocks.RUBYGLASS_GROWTH;
     }
 
     @Override
     public void postDecorate(@NotNull World world, @NotNull Chunk chunk) {
         TilePos queryPose = new TilePos();
-        int oceanY = world.getWorldType().getOceanY();
         int worldX = chunk.pos.x() * 16;
         int worldZ = chunk.pos.z() * 16;
-        int midY = oceanY + (world.getWorldType().getMaxY(world) - oceanY) / 2;
-        for (int dx = worldX; dx < worldX + 16; dx++) {
-            for (int dz = worldZ; dz < worldZ + 16; dz++) {
-                int dy = midY;
-                while (world.isAirBlock(queryPose.set(dx, dy, dz))) {
-                    dy--;
-                }
-                dy++;
-                Biome localBiome = world.getBlockBiome(queryPose.set(dx, dy, dz));
+        int startY = world.getWorldType().getMaxY(world);
 
-                if (localBiome.hasTag(BiomeTags.HAS_SURFACE_ASH) && dy > 0 && dy < world.getHeightBlocks()) {
-                    if (world.isAirBlock(queryPose.set(dx, dy, dz)) && world.getBlockMaterial(queryPose.set(dx, dy - 1, dz)).blocksMotion()) {
-                        world.setBlockType(queryPose.set(dx, dy, dz), Blocks.LAYER_ASH);
+        for (int dx = worldX; dx < worldX + 16; ++dx) {
+            for (int dz = worldZ; dz < worldZ + 16; ++dz) {
+                for (int dy = startY; dy > world.getWorldType().getOceanY(); --dy) {
+                    queryPose.set(dx, dy, dz);
+
+                    if (world.isAirBlock(queryPose)) {
+                        Block<?> blockBelow = world.getBlock(dx, dy - 1, dz);
+
+                        if (blockBelow != Blocks.OBSIDIAN && blockBelow.getMaterial().blocksMotion()
+                                && !isRubyglassAshSupportForbidden(blockBelow)) {
+
+                            Biome localBiome = world.getBlockBiome(queryPose);
+                            if (localBiome.hasTag(BiomeTags.HAS_SURFACE_ASH)) {
+                                world.setBlockType(queryPose, Blocks.LAYER_ASH);
+                            }
+                        }
                     }
                 }
             }
-
         }
     }
 }
diff --git a/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/nether/ChunkGeneratorNether.java b/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/nether/ChunkGeneratorNether.java
index 159e90c92..5ba7fd264 100644
--- a/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/nether/ChunkGeneratorNether.java
+++ b/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/nether/ChunkGeneratorNether.java
@@ -17,6 +17,7 @@ public class ChunkGeneratorNether
                 {
                     new CavesLargeFeature(64, 256),
                     new LavaFloeLargeFeature(),
+                    new RubyglassFloeLargeFeature(),
                     new ShelfLargeFeature()
                 }
         );
diff --git a/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/nether/DensityGeneratorNether.java b/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/nether/DensityGeneratorNether.java
index e5b822b5a..095d6727c 100644
--- a/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/nether/DensityGeneratorNether.java
+++ b/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/nether/DensityGeneratorNether.java
@@ -15,7 +15,7 @@ public class DensityGeneratorNether
     private final @NotNull Noise3D maxLimitNoise;
     private final @NotNull Noise3D mainNoise;
 
-    public DensityGeneratorNether(World world) {
+    public DensityGeneratorNether(@NotNull World world) {
         this.world = world;
 
         this.minLimitNoise = new FractalNoise3D<>(ImprovedPerlinNoise.genOctaves(world.getRandomSeed(), 16, 0));
@@ -38,7 +38,10 @@ public class DensityGeneratorNether
         int quarterYSize = ((terrainHeight / 4) / 8) + 1;
         int threeQuarterYSize = ((int) (terrainHeight * 0.75f) / 8) + 1;
 
-        double[] densityMapArray = new double[xSize * ySize * zSize];
+        final int outerSize = xSize * ySize * zSize;
+
+        // Allocate a fresh array locally for thread safety
+        double[] densityMapArray = new double[outerSize];
         double[] biomeDensityMap = this.generateDensityMap(xSize, threeQuarterYSize, zSize, x, y, z);
 
         for (int dx = 0; dx < xSize; dx++) {
@@ -53,8 +56,13 @@ public class DensityGeneratorNether
         return densityMapArray;
     }
 
-    private double[] generateDensityMap(int xSize, int ySize, int zSize, int x, int y, int z) {
-        double[] densityMapArray = new double[xSize * ySize * zSize];
+    private double @NotNull[] generateDensityMap(int xSize, int ySize, int zSize, int x, int y, int z) {
+        final int innerSize = xSize * ySize * zSize;
+
+        double[] densityMapArray = new double[innerSize];
+        double[] mainNoiseArray = new double[innerSize];
+        double[] minLimitArray = new double[innerSize];
+        double[] maxLimitArray = new double[innerSize];
 
         final double mainNoiseScaleX = 80.0;
         final double mainNoiseScaleY = 60.0;
@@ -66,10 +74,12 @@ public class DensityGeneratorNether
         final double upperLimitScale = 512.0;
         final double lowerLimitScale = 512.0;
 
-        double[] mainNoiseArray = this.mainNoise.getRegion(null, x, y, z, xSize, ySize, zSize, coordScale / mainNoiseScaleX, heightScale / mainNoiseScaleY, coordScale / mainNoiseScaleZ);
-        double[] minLimitArray = this.minLimitNoise.getRegion(null, x, y, z, xSize, ySize, zSize, coordScale, heightScale, coordScale);
-        double[] maxLimitArray = this.maxLimitNoise.getRegion(null, x, y, z, xSize, ySize, zSize, coordScale, heightScale, coordScale);
+        this.mainNoise.getRegion(mainNoiseArray, x, y, z, xSize, ySize, zSize, coordScale / mainNoiseScaleX, heightScale / mainNoiseScaleY, coordScale / mainNoiseScaleZ);
+        this.minLimitNoise.getRegion(minLimitArray, x, y, z, xSize, ySize, zSize, coordScale, heightScale, coordScale);
+        this.maxLimitNoise.getRegion(maxLimitArray, x, y, z, xSize, ySize, zSize, coordScale, heightScale, coordScale);
+
         int mainIndex = 0;
+
         double[] densityOffsetArray = new double[ySize];
         for (int dy = 0; dy < ySize; dy++) {
             densityOffsetArray[dy] = Math.cos(((double) dy * Math.PI * 6D) / (double) ySize) * 2D;
@@ -110,9 +120,7 @@ public class DensityGeneratorNether
                     densityMapArray[mainIndex] = density;
                     mainIndex++;
                 }
-
             }
-
         }
 
         return densityMapArray;
diff --git a/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/nether/SurfaceGeneratorNether.java b/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/nether/SurfaceGeneratorNether.java
index cc925031c..9f48d9c37 100644
--- a/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/nether/SurfaceGeneratorNether.java
+++ b/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/nether/SurfaceGeneratorNether.java
@@ -14,8 +14,7 @@ import org.jetbrains.annotations.NotNull;
 
 import java.util.Random;
 
-public class SurfaceGeneratorNether
-    implements SurfaceGenerator {
+public class SurfaceGeneratorNether implements SurfaceGenerator {
     private final @NotNull World world;
 
     private final @NotNull FractalNoise3D<ImprovedPerlinNoise> beachNoise;
@@ -24,7 +23,22 @@ public class SurfaceGeneratorNether
 
     private final boolean generateStoneVariants = true;
 
+    // Cache common block IDs to avoid lookups
+    private final short soulSandId = (short) Blocks.SOULSAND.id();
+    private final short sulfurId = (short) Blocks.SULFUR.id();
+    private final short sulfurHardenedId = (short) Blocks.SULFUR_HARDENED.id();
+    private final short brimsandId = (short) Blocks.BRIMSAND.id();
+    private final short brimstoneId = (short) Blocks.BRIMSTONE.id();
+    private final short netherrackId = (short) Blocks.NETHERRACK.id();
+    private final short cobbleNetherrackId = (short) Blocks.COBBLE_NETHERRACK.id();
+    private final short cobbleNetherrackCrystallineId = (short) Blocks.COBBLE_NETHERRACK_CRYSTALLINE.id();
+    private final short cobbleGloomstoneId = (short) Blocks.COBBLE_GLOOMSTONE.id();
+    private final short glowstoneId = (short) Blocks.GLOWSTONE.id();
+    private final short ashId = (short) Blocks.BLOCK_ASH.id();
+    private final short slateId = (short) Blocks.SLATE.id();
+
     public SurfaceGeneratorNether(@NotNull World world) {
+        super();
         this.world = world;
 
         this.beachNoise = new FractalNoise3D<>(ImprovedPerlinNoise.genOctaves(world.getRandomSeed(), 4, 40));
@@ -40,65 +54,70 @@ public class SurfaceGeneratorNether
         int chunkX = chunk.pos.x;
         int chunkZ = chunk.pos.z;
 
+        // Precompute
+        int absoluteOceanY = minY + oceanY;
+        int chunkWorldX = chunkX * Chunk.CHUNK_SIZE_X;
+        int chunkWorldZ = chunkZ * Chunk.CHUNK_SIZE_Z;
+
         int oceanBlock = this.world.getWorldType().getOceanBlockIds()[0];
         int worldFillBlock = this.world.getWorldType().getFillerBlockId();
 
         Random rand = new Random((long) chunkX * 0x4F9939F508L + (long) chunkZ * 0x1EF1565BD5L);
 
         double beachScale = 0.03125D;
-        double[] sandBeachNoise = this.beachNoise.getRegion(null, chunkX * 16, chunkZ * 16, 0.0D, 16, 16, 1, beachScale, beachScale, 1.0D);
-        double[] gravelBeachNoise = this.beachNoise.getRegion(null, chunkX * 16, 109.0134D, chunkZ * 16, 16, 1, 16, beachScale, 1.0D, beachScale);
-        double[] soilThicknessNoise = this.soilNoise.getRegion(null, chunkX * 16, chunkZ * 16, 0.0D, 16, 16, 1, beachScale * 2D, beachScale * 2D, beachScale * 2D);
+        double[] sandBeachNoise = this.beachNoise.getRegion(null, chunkWorldX, chunkWorldZ, 0.0D, 16, 16, 1, beachScale, beachScale, 1.0D);
+        double[] gravelBeachNoise = this.beachNoise.getRegion(null, chunkWorldX, 109.0134D, chunkWorldZ, 16, 1, 16, beachScale, 1.0D, beachScale);
+        double[] soilThicknessNoise = this.soilNoise.getRegion(null, chunkWorldX, chunkWorldZ, 0.0D, 16, 16, 1, beachScale * 2D, beachScale * 2D, beachScale * 2D);
 
-        double[] stoneLayerNoiseBasalt = null;
+/*        double[] stoneLayerNoiseBasalt = null;
         double[] stoneLayerNoiseNetherrack = null;
         double[] stoneLayerNoiseGloomstone = null;
         if (this.generateStoneVariants) {
             stoneLayerNoiseBasalt = this.soilNoise.getRegion(null, chunkX * Chunk.CHUNK_SIZE_X, chunkZ * Chunk.CHUNK_SIZE_Z, 0.0D, Chunk.CHUNK_SIZE_X, Chunk.CHUNK_SIZE_Z, 1, beachScale * 4D, beachScale * 4D, beachScale * 4D);
             stoneLayerNoiseNetherrack = this.mainNoise.getRegion(null, chunkX * Chunk.CHUNK_SIZE_X, chunkZ * Chunk.CHUNK_SIZE_Z, 0.0D, Chunk.CHUNK_SIZE_X, Chunk.CHUNK_SIZE_Z, 1, beachScale * 4D, beachScale * 4D, beachScale * 4D);
             stoneLayerNoiseGloomstone = this.beachNoise.getRegion(null, chunkX * Chunk.CHUNK_SIZE_X, chunkZ * Chunk.CHUNK_SIZE_Z, 0.0D, Chunk.CHUNK_SIZE_X, Chunk.CHUNK_SIZE_Z, 1, beachScale * 4D, beachScale * 4D, beachScale * 4D);
-        }
+        }*/
+
         for (int z = 0; z < 16; z++) {
             for (int x = 0; x < 16; x++) {
-                boolean generateSoulSandBeach = sandBeachNoise[z + x * 16] + rand.nextDouble() * 0.2D > 0.0D;
-                boolean generateGravelBeach = gravelBeachNoise[z + x * 16] + rand.nextDouble() * 0.2D > 0.0D;
-                int soilThickness = (int) (soilThicknessNoise[z + x * 16] / 3D + 3D + rand.nextDouble() * 0.25D);
-
-                boolean generateBasaltLayer = false;
-                boolean generateNetherrackLayer = false;
-                boolean generateGloomstoneLayer = false;
-
-                int basaltThicknessLevel = 0;
-                int netherrackThicknessLevel = 0;
-                int gloomstoneThicknessLevel = 0;
-
-                if (this.generateStoneVariants) {
-                    generateBasaltLayer = stoneLayerNoiseBasalt[z + (x * Chunk.CHUNK_SIZE_Z)] + rand.nextDouble() * 0.2D > 0D;
-                    generateNetherrackLayer = stoneLayerNoiseNetherrack[z + (x * Chunk.CHUNK_SIZE_Z)] + rand.nextDouble() * 0.2D > 2D;
-                    generateGloomstoneLayer = stoneLayerNoiseGloomstone[z + (x * Chunk.CHUNK_SIZE_Z)] + rand.nextDouble() * 0.2D > 3D;
-                    basaltThicknessLevel = (int)(stoneLayerNoiseBasalt[z + x] + rand.nextDouble() * 0.5D);
-                    netherrackThicknessLevel = (int)(stoneLayerNoiseNetherrack[z + x] + rand.nextDouble() * 0.5D);
-                    gloomstoneThicknessLevel = (int)(stoneLayerNoiseGloomstone[z + x] + rand.nextDouble() * 0.5D);
-                }
+                int noiseIndex = z + x * 16;
+                boolean generateSoulSandBeach = sandBeachNoise[noiseIndex] + rand.nextDouble() * 0.2D > 0.0D;
+                boolean generateGravelBeach = gravelBeachNoise[noiseIndex] + rand.nextDouble() * 0.2D > 0.0D;
+                int soilThickness = (int) (soilThicknessNoise[noiseIndex] / 3D + 3D + rand.nextDouble() * 0.25D);
 
                 int currentLayerDepth = -1;
                 short topBlock = -1;
                 short fillerBlock = -1;
 
                 Biome lastBiome = null;
+                int worldX = chunkWorldX + x;
+                int worldZ = chunkWorldZ + z;
+
+                short cachedBiomeTopBlock = -1;
+                short cachedBiomeFillerBlock = -1;
+                boolean cachedBiomeGeneratesSulfur = false;
+                int cachedBiomeStoneBlockId = worldFillBlock;
 
                 for (int y = maxY; y >= minY; y--) {
                     Biome biome = chunk.getBlockBiome(x, y, z);
-                    if (biome == null)
-                        biome = this.world.getBiomeProvider().getBiome(chunkX * Chunk.CHUNK_SIZE_X + x, y >> 3, chunkZ * Chunk.CHUNK_SIZE_Z + z);
+                    if (biome == null) {
+                        biome = this.world.getBiomeProvider().getBiome(worldX, y >> 3, worldZ);
+                    }
+
+                    if (biome != lastBiome) {
+                        cachedBiomeTopBlock = (short) biome.getSurfaceProperties().getTopBlock().id();
+                        cachedBiomeFillerBlock = (short) biome.getSurfaceProperties().getFillerBlock().id();
+                        cachedBiomeGeneratesSulfur = biome.hasTag(BiomeTags.HAS_SULFUR_POOLS);
+                        cachedBiomeStoneBlockId = computeBiomeStoneBlock(biome, worldFillBlock);
+                        lastBiome = biome;
+                    }
 
                     int block = result.getBlock(x, y, z);
 
-                    if ((biome != lastBiome || topBlock == -1 || fillerBlock == -1) && block == 0) {
-                        topBlock = (short) biome.getSurfaceProperties().getTopBlock().id();
-                        fillerBlock = (short) biome.getSurfaceProperties().getFillerBlock().id();
+                    if ((topBlock == -1 || fillerBlock == -1) && block == 0) {
+                        topBlock = cachedBiomeTopBlock;
+                        fillerBlock = cachedBiomeFillerBlock;
                     }
-                    lastBiome = biome;
 
                     // reset the currently generating surface thickness to -1 if encountered air
                     if (block == 0) {
@@ -116,38 +135,36 @@ public class SurfaceGeneratorNether
                         // if soil thickness is below 0, generate a stone basin where there is no top block layer
                         if (soilThickness <= 0) {
                             topBlock = 0;
-                            fillerBlock = (short) Blocks.NETHERRACK.id();
+                            if (biome == Biomes.NETHER_VOLCANIC_ISLANDS) {
+                                fillerBlock = brimsandId;
+                            } else if (biome == Biomes.NETHER_SULFUR_POOLS) {
+                                fillerBlock = sulfurId;
+                            } else if (biome == Biomes.NETHER_OLD_WORLD || biome == Biomes.NETHER_OLD_WORLD_DESERT) {
+                                fillerBlock = cobbleGloomstoneId;
+                            } else {
+                                fillerBlock = netherrackId;
+                            }
                         } else {
-                            boolean biomeGeneratesSulfur = biome.hasTag(BiomeTags.HAS_SULFUR_POOLS);
-
-                            if (y >= minY + oceanY - 4 && y <= minY + oceanY + 1) {
-                                // Generate coastlines
-                                topBlock = (short) biome.getSurfaceProperties().getTopBlock().id();
-                                fillerBlock = (short) biome.getSurfaceProperties().getFillerBlock().id();
-                                if (generateGravelBeach) {
-                                    topBlock = (short) Blocks.GRAVEL.id();
-                                    fillerBlock = (short) Blocks.GRAVEL.id();
-                                } if (generateSoulSandBeach) {
-                                    topBlock = (short) Blocks.SOULSAND.id();
-                                    fillerBlock = (short) Blocks.SOULSAND.id();
-                                }
-                            } else if (y <= oceanY) {
-                                if (biomeGeneratesSulfur) {
-                                    topBlock = (short) Blocks.BRIMSAND.id();
-                                    fillerBlock = (short) Blocks.BRIMSAND.id();
+                            if (y >= absoluteOceanY - 4 && y <= absoluteOceanY + 1) {
+                                topBlock = cachedBiomeTopBlock;
+                                fillerBlock = cachedBiomeFillerBlock;
+                                if (generateGravelBeach || generateSoulSandBeach) {
+                                    topBlock = soulSandId;
+                                    fillerBlock = soulSandId;
                                 }
+                            } else if (y <= oceanY && cachedBiomeGeneratesSulfur) {
+                                topBlock = sulfurId;
+                                fillerBlock = sulfurHardenedId;
                             }
                         }
 
-                        // failsafe so that if a basin is generated under the ocean, the ocean is not replaced with air
-                        if (y < minY + oceanY && topBlock == 0) {
+                        if (y < absoluteOceanY && topBlock == 0) {
                             topBlock = (short) oceanBlock;
                         }
-                        // if a new surface layer has been chosen to be generated, set the current layer depth to the generated soil thickness level and begin generating downwards
-                        currentLayerDepth = soilThickness+2;
 
-                        // set block at index to designated surface block above ocean level
-                        if (y >= minY + oceanY - 1) {
+                        currentLayerDepth = soilThickness + 2;
+
+                        if (y >= absoluteOceanY - 1) {
                             result.setBlock(x, y, z, topBlock);
                         } else {
                             // if the block is below ocean level, set it to the chosen filler block
@@ -156,45 +173,40 @@ public class SurfaceGeneratorNether
                         continue;
                     }
 
-                    if (this.generateStoneVariants) {
-                        if (currentLayerDepth <= 0) {
-                            if (y >= (minY + basaltThicknessLevel - rand.nextInt(3)) && y <= (maxY - basaltThicknessLevel + rand.nextInt(3))) {
-                                if (generateBasaltLayer) {
-                                    result.setBlock(x, y, z, Blocks.COBBLE_BASALT.id());
-                                    continue;
-                                }
-                            }
-                            if (y >= (minY + netherrackThicknessLevel - rand.nextInt(3)) && y <= (maxY - netherrackThicknessLevel + rand.nextInt(3))) {
-                                if (generateNetherrackLayer) {
-                                    result.setBlock(x, y, z, Blocks.NETHERRACK.id());
-                                    continue;
-                                }
-                            }
-                            if (y >= (minY + gloomstoneThicknessLevel - rand.nextInt(3)) && y <= (maxY - gloomstoneThicknessLevel + rand.nextInt(3))) {
-                                if (generateGloomstoneLayer) {
-                                    result.setBlock(x, y, z, Blocks.COBBLE_GLOOMSTONE.id());
-                                    continue;
-                                }
-                            }
-                            continue;
-                        }
-                    }
-
-                    // fill blocks with filler block until current layer level = -1 or air is encountered
                     if (currentLayerDepth > 0) {
-                        currentLayerDepth--;
                         result.setBlock(x, y, z, fillerBlock);
+                        currentLayerDepth--;
+                    }
+
+                    if (currentLayerDepth == 0 && biome == Biomes.NETHER_OLD_WORLD && fillerBlock == ashId) {
+                        currentLayerDepth = rand.nextInt(8) + 2;
+                        fillerBlock = slateId;
                     }
 
-                    if(currentLayerDepth == 0)
-                    {
-                        if(biome.hasTag(BiomeTags.HAS_SULFUR_POOLS) && fillerBlock == Blocks.BRIMSAND.id()) {
-                            currentLayerDepth = rand.nextInt(4) + 2;
-                            fillerBlock = (short)Blocks.BRIMSAND.id();
+                    if (this.generateStoneVariants && currentLayerDepth <= 0) {
+                        int stoneBlockId = cachedBiomeStoneBlockId;
+                        // Crag/shelf is the only biome with a randomized variant per block.
+                        if (biome == Biomes.NETHER_CRAG || biome == Biomes.NETHER_SHELF) {
+                            stoneBlockId = (rand.nextInt(2) == 0) ? netherrackId : cobbleNetherrackId;
                         }
+                        result.setBlock(x, y, z, stoneBlockId);
                     }
                 }
             }
         }
     }
-}
+
+    private int computeBiomeStoneBlock(Biome biome, int worldFillBlock) {
+        if (biome == Biomes.NETHER_VOLCANIC_ISLANDS) {
+            return brimsandId;
+        } else if (biome == Biomes.NETHER_SULFUR_POOLS) {
+            return brimstoneId;
+        } else if (biome == Biomes.NETHER_CRYSTAL_PLAINS || biome == Biomes.NETHER_CRYSTAL_FOREST) {
+            return cobbleNetherrackId;
+        } else if (biome == Biomes.NETHER_OLD_WORLD || biome == Biomes.NETHER_OLD_WORLD_DESERT) {
+            return cobbleGloomstoneId;
+        }
+        // Crag/Shelf use a randomized choice per block, so callers handle them separately.
+        return worldFillBlock;
+    }
+}
\ No newline at end of file
diff --git a/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/nether/TerrainGeneratorNether.java b/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/nether/TerrainGeneratorNether.java
index 296323d8b..6c0280e47 100644
--- a/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/nether/TerrainGeneratorNether.java
+++ b/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/nether/TerrainGeneratorNether.java
@@ -1,100 +1,111 @@
 package net.minecraft.core.world.generate.chunk.perlin.nether;
 
-import it.unimi.dsi.fastutil.objects.Object2IntArrayMap;
-import it.unimi.dsi.fastutil.objects.Object2IntMap;
 import net.minecraft.core.block.Blocks;
 import net.minecraft.core.world.World;
 import net.minecraft.core.world.biome.Biome;
-import net.minecraft.core.world.biome.BiomeTags;
 import net.minecraft.core.world.biome.Biomes;
 import net.minecraft.core.world.chunk.Chunk;
-import net.minecraft.core.world.chunk.ChunkSection;
 import net.minecraft.core.world.generate.chunk.perlin.DensityGenerator;
 import net.minecraft.core.world.generate.chunk.perlin.TerrainGeneratorLerp;
-import net.minecraft.core.world.noise.FractalNoise3D;
-import net.minecraft.core.world.noise.ImprovedPerlinNoise;
-import net.minecraft.core.world.noise.Noise3D;
-import net.minecraft.core.world.type.WorldType;
 import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
 
-public class TerrainGeneratorNether
-    extends TerrainGeneratorLerp {
-    private final DensityGenerator densityGenerator;
+public class TerrainGeneratorNether extends TerrainGeneratorLerp {
+    private static final int OCEAN_FLUID_UNCACHED = -1;
 
-    private final int lastX = Integer.MIN_VALUE;
-    private final int lastZ = Integer.MIN_VALUE;
-    private @Nullable Biome lastBiome = null;
-    private final Object2IntMap<Biome> fluidLookup = new Object2IntArrayMap<>();
-//    private int lastChunkX = Integer.MIN_VALUE;
-//    private int lastChunkZ = Integer.MIN_VALUE;
-//    private final @NotNull Noise3D noise = new FractalNoise3D<>(ImprovedPerlinNoise.genOctaves(0L, 4, 0));
-//    private final double @NotNull [] cache = new double[Chunk.CHUNK_SIZE_X * Chunk.CHUNK_SIZE_Z * (Chunk.CHUNK_SECTIONS * ChunkSection.SECTION_SIZE_Y)];
+    private final DensityGenerator densityGenerator;
     private final int minY;
     private final int maxY;
+    private final int quarterHeight;
+    private final int bedrockId;
+    private final int airId;
+    private final int rubyglassId;
+    private final int waterId;
+    private final int oceanY;
+    private final int fillerBlockId;
+    private final int lavaId;
+    private final int cobbleNetherrackId;
+    private final int obsidianId;
+
+    private final ThreadLocal<int[]> oceanFluidByXZ = ThreadLocal.withInitial(() -> new int[256]);
+    private final ThreadLocal<Chunk> lastChunk = new ThreadLocal<>();;
 
     public TerrainGeneratorNether(@NotNull World world) {
         super(world);
 
         this.densityGenerator = new DensityGeneratorNether(world);
 
-        this.fluidLookup.put(Biomes.NETHER_VOLCANIC_ISLANDS, Blocks.FLUID_LAVA_STILL.id());
-        this.fluidLookup.put(Biomes.NETHER_SULFUR_POOLS, Blocks.FLUID_LAVA_STILL.id());
-        this.fluidLookup.put(Biomes.NETHER_CRAG, Blocks.FLUID_LAVA_STILL.id());
-        this.fluidLookup.put(Biomes.NETHER_SHELF, Blocks.COBBLE_NETHERRACK.id());
-        this.fluidLookup.put(Biomes.NETHER_CRYSTAL_FOREST, Blocks.FLUID_WATER_STILL.id());
-        this.fluidLookup.put(Biomes.NETHER_CRYSTAL_PLAINS, Blocks.FLUID_WATER_STILL.id());
-        this.fluidLookup.put(Biomes.NETHER_OLD_WORLD, Blocks.FLUID_WATER_STILL.id());
-        this.fluidLookup.put(Biomes.NETHER_OLD_WORLD_DESERT, Blocks.FLUID_WATER_STILL.id());
-
         this.minY = world.getWorldType().getMinY(world);
         this.maxY = world.getWorldType().getMaxY(world);
+        this.quarterHeight = this.maxY / 4;
+
+        // Cache block IDs to avoid repeated lookups to the Block objects themselves.
+        this.bedrockId = Blocks.BEDROCK.id();
+        this.airId = Blocks.AIR.id();
+        this.rubyglassId = Blocks.RUBYGLASS_CRYSTAL.id();
+        this.waterId = Blocks.FLUID_WATER_STILL.id();
+        this.lavaId = Blocks.FLUID_LAVA_STILL.id();
+        this.cobbleNetherrackId = Blocks.COBBLE_NETHERRACK.id();
+        this.obsidianId = Blocks.OBSIDIAN.id();
 
+        this.oceanY = world.getWorldType().getOceanY();
+        this.fillerBlockId = world.getWorldType().getFillerBlockId();
     }
 
-    @Override
-    protected int getBlockAt(@NotNull Chunk chunk, int x, int y, int z, double density) {
-        if (this.lastX != x || this.lastZ != z) {
-            this.lastBiome = chunk.getBlockBiome(x, 0, z);
+    private int getFluidForBiome(Biome biome) {
+        if (biome == Biomes.NETHER_VOLCANIC_ISLANDS || biome == Biomes.NETHER_SULFUR_POOLS || biome == Biomes.NETHER_CRAG) {
+            return this.lavaId;
+        } else if (biome == Biomes.NETHER_CRYSTAL_FOREST || biome == Biomes.NETHER_CRYSTAL_PLAINS) {
+            return this.waterId;
+        } else if (biome == Biomes.NETHER_SHELF) {
+            return this.cobbleNetherrackId;
+        } else if (biome == Biomes.NETHER_OLD_WORLD) {
+            return this.obsidianId;
         }
+        return 0;
+    }
 
-        WorldType type = this.world.getWorldType();
-        int quarterHeight = maxY / 4;
+    private int getCachedOceanFluid(@NotNull Chunk chunk, int x, int z) {
+        int[] cache = this.oceanFluidByXZ.get();
 
-        if (y < quarterHeight) {
-            return 0;
-        } else if (y >= maxY - this.rand.nextInt(5)) {
-            return Blocks.BEDROCK.id();
-        } else if (y <= quarterHeight + this.rand.nextInt(5)) {
-            return Blocks.BEDROCK.id();
-        } else if (density > 0.0) {
-//            Disabled crystalline netherrack veins
-//            if (this.lastBiome != null && this.lastBiome.hasTag(BiomeTags.HAS_CRYSTAL_VEINS)) {
-//                int sizeY = Chunk.CHUNK_SECTIONS * ChunkSection.SECTION_SIZE_Y;
-//
-//                if (this.lastChunkX != chunk.pos.x || this.lastChunkZ != chunk.pos.z) {
-//                    this.lastChunkX = chunk.pos.x;
-//                    this.lastChunkZ = chunk.pos.z;
-//                    this.noise.getRegion(this.cache, chunk.pos.x * Chunk.CHUNK_SIZE_X, 0, chunk.pos.z * Chunk.CHUNK_SIZE_Z, Chunk.CHUNK_SIZE_X, Chunk.CHUNK_SECTIONS * ChunkSection.SECTION_SIZE_Y, Chunk.CHUNK_SIZE_Z, 0.2, 0.2, 0.2);
-//                }
-//
-//                // YZX ordering
-//                final double d = ((this.cache[(x * Chunk.CHUNK_SIZE_Z * sizeY) + (z * sizeY) + y]));
-//                if (d > -1 && d < 2) {
-//                    return Blocks.COBBLE_NETHERRACK_CRYSTALLINE.id();
-//                }
-//            }
-
-            return type.getFillerBlockId();
-        } else if (y < type.getOceanY()) {
-            return this.fluidLookup.getOrDefault(this.lastBiome, 0);
+        if (chunk != this.lastChunk.get()) {
+            java.util.Arrays.fill(cache, OCEAN_FLUID_UNCACHED);
+            this.lastChunk.set(chunk);
         }
 
-        return 0;
+        final int idx = (x << 4) | z;
+        int cached = cache[idx];
+        if (cached == OCEAN_FLUID_UNCACHED) {
+            cached = this.getFluidForBiome(chunk.getBlockBiome(x, this.oceanY, z));
+            cache[idx] = cached;
+        }
+        return cached;
+    }
+
+    @Override
+    protected int getBlockAt(@NotNull Chunk chunk, int x, int y, int z, double density) {
+        if (y < this.quarterHeight) {
+            return this.bedrockId;
+        } else if (y >= this.maxY - this.rand.nextInt(10)) {
+            return this.bedrockId;
+        } else if (y <= this.quarterHeight + this.rand.nextInt(10)) {
+            return this.bedrockId;
+        } else if (density > 0.0) {
+            return this.fillerBlockId;
+        } else if (y < this.oceanY) {
+            final int fluidId = this.getCachedOceanFluid(chunk, x, z);
+
+            if (fluidId == this.waterId && y == this.oceanY - 1) {
+                return this.rubyglassId;
+            }
+
+            return fluidId;
+        } else {
+            return this.airId;
+        }
     }
 
     @Override
     public @NotNull DensityGenerator getDensityGenerator() {
         return this.densityGenerator;
     }
-}
+}
\ No newline at end of file
diff --git a/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/overworld/ChunkDecoratorOverworld.java b/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/overworld/ChunkDecoratorOverworld.java
index 68869919e..08ede7be9 100644
--- a/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/overworld/ChunkDecoratorOverworld.java
+++ b/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/overworld/ChunkDecoratorOverworld.java
@@ -1,5 +1,6 @@
 package net.minecraft.core.world.generate.chunk.perlin.overworld;
 
+import com.mojang.logging.LogUtils;
 import net.minecraft.core.block.BlockLogicLeavesBase;
 import net.minecraft.core.block.BlockLogicMoss;
 import net.minecraft.core.block.BlockLogicOreCoal;
@@ -21,17 +22,29 @@ import net.minecraft.core.world.biome.overworld.BiomeOutback;
 import net.minecraft.core.world.chunk.Chunk;
 import net.minecraft.core.world.generate.chunk.ChunkDecorator;
 import net.minecraft.core.world.generate.feature.*;
+import net.minecraft.core.world.pos.TilePos;
 import net.minecraft.core.world.type.tag.WorldTypeTags;
 import org.jetbrains.annotations.NotNull;
+import org.slf4j.Logger;
 
 import java.util.Random;
 
 public class ChunkDecoratorOverworld
     implements ChunkDecorator {
+    private static final @NotNull Logger LOGGER = LogUtils.getLogger();
+
     private final @NotNull World world;
     private final @NotNull FractalNoise2D<ImprovedPerlinNoise> treeDensityNoise;
     private final int treeDensityOverride;
 
+    private static void runStep(@NotNull String name, int chunkX, int chunkZ, @NotNull Runnable action) {
+        try {
+            action.run();
+        } catch (final Throwable t) {
+            LOGGER.error("Decoration step '{}' failed at chunk ({}, {}); continuing.", name, chunkX, chunkZ, t);
+        }
+    }
+
     protected ChunkDecoratorOverworld(@NotNull World world, int treeDensityOverride) {
         this.world = world;
         this.treeDensityOverride = treeDensityOverride;
@@ -46,8 +59,12 @@ public class ChunkDecoratorOverworld
     @Override
     public void decorate(@NotNull Chunk chunk) {
         this.world.scheduledUpdatesAreImmediate = true;
-        int chunkX = chunk.pos.x;
-        int chunkZ = chunk.pos.z;
+        BlockLogicFallingBlock.fallInstantly = true;
+
+        final int chunkX = chunk.pos.x;
+        final int chunkZ = chunk.pos.z;
+
+        try {
 
         int minY = this.world.getWorldType().getMinY(world);
         int maxY = this.world.getWorldType().getMaxY(world);
@@ -55,7 +72,6 @@ public class ChunkDecoratorOverworld
 
         float oreHeightModifier = rangeY / 128f;
 
-        BlockLogicFallingBlock.fallInstantly = true;
         int x = chunkX * 16;
         int z = chunkZ * 16;
         int y = this.world.getHeightValue(x + 16, z + 16);
@@ -67,6 +83,7 @@ public class ChunkDecoratorOverworld
         Random swampRand = new Random((long) chunkX * l1 + (long) chunkZ * l2 ^ this.world.getRandomSeed());
 
         // Water pass for swamps
+        try {
         if (biome.hasTag(BiomeTags.HAS_SURFACE_WATER)) {
             for (int dx = 0; dx < 16; dx++) {
                 for (int dz = 0; dz < 16; dz++) {
@@ -95,7 +112,9 @@ public class ChunkDecoratorOverworld
                 }
             }
         }
+        } catch (final Throwable t) { LOGGER.error("Decoration step 'swamp_water' failed at chunk ({}, {}); continuing.", chunkX, chunkZ, t); }
 
+        try {
         int lakeChance = 4;
         if (biome == Biomes.OVERWORLD_SWAMPLAND)
             lakeChance = 2;
@@ -119,6 +138,9 @@ public class ChunkDecoratorOverworld
                 new WorldFeatureLake(Blocks.FLUID_LAVA_STILL.id()).place(this.world, rand, xf, yf, zf);
             }
         }
+        } catch (final Throwable t) { LOGGER.error("Decoration step 'lakes' failed at chunk ({}, {}); continuing.", chunkX, chunkZ, t); }
+
+        try {
         for (int k1 = 0; k1 < (8 * oreHeightModifier); k1++) {
             int j5 = x + rand.nextInt(16) + 8;
             int k8 = minY + rand.nextInt(rangeY);
@@ -148,7 +170,9 @@ public class ChunkDecoratorOverworld
                 new WorldFeatureDungeon(Blocks.COBBLE_BASALT.id(), Blocks.COBBLE_BASALT_MOSSY.id(), null).place(this.world, rand, j5, k8, j11);
             }
         }
+        } catch (final Throwable t) { LOGGER.error("Decoration step 'dungeons' failed at chunk ({}, {}); continuing.", chunkX, chunkZ, t); }
 
+        try {
         for (int k1 = 0; k1 < 1; k1++) {
             int j5 = x + rand.nextInt(16) + 8;
             int j11 = z + rand.nextInt(16) + 8;
@@ -164,7 +188,9 @@ public class ChunkDecoratorOverworld
                 (new WorldFeatureLabyrinth()).place(this.world, lRand, j5, k8, j11);
             }
         }
+        } catch (final Throwable t) { LOGGER.error("Decoration step 'labyrinth' failed at chunk ({}, {}); continuing.", chunkX, chunkZ, t); }
 
+        try {
         for (int i2 = 0; i2 < (20 * oreHeightModifier); i2++) {
             int k5 = x + rand.nextInt(16);
             int l8 = minY + rand.nextInt(rangeY);
@@ -186,71 +212,83 @@ public class ChunkDecoratorOverworld
                 (new WorldFeaturePermaice(32 + rand.nextInt(32), Blocks.PERMAFROST)).place(this.world, rand, j7, k10, j13);
             }
         }
+        } catch (final Throwable t) { LOGGER.error("Decoration step 'clay_dirt_permaice' failed at chunk ({}, {}); continuing.", chunkX, chunkZ, t); }
 
+        try {
         for (int j2 = 0; j2 < (20 * oreHeightModifier); j2++) {
             int l5 = x + rand.nextInt(16);
             int i9 = minY + rand.nextInt(rangeY);
             int l11 = z + rand.nextInt(16);
-            (new WorldFeatureOre(Blocks.DIRT.id(), 32)).place(this.world, rand, l5, i9, l11);
+            TilePos queryPos = new TilePos(l5, i9, l11);
+            (new WorldFeatureOre(Blocks.DIRT.id(), 32)).place(this.world, rand, queryPos);
         }
 
         for (int k2 = 0; k2 < (10 * oreHeightModifier); k2++) {
             int i6 = x + rand.nextInt(16);
             int j9 = minY + rand.nextInt(rangeY);
             int i12 = z + rand.nextInt(16);
-            (new WorldFeatureOre(Blocks.GRAVEL.id(), 32)).place(this.world, rand, i6, j9, i12);
+            TilePos queryPos = new TilePos(i6, j9, i12);
+            (new WorldFeatureOre(Blocks.GRAVEL.id(), 32)).place(this.world, rand, queryPos);
         }
 
         for (int i3 = 0; i3 < (20 * oreHeightModifier); i3++) {
             int j6 = x + rand.nextInt(16);
             int k9 = minY + rand.nextInt(rangeY);
             int j12 = z + rand.nextInt(16);
-            new WorldFeatureOre(BlockLogicOreCoal.variantMap, 16).place(this.world, rand, j6, k9, j12);
+            TilePos queryPos = new TilePos(j6, k9, j12);
+            new WorldFeatureOre(BlockLogicOreCoal.variantMap, 16).place(this.world, rand, queryPos);
         }
 
         for (int j3 = 0; j3 < (20 * oreHeightModifier); j3++) {
             int k6 = x + rand.nextInt(16);
             int l9 = minY + rand.nextInt(rangeY / 2);
             int k12 = z + rand.nextInt(16);
-            (new WorldFeatureOre(BlockLogicOreIron.variantMap, 8)).place(this.world, rand, k6, l9, k12);
+            TilePos queryPos = new TilePos(k6, l9, k12);
+            (new WorldFeatureOre(BlockLogicOreIron.variantMap, 8)).place(this.world, rand, queryPos);
         }
 
         for (int k3 = 0; k3 < (2 * oreHeightModifier); k3++) {
             int l6 = x + rand.nextInt(16);
             int i10 = minY + rand.nextInt(rangeY / 4);
             int l12 = z + rand.nextInt(16);
-            (new WorldFeatureOre(BlockLogicOreGold.variantMap, 8)).place(this.world, rand, l6, i10, l12);
+            TilePos queryPos = new TilePos(l6, i10, l12);
+            (new WorldFeatureOre(BlockLogicOreGold.variantMap, 8)).place(this.world, rand, queryPos);
         }
 
         for (int l3 = 0; l3 < (8 * oreHeightModifier); l3++) {
             int i7 = x + rand.nextInt(16);
             int j10 = minY + rand.nextInt(rangeY / 8);
             int i13 = z + rand.nextInt(16);
-            (new WorldFeatureOre(BlockLogicOreRedstone.variantMap, 7)).place(this.world, rand, i7, j10, i13);
+            TilePos queryPos = new TilePos(i7, j10, i13);
+            (new WorldFeatureOre(BlockLogicOreRedstone.variantMap, 7)).place(this.world, rand, queryPos);
         }
 
         for (int i4 = 0; i4 < oreHeightModifier; i4++) {
             int j7 = x + rand.nextInt(16);
             int k10 = minY + rand.nextInt(rangeY / 8);
             int j13 = z + rand.nextInt(16);
-            (new WorldFeatureOre(BlockLogicOreDiamond.variantMap, 7)).place(this.world, rand, j7, k10, j13);
+            TilePos queryPos = new TilePos(j7, k10, j13);
+            (new WorldFeatureOre(BlockLogicOreDiamond.variantMap, 7)).place(this.world, rand, queryPos);
         }
 
         for (int i4 = 0; i4 < oreHeightModifier; i4++) {
             int j7 = x + rand.nextInt(16);
             int k10 = minY + rand.nextInt(rangeY / 2);
             int j13 = z + rand.nextInt(16);
-            (new WorldFeatureMoss(BlockLogicMoss.variantMap, 32)).place(this.world, rand, j7, k10, j13);
+            TilePos queryPos = new TilePos(j7, k10, j13);
+            (new WorldFeatureMoss(BlockLogicMoss.variantMap, 32)).place(this.world, rand, queryPos);
         }
 
         for (int j4 = 0; j4 < oreHeightModifier; j4++) {
             int k7 = x + rand.nextInt(16);
             int l10 = minY + rand.nextInt(rangeY / 8) + rand.nextInt(rangeY / 8);
             int k13 = z + rand.nextInt(16);
-            (new WorldFeatureOre(BlockLogicOreLapis.variantMap, 6)).place(this.world, rand, k7, l10, k13);
+            TilePos queryPos = new TilePos(k7, l10, k13);
+            (new WorldFeatureOre(BlockLogicOreLapis.variantMap, 6)).place(this.world, rand, queryPos);
         }
+        } catch (final Throwable t) { LOGGER.error("Decoration step 'ores' failed at chunk ({}, {}); continuing.", chunkX, chunkZ, t); }
 
-
+        try {
         double d = 0.5D;
         int k4 = (int) ((this.treeDensityNoise.getValue((double) x * d, (double) z * d) / 8D + rand.nextDouble() * 4D + 4D) / 3D);
         int treeDensity = 0;
@@ -300,15 +338,24 @@ public class ChunkDecoratorOverworld
         try {
             BlockLogicLeavesBase.enableDecay = false;
             for (int i11 = 0; i11 < treeDensity; i11++) {
-                int l13 = x + rand.nextInt(16) + 8;
-                int j14 = z + rand.nextInt(16) + 8;
-                WorldFeature feature = biome.getTreeFeature(rand);
-                feature.init(1.0D, 1.0D, 1.0D);
-                feature.place(this.world, rand, l13, this.world.getHeightValue(l13, j14), j14);
+                // Per-tree try/catch: a single bad placement (e.g. NPE inside a tree feature)
+                // would otherwise abort the rest of the forest and produce visible cutoffs.
+                try {
+                    int l13 = x + rand.nextInt(16) + 8;
+                    int j14 = z + rand.nextInt(16) + 8;
+                    WorldFeature feature = biome.getTreeFeature(rand);
+                    feature.init(1.0D, 1.0D, 1.0D);
+                    feature.place(this.world, rand, l13, this.world.getHeightValue(l13, j14), j14);
+                } catch (final Throwable treeT) {
+                    LOGGER.error("Tree placement #{} failed at chunk ({}, {}); continuing.", i11, chunkX, chunkZ, treeT);
+                }
             }
         } finally {
             BlockLogicLeavesBase.enableDecay = true;
         }
+        } catch (final Throwable t) { LOGGER.error("Decoration step 'trees' failed at chunk ({}, {}); continuing.", chunkX, chunkZ, t); }
+
+        try {
         byte byteReeds = 0;
         if (biome == Biomes.OVERWORLD_RAINFOREST) {
             byteReeds = 1;
@@ -363,7 +410,9 @@ public class ChunkDecoratorOverworld
             int j24 = z + rand.nextInt(16) + 8;
             (new WorldFeatureFlowers(blockId, 128, true)).place(this.world, rand, l19, k22, j24);
         }
+        } catch (final Throwable t) { LOGGER.error("Decoration step 'reeds_orchids_meadows' failed at chunk ({}, {}); continuing.", chunkX, chunkZ, t); }
 
+        try {
         byte byteBluebells = 0;
         if (biome == Biomes.OVERWORLD_FOREST) {
             byteBluebells = 2;
@@ -435,7 +484,9 @@ public class ChunkDecoratorOverworld
             int k19 = z + rand.nextInt(16) + 8;
             (new WorldFeatureFlowers(Blocks.FLOWER_YELLOW.id(), 64, true)).place(this.world, rand, k14, l16, k19);
         }
+        } catch (final Throwable t) { LOGGER.error("Decoration step 'flowers_blue_orange_yellow' failed at chunk ({}, {}); continuing.", chunkX, chunkZ, t); }
 
+        try {
         byte byte1 = 0;
         if (biome == Biomes.OVERWORLD_FOREST) {
             byte1 = 2;
@@ -502,7 +553,9 @@ public class ChunkDecoratorOverworld
             int l22 = z + rand.nextInt(16) + 8;
             (new WorldFeatureDeadBush(Blocks.DEADBUSH.id())).place(this.world, rand, i17, i20, l22);
         }
+        } catch (final Throwable t) { LOGGER.error("Decoration step 'tallgrass_spinifex_deadbush' failed at chunk ({}, {}); continuing.", chunkX, chunkZ, t); }
 
+        try {
         if (rand.nextInt(2) == 0) {
             int j15 = x + rand.nextInt(16) + 8;
             int j17 = minY + rand.nextInt(rangeY);
@@ -554,7 +607,9 @@ public class ChunkDecoratorOverworld
             int k24 = z + rand.nextInt(16) + 8;
             (new WorldFeatureCactus()).place(this.world, rand, k21, j23, k24);
         }
+        } catch (final Throwable t) { LOGGER.error("Decoration step 'mushrooms_pumpkin_sponge_cactus' failed at chunk ({}, {}); continuing.", chunkX, chunkZ, t); }
 
+        try {
         for (int l18 = 0; l18 < 50; l18++) {
             int l21 = x + rand.nextInt(16) + 8;
             int k23 = minY + rand.nextInt(rand.nextInt(rangeY - (rangeY / 16)) + (rangeY / 16));
@@ -568,7 +623,9 @@ public class ChunkDecoratorOverworld
             int i25 = z + rand.nextInt(16) + 8;
             (new WorldFeatureLiquid(Blocks.FLUID_LAVA_FLOWING.id())).place(this.world, rand, i22, l23, i25);
         }
+        } catch (final Throwable t) { LOGGER.error("Decoration step 'liquids' failed at chunk ({}, {}); continuing.", chunkX, chunkZ, t); }
 
+        try {
         int numPebbles = 0;
         if (biome == Biomes.OVERWORLD_FOREST) {
             numPebbles = 2;
@@ -598,31 +655,104 @@ public class ChunkDecoratorOverworld
             final int zz = z + rand.nextInt(16) + 8;
             new WorldFeaturePebbles(Blocks.OVERLAY_PEBBLES, 16, true).place(this.world, rand, xx, yy, zz);
         }
+        } catch (final Throwable t) { LOGGER.error("Decoration step 'pebbles' failed at chunk ({}, {}); continuing.", chunkX, chunkZ, t); }
 
-        int oceanY = this.world.getWorldType().getOceanY();
-        for (int dx = x + 8; dx < x + 8 + 16; dx++) {
-            for (int dz = z + 8; dz < z + 8 + 16; dz++) {
-                int dy = this.world.getHeightValue(dx, dz);
-                Biome localBiome = this.world.getBlockBiome(dx, dy, dz);
-
-                if ((localBiome.hasTag(BiomeTags.HAS_SURFACE_SNOW) || this.world.getWorldType().hasTag(WorldTypeTags.ENDLESS_SNOW)) &&
-                    dy > 0 && dy < this.world.getHeightBlocks())
-                {
-                    if (this.world.isAirBlock(dx, dy, dz) && this.world.getBlockMaterial(dx, dy - 1, dz).blocksMotion()) {
-                        this.world.setBlockWithNotify(dx, dy, dz, Blocks.LAYER_SNOW.id());
+        // Snow/ice pass
+        final int oceanY = this.world.getWorldType().getOceanY();
+        try {
+            for (int dx = x + 8; dx < x + 8 + 16; dx++) {
+                for (int dz = z + 8; dz < z + 8 + 16; dz++) {
+                    try {
+                        applySnowAndIceForColumn(dx, dz, oceanY);
+                    } catch (final Throwable colT) {
+                        LOGGER.error("Snow/ice column ({}, {}) failed at chunk ({}, {}); continuing.", dx, dz, chunkX, chunkZ, colT);
                     }
                 }
-                if ((localBiome.hasTag(BiomeTags.HAS_SURFACE_ICE) || this.world.getWorldType().hasTag(WorldTypeTags.ENDLESS_SNOW)) &&
-                    (this.world.getBlockId(dx, oceanY - 1, dz) == Blocks.FLUID_WATER_STILL.id() || this.world.getBlockId(dx, oceanY - 1, dz) == Blocks.FLUID_WATER_FLOWING.id())
-                ) {
-                    this.world.setBlockWithNotify(dx, oceanY - 1, dz, Blocks.ICE.id());
-                }
+            }
+        } catch (final Throwable t) { LOGGER.error("Decoration step 'snow_ice' failed at chunk ({}, {}); continuing.", chunkX, chunkZ, t); }
 
+        // Spillover re-snow pass
+        try {
+            for (int dx = x; dx < x + 32; dx++) {
+                for (int dz = z; dz < z + 32; dz++) {
+                    final int ownerChunkX = (dx - 8) >> 4;
+                    final int ownerChunkZ = (dz - 8) >> 4;
+                    if (ownerChunkX == chunkX && ownerChunkZ == chunkZ) { continue; }
+                    if (!this.world.isChunkLoaded(ownerChunkX, ownerChunkZ)) { continue; }
+                    final Chunk owner = this.world.getChunkFromChunkCoords(ownerChunkX, ownerChunkZ);
+                    if (!owner.isTerrainPopulated) { continue; }
+                    final int physicalChunkX = dx >> 4;
+                    final int physicalChunkZ = dz >> 4;
+                    if (!this.world.isChunkLoaded(physicalChunkX, physicalChunkZ)) { continue; }
+                    try {
+                        applySnowAndIceForColumn(dx, dz, oceanY);
+                    } catch (final Throwable colT) {
+                        LOGGER.error("Spillover snow/ice column ({}, {}) failed at chunk ({}, {}); continuing.", dx, dz, chunkX, chunkZ, colT);
+                    }
+                }
             }
+        } catch (final Throwable t) { LOGGER.error("Decoration step 'snow_ice_spillover' failed at chunk ({}, {}); continuing.", chunkX, chunkZ, t); }
 
+        } finally {
+            BlockLogicFallingBlock.fallInstantly = false;
+            this.world.scheduledUpdatesAreImmediate = false;
         }
+    }
 
-        BlockLogicFallingBlock.fallInstantly = false;
-        this.world.scheduledUpdatesAreImmediate = false;
+    /**
+     * Places snow on the topmost surface of the column AND on the ground beneath any
+     * tree canopy in the column, plus ice on top of water at the ocean surface.
+     * Existing snow is not duplicated. Safe to call multiple times for the same column
+     */
+    private void applySnowAndIceForColumn(final int dx, final int dz, final int oceanY) {
+        final int dy = this.world.getHeightValue(dx, dz);
+        final Biome localBiome = this.world.getBlockBiome(dx, dy, dz);
+
+        final boolean wantsSnow = localBiome.hasTag(BiomeTags.HAS_SURFACE_SNOW) || this.world.getWorldType().hasTag(WorldTypeTags.ENDLESS_SNOW);
+        if (wantsSnow && dy > 0 && dy < this.world.getHeightBlocks()) {
+            if (this.world.isAirBlock(dx, dy, dz) && this.world.getBlockMaterial(dx, dy - 1, dz).blocksMotion()) {
+                this.world.setBlockWithNotify(dx, dy, dz, Blocks.LAYER_SNOW.id());
+            }
+            final int minScanY = Math.max(1, dy - 32);
+            boolean passedTree = false;
+            for (int scanY = dy - 1; scanY > minScanY; scanY--) {
+                final int hereId = this.world.getBlockId(dx, scanY, dz);
+                final net.minecraft.core.block.Block<?> here = hereId > 0 ? Blocks.blocksList[hereId] : null;
+                if (here != null && (here.getLogic() instanceof BlockLogicLeavesBase || here.getMaterial() == Materials.WOOD)) {
+                    passedTree = true;
+                    continue;
+                }
+                if (here == Blocks.LAYER_SNOW) {
+                    continue;
+                }
+                if (hereId == 0) {
+                    if (!passedTree) { continue; }
+                    final int belowId = this.world.getBlockId(dx, scanY - 1, dz);
+                    if (belowId == 0) { continue; }
+                    final net.minecraft.core.block.Block<?> below = Blocks.blocksList[belowId];
+                    if (below == null) { continue; }
+                    if (below == Blocks.LAYER_SNOW) { continue; }
+                    if (below.getLogic() instanceof BlockLogicLeavesBase) {
+                        this.world.setBlockWithNotify(dx, scanY, dz, Blocks.LAYER_SNOW.id());
+                        continue;
+                    }
+                    if (below.getMaterial() == Materials.WOOD) { continue; }
+                    if (below.getMaterial().blocksMotion()) {
+                        this.world.setBlockWithNotify(dx, scanY, dz, Blocks.LAYER_SNOW.id());
+                        break;
+                    }
+                    continue;
+                }
+                break;
+            }
+        }
+
+        final boolean wantsIce = localBiome.hasTag(BiomeTags.HAS_SURFACE_ICE) || this.world.getWorldType().hasTag(WorldTypeTags.ENDLESS_SNOW);
+        if (wantsIce) {
+            final int oceanFluidId = this.world.getBlockId(dx, oceanY - 1, dz);
+            if (oceanFluidId == Blocks.FLUID_WATER_STILL.id() || oceanFluidId == Blocks.FLUID_WATER_FLOWING.id()) {
+                this.world.setBlockWithNotify(dx, oceanY - 1, dz, Blocks.ICE.id());
+            }
+        }
     }
 }
diff --git a/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/overworld/b173/ChunkDecoratorOverworldB173.java b/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/overworld/b173/ChunkDecoratorOverworldB173.java
index f71d51782..d518830a0 100644
--- a/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/overworld/b173/ChunkDecoratorOverworldB173.java
+++ b/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/overworld/b173/ChunkDecoratorOverworldB173.java
@@ -1,7 +1,10 @@
 package net.minecraft.core.world.generate.chunk.perlin.overworld.b173;
 
+import com.mojang.logging.LogUtils;
 import net.minecraft.core.block.BlockLogicFallingBlock;
+import net.minecraft.core.block.BlockLogicLeavesBase;
 import net.minecraft.core.block.Blocks;
+import net.minecraft.core.block.material.Materials;
 import net.minecraft.core.world.World;
 import net.minecraft.core.world.biome.Biome;
 import net.minecraft.core.world.biome.BiomeTags;
@@ -11,12 +14,16 @@ import net.minecraft.core.world.generate.chunk.ChunkDecorator;
 import net.minecraft.core.world.generate.feature.*;
 import net.minecraft.core.world.noise.FractalNoise2D;
 import net.minecraft.core.world.noise.ImprovedPerlinNoise;
+import net.minecraft.core.world.pos.TilePos;
 import org.jetbrains.annotations.NotNull;
+import org.slf4j.Logger;
 
 import java.util.Random;
 
 public class ChunkDecoratorOverworldB173
     implements ChunkDecorator {
+    private static final @NotNull Logger LOGGER = LogUtils.getLogger();
+
     private final @NotNull World world;
     private final @NotNull FractalNoise2D<ImprovedPerlinNoise> forestNoise;
 
@@ -34,6 +41,8 @@ public class ChunkDecoratorOverworldB173
         final int x = chunk.pos.x;
         final int z = chunk.pos.z;
 
+        try {
+
         final int minY = this.world.getWorldType().getMinY(world);
         final int maxY = this.world.getWorldType().getMaxY(world);
         final int rangeY = (maxY + 1) - minY;
@@ -50,6 +59,7 @@ public class ChunkDecoratorOverworldB173
 
         rand.setSeed(rx * x + rz * z ^ this.world.getRandomSeed());
 
+        try {
         if (rand.nextInt(4) == 0) {
             final int fx = wx + rand.nextInt(16) + 8;
             final int fy = rand.nextInt(rangeY);
@@ -65,7 +75,9 @@ public class ChunkDecoratorOverworldB173
                 new WorldFeatureLake(Blocks.FLUID_LAVA_STILL.id()).place(this.world, rand, fx, fy, fz);
             }
         }
+        } catch (final Throwable t) { LOGGER.error("Decoration step 'lakes' failed at chunk ({}, {}); continuing.", x, z, t); }
 
+        try {
         for (int i = 0; i < 8; i++) {
             final int fx = wx + rand.nextInt(16) + 8;
             final int fy = rand.nextInt(rangeY);
@@ -84,58 +96,68 @@ public class ChunkDecoratorOverworldB173
             final int fx = wx + rand.nextInt(16);
             final int fy = rand.nextInt(rangeY);
             final int fz = wz + rand.nextInt(16);
-            new WorldFeatureOre(Blocks.DIRT.id(), 32).place(this.world, rand, fx, fy, fz);
+            final TilePos queryPos = new TilePos(fx, fy, fz);
+            new WorldFeatureOre(Blocks.DIRT.id(), 32).place(this.world, rand, queryPos);
         }
 
         for (int i = 0; i < 10; i++) {
             final int fx = wx + rand.nextInt(16);
             final int fy = rand.nextInt(rangeY);
             final int fz = wz + rand.nextInt(16);
-            new WorldFeatureOre(Blocks.GRAVEL.id(), 32).place(this.world, rand, fx, fy, fz);
+            final TilePos queryPos = new TilePos(fx, fy, fz);
+            new WorldFeatureOre(Blocks.GRAVEL.id(), 32).place(this.world, rand, queryPos);
         }
 
         for (int i = 0; i < 20; i++) {
             final int fx = wx + rand.nextInt(16);
             final int fy = rand.nextInt(rangeY);
             final int fz = wz + rand.nextInt(16);
-            new WorldFeatureOre(Blocks.ORE_COAL_STONE.id(), 16).place(this.world, rand, fx, fy, fz);
+            final TilePos queryPos = new TilePos(fx, fy, fz);
+            new WorldFeatureOre(Blocks.ORE_COAL_STONE.id(), 16).place(this.world, rand, queryPos);
         }
 
         for (int i = 0; i < 20; i++) {
             final int fx = wx + rand.nextInt(16);
             final int fy = rand.nextInt(rangeY / 2);
             final int fz = wz + rand.nextInt(16);
-            new WorldFeatureOre(Blocks.ORE_IRON_STONE.id(), 8).place(this.world, rand, fx, fy, fz);
+            final TilePos queryPos = new TilePos(fx, fy, fz);
+            new WorldFeatureOre(Blocks.ORE_IRON_STONE.id(), 8).place(this.world, rand, queryPos);
         }
 
         for (int i = 0; i < 2; i++) {
             final int fx = wx + rand.nextInt(16);
             final int fy = rand.nextInt(rangeY / 4);
             final int fz = wz + rand.nextInt(16);
-            new WorldFeatureOre(Blocks.ORE_GOLD_STONE.id(), 8).place(this.world, rand, fx, fy, fz);
+            final TilePos queryPos = new TilePos(fx, fy, fz);
+            new WorldFeatureOre(Blocks.ORE_GOLD_STONE.id(), 8).place(this.world, rand, queryPos);
         }
 
         for (int i = 0; i < 8; i++) {
             final int fx = wx + rand.nextInt(16);
             final int fy = rand.nextInt(rangeY / 8);
             final int fz = wz + rand.nextInt(16);
-            new WorldFeatureOre(Blocks.ORE_REDSTONE_STONE.id(), 7).place(this.world, rand, fx, fy, fz);
+            final TilePos queryPos = new TilePos(fx, fy, fz);
+            new WorldFeatureOre(Blocks.ORE_REDSTONE_STONE.id(), 7).place(this.world, rand, queryPos);
         }
 
         for (int i = 0; i < 1; i++) {
             final int fx = wx + rand.nextInt(16);
             final int fy = rand.nextInt(rangeY / 16);
             final int fz = wz + rand.nextInt(16);
-            new WorldFeatureOre(Blocks.ORE_DIAMOND_STONE.id(), 7).place(this.world, rand, fx, fy, fz);
+            final TilePos queryPos = new TilePos(fx, fy, fz);
+            new WorldFeatureOre(Blocks.ORE_DIAMOND_STONE.id(), 7).place(this.world, rand, queryPos);
         }
 
         for (int i = 0; i < 1; i++) {
             final int fx = wx + rand.nextInt(16);
             final int fy = rand.nextInt(rangeY / 16) + rand.nextInt(rangeY / 16);
             final int fz = wz + rand.nextInt(16);
-            new WorldFeatureOre(Blocks.ORE_LAPIS_STONE.id(), 6).place(this.world, rand, fx, fy, fz);
+            final TilePos queryPos = new TilePos(fx, fy, fz);
+            new WorldFeatureOre(Blocks.ORE_LAPIS_STONE.id(), 6).place(this.world, rand, queryPos);
         }
+        } catch (final Throwable t) { LOGGER.error("Decoration step 'ores_dungeons_clay' failed at chunk ({}, {}); continuing.", x, z, t); }
 
+        try {
         final double forestNoiseScale = 0.5;
         final int forestNoiseValue = (int) ((this.forestNoise.getValue(wx * forestNoiseScale, wz * forestNoiseScale) / 8.0 + rand.nextDouble() * 4.0 + 4.0) / 3.0);
         int numTrees = 0;
@@ -167,13 +189,19 @@ public class ChunkDecoratorOverworldB173
         }
 
         for (int i = 0; i < numTrees; i++) {
-            final int fx = wx + rand.nextInt(16) + 8;
-            final int fz = wz + rand.nextInt(16) + 8;
-            final @NotNull WorldFeature feature = biome.getTreeFeature(rand);
-            feature.init(1.0, 1.0, 1.0);
-            biome.getTreeFeature(rand).place(this.world, rand, fx, this.world.getHeightValue(fx, fz), fz);
+            try {
+                final int fx = wx + rand.nextInt(16) + 8;
+                final int fz = wz + rand.nextInt(16) + 8;
+                final @NotNull WorldFeature feature = biome.getTreeFeature(rand);
+                feature.init(1.0, 1.0, 1.0);
+                biome.getTreeFeature(rand).place(this.world, rand, fx, this.world.getHeightValue(fx, fz), fz);
+            } catch (final Throwable treeT) {
+                LOGGER.error("Tree placement #{} failed at chunk ({}, {}); continuing.", i, x, z, treeT);
+            }
         }
+        } catch (final Throwable t) { LOGGER.error("Decoration step 'trees' failed at chunk ({}, {}); continuing.", x, z, t); }
 
+        try {
         int numYellowFlowers = 0;
         if (biome == Biomes.OVERWORLD_LEGACY_FOREST) {
             numYellowFlowers += 2;
@@ -296,25 +324,67 @@ public class ChunkDecoratorOverworldB173
             final int fz = wz + rand.nextInt(16) + 8;
             new WorldFeatureLiquid(Blocks.FLUID_LAVA_FLOWING.id()).place(this.world, rand, fx, fy, fz);
         }
+        } catch (final Throwable t) { LOGGER.error("Decoration step 'plants_liquids' failed at chunk ({}, {}); continuing.", x, z, t); }
 
         final int oceanY = this.world.getWorldType().getOceanY();
-        for (int xx = x + 8; xx < x + 8 + 16; xx++) {
-            for (int zz = z + 8; zz < z + 8 + 16; zz++) {
-                int yy = this.world.getHeightValue(xx, zz);
-                final Biome localBiome = this.world.getBlockBiome(xx, yy, zz);
-
-                if (localBiome.hasTag(BiomeTags.HAS_SURFACE_SNOW) && yy > 0 && yy < this.world.getHeightBlocks()) {
-                    if (this.world.isAirBlock(xx, yy, zz) && this.world.getBlockMaterial(xx, yy - 1, zz).blocksMotion()) {
-                        this.world.setBlockWithNotify(xx, yy, zz, Blocks.LAYER_SNOW.id());
+        try {
+            for (int xx = wx + 8; xx < wx + 8 + 16; xx++) {
+                for (int zz = wz + 8; zz < wz + 8 + 16; zz++) {
+                    try {
+                        applySnowAndIceForColumn(xx, zz, oceanY);
+                    } catch (final Throwable colT) {
+                        LOGGER.error("Snow/ice column ({}, {}) failed at chunk ({}, {}); continuing.", xx, zz, x, z, colT);
                     }
                 }
-                if (localBiome.hasTag(BiomeTags.HAS_SURFACE_ICE) && (this.world.getBlockId(xx, oceanY - 1, zz) == Blocks.FLUID_WATER_STILL.id() || this.world.getBlockId(xx, oceanY - 1, zz) == Blocks.FLUID_WATER_FLOWING.id())) {
-                    this.world.setBlockWithNotify(xx, oceanY - 1, zz, Blocks.ICE.id());
+            }
+        } catch (final Throwable t) { LOGGER.error("Decoration step 'snow_ice' failed at chunk ({}, {}); continuing.", x, z, t); }
+
+        //resnow pass
+        try {
+            for (int dx = wx; dx < wx + 32; dx++) {
+                for (int dz = wz; dz < wz + 32; dz++) {
+                    final int ownerChunkX = (dx - 8) >> 4;
+                    final int ownerChunkZ = (dz - 8) >> 4;
+                    if (ownerChunkX == x && ownerChunkZ == z) { continue; }
+                    if (!this.world.isChunkLoaded(ownerChunkX, ownerChunkZ)) { continue; }
+                    final Chunk owner = this.world.getChunkFromChunkCoords(ownerChunkX, ownerChunkZ);
+                    if (!owner.isTerrainPopulated) { continue; }
+                    final int physicalChunkX = dx >> 4;
+                    final int physicalChunkZ = dz >> 4;
+                    if (!this.world.isChunkLoaded(physicalChunkX, physicalChunkZ)) { continue; }
+                    try {
+                        applySnowAndIceForColumn(dx, dz, oceanY);
+                    } catch (final Throwable colT) {
+                        LOGGER.error("Spillover snow/ice column ({}, {}) failed at chunk ({}, {}); continuing.", dx, dz, x, z, colT);
+                    }
                 }
             }
+        } catch (final Throwable t) { LOGGER.error("Decoration step 'snow_ice_spillover' failed at chunk ({}, {}); continuing.", x, z, t); }
+
+        } finally {
+            BlockLogicFallingBlock.fallInstantly = false;
+            this.world.scheduledUpdatesAreImmediate = false;
         }
+    }
+
+    /**
+     * Places snow on the topmost surface of the column, plus ice on top of water at the ocean surface.
+     */
+    private void applySnowAndIceForColumn(final int xx, final int zz, final int oceanY) {
+        final int yy = this.world.getHeightValue(xx, zz);
+        final Biome localBiome = this.world.getBlockBiome(xx, yy, zz);
 
-        BlockLogicFallingBlock.fallInstantly = false;
-        this.world.scheduledUpdatesAreImmediate = false;
+        if (localBiome.hasTag(BiomeTags.HAS_SURFACE_SNOW) && yy > 0 && yy < this.world.getHeightBlocks()) {
+            if (this.world.isAirBlock(xx, yy, zz) && this.world.getBlockMaterial(xx, yy - 1, zz).blocksMotion()) {
+                this.world.setBlockWithNotify(xx, yy, zz, Blocks.LAYER_SNOW.id());
+            }
+        }
+
+        if (localBiome.hasTag(BiomeTags.HAS_SURFACE_ICE)) {
+            final int oceanFluidId = this.world.getBlockId(xx, oceanY - 1, zz);
+            if (oceanFluidId == Blocks.FLUID_WATER_STILL.id() || oceanFluidId == Blocks.FLUID_WATER_FLOWING.id()) {
+                this.world.setBlockWithNotify(xx, oceanY - 1, zz, Blocks.ICE.id());
+            }
+        }
     }
 }
diff --git a/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/overworld/hell/ChunkDecoratorOverworldHell.java b/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/overworld/hell/ChunkDecoratorOverworldHell.java
index e53cd9eee..5a6d86159 100644
--- a/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/overworld/hell/ChunkDecoratorOverworldHell.java
+++ b/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/overworld/hell/ChunkDecoratorOverworldHell.java
@@ -15,6 +15,7 @@ import net.minecraft.core.world.generate.feature.tree.WorldFeatureTree;
 import net.minecraft.core.world.generate.feature.tree.WorldFeatureTreeFancy;
 import net.minecraft.core.world.noise.FractalNoise2D;
 import net.minecraft.core.world.noise.ImprovedPerlinNoise;
+import net.minecraft.core.world.pos.TilePos;
 import org.jetbrains.annotations.NotNull;
 
 public class ChunkDecoratorOverworldHell
@@ -107,7 +108,8 @@ public class ChunkDecoratorOverworldHell
                 final int xx = worldX + rand.nextInt(16);
                 final int yy = minY + rand.nextInt(rangeY);
                 final int zz = worldZ + rand.nextInt(16);
-                (new WorldFeatureOre(Blocks.DIRT.id(), 32)).place(world, rand, xx, yy, zz);
+                TilePos pos = new TilePos(xx, yy, zz);
+                (new WorldFeatureOre(Blocks.DIRT.id(), 32)).place(world, rand, pos);
             }
         });
 
@@ -116,7 +118,8 @@ public class ChunkDecoratorOverworldHell
                 final int xx = worldX + rand.nextInt(16);
                 final int yy = minY + rand.nextInt(rangeY);
                 final int zz = worldZ + rand.nextInt(16);
-                (new WorldFeatureOre(Blocks.GRAVEL.id(), 32)).place(world, rand, xx, yy, zz);
+                TilePos pos = new TilePos(xx, yy, zz);
+                (new WorldFeatureOre(Blocks.GRAVEL.id(), 32)).place(world, rand, pos);
             }
         });
 
@@ -125,7 +128,8 @@ public class ChunkDecoratorOverworldHell
                 final int xx = worldX + rand.nextInt(16);
                 final int yy = minY + rand.nextInt(rangeY);
                 final int zz = worldZ + rand.nextInt(16);
-                (new WorldFeatureOre(BlockLogicOreCoal.variantMap, 16)).place(world, rand, xx, yy, zz);
+                TilePos pos = new TilePos(xx, yy, zz);
+                (new WorldFeatureOre(BlockLogicOreCoal.variantMap, 16)).place(world, rand, pos);
             }
         });
 
@@ -134,7 +138,8 @@ public class ChunkDecoratorOverworldHell
                 final int xx = worldX + rand.nextInt(16);
                 final int yy = minY + rand.nextInt(rangeY / 2);
                 final int zz = worldZ + rand.nextInt(16);
-                (new WorldFeatureOre(BlockLogicOreIron.variantMap, 8)).place(world, rand, xx, yy, zz);
+                TilePos pos = new TilePos(xx, yy, zz);
+                (new WorldFeatureOre(BlockLogicOreIron.variantMap, 8)).place(world, rand, pos);
             }
         });
 
@@ -143,7 +148,8 @@ public class ChunkDecoratorOverworldHell
                 final int xx = worldX + rand.nextInt(16);
                 final int yy = minY + rand.nextInt(rangeY / 4);
                 final int zz = worldZ + rand.nextInt(16);
-                (new WorldFeatureOre(BlockLogicOreGold.variantMap, 8)).place(world, rand, xx, yy, zz);
+                TilePos pos = new TilePos(xx, yy, zz);
+                (new WorldFeatureOre(BlockLogicOreGold.variantMap, 8)).place(world, rand, pos);
             }
         });
 
@@ -152,7 +158,8 @@ public class ChunkDecoratorOverworldHell
                 final int xx = worldX + rand.nextInt(16);
                 final int yy = minY + rand.nextInt(rangeY / 8);
                 final int zz = worldZ + rand.nextInt(16);
-                (new WorldFeatureOre(BlockLogicOreRedstone.variantMap, 7)).place(world, rand, xx, yy, zz);
+                TilePos pos = new TilePos(xx, yy, zz);
+                (new WorldFeatureOre(BlockLogicOreRedstone.variantMap, 7)).place(world, rand, pos);
             }
         });
 
@@ -161,7 +168,8 @@ public class ChunkDecoratorOverworldHell
                 final int xx = worldX + rand.nextInt(16);
                 final int yy = minY + rand.nextInt(rangeY / 8);
                 final int zz = worldZ + rand.nextInt(16);
-                (new WorldFeatureOre(BlockLogicOreDiamond.variantMap, 7)).place(world, rand, xx, yy, zz);
+                TilePos pos = new TilePos(xx, yy, zz);
+                (new WorldFeatureOre(BlockLogicOreDiamond.variantMap, 7)).place(world, rand, pos);
             }
         });
 
@@ -170,7 +178,8 @@ public class ChunkDecoratorOverworldHell
                 final int xx = worldX + rand.nextInt(16);
                 final int yy = minY + rand.nextInt(rangeY / 8) + rand.nextInt(rangeY / 8);
                 final int zz = worldZ + rand.nextInt(16);
-                (new WorldFeatureOre(BlockLogicOreLapis.variantMap, 6)).place(world, rand, xx, yy, zz);
+                TilePos pos = new TilePos(xx, yy, zz);
+                (new WorldFeatureOre(BlockLogicOreLapis.variantMap, 6)).place(world, rand, pos);
             }
         });
 
diff --git a/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/overworld/retro/ChunkDecoratorOverworldRetro.java b/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/overworld/retro/ChunkDecoratorOverworldRetro.java
index adf3fd0ff..b70de55bf 100644
--- a/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/overworld/retro/ChunkDecoratorOverworldRetro.java
+++ b/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/overworld/retro/ChunkDecoratorOverworldRetro.java
@@ -1,5 +1,7 @@
 package net.minecraft.core.world.generate.chunk.perlin.overworld.retro;
 
+import com.mojang.logging.LogUtils;
+import net.minecraft.core.block.BlockLogicLeavesBase;
 import net.minecraft.core.block.BlockLogicOreCoal;
 import net.minecraft.core.block.BlockLogicOreDiamond;
 import net.minecraft.core.block.BlockLogicOreGold;
@@ -16,12 +18,16 @@ import net.minecraft.core.world.generate.feature.tree.WorldFeatureTree;
 import net.minecraft.core.world.generate.feature.tree.WorldFeatureTreeFancy;
 import net.minecraft.core.world.noise.FractalNoise2D;
 import net.minecraft.core.world.noise.ImprovedPerlinNoise;
+import net.minecraft.core.world.pos.TilePos;
 import org.jetbrains.annotations.NotNull;
+import org.slf4j.Logger;
 
 import java.util.Random;
 
 public class ChunkDecoratorOverworldRetro
     implements ChunkDecorator {
+    private static final @NotNull Logger LOGGER = LogUtils.getLogger();
+
     private final @NotNull World world;
     private final @NotNull FractalNoise2D<ImprovedPerlinNoise> treeDensityNoise;
 
@@ -38,20 +44,25 @@ public class ChunkDecoratorOverworldRetro
     @Override
     public void decorate(@NotNull Chunk chunk) {
         this.world.scheduledUpdatesAreImmediate = true;
-        int chunkX = chunk.pos.x;
-        int chunkZ = chunk.pos.z;
+        BlockLogicFallingBlock.fallInstantly = true;
+
+        final int chunkX = chunk.pos.x;
+        final int chunkZ = chunk.pos.z;
+
+        try {
 
         int minY = this.world.getWorldType().getMinY(world);
         int maxY = this.world.getWorldType().getMaxY(world);
         int rangeY = (maxY + 1) - minY;
 
-        BlockLogicFallingBlock.fallInstantly = true;
         int k = chunkX * 16;
         int l = chunkZ * 16;
         Random rand = new Random(this.world.getRandomSeed());
         long l1 = (rand.nextLong() / 2L) * 2L + 1L;
         long l2 = (rand.nextLong() / 2L) * 2L + 1L;
         rand.setSeed((long) chunkX * l1 + (long) chunkZ * l2 ^ this.world.getRandomSeed());
+
+        try {
         for (int i1 = 0; i1 < 8; i1++) {
             int i4 = k + rand.nextInt(16) + 8;
             int j6 = minY + rand.nextInt(rangeY);
@@ -70,51 +81,60 @@ public class ChunkDecoratorOverworldRetro
             int k4 = k + rand.nextInt(16);
             int l6 = minY + rand.nextInt(rangeY);
             int k11 = l + rand.nextInt(16);
-            (new WorldFeatureOre(Blocks.DIRT.id(), 32)).place(this.world, rand, k4, l6, k11);
+            TilePos queryPos = new TilePos(k4, l6, k11);
+            (new WorldFeatureOre(Blocks.DIRT.id(), 32)).place(this.world, rand, queryPos);
         }
 
         for (int i2 = 0; i2 < 10; i2++) {
             int l4 = k + rand.nextInt(16);
             int i7 = minY + rand.nextInt(rangeY);
             int l11 = l + rand.nextInt(16);
-            (new WorldFeatureOre(Blocks.GRAVEL.id(), 32)).place(this.world, rand, l4, i7, l11);
+            TilePos queryPos = new TilePos(l4, i7, l11);
+            (new WorldFeatureOre(Blocks.GRAVEL.id(), 32)).place(this.world, rand, queryPos);
         }
 
         for (int j2 = 0; j2 < 20; j2++) {
             int i5 = k + rand.nextInt(16);
             int j7 = minY + rand.nextInt(rangeY);
             int i12 = l + rand.nextInt(16);
-            (new WorldFeatureOre(BlockLogicOreCoal.variantMap, 16)).place(this.world, rand, i5, j7, i12);
+            TilePos queryPos = new TilePos(i5, j7, i12);
+            (new WorldFeatureOre(BlockLogicOreCoal.variantMap, 16)).place(this.world, rand, queryPos);
         }
 
         for (int k2 = 0; k2 < 20; k2++) {
             int j5 = k + rand.nextInt(16);
             int k7 = minY + rand.nextInt(rangeY / 2);
             int j12 = l + rand.nextInt(16);
-            (new WorldFeatureOre(BlockLogicOreIron.variantMap, 8)).place(this.world, rand, j5, k7, j12);
+            TilePos queryPos = new TilePos(j5, k7, j12);
+            (new WorldFeatureOre(BlockLogicOreIron.variantMap, 8)).place(this.world, rand, queryPos);
         }
 
         for (int i3 = 0; i3 < 2; i3++) {
             int k5 = k + rand.nextInt(16);
             int l7 = minY + rand.nextInt(rangeY / 4);
             int k12 = l + rand.nextInt(16);
-            (new WorldFeatureOre(BlockLogicOreGold.variantMap, 8)).place(this.world, rand, k5, l7, k12);
+            TilePos queryPos = new TilePos(k5, l7, k12);
+            (new WorldFeatureOre(BlockLogicOreGold.variantMap, 8)).place(this.world, rand, queryPos);
         }
 
         for (int j3 = 0; j3 < 8; j3++) {
             int l5 = k + rand.nextInt(16);
             int i8 = minY + rand.nextInt(rangeY / 8);
             int l12 = l + rand.nextInt(16);
-            (new WorldFeatureOre(BlockLogicOreRedstone.variantMap, 7)).place(this.world, rand, l5, i8, l12);
+            TilePos queryPos = new TilePos(l5, i8, l12);
+            (new WorldFeatureOre(BlockLogicOreRedstone.variantMap, 7)).place(this.world, rand, queryPos);
         }
 
         for (int k3 = 0; k3 < 1; k3++) {
             int i6 = k + rand.nextInt(16);
             int j8 = minY + rand.nextInt(rangeY / 8);
             int i13 = l + rand.nextInt(16);
-            (new WorldFeatureOre(BlockLogicOreDiamond.variantMap, 7)).place(this.world, rand, i6, j8, i13);
+            TilePos queryPos = new TilePos(i6, j8, i13);
+            (new WorldFeatureOre(BlockLogicOreDiamond.variantMap, 7)).place(this.world, rand, queryPos);
         }
+        } catch (final Throwable t) { LOGGER.error("Decoration step 'dungeons_clay_ores' failed at chunk ({}, {}); continuing.", chunkX, chunkZ, t); }
 
+        try {
         double d = 0.5D;
         int l3 = (int) ((this.treeDensityNoise.getValue((double) k * d, (double) l * d) / 8D + rand.nextDouble() * 4D + 4D) / 3D);
         if (l3 < 0) {
@@ -128,12 +148,18 @@ public class ChunkDecoratorOverworldRetro
             tree = new WorldFeatureTreeFancy(Blocks.LEAVES_OAK_RETRO.id(), Blocks.LOG_OAK.id());
         }
         for (int k8 = 0; k8 < l3; k8++) {
-            int j13 = k + rand.nextInt(16) + 8;
-            int l15 = l + rand.nextInt(16) + 8;
-            tree.init(1.0D, 1.0D, 1.0D);
-            tree.place(this.world, rand, j13, this.world.getHeightValue(j13, l15), l15);
+            try {
+                int j13 = k + rand.nextInt(16) + 8;
+                int l15 = l + rand.nextInt(16) + 8;
+                tree.init(1.0D, 1.0D, 1.0D);
+                tree.place(this.world, rand, j13, this.world.getHeightValue(j13, l15), l15);
+            } catch (final Throwable treeT) {
+                LOGGER.error("Tree placement #{} failed at chunk ({}, {}); continuing.", k8, chunkX, chunkZ, treeT);
+            }
         }
+        } catch (final Throwable t) { LOGGER.error("Decoration step 'trees' failed at chunk ({}, {}); continuing.", chunkX, chunkZ, t); }
 
+        try {
         for (int l8 = 0; l8 < 2; l8++) {
             int k13 = k + rand.nextInt(16) + 8;
             int i16 = minY + rand.nextInt(rangeY);
@@ -172,7 +198,9 @@ public class ChunkDecoratorOverworldRetro
             int l18 = l + rand.nextInt(16) + 8;
             (new WorldFeatureCactus()).place(this.world, rand, l14, j17, l18);
         }
+        } catch (final Throwable t) { LOGGER.error("Decoration step 'flowers_cactus' failed at chunk ({}, {}); continuing.", chunkX, chunkZ, t); }
 
+        try {
         for (int j10 = 0; j10 < 50; j10++) {
             int i15 = k + rand.nextInt(16) + 8;
             int k17 = minY + rand.nextInt(rand.nextInt(rangeY - 8) + 8);
@@ -186,21 +214,65 @@ public class ChunkDecoratorOverworldRetro
             int j19 = l + rand.nextInt(16) + 8;
             (new WorldFeatureLiquid(Blocks.FLUID_LAVA_FLOWING.id())).place(this.world, rand, j15, l17, j19);
         }
+        } catch (final Throwable t) { LOGGER.error("Decoration step 'liquids' failed at chunk ({}, {}); continuing.", chunkX, chunkZ, t); }
 
-        for (int l10 = k + 8; l10 < k + 8 + 16; l10++) {
-            for (int k15 = l + 8; k15 < l + 8 + 16; k15++) {
-                int i18 = this.world.findTopSolidBlock(l10, k15);
-                if (this.snowCovered && i18 > minY && i18 <= maxY && this.world.getBlockId(l10, i18, k15) == 0 && this.world.getBlockMaterial(l10, i18 - 1, k15).blocksMotion() && this.world.getBlockMaterial(l10, i18 - 1, k15) != Materials.ICE) {
-                    this.world.setBlockWithNotify(l10, i18, k15, Blocks.LAYER_SNOW.id());
+        // First snow/ice pass
+        try {
+            for (int l10 = k + 8; l10 < k + 8 + 16; l10++) {
+                for (int k15 = l + 8; k15 < l + 8 + 16; k15++) {
+                    try {
+                        applySnowAndIceForColumn(l10, k15, minY, maxY);
+                    } catch (final Throwable colT) {
+                        LOGGER.error("Snow/ice column ({}, {}) failed at chunk ({}, {}); continuing.", l10, k15, chunkX, chunkZ, colT);
+                    }
                 }
-                if (this.snowCovered && this.world.getBlockMaterial(l10, this.world.getWorldType().getOceanY() - 1, k15) == Materials.WATER) {
-                    this.world.setBlockWithNotify(l10, this.world.getWorldType().getOceanY() - 1, k15, Blocks.ICE.id());
+            }
+        } catch (final Throwable t) { LOGGER.error("Decoration step 'snow_ice' failed at chunk ({}, {}); continuing.", chunkX, chunkZ, t); }
+
+        // Snow/ice repass
+        try {
+            for (int dx = k; dx < k + 32; dx++) {
+                for (int dz = l; dz < l + 32; dz++) {
+                    final int ownerChunkX = (dx - 8) >> 4;
+                    final int ownerChunkZ = (dz - 8) >> 4;
+                    if (ownerChunkX == chunkX && ownerChunkZ == chunkZ) { continue; }
+                    if (!this.world.isChunkLoaded(ownerChunkX, ownerChunkZ)) { continue; }
+                    final Chunk owner = this.world.getChunkFromChunkCoords(ownerChunkX, ownerChunkZ);
+                    if (!owner.isTerrainPopulated) { continue; }
+                    final int physicalChunkX = dx >> 4;
+                    final int physicalChunkZ = dz >> 4;
+                    if (!this.world.isChunkLoaded(physicalChunkX, physicalChunkZ)) { continue; }
+                    try {
+                        applySnowAndIceForColumn(dx, dz, minY, maxY);
+                    } catch (final Throwable colT) {
+                        LOGGER.error("Spillover snow/ice column ({}, {}) failed at chunk ({}, {}); continuing.", dx, dz, chunkX, chunkZ, colT);
+                    }
                 }
             }
+        } catch (final Throwable t) { LOGGER.error("Decoration step 'snow_ice_spillover' failed at chunk ({}, {}); continuing.", chunkX, chunkZ, t); }
 
+        } finally {
+            BlockLogicFallingBlock.fallInstantly = false;
+            this.world.scheduledUpdatesAreImmediate = false;
         }
+    }
+
+    /**
+     * Places snow on the topmost surface of the column, plus ice on top of water at the ocean surface.
+     */
+    private void applySnowAndIceForColumn(final int dx, final int dz, final int minY, final int maxY) {
+        if (!this.snowCovered) { return; }
+
+        final int topY = this.world.findTopSolidBlock(dx, dz);
 
-        BlockLogicFallingBlock.fallInstantly = false;
-        this.world.scheduledUpdatesAreImmediate = false;
+        if (topY > minY && topY <= maxY && this.world.getBlockId(dx, topY, dz) == 0
+                && this.world.getBlockMaterial(dx, topY - 1, dz).blocksMotion()
+                && this.world.getBlockMaterial(dx, topY - 1, dz) != Materials.ICE) {
+            this.world.setBlockWithNotify(dx, topY, dz, Blocks.LAYER_SNOW.id());
+        }
+
+        if (this.world.getBlockMaterial(dx, this.world.getWorldType().getOceanY() - 1, dz) == Materials.WATER) {
+            this.world.setBlockWithNotify(dx, this.world.getWorldType().getOceanY() - 1, dz, Blocks.ICE.id());
+        }
     }
 }
diff --git a/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureBoulder.java b/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureBoulder.java
new file mode 100644
index 000000000..48bb52c9a
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureBoulder.java
@@ -0,0 +1,104 @@
+package net.minecraft.core.world.generate.feature;
+
+import net.minecraft.core.block.Block;
+import net.minecraft.core.block.tag.BlockTags;
+import net.minecraft.core.world.World;
+import net.minecraft.core.world.pos.TilePos;
+import net.minecraft.core.world.pos.TilePosc;
+import org.jetbrains.annotations.NotNull;
+import org.joml.Vector2i;
+
+import java.util.Random;
+
+public class WorldFeatureBoulder implements WorldFeatureInterface {
+	final double[][] RADIUS_MAPS = new double[][]{{(double)1.5F, (double)1.5F, (double)1.0F}, {(double)1.0F, (double)1.5F, (double)1.0F}};
+	private final Block<?> boulderBlock;
+	private final Block<?> extraBlock;
+
+	public WorldFeatureBoulder(Block<?> boulderBlock, Block<?> extraBlock) {
+		this.boulderBlock = boulderBlock;
+		this.extraBlock = extraBlock;
+	}
+
+	private Block<?> getBlockAt(Vector2i centerXZ, int x, int z) {
+		return boulderBlock;
+	}
+
+	private void plotBlocks(World world, @NotNull Vector2i centerXZ, TilePos queryPos, int x, int y, int z) {
+		for(int xx = centerXZ.x - x; xx <= centerXZ.x + x; ++xx) {
+			Block<?> block = this.getBlockAt(centerXZ, xx, centerXZ.y + z);
+			world.setBlockType(queryPos.set(xx, y, centerXZ.y + z), block);
+			world.setBlockType(queryPos.set(xx, y, centerXZ.y - z), block);
+		}
+
+		for(int xx = centerXZ.x - z; xx <= centerXZ.x + z; ++xx) {
+			Block<?> block = this.getBlockAt(centerXZ, xx, centerXZ.y + x);
+			world.setBlockType(queryPos.set(xx, y, centerXZ.y + x), block);
+			world.setBlockType(queryPos.set(xx, y, centerXZ.y - x), block);
+		}
+	}
+
+	private void generateRadius(World world, Vector2i centerXZ, TilePos queryPos, double r, int y) {
+		int x = 0;
+		int z = (int)r;
+		double d = (double)3.0F - (double)2.0F * r;
+		this.plotBlocks(world, centerXZ, queryPos, x, y, z);
+
+		for(; z > x; this.plotBlocks(world, centerXZ, queryPos, x, y, z)) {
+			++x;
+			if (d > (double)0.0F) {
+				--z;
+				d = d + (double)(4 * (x - z)) + (double)10.0F;
+			} else {
+				d = d + (double)(4 * x) + (double)6.0F;
+			}
+		}
+	}
+
+	private boolean hasSpace(World world, int xc, int y, int zc) {
+		TilePos queryPos = new TilePos();
+
+		for(queryPos.y = y - 2; queryPos.y < y + 2; ++queryPos.y) {
+			for(queryPos.x = xc - 2; queryPos.x < xc + 2; ++queryPos.x) {
+				for(queryPos.z = zc - 2; queryPos.z < zc + 2; ++queryPos.z) {
+					if (!world.isAirBlock(queryPos) && !world.getBlockType(queryPos).hasTag(BlockTags.NETHER_SURFACE_BLOCK)) {
+						return false;
+					}
+				}
+			}
+		}
+		return true;
+	}
+
+	@Override
+	public boolean place(@NotNull World world, @NotNull Random random, @NotNull TilePosc tilePos) {
+		TilePos currentPos = new TilePos(tilePos);
+		while(world.isAirBlock(currentPos) && currentPos.y > 1) {
+			currentPos.y--;
+		}
+
+		double[] map = this.RADIUS_MAPS[random.nextInt(2)];
+		if (this.hasSpace(world, currentPos.x, currentPos.y, currentPos.z)) {
+			Vector2i centerXZ = new Vector2i(currentPos.x, currentPos.z);
+			TilePos queryPos = new TilePos();
+
+			for(int i = 0; i < map.length; ++i) {
+				this.generateRadius(world, centerXZ, queryPos, map[i], currentPos.y + i);
+			}
+
+			TilePos circlePos = new TilePos();
+
+			for(circlePos.x = currentPos.x - 2; circlePos.x < currentPos.x + 2; ++circlePos.x) {
+				for(circlePos.z = currentPos.z - 2; circlePos.z < currentPos.z + 2; ++circlePos.z) {
+					for(circlePos.y = currentPos.y - 4; circlePos.y < currentPos.y + 4; ++circlePos.y) {
+						if (world.getBlockType(circlePos) == boulderBlock && random.nextInt(8) == 0) {
+							world.setBlockType(circlePos, extraBlock);
+						}
+					}
+				}
+			}
+		}
+
+		return true;
+	}
+}
\ No newline at end of file
diff --git a/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureBoulderGlowstone.java b/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureBoulderGlowstone.java
new file mode 100644
index 000000000..ed8ea7192
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureBoulderGlowstone.java
@@ -0,0 +1,110 @@
+package net.minecraft.core.world.generate.feature;
+
+import net.minecraft.core.block.Block;
+import net.minecraft.core.block.BlockLogicFluid;
+import net.minecraft.core.block.Blocks;
+import net.minecraft.core.block.tag.BlockTags;
+import net.minecraft.core.world.World;
+import net.minecraft.core.world.pos.TilePos;
+import net.minecraft.core.world.pos.TilePosc;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.Random;
+
+import static net.minecraft.core.block.BlockLogicFluid.isAcid;
+
+public class WorldFeatureBoulderGlowstone implements WorldFeatureInterface {
+
+    private final Block<?> stoneBlock;
+    private final Block<?> fireBlock;
+    private final int radius;
+
+    /**
+     * Creates a customizable boulder.
+     *
+     * @param stoneBlock The primary block used to build the boulder.
+     * @param fireBlock  The block to place on top (e.g., Fire). Set to null for no top blocks.
+     * @param radius     The size. 1 is 3x3, 2 is a 5x5, etc.
+     */
+    public WorldFeatureBoulderGlowstone(@NotNull Block<?> stoneBlock, @Nullable Block<?> fireBlock, int radius) {
+        this.stoneBlock = stoneBlock;
+        this.fireBlock = fireBlock;
+        this.radius = Math.max(1, radius);
+    }
+
+    private static final int MAX_DOWN_SCAN = 64;
+
+    @Override
+    public boolean place(@NotNull World world, @NotNull Random random, @NotNull TilePosc tilePosc) {
+        TilePos tilePos = new TilePos(tilePosc.x(), tilePosc.y(), tilePosc.z());
+
+        // Find the ground or the bottom of the acid pool. Cap the search distance.
+        int scanned = 0;
+        while ((world.isAirBlock(tilePos) || isAcid(world, tilePos)) && tilePos.y() - 1 > 0 && scanned < MAX_DOWN_SCAN) {
+            tilePos.down();
+            scanned++;
+        }
+
+        // Must be resting on a Nether surface block
+        if (!world.getBlockType(tilePos).hasTag(BlockTags.NETHER_SURFACE_BLOCK)) {
+            return false;
+        }
+
+        // Must be generated inside/under an acid pool
+        TilePos queryPos = new TilePos();
+        if (!isAcid(world, tilePos.up(queryPos))) {
+            return false;
+        }
+
+        int maxExpectedHeight = (this.radius * 2) + 2;
+
+        if (hasSpace(world, tilePos.x(), tilePos.y(), tilePos.z(), maxExpectedHeight)) {
+            TilePos centerPos = new TilePos(tilePos.x(), tilePos.y() + 1, tilePos.z());
+
+            for (int x1 = -this.radius; x1 <= this.radius; x1++) {
+                for (int z1 = -this.radius; z1 <= this.radius; z1++) {
+
+                    int dist = Math.abs(x1) + Math.abs(z1);
+
+                    int height = random.nextInt(2) + Math.max(1, (this.radius * 2 + 1) - dist);
+                    if (height <= 0) continue;
+
+                    // Place the main boulder
+                    for(int y1 = 0; y1 < height; y1++){
+                        centerPos.add(x1, y1, z1, queryPos);
+                        world.setBlockType(queryPos, this.stoneBlock);
+                    }
+
+                    // Place the top-layer
+                    if (this.fireBlock != null) {
+                        centerPos.add(x1, height, z1, queryPos);
+                        if (world.isAirBlock(queryPos) || isAcid(world, queryPos)) {
+                            if(random.nextInt(2) == 0){
+                                world.setBlockType(queryPos, this.fireBlock);
+                            }
+                        }
+                    }
+                }
+            }
+
+            return true;
+        }
+
+        return false;
+    }
+
+    private boolean hasSpace(World world, int xc, int y, int zc, int maxExpectedHeight) {
+        TilePos queryPos = new TilePos();
+
+        for (queryPos.y = y + 1; queryPos.y < y + maxExpectedHeight; queryPos.y++) {
+            queryPos.x = xc;
+            queryPos.z = zc;
+
+            if (!world.isAirBlock(queryPos) && !isAcid(world, queryPos)) {
+                return false;
+            }
+        }
+        return true;
+    }
+}
diff --git a/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureMagmaBoulder.java b/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureBoulderMagma.java
similarity index 57%
rename from game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureMagmaBoulder.java
rename to game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureBoulderMagma.java
index 6146fcf30..8ce53846e 100644
--- a/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureMagmaBoulder.java
+++ b/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureBoulderMagma.java
@@ -5,46 +5,51 @@ import net.minecraft.core.block.Blocks;
 import net.minecraft.core.block.tag.BlockTags;
 import net.minecraft.core.world.World;
 import net.minecraft.core.world.pos.TilePos;
+import net.minecraft.core.world.pos.TilePosc;
+import org.jetbrains.annotations.NotNull;
 import org.joml.Vector2i;
 
 import java.util.Random;
 
-public class WorldFeatureMagmaBoulder extends WorldFeature{
+public class WorldFeatureBoulderMagma implements WorldFeatureInterface {
 
     final double[][] RADIUS_MAPS = {
-            {1.5D, 1.5D, 1D},
-            {1D, 1.5D, 1D},
+            {1.5D, 1.5D, 1.0D},
+            {1.0D, 1.5D, 1.0D},
     };
 
     @Override
-    public boolean place(World world, Random random, int x, int y, int z) {
-        TilePos pos = new TilePos(x, y, z);
-        while(world.isAirBlock(pos) && pos.y - 1 > 0) {pos.down(); }
+    public boolean place(@NotNull World world, @NotNull Random random, @NotNull TilePosc tilePos) {
+        TilePos pos = new TilePos(tilePos);
+
+        while(world.isAirBlock(pos) && pos.y() - 1 > 0) {
+            pos.down();
+        }
 
         double[] map = RADIUS_MAPS[random.nextInt(2)];
-        if (hasSpace(world, pos.x, pos.y, pos.z)) {
-            Vector2i centerXZ = new Vector2i(pos.x, pos.z);
-            TilePos queryPos = new TilePos(pos.x, pos.y, pos.z);
+        if (hasSpace(world, pos.x(), pos.y(), pos.z())) {
+            Vector2i centerXZ = new Vector2i(pos.x(), pos.z());
+            TilePos queryPos = new TilePos(pos);
 
             for (int i = 0; i < map.length; i++) {
-                generateRadius(world, centerXZ, queryPos, map[i], pos.y + i);
+                generateRadius(world, centerXZ, queryPos, map[i], pos.y() + i);
             }
 
             TilePos circlePos = new TilePos();
-            for (circlePos.x = pos.x() - 2; circlePos.x < pos.x() + 2; circlePos.x++) {
-                for (circlePos.z = pos.z() - 2; circlePos.z < pos.z() + 2; circlePos.z++) {
-                    for (circlePos.y = pos.y() - 4; circlePos.y < pos.y + 4; circlePos.y++) {
-
+            for (int x = pos.x() - 2; x < pos.x() + 2; x++) {
+                for (int z = pos.z() - 2; z < pos.z() + 2; z++) {
+                    for (int y = pos.y() - 4; y < pos.y() + 4; y++) {
+                        circlePos.set(x, y, z);
                         if (!world.isAirBlock(circlePos)) {
-                            if (random.nextInt(8) == 0)
+                            if (random.nextInt(8) == 0) {
                                 world.setBlockType(circlePos, Blocks.COBBLE_BASALT);
+                            }
                         }
                     }
                 }
             }
         }
 
-
         return true;
     }
 
@@ -53,13 +58,11 @@ public class WorldFeatureMagmaBoulder extends WorldFeature{
     }
 
     private void plotBlocks(World world, Vector2i center, TilePos queryPos, int x, int y, int z){
+        plotBlock(world, center, queryPos, z, y, x);
+        plotBlock(world, center, queryPos, x, y, z);
+    }
 
-        for(int xx = center.x - x; xx <= center.x + x; xx++) {
-            Block<?> block = getBlockAt(center, xx, center.y + z);
-            world.setBlockType(queryPos.set(xx, queryPos.y, center.y + z), block);
-            world.setBlockType(queryPos.set(xx, queryPos.y, center.y - z), block);
-        }
-
+    private void plotBlock(World world, Vector2i center, TilePos queryPos, int x, int y, int z) {
         for(int xx = center.x - z; xx <= center.x + z; xx++) {
             Block<?> block = getBlockAt(center, xx, center.y + x);
             world.setBlockType(queryPos.set(xx, y, center.y + x), block);
@@ -77,20 +80,22 @@ public class WorldFeatureMagmaBoulder extends WorldFeature{
             if (d > 0) {
                 z--;
                 d = d + 4 * (x - z) + 10;
-            }  else
+            } else {
                 d = d + 4 * x + 6;
-
+            }
             plotBlocks(world, center, queryPos, x, y, z);
         }
     }
 
     private boolean hasSpace(World world, int xc, int y, int zc){
-        TilePos queryPos =  new TilePos();
-        for (queryPos.y = y - 2; queryPos.y < y + 2; queryPos.y++) {
-            for (queryPos.x = xc - 2; queryPos.x < xc + 2; queryPos.x++) {
-                for (queryPos.z = zc - 2; queryPos.z < zc + 2; queryPos.z++) {
-                    if (!world.isAirBlock(queryPos) && !world.getBlockType(queryPos).hasTag(BlockTags.NETHER_SURFACE_BLOCK))
+        TilePos queryPos = new TilePos();
+        for (int py = y - 2; py < y + 2; py++) {
+            for (int px = xc - 2; px < xc + 2; px++) {
+                for (int pz = zc - 2; pz < zc + 2; pz++) {
+                    queryPos.set(px, py, pz);
+                    if (!world.isAirBlock(queryPos) && !world.getBlockType(queryPos).hasTag(BlockTags.NETHER_SURFACE_BLOCK)) {
                         return false;
+                    }
                 }
             }
         }
diff --git a/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureFire.java b/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureFire.java
index 8560a7512..476a470e2 100644
--- a/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureFire.java
+++ b/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureFire.java
@@ -1,32 +1,65 @@
 package net.minecraft.core.world.generate.feature;
 
+import net.minecraft.core.block.Block;
 import net.minecraft.core.block.Blocks;
+import net.minecraft.core.block.tag.BlockTags;
 import net.minecraft.core.world.World;
+import net.minecraft.core.world.pos.TilePos;
+import net.minecraft.core.world.pos.TilePosc;
+import org.jetbrains.annotations.NotNull;
 
 import java.util.Random;
 
-public class WorldFeatureFire extends WorldFeature
-{
+public class WorldFeatureFire implements WorldFeatureInterface {
 
-    public WorldFeatureFire()
-    {
+    private final Block<?> overrideFireBlock;
+    private final Block<?> overrideGroundBlock;
+
+    public WorldFeatureFire() {
+        this.overrideFireBlock = null;
+        this.overrideGroundBlock = null;
+    }
+
+    public WorldFeatureFire(Block<?> overrideFireBlock, Block<?> overrideGroundBlock) {
+        this.overrideFireBlock = overrideFireBlock;
+        this.overrideGroundBlock = overrideGroundBlock;
     }
 
     @Override
-	public boolean place(World world, Random random, int x, int y, int z)
-    {
-        for(int i = 0; i < 32; i++)
-        {
-            int x1 = (x + random.nextInt(8)) - random.nextInt(8);
-            int y1 = (y + random.nextInt(4)) - random.nextInt(4);
-            int z1 = (z + random.nextInt(8)) - random.nextInt(8);
-            if(world.isAirBlock(x1, y1, z1) && (world.getBlockId(x1, y1 - 1, z1) == Blocks.COBBLE_NETHERRACK.id() || world.getBlockId(x1, y1 - 1, z1) == Blocks.BRIMSAND.id()))
-            {
-                world.setBlockWithNotify(x1, y1, z1, Blocks.FIRE.id());
-                world.setBlock(x1, y1 - 1, z1, Blocks.COBBLE_NETHERRACK.id());
+    public boolean place(@NotNull World world, @NotNull Random random, @NotNull TilePosc tilePos) {
+        TilePos queryPos = new TilePos();
+        TilePos downPos = new TilePos();
+
+        for (int i = 0; i < 32; ++i) {
+            queryPos.set(
+                    tilePos.x() + random.nextInt(8) - random.nextInt(8),
+                    tilePos.y() + random.nextInt(4) - random.nextInt(4),
+                    tilePos.z() + random.nextInt(8) - random.nextInt(8)
+            );
+
+            downPos.set(queryPos).down();
+
+            if (world.isAirBlock(queryPos)) {
+                Block<?> surfaceBlock = world.getBlockType(downPos);
+
+                if (this.overrideFireBlock != null && this.overrideGroundBlock != null) {
+                    if (surfaceBlock.hasTag(BlockTags.NETHER_SURFACE_BLOCK)) {
+                        world.setBlockTypeNotify(queryPos, this.overrideFireBlock);
+                        world.setBlockTypeNotify(downPos, this.overrideGroundBlock);
+                    }
+                }
+
+                else {
+                    if (surfaceBlock.hasTag(BlockTags.INFINITE_BURN_SULFURIC)) {
+                        world.setBlockTypeNotify(queryPos, Blocks.FIRE_SULFURIC);
+                    }
+                    else if (surfaceBlock.hasTag(BlockTags.INFINITE_BURN)) {
+                        world.setBlockTypeNotify(queryPos, Blocks.FIRE);
+                    }
+                }
             }
         }
 
         return true;
     }
-}
+}
\ No newline at end of file
diff --git a/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureFloorVent.java b/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureFloorVent.java
index e9efe2239..5f12bdf2d 100644
--- a/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureFloorVent.java
+++ b/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureFloorVent.java
@@ -9,70 +9,73 @@ import org.jetbrains.annotations.NotNull;
 
 import java.util.Random;
 
-public class WorldFeatureFloorVent implements WorldFeatureInterface
-{
+public class WorldFeatureFloorVent implements WorldFeatureInterface {
     private final int minRadius;
     private final int radiusMod;
 
-
-    public WorldFeatureFloorVent(int minRadius, int radiusMod)
-    {
+    public WorldFeatureFloorVent(int minRadius, int radiusMod) {
         this.minRadius = minRadius;
         this.radiusMod = radiusMod;
     }
 
-    public WorldFeatureFloorVent()
-    {
+    public WorldFeatureFloorVent() {
         this.minRadius = 1;
         this.radiusMod = 2;
     }
 
-    private boolean isAirOrLiquid(World world, TilePos tilePos){
-        return world.isAirBlock(tilePos) ||
-                world.getBlockType(tilePos).hasTag(BlockTags.IS_LAVA) ||
-                world.getBlockType(tilePos).hasTag(BlockTags.IS_WATER);
-    }
-
     @Override
     public boolean place(@NotNull World world, @NotNull Random random, @NotNull TilePosc tilePos) {
-        TilePos pos = (TilePos) tilePos;
-        while(isAirOrLiquid(world, pos) && pos.y - 1 > 0) {pos.down(); }
+        TilePos pos = new TilePos(tilePos.x(), tilePos.y(), tilePos.z());
 
-        int radius = minRadius + random.nextInt(radiusMod);
+        pos.x += random.nextInt(8);
+        pos.z += random.nextInt(8);
 
-        TilePos centerPos = new TilePos(pos.x() + random.nextInt(8), pos.y(), pos.z() + random.nextInt(8));
-        TilePos cursor = new TilePos();
+        while (world.isAirBlock(pos) && pos.y > 0) {
+            pos.down();
+        }
 
-        if(!canPlace(centerPos, world) && !isAirOrLiquid(world, centerPos.up(new TilePos()))){
+        if (pos.y <= 0 || !canPlace(pos, world)) {
             return false;
         }
 
-        for(cursor.x = centerPos.x() - radius; cursor.x <= centerPos.x() + radius; cursor.x++) {
-            for (cursor.z = centerPos.z() - radius; cursor.z <= centerPos.z() + radius; cursor.z++) {
-                int xd = cursor.x() - centerPos.x();
-                int zd = cursor.z() - centerPos.z();
+        if (!world.isAirBlock(new TilePos(pos.x, pos.y + 1, pos.z)) ||
+                !world.isAirBlock(new TilePos(pos.x + 1, pos.y + 1, pos.z)) ||
+                !world.isAirBlock(new TilePos(pos.x - 1, pos.y + 1, pos.z)) ||
+                !world.isAirBlock(new TilePos(pos.x, pos.y + 1, pos.z + 1)) ||
+                !world.isAirBlock(new TilePos(pos.x, pos.y + 1, pos.z - 1))) {
+            return false;
+        }
 
-                float dist = xd * xd + zd * zd;
+        int radius = minRadius + random.nextInt(radiusMod);
+        TilePos cursor = new TilePos();
 
-                for(cursor.y = centerPos.y(); cursor.y > pos.y - 4; cursor.y--) {
+        for (cursor.x = pos.x - radius; cursor.x <= pos.x + radius; cursor.x++) {
+            for (cursor.z = pos.z - radius; cursor.z <= pos.z + radius; cursor.z++) {
+                int xd = cursor.x - pos.x;
+                int zd = cursor.z - pos.z;
+                float distSq = xd * xd + zd * zd;
 
-                    if(cursor.y <= 0)
-                        continue;
+                if (distSq > radius * radius) {
+                    continue;
+                }
 
-                    // Skip placement if cursor hasn't hit ground yet
+                for (cursor.y = pos.y; cursor.y > pos.y - 4; cursor.y--) {
+                    if (cursor.y <= 0) continue;
 
-                    if(dist <= (radius * radius) && canPlace(cursor, world)) {
-                        if(cursor.equals(centerPos)){
+                    if (canPlace(cursor, world)) {
+                        if (cursor.x == pos.x && cursor.y == pos.y && cursor.z == pos.z) {
                             world.setBlockTypeNotify(cursor, Blocks.THERMAL_VENT);
                         } else {
-                            if(random.nextInt(4) != 0) {
+                            if (random.nextInt(4) != 0) {
                                 world.setBlockTypeNotify(cursor, Blocks.BASALT);
-                            } else if(random.nextInt(20) == 0) {
+                            } else if (random.nextInt(20) == 0) {
+                                if (cursor.y == pos.y && world.isAirBlock(new TilePos(cursor.x, cursor.y + 1, cursor.z))) {
                                     world.setBlockTypeNotify(cursor, Blocks.THERMAL_VENT);
+                                } else {
+                                    world.setBlockTypeNotify(cursor, Blocks.BASALT);
+                                }
                             }
-
                         }
-
                     }
                 }
             }
@@ -80,12 +83,13 @@ public class WorldFeatureFloorVent implements WorldFeatureInterface
         return true;
     }
 
-    private boolean canPlace(TilePos cursor, World world) {
-
-        if (isAirOrLiquid(world, cursor)) {
+    private boolean canPlace(TilePos cursor, @NotNull World world) {
+        if (world.isAirBlock(cursor) ||
+            world.getBlockType(cursor).hasTag(BlockTags.IS_LAVA) ||
+            world.getBlockType(cursor).hasTag(BlockTags.IS_WATER)) {
             return false;
         }
 
         return world.getBlockType(cursor).hasTag(BlockTags.NETHER_SURFACE_BLOCK);
     }
-}
+}
\ No newline at end of file
diff --git a/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureNetherPatch.java b/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureNetherPatch.java
index 77eee77a1..96be52cc4 100644
--- a/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureNetherPatch.java
+++ b/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureNetherPatch.java
@@ -1,34 +1,56 @@
 package net.minecraft.core.world.generate.feature;
 
-import net.minecraft.core.block.Blocks;
+import net.minecraft.core.block.Block;
 import net.minecraft.core.world.World;
+import net.minecraft.core.world.pos.TilePos;
+import net.minecraft.core.world.pos.TilePosc;
+import org.jetbrains.annotations.NotNull;
 
 import java.util.Random;
 
-public class WorldFeatureNetherPatch extends WorldFeature
-{
-    private final int blockId;
-    @MethodParametersAnnotation(names = {"blockId"})
-    public WorldFeatureNetherPatch(int blockId)
-    {
-        this.blockId = blockId;
+public class WorldFeatureNetherPatch implements WorldFeatureInterface {
+    protected final Block<?> defaultBlock;
+    protected final int attempts;
+
+    public WorldFeatureNetherPatch(Block<?> defaultBlock, int attempts) {
+        this.defaultBlock = defaultBlock;
+        this.attempts = attempts;
+    }
+
+    public WorldFeatureNetherPatch(Block<?> defaultBlock) {
+        this(defaultBlock, 64);
     }
 
     @Override
-	public boolean place(World world, Random random, int x, int y, int z)
-    {
-        while(world.getBlockId(x, y, z) == 0 && y > 0) { y--; }
-        for(int i = 0; i < 8; i++)
-        {
-            int x1 = (x + random.nextInt(4)) - random.nextInt(4);
-            int y1 = (y + random.nextInt(4)) - random.nextInt(4);
-            int z1 = (z + random.nextInt(4)) - random.nextInt(4);
-            if(world.isAirBlock(x1, y1, z1) && Blocks.blocksList[blockId].canBlockStay(world, x1, y1, z1))
-            {
-                world.setBlock(x1, y1, z1, blockId);
+    public boolean place(@NotNull World world, @NotNull Random random, @NotNull TilePosc tilePos) {
+        TilePos pos = new TilePos(tilePos);
+
+        while (world.isAirBlock(pos) && pos.y() > 0) {
+            pos.down();
+        }
+
+        TilePos queryPos = new TilePos();
+
+        for (int i = 0; i < this.attempts; i++) {
+            queryPos.set(
+                pos.x() + random.nextInt(4) - random.nextInt(4),
+                pos.y() + random.nextInt(4) - random.nextInt(4),
+                pos.z() + random.nextInt(4) - random.nextInt(4)
+            );
+
+            if (world.isAirBlock(queryPos)) {
+                Block<?> blockToPlace = this.getBlockToPlace(world, queryPos);
+
+                if (blockToPlace != null && blockToPlace.canStay(world, queryPos)) {
+                    world.setBlockType(queryPos, blockToPlace);
+                }
             }
         }
 
         return true;
     }
-}
+
+    protected Block<?> getBlockToPlace(World world, TilePos queryPos) {
+        return this.defaultBlock;
+    }
+}
\ No newline at end of file
diff --git a/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureNetherPatchBelow.java b/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureNetherPatchBelow.java
new file mode 100644
index 000000000..1181b6583
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureNetherPatchBelow.java
@@ -0,0 +1,59 @@
+package net.minecraft.core.world.generate.feature;
+
+import net.minecraft.core.block.Block;
+import net.minecraft.core.block.Blocks;
+import net.minecraft.core.world.World;
+import net.minecraft.core.world.pos.TilePos;
+import net.minecraft.core.world.pos.TilePosc;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Random;
+
+public class WorldFeatureNetherPatchBelow implements WorldFeatureInterface {
+	private final int blockId;
+
+	@MethodParametersAnnotation(names = {"blockId"})
+	public WorldFeatureNetherPatchBelow(int blockId) {
+		this.blockId = blockId;
+	}
+
+	@Override
+	public boolean place(@NotNull World world, @NotNull Random random, @NotNull TilePosc tilePos) {
+		TilePos pos = new TilePos(tilePos);
+
+		while (world.isAirBlock(pos) && pos.y() > 0) {
+			pos.down();
+		}
+
+		TilePos queryPos = new TilePos();
+		TilePos targetPos = new TilePos();
+		TilePos tempPos = new TilePos();
+		Block<?> block = Blocks.blocksList[this.blockId];
+
+		for (int i = 0; i < 8; ++i) {
+			queryPos.set(
+				pos.x() + random.nextInt(4) - random.nextInt(4),
+				pos.y() + random.nextInt(4) - random.nextInt(4),
+				pos.z() + random.nextInt(4) - random.nextInt(4)
+			);
+
+			targetPos.set(queryPos.x(), queryPos.y() - 1, queryPos.z());
+
+			if (world.isAirBlock(queryPos) && world.getBlockType(targetPos) == Blocks.BLOCK_ASH) {
+
+				boolean surroundedByAsh =
+					world.getBlockType(tempPos.set(targetPos).east()) == Blocks.BLOCK_ASH &&
+						world.getBlockType(tempPos.set(targetPos).west()) == Blocks.BLOCK_ASH &&
+						world.getBlockType(tempPos.set(targetPos).south()) == Blocks.BLOCK_ASH &&
+						world.getBlockType(tempPos.set(targetPos).north()) == Blocks.BLOCK_ASH;
+
+				if (surroundedByAsh && block.canStay(world, queryPos)) {
+					world.setBlockType(targetPos, block);
+					world.setBlockType(queryPos, Blocks.AIR);
+				}
+			}
+		}
+
+		return true;
+	}
+}
diff --git a/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureNetherScatter.java b/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureNetherScatter.java
new file mode 100644
index 000000000..fe0c402e1
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureNetherScatter.java
@@ -0,0 +1,148 @@
+package net.minecraft.core.world.generate.feature;
+
+import net.minecraft.core.block.Block;
+import net.minecraft.core.block.Blocks;
+import net.minecraft.core.world.World;
+import net.minecraft.core.world.biome.Biome;
+import net.minecraft.core.world.biome.Biomes;
+import net.minecraft.core.world.pos.TilePos;
+import net.minecraft.core.world.pos.TilePosc;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.Random;
+
+public class WorldFeatureNetherScatter implements WorldFeatureInterface {
+    private static final int MAX_DOWN_SCAN = 96;
+    private static final int BONE_PILE_ON_ASH_RARE_ONE_IN = 8;
+    private static final int EMBER_FIRST_ROLL_SIDES = 4;
+    private static final int EMBER_FIRST_ROLL_THRESHOLD = 1;
+
+    private final TilePos scratchGround = new TilePos();
+    private final TilePos scratchPlace = new TilePos();
+    private final TilePos scratchProbe = new TilePos();
+    private final TilePos scratchNeighbor = new TilePos();
+
+    @Override
+    public boolean place(@NotNull World world, @NotNull Random random, @NotNull TilePosc tilePos) {
+        if (random.nextInt(EMBER_FIRST_ROLL_SIDES) >= EMBER_FIRST_ROLL_THRESHOLD) {
+            final Biome seedBiome = world.getBlockBiome(tilePos);
+            if (isOldWorldBiome(seedBiome)) {
+                if (this.tryPlaceEmberReplacingAsh(world, tilePos)) {
+                    return true;
+                }
+            }
+        }
+
+        final TilePos ground = this.scratchGround.set(tilePos.x(), tilePos.y(), tilePos.z());
+        int scanned = 0;
+        while (world.isAirBlock(ground) && ground.y() > 0 && scanned < MAX_DOWN_SCAN) {
+            ground.down();
+            scanned++;
+        }
+        if (ground.y() <= 0) { return false; }
+
+        final Block<?> surface = world.getBlockType(ground);
+
+        if (isRubyglassScatterForbiddenSurface(surface)) {
+            return false;
+        }
+
+        final TilePos placePos = this.scratchPlace.set(ground.x(), ground.y() + 1, ground.z());
+        if (!world.isAirBlock(placePos)) { return false; }
+
+        final Block<?> chosen = pickDecoration(world, random, placePos, surface);
+
+        if (chosen == Blocks.BOULDER_MAGMATIC && surface != Blocks.COBBLE_NETHERRACK && surface != Blocks.NETHERRACK) {
+            return false;
+        }
+        if (chosen == Blocks.BOULDER_SULFURIC && surface != Blocks.BRIMSAND && surface != Blocks.BRIMSTONE && surface != Blocks.BRIMTHAW) {
+            return false;
+        }
+        if (chosen == Blocks.BONE_PILE && isAshSurface(surface)) {
+            if (random.nextInt(BONE_PILE_ON_ASH_RARE_ONE_IN) != 0) {
+                return false;
+            }
+        }
+        if (!chosen.canStay(world, placePos)) { return false; }
+
+        world.setBlockType(placePos, chosen);
+        return true;
+    }
+
+    private static boolean isOldWorldBiome(final @Nullable Biome biome) {
+        return biome == Biomes.NETHER_OLD_WORLD || biome == Biomes.NETHER_OLD_WORLD_DESERT;
+    }
+
+    private static boolean isAshSurface(final @NotNull Block<?> surface) {
+        return surface == Blocks.BLOCK_ASH || surface == Blocks.LAYER_ASH;
+    }
+
+    private static boolean isRubyglassScatterForbiddenSurface(final @NotNull Block<?> surface) {
+        return surface == Blocks.RUBYGLASS_NODE
+                || surface == Blocks.RUBYGLASS_COLUMN
+                || surface == Blocks.RUBYGLASS_GROWTH;
+    }
+
+    private boolean tryPlaceEmberReplacingAsh(final @NotNull World world, final @NotNull TilePosc start) {
+        final int x = start.x();
+        final int z = start.z();
+        final TilePos probe = this.scratchProbe;
+
+        int scanned = 0;
+        for (int y = start.y(); y > 0 && scanned < MAX_DOWN_SCAN; y--, scanned++) {
+            probe.set(x, y, z);
+
+            if (world.getBlockType(probe) != Blocks.BLOCK_ASH) {
+                continue;
+            }
+
+            final TilePos above = this.scratchPlace.set(x, y + 1, z);
+            if (!world.isAirBlock(above)) {
+                continue;
+            }
+
+            if (!isOldWorldBiome(world.getBlockBiome(probe))) {
+                continue;
+            }
+
+            if (!this.isAshSurroundedByAsh(world, x, y, z)) {
+                continue;
+            }
+
+            world.setBlockType(probe, Blocks.EMBER);
+            if (Blocks.LAYER_ASH.canPlaceAt(world, above)) {
+                world.setBlockType(above, Blocks.LAYER_ASH);
+            }
+            return true;
+        }
+        return false;
+    }
+
+    private boolean isAshSurroundedByAsh(final @NotNull World world, final int ax, final int ay, final int az) {
+        final TilePos n = this.scratchNeighbor;
+        return world.getBlockType(n.set(ax + 1, ay, az)) == Blocks.BLOCK_ASH
+                && world.getBlockType(n.set(ax - 1, ay, az)) == Blocks.BLOCK_ASH
+                && world.getBlockType(n.set(ax, ay, az + 1)) == Blocks.BLOCK_ASH
+                && world.getBlockType(n.set(ax, ay, az - 1)) == Blocks.BLOCK_ASH;
+    }
+
+    private @NotNull Block<?> pickDecoration(final @NotNull World world, final @NotNull Random random,
+                                             final @NotNull TilePos placePos, final @NotNull Block<?> surface) {
+        if (surface == Blocks.OBSIDIAN) {
+            return Blocks.BONE_PILE;
+        }
+
+        final Biome biome = world.getBlockBiome(placePos);
+        if (biome == Biomes.NETHER_SULFUR_POOLS) {
+            return random.nextBoolean() ? Blocks.BONE_PILE : Blocks.BOULDER_SULFURIC;
+        }
+        if (biome == Biomes.NETHER_VOLCANIC_ISLANDS || biome == Biomes.NETHER_CRAG
+                || biome == Biomes.NETHER_CRYSTAL_PLAINS || biome == Biomes.NETHER_CRYSTAL_FOREST
+                || biome == Biomes.NETHER_SHELF) {
+            return random.nextBoolean() ? Blocks.BONE_PILE : Blocks.BOULDER_MAGMATIC;
+        }
+
+        return Blocks.BONE_PILE;
+    }
+}
\ No newline at end of file
diff --git a/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureNetherSpring.java b/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureNetherSpring.java
index b71f37b38..e5b7bf40d 100644
--- a/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureNetherSpring.java
+++ b/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureNetherSpring.java
@@ -1,79 +1,65 @@
 package net.minecraft.core.world.generate.feature;
 
+import net.minecraft.core.block.Block;
 import net.minecraft.core.block.Blocks;
+import net.minecraft.core.block.tag.BlockTags;
 import net.minecraft.core.world.World;
+import net.minecraft.core.world.pos.TilePos;
+import net.minecraft.core.world.pos.TilePosc;
+import org.jetbrains.annotations.NotNull;
 
 import java.util.Random;
 
-public class WorldFeatureNetherSpring extends WorldFeature
-{
-    private final int blockId;
-    @MethodParametersAnnotation(names = {"blockId"})
-    public WorldFeatureNetherSpring(int blockId)
-    {
-        this.blockId = blockId;
-    }
+public class WorldFeatureNetherSpring implements WorldFeatureInterface {
+	private final int blockId;
 
-    @Override
-	public boolean place(World world, Random random, int x, int y, int z)
-    {
-        if(world.getBlockId(x, y + 1, z) != Blocks.COBBLE_NETHERRACK.id())
-        {
-            return false;
-        }
-        if(world.getBlockId(x, y, z) != 0 && world.getBlockId(x, y, z) != Blocks.COBBLE_NETHERRACK.id())
-        {
-            return false;
-        }
-        int l = 0;
-        if(world.getBlockId(x - 1, y, z) == Blocks.COBBLE_NETHERRACK.id())
-        {
-            l++;
-        }
-        if(world.getBlockId(x + 1, y, z) == Blocks.COBBLE_NETHERRACK.id())
-        {
-            l++;
-        }
-        if(world.getBlockId(x, y, z - 1) == Blocks.COBBLE_NETHERRACK.id())
-        {
-            l++;
-        }
-        if(world.getBlockId(x, y, z + 1) == Blocks.COBBLE_NETHERRACK.id())
-        {
-            l++;
-        }
-        if(world.getBlockId(x, y - 1, z) == Blocks.COBBLE_NETHERRACK.id())
-        {
-            l++;
-        }
-        int i1 = 0;
-        if(world.isAirBlock(x - 1, y, z))
-        {
-            i1++;
-        }
-        if(world.isAirBlock(x + 1, y, z))
-        {
-            i1++;
-        }
-        if(world.isAirBlock(x, y, z - 1))
-        {
-            i1++;
-        }
-        if(world.isAirBlock(x, y, z + 1))
-        {
-            i1++;
-        }
-        if(world.isAirBlock(x, y - 1, z))
-        {
-            i1++;
-        }
-        if(l == 4 && i1 == 1)
-        {
-            world.setBlockWithNotify(x, y, z, blockId);
-            world.scheduledUpdatesAreImmediate = true;
-            Blocks.blocksList[blockId].updateTick(world, x, y, z, random, false);
-            world.scheduledUpdatesAreImmediate = false;
-        }
-        return true;
-    }
-}
+	@MethodParametersAnnotation(names = {"blockId"})
+	public WorldFeatureNetherSpring(int blockId) {
+		this.blockId = blockId;
+	}
+
+	@Override
+	public boolean place(@NotNull World world, @NotNull Random random, @NotNull TilePosc tilePos) {
+		TilePos upPos = new TilePos(tilePos).up();
+
+		if (world.getBlockType(upPos).hasTag(BlockTags.NETHER_SURFACE_BLOCK)) {
+			return false;
+		} else if (!world.isAirBlock(tilePos) && world.getBlockType(tilePos).hasTag(BlockTags.NETHER_SURFACE_BLOCK)) {
+			return false;
+		} else {
+			int solidCount = 0;
+			int airCount = 0;
+
+			TilePos queryPos = new TilePos();
+			int[][] offsets = {{-1, 0, 0}, {1, 0, 0}, {0, 0, -1}, {0, 0, 1}, {0, -1, 0}};
+
+			for (int[] offset : offsets) {
+				queryPos.set(tilePos.x() + offset[0], tilePos.y() + offset[1], tilePos.z() + offset[2]);
+				if (world.getBlockType(queryPos).hasTag(BlockTags.NETHER_SURFACE_BLOCK)) {
+					solidCount++;
+				}
+				if (world.isAirBlock(queryPos)) {
+					airCount++;
+				}
+			}
+
+			if (solidCount == 4 && airCount == 1) {
+				if (this.blockId == Blocks.FLUID_ACID_STILL.id() || this.blockId == Blocks.FLUID_ACID_FLOWING.id()) {
+					if (random.nextInt(50) == 0 && world.isAirBlock(upPos)) {
+						world.setBlockTypeNotify(tilePos, Blocks.GLOWSTONE);
+						world.setBlockTypeNotify(upPos, Blocks.FIRE_SULFURIC);
+					}
+				} else {
+					Block<?> block = Blocks.blocksList[this.blockId];
+					world.setBlockTypeNotify(tilePos, block);
+
+					world.scheduledUpdatesAreImmediate = true;
+					block.updateTick(world, tilePos, random, false);
+					world.scheduledUpdatesAreImmediate = false;
+				}
+			}
+
+			return true;
+		}
+	}
+}
\ No newline at end of file
diff --git a/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureOre.java b/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureOre.java
index 138b96771..908bebe16 100644
--- a/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureOre.java
+++ b/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureOre.java
@@ -1,44 +1,51 @@
 package net.minecraft.core.world.generate.feature;
 
 import it.unimi.dsi.fastutil.ints.Int2IntArrayMap;
+import net.minecraft.core.block.Block;
 import net.minecraft.core.block.Blocks;
 import net.minecraft.core.util.helper.MathHelper;
 import net.minecraft.core.world.World;
+import net.minecraft.core.world.pos.TilePos;
+import net.minecraft.core.world.pos.TilePosc;
 import org.jetbrains.annotations.NotNull;
 
 import java.util.Random;
 
-public class WorldFeatureOre extends WorldFeature
-{
-    private int minableBlockId;
-    private int numberOfBlocks;
+public class WorldFeatureOre implements WorldFeatureInterface {
+    private final int minableBlockId;
+    private final int numberOfBlocks;
     private final Int2IntArrayMap variantMap;
     @MethodParametersAnnotation(names = {"blockId", "numberOfBlocks"})
-    public WorldFeatureOre(int blockId, int numberOfBlocks)
-    {
-        minableBlockId = blockId;
+    public WorldFeatureOre(int blockId, int numberOfBlocks) {
+        this.minableBlockId = blockId;
         this.numberOfBlocks = numberOfBlocks;
         this.variantMap = null;
     }
     @MethodParametersAnnotation(names = {"blockId", "numberOfBlocks", "variantMap"})
-    public WorldFeatureOre(@NotNull Int2IntArrayMap variantMap, int numberOfBlocks)
-    {
+    public WorldFeatureOre(@NotNull Int2IntArrayMap variantMap, int numberOfBlocks) {
+        this.minableBlockId = 0;
         this.numberOfBlocks = numberOfBlocks;
         this.variantMap = variantMap;
     }
 
     @Override
-	public boolean place(World world, Random random, int xStart, int yStart, int zStart)
-    {
+    public boolean place(@NotNull World world, @NotNull Random random, @NotNull TilePosc tilePos) {
         float f = random.nextFloat() * MathHelper.PI;
+
+        int xStart = tilePos.x();
+        int yStart = tilePos.y();
+        int zStart = tilePos.z();
+
         double xMax = (float)(xStart + 8) + MathHelper.sin(f) * (float)numberOfBlocks / 8F;
         double xMin = (float)(xStart + 8) - MathHelper.sin(f) * (float)numberOfBlocks / 8F;
         double zMax = (float)(zStart + 8) + MathHelper.cos(f) * (float)numberOfBlocks / 8F;
         double zMin = (float)(zStart + 8) - MathHelper.cos(f) * (float)numberOfBlocks / 8F;
         double yMax = yStart + random.nextInt(3) + 2;
         double yMin = yStart - random.nextInt(3) + 2;
-        for(int l = 0; l <= numberOfBlocks; l++)
-        {
+
+        TilePos queryPos = new TilePos();
+
+        for(int l = 0; l <= numberOfBlocks; l++) {
             double d6 = xMax + (xMin - xMax) * (double)l / (double)numberOfBlocks;
             double d7 = yMax + (yMin - yMax) * (double)l / (double)numberOfBlocks;
             double d8 = zMax + (zMin - zMax) * (double)l / (double)numberOfBlocks;
@@ -51,52 +58,45 @@ public class WorldFeatureOre extends WorldFeature
             int xVeinEnd = MathHelper.floor(d6 + d10 / 2D);
             int yVeinEnd = MathHelper.floor(d7 + d11 / 2D);
             int zVeinEnd = MathHelper.floor(d8 + d10 / 2D);
-            for(int x = xVeinStart; x <= xVeinEnd; x++)
-            {
+
+            for(int x = xVeinStart; x <= xVeinEnd; x++) {
                 double d12 = (((double)x + 0.5D) - d6) / (d10 / 2D);
-                if(d12 * d12 >= 1.0D)
-                {
+                if(d12 * d12 >= 1.0D) {
                     continue;
                 }
-                for(int y = yVeinStart; y <= yVeinEnd; y++)
-                {
+                for(int y = yVeinStart; y <= yVeinEnd; y++) {
                     double d13 = (((double)y + 0.5D) - d7) / (d11 / 2D);
-                    if(d12 * d12 + d13 * d13 >= 1.0D)
-                    {
+                    if(d12 * d12 + d13 * d13 >= 1.0D) {
                         continue;
                     }
-                    for(int z = zVeinStart; z <= zVeinEnd; z++)
-                    {
+                    for(int z = zVeinStart; z <= zVeinEnd; z++) {
                         double d14 = (((double)z + 0.5D) - d8) / (d10 / 2D);
-                        if(d12 * d12 + d13 * d13 + d14 * d14 < 1.0D)
-                        {
-                        	if (variantMap != null)
-                        	{
-                                int blockId = world.getBlockId(x, y, z);
-                                if (blockId <= 0) continue;
-                                if (variantMap.containsKey(blockId)){
-                                    world.setBlock(x, y, z, variantMap.get(blockId));
+                        if(d12 * d12 + d13 * d13 + d14 * d14 < 1.0D) {
+
+                            queryPos.set(x, y, z);
+                            Block<?> currentBlock = world.getBlockType(queryPos);
+
+                            if (currentBlock == Blocks.AIR) continue;
+
+                            int id = currentBlock.id();
+
+                            if (variantMap != null) {
+                                if (variantMap.containsKey(id)){
+                                    world.setBlockType(queryPos, Blocks.blocksList[variantMap.get(id)]);
+                                }
+                            } else {
+                                if (id == Blocks.STONE.id() || id == Blocks.COBBLE_NETHERRACK.id() ||
+                                    id == Blocks.BASALT.id() || id == Blocks.LIMESTONE.id() ||
+                                    id == Blocks.GRANITE.id()) {
+
+                                    world.setBlockType(queryPos, Blocks.blocksList[minableBlockId]);
                                 }
-                        	}
-                        	else
-                        	{
-                        		int id = world.getBlockId(x, y, z);
-                        		if (id == Blocks.STONE.id() || id == Blocks.COBBLE_NETHERRACK.id() ||
-                        				id == Blocks.BASALT.id() || id == Blocks.LIMESTONE.id() ||
-                        				id == Blocks.GRANITE.id())
-                        		{
-                        			world.setBlock(x, y, z, minableBlockId);
-                        		}
-                        	}
+                            }
                         }
                     }
-
                 }
-
             }
-
         }
-
         return true;
     }
 }
diff --git a/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeaturePerlinVein.java b/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeaturePerlinVein.java
new file mode 100644
index 000000000..a160d7226
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeaturePerlinVein.java
@@ -0,0 +1,72 @@
+package net.minecraft.core.world.generate.feature;
+
+import net.minecraft.core.block.Block;
+import net.minecraft.core.world.World;
+import net.minecraft.core.world.noise.ImprovedPerlinNoise;
+import net.minecraft.core.world.pos.TilePos;
+import net.minecraft.core.world.pos.TilePosc;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Random;
+
+public class WorldFeaturePerlinVein implements WorldFeatureInterface {
+    private final Block<?> placeBlock;
+    private final Block<?> targetBlock;
+    private ImprovedPerlinNoise noiseGen;
+    private final double scale;
+    private final double thickness;
+
+    public WorldFeaturePerlinVein(Block<?> placeBlock, Block<?> targetBlock, double scale, double thickness) {
+        this.placeBlock = placeBlock;
+        this.targetBlock = targetBlock;
+        this.scale = scale;
+        this.thickness = thickness;
+    }
+
+    @Override
+    public boolean place(@NotNull World world, @NotNull Random random, @NotNull TilePosc tilePos) {
+        if (this.noiseGen == null) {
+            this.noiseGen = new ImprovedPerlinNoise(new Random(world.getRandomSeed() + this.placeBlock.id()));
+        }
+
+        int startX = tilePos.x();
+        int startZ = tilePos.z();
+        int maxY = world.getWorldType().getMaxY(world);
+        int minY = world.getWorldType().getMinY(world);
+        int ySize = maxY - minY;
+        if (ySize <= 0) {
+            return false;
+        }
+
+        // Single bulk noise sample for the full chunk column instead of per-block.
+        double[] noiseRegion = this.noiseGen.getRegion(null, startX, minY, startZ, 16, ySize, 16, this.scale, this.scale, this.scale);
+
+        boolean placedAny = false;
+        TilePos queryPos = new TilePos();
+
+        for (int xi = 0; xi < 16; xi++) {
+            int worldX = startX + xi;
+            int xBase = xi * 16 * ySize;
+            for (int zi = 0; zi < 16; zi++) {
+                int worldZ = startZ + zi;
+                int zBase = xBase + zi * ySize;
+                for (int yi = 0; yi < ySize; yi++) {
+                    double noiseVal = noiseRegion[zBase + yi];
+                    if (Math.abs(noiseVal) > this.thickness) {
+                        continue;
+                    }
+
+                    queryPos.set(worldX, minY + yi, worldZ);
+                    if (world.getBlockType(queryPos) != this.targetBlock) {
+                        continue;
+                    }
+
+                    world.setBlockType(queryPos, this.placeBlock);
+                    placedAny = true;
+                }
+            }
+        }
+
+        return placedAny;
+    }
+}
\ No newline at end of file
diff --git a/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureRoofSpire.java b/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureRoofSpire.java
new file mode 100644
index 000000000..d60ce0ab1
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureRoofSpire.java
@@ -0,0 +1,74 @@
+package net.minecraft.core.world.generate.feature;
+
+import net.minecraft.core.block.Block;
+import net.minecraft.core.block.Blocks;
+import net.minecraft.core.block.tag.BlockTags;
+import net.minecraft.core.world.World;
+import net.minecraft.core.world.pos.TilePos;
+import org.joml.Vector3i;
+
+import java.util.Random;
+
+public class WorldFeatureRoofSpire extends WorldFeature {
+	private final Block<?> boulderBlock;
+
+	public WorldFeatureRoofSpire(Block<?> boulderBlock) {
+		this.boulderBlock = boulderBlock;
+	}
+
+	public boolean place(World world, Random random, int x, int y, int z) {
+		TilePos pos = new TilePos(x, y, z);
+
+		while (world.isAirBlock(pos) && pos.y < 245) {
+			pos.up();
+		}
+
+		if (this.hasSpace(world, pos.x, pos.y, pos.z)) {
+			Vector3i highestOffset = new Vector3i(0, 0, 0);
+			TilePos centerPos = new TilePos(pos.x, pos.y, pos.z);
+			TilePos queryPos = new TilePos();
+
+			for (int x1 = 0; x1 < 2; ++x1) {
+				for (int z1 = 0; z1 < 2; ++z1) {
+					int height = random.nextInt(6) + 4;
+					if (height > highestOffset.y) {
+						highestOffset.set(x1, height, z1);
+					}
+
+					for (int y1 = 0; y1 < height; ++y1) {
+						centerPos.add(x1, -y1, z1, queryPos);
+
+						Block<?> currentBlock = world.getBlockType(queryPos);
+
+						if (currentBlock != Blocks.BEDROCK) {
+							world.setBlockType(queryPos, boulderBlock);
+						}
+					}
+				}
+			}
+
+			for (int y1 = 0; y1 < 2; ++y1) {
+				centerPos.add(highestOffset.x, -(highestOffset.y + y1), highestOffset.z, queryPos);
+				world.setBlockType(queryPos, boulderBlock);
+			}
+		}
+
+		return true;
+	}
+
+	private boolean hasSpace(World world, int xc, int y, int zc) {
+		TilePos queryPos = new TilePos();
+
+		for (queryPos.y = y - 3; queryPos.y > y - 9; --queryPos.y) {
+			for (queryPos.x = xc - 3; queryPos.x < xc + 3; ++queryPos.x) {
+				for (queryPos.z = zc - 3; queryPos.z < zc + 3; ++queryPos.z) {
+					if (!world.isAirBlock(queryPos) && !world.getBlockType(queryPos).hasTag(BlockTags.NETHER_SURFACE_BLOCK)) {
+						return false;
+					}
+				}
+			}
+		}
+
+		return true;
+	}
+}
diff --git a/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureRubyglassCrystalline.java b/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureRubyglassCrystalline.java
new file mode 100644
index 000000000..c47fd7f3b
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureRubyglassCrystalline.java
@@ -0,0 +1,164 @@
+package net.minecraft.core.world.generate.feature;
+
+import net.minecraft.core.block.Block;
+import net.minecraft.core.block.Blocks;
+import net.minecraft.core.util.helper.MathHelper;
+import net.minecraft.core.world.World;
+import net.minecraft.core.world.generate.voxelizer.ConvexShapeVoxelizer;
+import net.minecraft.core.world.pos.TilePos;
+import net.minecraft.core.world.pos.TilePosc;
+import org.jetbrains.annotations.NotNull;
+import org.joml.Matrix4f;
+import org.joml.Vector3f;
+
+import java.util.Objects;
+import java.util.Random;
+
+public class WorldFeatureRubyglassCrystalline implements WorldFeatureInterface {
+	private static final float BASE_PCTG = 0.5F;
+	private final boolean isCeiling;
+	private final int minHeight;
+	private final int heightMod;
+
+	public WorldFeatureRubyglassCrystalline(boolean isCeiling, int minHeight, int heightMod) {
+		this.isCeiling = isCeiling;
+		this.minHeight = minHeight;
+		this.heightMod = heightMod;
+	}
+
+	public boolean place(@NotNull World world, @NotNull Random random, @NotNull TilePosc tilePos) {
+		TilePos ti = new TilePos(tilePos.x() + random.nextInt(2) - random.nextInt(2), tilePos.y(), tilePos.z() + random.nextInt(2) - random.nextInt(2));
+		int floorY = -1;
+		int checksForGround = 0;
+		boolean foundGround = false;
+		boolean canGenerate = false;
+		int groundDepth = 0;
+
+		while (true) {
+			if (this.isCeiling) {
+				++ti.y;
+			} else {
+				--ti.y;
+			}
+
+			if (checksForGround > 64 || ti.y < 0 || ti.y >= world.getHeightBlocks()) {
+				break;
+			}
+
+			Block<?> block = world.getBlockType(ti);
+			if (!foundGround) {
+				if (block == Blocks.COBBLE_NETHERRACK) {
+					floorY = ti.y;
+					foundGround = true;
+				}
+			} else {
+				++groundDepth;
+				if (block != Blocks.COBBLE_NETHERRACK) {
+					break;
+				}
+
+				if (groundDepth >= 6) {
+					canGenerate = true;
+					break;
+				}
+			}
+
+			++checksForGround;
+		}
+
+		Block<?> block = world.getBlockType(ti);
+		if (canGenerate && (block == Blocks.COBBLE_NETHERRACK || block == Blocks.RUBYGLASS_CRYSTAL || block == Blocks.SOULSAND)) {
+			if (this.isCeiling) {
+				++floorY;
+			} else {
+				--floorY;
+			}
+
+			this.placeCrystal(world, random, new TilePos(ti.x, floorY, ti.z));
+			return true;
+		} else {
+			return false;
+		}
+	}
+
+	private float randomOffset(int length, @NotNull Random random) {
+		int base = length / 16;
+		return random.nextFloat() * (float) base;
+	}
+
+	private void placeCrystal(@NotNull World world, @NotNull Random random, @NotNull TilePosc tilePos) {
+		float r = random.nextFloat();
+		int length = this.minHeight + MathHelper.floor(Math.pow(r, 4.0F) * (double) this.heightMod);
+		int base = length / 4;
+		float rx = 0.0F;
+		float ry = 0.0F;
+		Block<?> column = Blocks.RUBYGLASS_COLUMN;
+		Block<?> bed = Blocks.COBBLE_NETHERRACK_CRYSTALLINE;
+		Block<?> node = Blocks.RUBYGLASS_NODE;
+		TilePos ti = new TilePos(tilePos);
+		ti.y = this.isCeiling ? ti.y + base / 2 : ti.y - base / 2;
+		if (this.hasSpace(world, ti, base)) {
+			float minBase = -((float) base / 2.0F);
+			float maxBase = (float) base / 2.0F;
+			float randX = this.randomOffset(length, random);
+			float randZ = this.randomOffset(length, random);
+			Vector3f[] pts = new Vector3f[]{new Vector3f(minBase - randX, 0.0F, minBase - randZ), new Vector3f(maxBase + randX, 0.0F, minBase - randZ), new Vector3f(minBase - randX, 0.0F, maxBase + randZ), new Vector3f(maxBase + randX, 0.0F, maxBase + randZ), new Vector3f(minBase - randX, (float) length, minBase - randZ), new Vector3f(maxBase + randX, (float) length, minBase - randZ), new Vector3f(minBase - randX, (float) length, maxBase + randZ), new Vector3f(maxBase + randX, (float) length, maxBase + randZ), new Vector3f(0.0F, (float) length + maxBase * 4.0F * (random.nextFloat() * 0.5F + 0.5F), 0.0F)};
+			if (this.isCeiling) {
+				rx = (float) Math.PI;
+			}
+
+			rx += (float) ((random.nextDouble() - (double) 0.5F) * (Math.PI / 2D)) * 0.8F;
+			ry += (float) (random.nextDouble() * (double) 2.0F * Math.PI);
+			Matrix4f matrix = Objects.requireNonNull((new Matrix4f()).rotateY(ry).rotateX(rx));
+			int[][] faces = new int[][]{{0, 1, 3}, {0, 1, 5}, {1, 3, 7}, {3, 2, 6}, {2, 0, 4}, {4, 5, 8}, {5, 7, 8}, {7, 6, 8}, {6, 4, 8}};
+			ConvexShapeVoxelizer voxelizer = new ConvexShapeVoxelizer(pts, faces, matrix, (worldPos, replacing, faceDistances) -> {
+				if (replacing == Blocks.BEDROCK) {
+					return null;
+				} else {
+					boolean ore = random.nextInt(100) == 0;
+					boolean generateBed = false;
+					float distancePctg = faceDistances[0] / (float) length;
+					if (distancePctg < 0.5F) {
+						float innerDistancePctg = distancePctg / 0.5F;
+						if ((double) innerDistancePctg < (double) random.nextFloat() * (double) 0.75F) {
+							generateBed = true;
+						}
+					}
+
+					float minDistToSide = 10.0F;
+
+					for (int i = 1; i <= 4; ++i) {
+						minDistToSide = Math.min(faceDistances[i], minDistToSide);
+					}
+
+					float distFromCenter = maxBase - minDistToSide;
+					float distanceToCenterPctg = minDistToSide / maxBase;
+					if ((double) distFromCenter > (double) 0.5F) {
+						return generateBed ? bed : (ore ? node : column);
+					} else {
+						float centerNoise = (float) Math.sin((double) distanceToCenterPctg * Math.PI * (double) 3.0F) * 0.5F;
+						float finalNoise = random.nextFloat() + centerNoise;
+						return (double) finalNoise > (double) 0.5F ? node : column;
+					}
+				}
+			});
+			voxelizer.voxelize(world, ti);
+		}
+	}
+
+	private boolean hasSpace(@NotNull World world, @NotNull TilePosc tilePos, int base) {
+		TilePos ti = new TilePos();
+
+		for (ti.y = tilePos.y(); ti.y < tilePos.y() + base; ++ti.y) {
+			for (ti.x = tilePos.x() - base; ti.x < tilePos.x() + base; ++ti.x) {
+				for (ti.z = tilePos.z() - base; ti.z < tilePos.z() + base; ++ti.z) {
+					if (world.getBlockType(ti) == Blocks.RUBYGLASS_COLUMN) {
+						return false;
+					}
+				}
+			}
+		}
+
+		return true;
+	}
+}
diff --git a/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureRubyglassGrowthPatch.java b/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureRubyglassGrowthPatch.java
new file mode 100644
index 000000000..b76a184f9
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureRubyglassGrowthPatch.java
@@ -0,0 +1,122 @@
+package net.minecraft.core.world.generate.feature;
+
+import net.minecraft.core.block.Block;
+import net.minecraft.core.block.BlockLogicGrowthRubyglass;
+import net.minecraft.core.block.BlockLogicRotatable;
+import net.minecraft.core.block.Blocks;
+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.pos.TilePos;
+import net.minecraft.core.world.pos.TilePosc;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Random;
+
+public class WorldFeatureRubyglassGrowthPatch implements WorldFeatureInterface {
+
+	private final float tallGrowthChance;
+
+	private final TilePos scratchPos = new TilePos();
+	private final TilePos scratchSupportPos = new TilePos();
+	private final TilePos scratchOutwardPos = new TilePos();
+	private final TilePos scratchCubeQueryPos = new TilePos();
+
+	/**
+	 * Generates a mixed patch with a 60% chance for buds to grow into 2-tall crystals.
+	 */
+	public WorldFeatureRubyglassGrowthPatch() {
+		this(0.6F);
+	}
+
+	/**
+	 * Generates a configurable Rubyglass patch.
+	 * @param tallGrowthChance The chance (0.0F to 1.0F) that a crystal will attempt to grow 2 blocks tall.
+	 * Set to 0 to generate only buds.
+	 */
+	public WorldFeatureRubyglassGrowthPatch(float tallGrowthChance) {
+		this.tallGrowthChance = tallGrowthChance;
+	}
+
+	@Override
+	public boolean place(@NotNull World world, @NotNull Random random, @NotNull TilePosc tilePos) {
+		// Cache the static side array; Side.values() allocates a fresh array on every call.
+		final Side[] sides = Side.sides;
+
+		for(int i1 = 0; i1 < 20; i1++) {
+			int x1 = (tilePos.x() + random.nextInt(4)) - random.nextInt(4);
+			int y1 = (tilePos.y() + random.nextInt(4)) - random.nextInt(4);
+			int z1 = (tilePos.z() + random.nextInt(4)) - random.nextInt(4);
+
+			final TilePos pos = this.scratchPos.set(x1, y1, z1);
+
+			if (!world.isAirBlock(pos)) {
+				continue;
+			}
+
+			for (int s = 0, n = sides.length; s < n; s++) {
+				final Side side = sides[s];
+				final Direction growthDir = side.direction();
+				pos.add(growthDir.opposite(), this.scratchSupportPos);
+
+				if (!this.isValidSupport(world, this.scratchSupportPos)) {
+					continue;
+				}
+
+				int rot = BlockLogicRotatable.setDirection(0, growthDir);
+				pos.add(growthDir, this.scratchOutwardPos);
+
+				if (this.tallGrowthChance > 0.0F && world.isAirBlock(this.scratchOutwardPos) && random.nextFloat() < this.tallGrowthChance) {
+					world.setBlockTypeData(pos, Blocks.RUBYGLASS_GROWTH, BlockLogicGrowthRubyglass.STATE_BOTTOM | rot);
+					world.setBlockTypeData(this.scratchOutwardPos, Blocks.RUBYGLASS_GROWTH, BlockLogicGrowthRubyglass.STATE_TOP | rot);
+				} else {
+					world.setBlockTypeData(pos, Blocks.RUBYGLASS_GROWTH, BlockLogicGrowthRubyglass.STATE_BUD | rot);
+				}
+
+				break;
+			}
+		}
+		return true;
+	}
+
+	/**
+	 * Determines if the growth is allowed to attach to the given block.
+	 */
+	private boolean isValidSupport(@NotNull World world, TilePos pos) {
+		if (!world.isBlockNormalCube(pos)) {
+			return false;
+		}
+
+		Block<?> block = world.getBlockType(pos);
+
+		if (block == Blocks.RUBYGLASS_GROWTH) {
+			return false;
+		}
+
+		if (block == Blocks.RUBYGLASS_COLUMN ||
+				block == Blocks.RUBYGLASS_NODE ||
+				block == Blocks.COBBLE_NETHERRACK_CRYSTALLINE) {
+			return true;
+		}
+
+		if (block == Blocks.COBBLE_NETHERRACK) {
+			final TilePos checkPos = this.scratchCubeQueryPos;
+			final int px = pos.x();
+			final int py = pos.y();
+			final int pz = pos.z();
+
+			for (int x = -1; x <= 1; x++) {
+				for (int y = -1; y <= 1; y++) {
+					for (int z = -1; z <= 1; z++) {
+						checkPos.set(px + x, py + y, pz + z);
+						if (world.getBlockType(checkPos) == Blocks.COBBLE_NETHERRACK_CRYSTALLINE) {
+							return true;
+						}
+					}
+				}
+			}
+		}
+
+		return false;
+	}
+}
\ No newline at end of file
diff --git a/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureRubyglassSproutPatch.java b/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureRubyglassSproutPatch.java
deleted file mode 100644
index 9eca06b81..000000000
--- a/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureRubyglassSproutPatch.java
+++ /dev/null
@@ -1,36 +0,0 @@
-package net.minecraft.core.world.generate.feature;
-
-import net.minecraft.core.block.BlockLogicRubyglassSprout;
-import net.minecraft.core.block.Blocks;
-import net.minecraft.core.world.World;
-import net.minecraft.core.world.pos.TilePos;
-
-import java.util.Random;
-
-public class WorldFeatureRubyglassSproutPatch extends WorldFeature
-{
-    private final int blockId;
-
-    public WorldFeatureRubyglassSproutPatch()
-    {
-        this.blockId = Blocks.RUBYGLASS_SPROUT.id();
-    }
-
-    @Override
-	public boolean place(World world, Random random, int x, int y, int z)
-    {
-        for(int i1 = 0; i1 < 20; i1++)
-        {
-            int x1 = (x + random.nextInt(4)) - random.nextInt(4);
-            int y1 = (y + random.nextInt(4)) - random.nextInt(4);
-            int z1 = (z + random.nextInt(4)) - random.nextInt(4);
-            if(world.isAirBlock(x1, y1, z1) && Blocks.blocksList[blockId].canBlockStay(world, x1, y1, z1))
-            {
-                world.setBlock(x1, y1, z1, blockId);
-                BlockLogicRubyglassSprout.setDefaultDirection(world, new TilePos(x1, y1, z1));
-            }
-        }
-
-        return true;
-    }
-}
diff --git a/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureSoulCatcherPatch.java b/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureSoulCatcherPatch.java
new file mode 100644
index 000000000..4d9ab4857
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureSoulCatcherPatch.java
@@ -0,0 +1,53 @@
+package net.minecraft.core.world.generate.feature;
+
+import net.minecraft.core.block.Blocks;
+import net.minecraft.core.world.World;
+import net.minecraft.core.world.pos.TilePos;
+import net.minecraft.core.world.pos.TilePosc;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Random;
+
+public class WorldFeatureSoulCatcherPatch implements WorldFeatureInterface {
+    private static final int SOULSAND_ATTEMPTS = 28;
+    private static final int OTHER_ATTEMPTS = 6;
+    private static final int SPORADIC_CHANCE_DENOM = 5;
+    private static final int OFFSET_RAND_BOUND = 3;
+
+    private final TilePos scratchGround = new TilePos();
+    private final TilePos scratchQuery = new TilePos();
+    private final TilePos scratchBelow = new TilePos();
+
+    @Override
+    public boolean place(@NotNull World world, @NotNull Random random, @NotNull TilePosc tilePos) {
+        final TilePos ground = this.scratchGround.set(tilePos.x(), tilePos.y(), tilePos.z());
+        while (world.isAirBlock(ground) && ground.y() > 0) {
+            ground.down();
+        }
+        if (ground.y() <= 0) { return false; }
+
+        final boolean seedOnSoulSand = world.getBlockType(ground) == Blocks.SOULSAND;
+        final int attempts = seedOnSoulSand ? SOULSAND_ATTEMPTS : OTHER_ATTEMPTS;
+
+        for (int i = 0; i < attempts; i++) {
+            final int qx = ground.x() + random.nextInt(OFFSET_RAND_BOUND) - random.nextInt(OFFSET_RAND_BOUND);
+            final int qy = ground.y() + 1 + random.nextInt(2) - random.nextInt(2);
+            final int qz = ground.z() + random.nextInt(OFFSET_RAND_BOUND) - random.nextInt(OFFSET_RAND_BOUND);
+            this.scratchQuery.set(qx, qy, qz);
+
+            if (!world.isAirBlock(this.scratchQuery)) { continue; }
+
+            this.scratchBelow.set(qx, qy - 1, qz);
+            final boolean overSoulSand = world.getBlockType(this.scratchBelow) == Blocks.SOULSAND;
+
+            if (!overSoulSand && random.nextInt(SPORADIC_CHANCE_DENOM) != 0) {
+                continue;
+            }
+
+            if (Blocks.SOUL_CATCHER.canStay(world, this.scratchQuery)) {
+                world.setBlockType(this.scratchQuery, Blocks.SOUL_CATCHER);
+            }
+        }
+        return true;
+    }
+}
diff --git a/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureSpeleothems.java b/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureSpeleothems.java
deleted file mode 100644
index b39c1d7b7..000000000
--- a/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureSpeleothems.java
+++ /dev/null
@@ -1,177 +0,0 @@
-package net.minecraft.core.world.generate.feature;
-
-import net.minecraft.core.block.Block;
-import net.minecraft.core.block.Blocks;
-import net.minecraft.core.util.helper.MathHelper;
-import net.minecraft.core.world.World;
-import net.minecraft.core.world.pos.TilePos;
-import net.minecraft.core.world.pos.TilePosc;
-import org.jetbrains.annotations.NotNull;
-
-import java.util.Random;
-
-public class WorldFeatureSpeleothems extends WorldFeature {
-    protected final int maxRadius;
-    protected final boolean generateBottom;
-    protected final int maxHeight;
-    protected final boolean checkDepth;
-
-    public WorldFeatureSpeleothems(final int maxRadius, final boolean generateBottom, final int maxHeight, final boolean checkDepth) {
-        this.maxRadius = maxRadius;
-        this.generateBottom = generateBottom;
-        this.maxHeight = maxHeight;
-        this.checkDepth = checkDepth;
-    }
-
-    @Override
-    public boolean place(final @NotNull World world, final @NotNull Random random, int x, int y, int z) {
-        final @NotNull TilePosc tilePos = new TilePos(x, y, z);
-        final @NotNull TilePos queryPos = new TilePos();
-
-        final @NotNull Block<?> baseBlock = Blocks.COBBLE_NETHERRACK;
-
-        if (world.getBlockType(tilePos) == Blocks.AIR) {
-            tilePos.up(queryPos);
-            if (world.getBlockType(queryPos) != Blocks.AIR) {
-                y = y + 1;
-            } else
-                return false;
-
-        }
-
-        final int radius = random.nextInt(this.maxRadius) + this.maxRadius; // Radius 5-10
-        final int depth = radius * 2;
-
-        // Check we have enough space underneath proportionally to our radius
-        if (this.checkDepth) {
-            for (int i = y - 1; i > y - depth; i--) {
-                queryPos.set(x, i, z);
-                if (world.getBlockType(queryPos) != Blocks.AIR) {
-                    return false;
-                }
-            }
-        }
-
-        // Determine the number of base blocks we need to generate
-        final int numBaseBlocks = (radius * 2) * (radius * 2);
-
-        for (int i = 0; i < numBaseBlocks; i++) {
-            // Determine our base block's location
-            final int dx = radius - random.nextInt(radius * 2);
-            int dy = 0;
-            final int dz = radius - random.nextInt(radius * 2);
-
-            if (world.getBlockType(queryPos.set(x + dx, y, z + dz)) == baseBlock) {
-                continue;
-            }
-
-            int distance = dx * dz;
-
-            // Make sure our distance is never 0
-            if (distance == 0) {
-                if (dx == 0 && dz == 0) {
-                    distance = 1;
-                } else if (dx == 0) {
-                    distance = dz;
-                } else if (dz == 0) {
-                    distance = dx;
-                }
-            }
-
-            // Bias so we're more likely to generate base blocks closer to the middle
-            if (random.nextInt(Math.abs(distance)) != 0) {
-                i--;
-                continue;
-            }
-
-            // Move up one block if needed
-            if (world.getBlockType(queryPos.set(x + dx, y, z + dz)) == Blocks.AIR) {
-                dy = 1;
-                if (world.getBlockType(queryPos.set(x + dx, y + 1, z + dz)) == Blocks.AIR) {
-                    continue;
-                }
-            }
-
-            // Move down one block if needed
-            if (world.getBlockType(queryPos.set(x + dx, y - 1, z + dz)) != Blocks.AIR) {
-                dy = -1;
-                if (world.getBlockType(queryPos.set(x + dx, y - 2, z + dz)) != Blocks.AIR) {
-                    continue;
-                }
-            }
-
-            // Set base block
-            world.setBlockTypeNotify(queryPos.set(x + dx, y + dy, z + dz), baseBlock);
-
-            // Generate stalactite
-            if (random.nextInt(5) == 0) {
-                int height = random.nextInt(depth - radius - 1) + 1; // 2-12
-
-                if (this.maxHeight > -1 && height > this.maxHeight) {
-                    height = this.maxHeight;
-                }
-                this.placeSpeleothem(world, queryPos.set(x + dx, y + dy, z + dz), height);
-
-                // Possibly generate stalagmite
-                if (this.generateBottom) {
-                    if (random.nextInt(3) == 0) {
-                        int bottom = -1;
-
-                        for (int j = height + 1; j < depth * 2; j++) {
-                            if (world.getBlockType(queryPos.set(x + dx, y + dy - j, z + dz)) != Blocks.AIR) {
-                                bottom = y + dy - j;
-                                break;
-                            }
-                        }
-
-                        if (bottom != -1 && world.getBlockType(queryPos.set(x + dx, bottom - 1, z + dz)).solid()) {
-                            this.placeSpeleothem(world, queryPos.set(x + dx, bottom, z + dz), -height / 2);
-                        }
-                    }
-                }
-            }
-        }
-
-        return true;
-    }
-
-    private boolean placeSpeleothem(final @NotNull World world, final @NotNull TilePosc pos, final int height) {
-        if (height == 0) {
-            return false;
-        }
-
-        final @NotNull Block<?> block = Blocks.FLOWSTONE;
-
-        final @NotNull TilePos queryPos = new TilePos(pos);
-        final int absHeight = Math.abs(height);
-        final int dy = height < 0 ? 1 : -1;
-
-        // Check space is clear
-        for (int i = 0; i < absHeight; i++) {
-            queryPos.y += dy;
-            if (world.getBlockType(queryPos) != Blocks.AIR) {
-                return false;
-            }
-        }
-
-        // Handle edge cases first so we get nice-looking short speleothems
-        queryPos.set(pos);
-        if (absHeight <= 4) {
-            int data = 4 - absHeight;
-            for (int i = 0; i < absHeight; i++) {
-                queryPos.y += dy;
-                world.setBlockTypeData(queryPos, block, data);
-                data++;
-            }
-        } else {
-            for (int i = 0; i < absHeight; i++) {
-                final float progress = (float) i / (float) absHeight;
-                final int data = (int) (progress * 4);
-                queryPos.y += dy;
-                world.setBlockTypeData(queryPos, block, data);
-            }
-        }
-
-        return true;
-    }
-}
diff --git a/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureSpireVent.java b/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureSpireVent.java
new file mode 100644
index 000000000..fd316d6fb
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureSpireVent.java
@@ -0,0 +1,92 @@
+package net.minecraft.core.world.generate.feature;
+
+import net.minecraft.core.block.Blocks;
+import net.minecraft.core.block.tag.BlockTags;
+import net.minecraft.core.world.World;
+import net.minecraft.core.world.pos.TilePos;
+import net.minecraft.core.world.pos.TilePosc;
+import org.jetbrains.annotations.NotNull;
+import org.joml.Vector3i;
+
+import java.util.Random;
+
+public class WorldFeatureSpireVent implements WorldFeatureInterface{
+
+    /**
+     * Maximum vertical drop allowed when scanning down for a placement surface.
+     * Caps the cost of feature placement when called inside large open caves.
+     */
+    private static final int MAX_DOWN_SCAN = 64;
+
+    @Override
+    public boolean place(@NotNull World world, @NotNull Random random, @NotNull TilePosc tilePosc) {
+        if (!world.isAirBlock(tilePosc)) {
+            return false;
+        }
+
+        TilePos tilePos = new TilePos(tilePosc.x(), tilePosc.y(), tilePosc.z());
+
+        int scanned = 0;
+        while(world.isAirBlock(tilePos) && tilePos.y() - 1 > 0 && scanned < MAX_DOWN_SCAN) {
+            tilePos.down();
+            scanned++;
+        }
+
+        if (!world.getBlockType(tilePos).hasTag(BlockTags.NETHER_SURFACE_BLOCK)) {
+            return false;
+        }
+
+        if (hasSpace(world, tilePos.x(), tilePos.y(), tilePos.z())) {
+
+            Vector3i highestOffset = new Vector3i(0, 0, 0);
+            TilePos centerPos = new TilePos(tilePos.x(), tilePos.y(), tilePos.z());
+            TilePos queryPos = new TilePos();
+
+            for (int x1 = 0; x1 < 2; x1++) {
+                for (int z1 = 0; z1 < 2; z1++) {
+                    int height = random.nextInt(6) + 4;
+                    if(height > highestOffset.y) {
+                        highestOffset.set(x1, height, z1);
+                    }
+
+                    // Bottom third of each column gets scattered nethercoal ore.
+                    int oreRange = Math.max(1, height / 3);
+
+                    for(int y1 = 0; y1 < height; y1++){
+                        centerPos.add(x1, y1, z1, queryPos);
+                        if (y1 < oreRange && random.nextInt(2) == 0) {
+                            world.setBlockType(queryPos, Blocks.ORE_NETHERCOAL_BASALT);
+                        } else {
+                            world.setBlockType(queryPos, Blocks.BASALT);
+                        }
+                    }
+                }
+            }
+
+            for(int y1 = 0; y1 < 2; y1++){
+                centerPos.add(highestOffset.x, highestOffset.y + y1, highestOffset.z, queryPos);
+                world.setBlockType(queryPos, Blocks.THERMAL_VENT);
+            }
+
+            return true;
+        }
+
+        return false;
+    }
+
+    private boolean hasSpace(World world, int xc, int y, int zc){
+        TilePos queryPos =  new TilePos();
+
+        for (queryPos.y = y + 1; queryPos.y < y + 5; queryPos.y++) {
+            for (queryPos.x = xc; queryPos.x < xc + 2; queryPos.x++) {
+                for (queryPos.z = zc; queryPos.z < zc + 2; queryPos.z++) {
+                    if (!world.isAirBlock(queryPos)) {
+                        return false;
+                    }
+                }
+            }
+        }
+        return true;
+    }
+
+}
diff --git a/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureThermalSpire.java b/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureThermalSpire.java
deleted file mode 100644
index d3d03df70..000000000
--- a/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureThermalSpire.java
+++ /dev/null
@@ -1,63 +0,0 @@
-package net.minecraft.core.world.generate.feature;
-
-import net.minecraft.core.block.Block;
-import net.minecraft.core.block.Blocks;
-import net.minecraft.core.block.tag.BlockTags;
-import net.minecraft.core.world.World;
-import net.minecraft.core.world.pos.TilePos;
-import org.joml.Vector2i;
-import org.joml.Vector3i;
-import org.joml.Vector3ic;
-
-import java.util.Random;
-
-public class WorldFeatureThermalSpire extends WorldFeature{
-    @Override
-    public boolean place(World world, Random random, int x, int y, int z) {
-        TilePos pos = new TilePos(x, y, z);
-        while(world.isAirBlock(pos) && pos.y - 1 > 0) {pos.down(); }
-        if (hasSpace(world, pos.x, pos.y, pos.z)) {
-
-            Vector3i highestOffset = new Vector3i(0, 0, 0);
-            TilePos centerPos = new TilePos(pos.x, pos.y, pos.z);
-            TilePos queryPos = new TilePos();
-
-            for (int x1 = 0; x1 < 2; x1++) {
-                for (int z1 = 0; z1 < 2; z1++) {
-                    int height = random.nextInt(6) + 4;
-                    if(height > highestOffset.y) {
-                        highestOffset.set(x1, height, z1);
-                    }
-
-                    for(int y1 = 0; y1 < height; y1++){
-                        centerPos.add(x1, y1, z1, queryPos);
-                        world.setBlockType(queryPos, Blocks.BASALT);
-                    }
-                }
-            }
-
-            for(int y1 = 0; y1 < 2; y1++){
-                centerPos.add(highestOffset.x, highestOffset.y + y1, highestOffset.z, queryPos);
-                world.setBlockType(queryPos, Blocks.BASALT);
-            }
-        }
-
-
-        return true;
-    }
-
-    private boolean hasSpace(World world, int xc, int y, int zc){
-        TilePos queryPos =  new TilePos();
-        for (queryPos.y = y - 3; queryPos.y < y + 3; queryPos.y++) {
-            for (queryPos.x = xc - 3; queryPos.x < xc + 3; queryPos.x++) {
-                for (queryPos.z = zc - 3; queryPos.z < zc + 3; queryPos.z++) {
-                    if (!world.isAirBlock(queryPos) && !world.getBlockType(queryPos).hasTag(BlockTags.NETHER_SURFACE_BLOCK))
-                        return false;
-                }
-            }
-        }
-        return true;
-    }
-
-
-}
diff --git a/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureChimney.java b/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureThermalVent.java
similarity index 98%
rename from game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureChimney.java
rename to game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureThermalVent.java
index c895b7b97..be95987e0 100644
--- a/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureChimney.java
+++ b/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureThermalVent.java
@@ -9,7 +9,7 @@ import org.joml.Vector2i;
 
 import java.util.Random;
 
-public class WorldFeatureChimney extends WorldFeature{
+public class WorldFeatureThermalVent extends WorldFeature{
 
     final double[][] RADIUS_MAPS = {
             {1.5D, 1.5D, 1D, 1D, 0D, 0D},
diff --git a/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureWallClump.java b/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureWallClump.java
new file mode 100644
index 000000000..a4303ab4e
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureWallClump.java
@@ -0,0 +1,78 @@
+package net.minecraft.core.world.generate.feature;
+
+import net.minecraft.core.block.Block;
+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;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Random;
+
+public class WorldFeatureWallClump implements WorldFeatureInterface {
+    private final Block<?> placeBlock;
+    private final Block<?> targetBlock;
+    private final int maxClumpSize;
+
+    public WorldFeatureWallClump(Block<?> placeBlock, Block<?> targetBlock, int maxClumpSize) {
+        this.placeBlock = placeBlock;
+        this.targetBlock = targetBlock;
+        this.maxClumpSize = maxClumpSize;
+    }
+
+    @Override
+    public boolean place(@NotNull World world, @NotNull Random random, @NotNull TilePosc tilePos) {
+
+        if (world.getBlockType(tilePos) != this.targetBlock) {
+            return false;
+        }
+
+        TilePos checkPos = new TilePos();
+        if (!hasAirNeighbor(world, tilePos, checkPos)) {
+            return false;
+        }
+
+        TilePos currentPos = new TilePos();
+        currentPos.set(tilePos);
+
+        world.setBlockType(currentPos, this.placeBlock);
+        int blocksPlaced = 1;
+
+        TilePos nextPos = new TilePos();
+        final Direction[] directions = Direction.ID_MAP;
+
+        for (int i = 0; i < this.maxClumpSize * 2 && blocksPlaced < this.maxClumpSize; i++) {
+            Direction dir = directions[random.nextInt(directions.length)];
+            nextPos.set(currentPos).add(dir);
+
+            if (isValid(world, nextPos, checkPos)) {
+                world.setBlockType(nextPos, this.placeBlock);
+                currentPos.set(nextPos);
+                blocksPlaced++;
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Checks if the position is the target block AND is exposed to air on at least one side.
+     * Reuses checkPos to prevent memory allocation in loops.
+     */
+    private boolean isValid(@NotNull World world, TilePosc pos, TilePos checkPos) {
+        if (world.getBlockType(pos) != this.targetBlock) {
+            return false;
+        }
+        return hasAirNeighbor(world, pos, checkPos);
+    }
+
+    private boolean hasAirNeighbor(@NotNull World world, TilePosc pos, TilePos checkPos) {
+        final Direction[] directions = Direction.ID_MAP;
+        for (int d = 0, n = directions.length; d < n; d++) {
+            if (world.isAirBlock(checkPos.set(pos).add(directions[d]))) {
+                return true;
+            }
+        }
+        return false;
+    }
+}
\ No newline at end of file
diff --git a/game/core/src/main/java/net/minecraft/core/world/generate/feature/hanging/WorldFeatureHangingDungeon.java b/game/core/src/main/java/net/minecraft/core/world/generate/feature/hanging/WorldFeatureHangingDungeon.java
new file mode 100644
index 000000000..ff627fae1
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/world/generate/feature/hanging/WorldFeatureHangingDungeon.java
@@ -0,0 +1,198 @@
+package net.minecraft.core.world.generate.feature.hanging;
+
+import net.minecraft.core.block.Blocks;
+import net.minecraft.core.WeightedRandomBag;
+import net.minecraft.core.WeightedRandomLootObject;
+import net.minecraft.core.block.Block;
+import net.minecraft.core.item.Item;
+import net.minecraft.core.item.ItemStack;
+import net.minecraft.core.item.Items;
+import net.minecraft.core.world.World;
+import net.minecraft.core.world.generate.feature.WorldFeatureInterface;
+import net.minecraft.core.world.pos.TilePosc;
+import org.jetbrains.annotations.NotNull;
+
+
+import java.util.Random;
+
+public class WorldFeatureHangingDungeon {
+
+	public static final WeightedRandomBag<WeightedRandomLootObject> lootNormal = new WeightedRandomBag<>();
+
+	static {
+		lootNormal.addEntry(new WeightedRandomLootObject(Items.INGOT_STEEL_CRUDE.getDefaultStack(), 1, 6), 50.0F);
+		lootNormal.addEntry(new WeightedRandomLootObject(Items.FOOD_APPLE_GOLD.getDefaultStack()), 3.0F);
+		lootNormal.addEntry(new WeightedRandomLootObject(Items.DUST_REDSTONE.getDefaultStack(), 6, 24), 100.0F);
+		lootNormal.addEntry(new WeightedRandomLootObject(Items.DUST_GLOWSTONE.getDefaultStack(), 6, 24), 100.0F);
+		lootNormal.addEntry(new WeightedRandomLootObject(Items.NETHERCOAL.getDefaultStack(), 3, 9), 100.0F);
+		lootNormal.addEntry(new WeightedRandomLootObject(Blocks.TNT.getDefaultStack(), 4, 12), 50.0F);
+		lootNormal.addEntry(new WeightedRandomLootObject(Blocks.RAIL.getDefaultStack(), 12, 32), 50.0F);
+		lootNormal.addEntry(new WeightedRandomLootObject(Blocks.RAIL_POWERED.getDefaultStack(), 9, 16), 50.0F);
+		lootNormal.addEntry(new WeightedRandomLootObject(Items.INGOT_GOLD.getDefaultStack(), 5, 8), 100.0F);
+		lootNormal.addEntry(new WeightedRandomLootObject(Items.INGOT_IRON.getDefaultStack(), 5, 10), 100.0F);
+
+		for (int i = 0; i < 9; ++i) {
+			lootNormal.addEntry(new WeightedRandomLootObject(new ItemStack(Item.itemsList[Items.RECORD_13.id + i]), 1), 1.0F);
+		}
+
+		lootNormal.addEntry(new WeightedRandomLootObject(Blocks.PUMICE_DRY.getDefaultStack(), 2, 7), 100.0F);
+		lootNormal.addEntry(new WeightedRandomLootObject(Items.BONE.getDefaultStack(), 2, 7), 100.0F);
+		lootNormal.addEntry(new WeightedRandomLootObject(null), 180.0F);
+	}
+
+
+	public static class RandomType implements WorldFeatureInterface {
+		private final WorldFeatureInterface[] features;
+
+		public RandomType() {
+			this.features = new WorldFeatureInterface[] {
+				new Basalt(),
+				new Slate(),
+				new Gloomstone(),
+				new Netherrack()
+			};
+		}
+
+		@Override
+		public boolean place(@NotNull World world, @NotNull Random random, @NotNull TilePosc tilePosc) {
+			int index = random.nextInt(features.length);
+			return features[index].place(world, random, tilePosc);
+		}
+	}
+
+	public static class Basalt implements WorldFeatureInterface {
+		private static final WeightedRandomBag<Block<?>> hangingDungeonBase = new WeightedRandomBag<>();
+
+		static {
+			hangingDungeonBase.addEntry(Blocks.BRICK_BASALT, 1);
+			hangingDungeonBase.addEntry(Blocks.BASALT_CARVED, .75);
+			hangingDungeonBase.addEntry(Blocks.BASALT_POLISHED, .10);
+		}
+
+		private static final WeightedRandomBag<Block<?>> hangingDungeonRope = new WeightedRandomBag<>();
+
+		static {
+			hangingDungeonRope.addEntry(Blocks.ROPE, 1);
+		}
+
+		private static final WorldFeatureInterface dungeon = new WorldFeatureHangingDungeonBase(
+			7,
+			32,
+			10,
+			6,
+			hangingDungeonBase, hangingDungeonRope,
+			lootNormal
+		);
+
+		public Basalt() {
+		}
+
+		@Override
+		public boolean place(@NotNull World world, @NotNull Random random, @NotNull TilePosc tilePosc) {
+			return dungeon.place(world, random, tilePosc);
+		}
+
+	}
+
+	public static class Slate implements WorldFeatureInterface {
+		private static final WeightedRandomBag<Block<?>> hangingDungeonBase = new WeightedRandomBag<>();
+
+		static {
+			hangingDungeonBase.addEntry(Blocks.BRICK_SLATE, 1);
+			hangingDungeonBase.addEntry(Blocks.SLATE_POLISHED, .75);
+			hangingDungeonBase.addEntry(Blocks.SLATE_CARVED, .10);
+		}
+
+		private static final WeightedRandomBag<Block<?>> hangingDungeonRope = new WeightedRandomBag<>();
+
+		static {
+			hangingDungeonRope.addEntry(Blocks.ROPE, 1);
+		}
+
+		private static final WorldFeatureInterface dungeon = new WorldFeatureHangingDungeonBase(
+			7,
+			32,
+			10,
+			6,
+			hangingDungeonBase, hangingDungeonRope,
+			lootNormal
+		);
+
+		public Slate() {
+		}
+
+		@Override
+		public boolean place(@NotNull World world, @NotNull Random random, @NotNull TilePosc tilePosc) {
+			return dungeon.place(world, random, tilePosc);
+		}
+
+	}
+
+	public static class Gloomstone implements WorldFeatureInterface {
+		private static final WeightedRandomBag<Block<?>> hangingDungeonBase = new WeightedRandomBag<>();
+
+		static {
+			hangingDungeonBase.addEntry(Blocks.BRICK_GLOOMSTONE, 1);
+			hangingDungeonBase.addEntry(Blocks.GLOOMSTONE_CARVED, .75);
+			hangingDungeonBase.addEntry(Blocks.GLOOMSTONE_POLISHED, .10);
+		}
+
+		private static final WeightedRandomBag<Block<?>> hangingDungeonRope = new WeightedRandomBag<>();
+
+		static {
+			hangingDungeonRope.addEntry(Blocks.ROPE, 1);
+		}
+
+		private static final WorldFeatureInterface dungeon = new WorldFeatureHangingDungeonBase(
+			7,
+			32,
+			10,
+			6,
+			hangingDungeonBase, hangingDungeonRope,
+			lootNormal
+		);
+
+		public Gloomstone() {
+		}
+
+		@Override
+		public boolean place(@NotNull World world, @NotNull Random random, @NotNull TilePosc tilePosc) {
+			return dungeon.place(world, random, tilePosc);
+		}
+
+	}
+
+	public static class Netherrack implements WorldFeatureInterface {
+		private static final WeightedRandomBag<Block<?>> hangingDungeonBase = new WeightedRandomBag<>();
+
+		static {
+			hangingDungeonBase.addEntry(Blocks.BRICK_NETHERRACK, 1);
+			hangingDungeonBase.addEntry(Blocks.NETHERRACK_CARVED, .75);
+			hangingDungeonBase.addEntry(Blocks.NETHERRACK_POLISHED, .10);
+		}
+
+		private static final WeightedRandomBag<Block<?>> hangingDungeonRope = new WeightedRandomBag<>();
+
+		static {
+			hangingDungeonRope.addEntry(Blocks.ROPE, 1);
+		}
+
+		private static final WorldFeatureInterface dungeon = new WorldFeatureHangingDungeonBase(
+			7,
+			32,
+			10,
+			6,
+			hangingDungeonBase, hangingDungeonRope,
+			lootNormal
+		);
+
+		public Netherrack() {
+		}
+
+		@Override
+		public boolean place(@NotNull World world, @NotNull Random random, @NotNull TilePosc tilePosc) {
+			return dungeon.place(world, random, tilePosc);
+		}
+
+	}
+}
diff --git a/game/core/src/main/java/net/minecraft/core/world/generate/feature/hanging/WorldFeatureHangingDungeonBase.java b/game/core/src/main/java/net/minecraft/core/world/generate/feature/hanging/WorldFeatureHangingDungeonBase.java
new file mode 100644
index 000000000..0e97f2f0a
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/world/generate/feature/hanging/WorldFeatureHangingDungeonBase.java
@@ -0,0 +1,162 @@
+package net.minecraft.core.world.generate.feature.hanging;
+
+import net.minecraft.core.block.Blocks;
+import net.minecraft.core.WeightedRandomBag;
+import net.minecraft.core.WeightedRandomLootObject;
+import net.minecraft.core.block.Block;
+import net.minecraft.core.block.entity.TileEntityChest;
+import net.minecraft.core.block.entity.TileEntityMobSpawner;
+import net.minecraft.core.util.helper.DyeColor;
+import net.minecraft.core.util.helper.MathHelper;
+import net.minecraft.core.world.World;
+import net.minecraft.core.world.generate.feature.WorldFeatureInterface;
+import net.minecraft.core.world.pos.TilePos;
+import net.minecraft.core.world.pos.TilePosc;
+import org.jetbrains.annotations.NotNull;
+import org.joml.Vector2d;
+
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Random;
+
+public record WorldFeatureHangingDungeonBase(
+	int size, int maxDrop, int minDrop, int clumping,
+	WeightedRandomBag<Block<?>> base, WeightedRandomBag<Block<?>> ropes,
+	WeightedRandomBag<WeightedRandomLootObject> lootTable) implements WorldFeatureInterface {
+
+	public WorldFeatureHangingDungeonBase {
+		assert size >= 7;
+		// You don't want less than 7 because it will look ass.
+
+		assert size % 2 != 0;
+		// You don't want an even number because the spawner will be crooked.
+
+	}
+
+	@Override
+	public boolean place(@NotNull World world, @NotNull Random random, @NotNull TilePosc tilePosc) {
+		// 1. find the highest block from current Y.
+		// 2. check of an empty column on the 4 corners
+		// 3. place all ropes to a random height
+		// 4. fill platform
+
+		final TilePos[] corners = {
+			new TilePos(tilePosc.x(), tilePosc.y(), tilePosc.z()),
+			new TilePos(tilePosc.x() + this.size - 1, tilePosc.y(), tilePosc.z()),
+			new TilePos(tilePosc.x(), tilePosc.y(), tilePosc.z() + this.size - 1),
+			new TilePos(tilePosc.x() + this.size - 1, tilePosc.y(), tilePosc.z() + this.size - 1),
+		};
+
+		for (TilePos tilePos : corners) {
+			for (int currY = tilePos.y(); currY < world.getWorldType().getMaxY(world); currY++) {
+				if (currY > world.getWorldType().getMaxY(world)) {
+					return false;
+				}
+
+				var block = new TilePos(tilePos);
+				block.y = currY;
+
+				if (world.getBlockType(block).solid()) {
+					tilePos.y = currY;
+					break;
+				}
+			}
+		}
+
+		int minMaxHeight = Arrays.stream(corners).map(TilePos::y).min(Integer::compareTo).get();
+		int maxMaxHeight = Arrays.stream(corners).map(TilePos::y).max(Integer::compareTo).get();
+
+		if (minMaxHeight - tilePosc.y() < this.minDrop) {
+			return false;
+		}
+
+		var minHeight = minMaxHeight - MathHelper.clamp(random.nextInt(minMaxHeight - tilePosc.y()), this.minDrop, this.maxDrop);
+
+		if (maxMaxHeight - minHeight > this.maxDrop * 2) {
+			return false;
+		}
+
+		for (TilePos corner : corners) {
+			for (int y = minHeight; y < corner.y(); y++) {
+				var block = new TilePos(corner);
+				block.y = y;
+
+				var variant = ropes.getRandom(random);
+				world.setBlockTypeDataNotify(block, variant, 0);
+			}
+		}
+
+		for (int x = corners[0].x(); x <= corners[3].x(); x++) {
+			for (int z = corners[0].z(); z <= corners[3].z(); z++) {
+				var block = new TilePos(x, minHeight, z);
+				var variant = base.getRandom(random);
+
+				world.setBlockTypeData(block, variant, 0);
+			}
+		}
+
+		for (int step = 1; step < this.size -1; step++) {
+			var block = new TilePos(corners[0].x() + step, minHeight + 4, corners[0].z());
+			var block2 = new TilePos(corners[2].x() + step, minHeight + 4, corners[2].z());
+			var block3 = new TilePos(corners[0].x(), minHeight + 4, corners[0].z() + step);
+			var block4 = new TilePos(corners[1].x(), minHeight + 4, corners[1].z() + step);
+
+			var variant  = ropes.getRandom(random);
+			var variant2 = ropes.getRandom(random);
+			var variant3 = ropes.getRandom(random);
+			var variant4 = ropes.getRandom(random);
+
+			world.setBlockTypeNotify(block,  variant);
+			world.setBlockTypeNotify(block2, variant2);
+			world.setBlockTypeNotify(block3, variant3);
+			world.setBlockTypeNotify(block4, variant4);
+		}
+
+		final int half = this.size / 2;
+		var center = new TilePos(tilePosc.x() + half, minHeight + 1, tilePosc.z() + half);
+
+		world.setBlockTypeDataNotify(center, Blocks.MOBSPAWNER, 0);
+		var spawner = (TileEntityMobSpawner) world.getTileEntity(center);
+		if (spawner != null) {
+			spawner.setMobId("minecraft:zombie_pigman");
+		}
+
+		final var positions = new ArrayList<TilePos>();
+		positions.add(new TilePos(half, 0, 0));
+		positions.add(new TilePos(-half, 0, 0));
+		positions.add(new TilePos(0, 0, half));
+		positions.add(new TilePos(0, 0, -half));
+
+		for (int ii = 0; ii < 2; ii++) {
+			var chestPos = positions.remove(random.nextInt(positions.size() - 1));
+			chestPos.add(center.x(), center.y(), center.z());
+
+			world.setBlockTypeData(chestPos, Blocks.CHEST_PLANKS_OAK_PAINTED, DyeColor.RED.blockMeta * 16 + random.nextInt(3));
+
+			var chest = (TileEntityChest) world.getTileEntity(chestPos);
+
+			if (chest != null) {
+				for (int i = 0; i < 3 + random.nextInt(8); i++) {
+					chest.setItem((int) Math.floor(chest.getContainerSize() * random.nextFloat()), this.lootTable.getRandom(random).getItemStack(random));
+				}
+			}
+		}
+
+		if (random.nextInt(clumping) == 0) {
+			var vec2 = new Vector2d(1, 0)
+				.mul(MathHelper.rotateVec2(Math.PI * 2 * random.nextFloat()))
+				.mul(this.size * 2 + (this.size * random.nextFloat()));
+
+			var block = new TilePos(
+				tilePosc.x() + Math.floor(vec2.x()),
+				tilePosc.y() - 2 + random.nextInt(5),
+				tilePosc.z() + Math.floor(vec2.y())
+			);
+
+			this.place(world, random, block);
+		}
+
+		return true;
+	}
+}
diff --git a/game/core/src/main/java/net/minecraft/core/world/lighting/LightingEngineLegacy.java b/game/core/src/main/java/net/minecraft/core/world/lighting/LightingEngineLegacy.java
index f6847acde..98c8d6e15 100644
--- a/game/core/src/main/java/net/minecraft/core/world/lighting/LightingEngineLegacy.java
+++ b/game/core/src/main/java/net/minecraft/core/world/lighting/LightingEngineLegacy.java
@@ -26,26 +26,33 @@ public class LightingEngineLegacy implements LightingEngine {
 
     @Override
     public boolean updatingLighting() {
-        if(this.lightingUpdatesCounter >= 50)
-        {
+        if(this.lightingUpdatesCounter >= 50) {
             return false;
         }
         this.lightingUpdatesCounter++;
-        try
-        {
-            for (int i = 499; !this.lightingToUpdate.isEmpty() && i >= 0; i--)
-            {
+        try {
+            // Why was this changed? During the development of 8.0, after adding many light sources to the nether,
+            // the lighting propagation/updates were getting caught up, and needed to be adjusted/upgraded
+
+            // Lighting updates are allowed to take up 7% of the tick budget max
+            final long timeBudgetNanos = 3_000_000L;
+            final long startTime = System.nanoTime();
+            int processed = 0;
+
+            while (!this.lightingToUpdate.isEmpty()) {
                 this.lightingToUpdate.remove(this.lightingToUpdate.size() - 1).performLightUpdate(this, this.world);
-                if (i == 0)
-                {
-                    return true;
+                processed++;
+
+                // Check the clock every 64 updates
+                if (processed % 64 == 0) {
+                    if (System.nanoTime() - startTime > timeBudgetNanos) {
+                        return true; // cut it short
+                    }
                 }
             }
 
-            return false;
-        }
-        finally
-        {
+            return false; // emptied the queue
+        } finally {
             this.lightingUpdatesCounter--;
         }
     }
@@ -80,9 +87,9 @@ public class LightingEngineLegacy implements LightingEngine {
             }
 
             int numLightingUpdates = this.lightingToUpdate.size();
-            if(numLightingUpdates > 5)
-            {
-                numLightingUpdates = 5;
+
+            if(numLightingUpdates > 12) {
+                numLightingUpdates = 12;
             }
 
             for(int i = 0; i < numLightingUpdates; i++)
diff --git a/game/core/src/main/java/net/minecraft/core/world/particle/ParticleEmitterRange.java b/game/core/src/main/java/net/minecraft/core/world/particle/ParticleEmitterRange.java
new file mode 100644
index 000000000..5ce6e423d
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/world/particle/ParticleEmitterRange.java
@@ -0,0 +1,34 @@
+package net.minecraft.core.world.particle;
+
+public enum ParticleEmitterRange {
+    // BASIC fixed 16-block radius
+    BASIC(0.0f),
+
+    // SHORT scales to 25% of the players' particle render distance
+    SHORT(0.25f),
+
+    // MEDIUM scales to 50%
+    MEDIUM(0.5f),
+
+    // FAR scales to 75%
+    FAR(0.75f),
+
+    // EXTREME uses 100%
+    EXTREME(1.0f);
+
+    public final float multiplier;
+
+    ParticleEmitterRange(float multiplier) {
+        this.multiplier = multiplier;
+    }
+
+    public double getMaxDistanceSq(int renderDistanceChunks) {
+        if (this == BASIC) {
+            return 16.0 * 16.0;
+        }
+
+        double maxBlocks = (renderDistanceChunks * 16.0) * this.multiplier;
+        maxBlocks = Math.max(maxBlocks, 16.0);
+        return maxBlocks * maxBlocks;
+    }
+}
diff --git a/game/core/src/main/java/net/minecraft/core/world/particle/ParticleEmitterRegistry.java b/game/core/src/main/java/net/minecraft/core/world/particle/ParticleEmitterRegistry.java
new file mode 100644
index 000000000..7e7d6b532
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/world/particle/ParticleEmitterRegistry.java
@@ -0,0 +1,103 @@
+package net.minecraft.core.world.particle;
+
+import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
+import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
+import net.minecraft.core.block.Block;
+import net.minecraft.core.entity.player.Player;
+import net.minecraft.core.world.World;
+import net.minecraft.core.world.pos.TilePos;
+import net.minecraft.core.world.pos.TilePosc;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+public class ParticleEmitterRegistry {
+
+    private record EmitterNode(TilePos pos, ParticleEmitterRange type) { }
+
+    private final Long2ObjectOpenHashMap<EmitterNode> emitters = new Long2ObjectOpenHashMap<>();
+
+    private final ConcurrentLinkedQueue<EmitterNode> pendingAdds = new ConcurrentLinkedQueue<>();
+    private final ConcurrentLinkedQueue<Long> pendingRemoves = new ConcurrentLinkedQueue<>();
+
+    private long getPosHash(int x, int y, int z) {
+        long hash = x & 0x3FFFFFF;
+        hash |= ((long)(z & 0x3FFFFFF)) << 26;
+        hash |= ((long)(y & 0xFFF)) << 52;
+        return hash;
+    }
+
+    public void add(TilePos pos) {
+        this.add(pos, ParticleEmitterRange.BASIC);
+    }
+
+    public void add(@NotNull TilePos pos, ParticleEmitterRange type) {
+        this.pendingAdds.add(new EmitterNode(new TilePos(pos), type));
+    }
+
+    public void add(TilePosc pos) {
+        this.add(pos, ParticleEmitterRange.BASIC);
+    }
+
+    public void add(@NotNull TilePosc pos, ParticleEmitterRange type) {
+        this.pendingAdds.add(new EmitterNode(new TilePos(pos), type));
+    }
+
+    public void remove(@NotNull TilePos pos) {
+        this.pendingRemoves.add(getPosHash(pos.x(), pos.y(), pos.z()));
+    }
+
+    public void remove(@NotNull TilePosc pos) {
+        this.pendingRemoves.add(getPosHash(pos.x(), pos.y(), pos.z()));
+    }
+
+    public void tick(World world, Player player, int renderDistanceChunks) {
+        while (!this.pendingRemoves.isEmpty()) {
+            this.emitters.remove(this.pendingRemoves.poll().longValue());
+        }
+        while (!this.pendingAdds.isEmpty()) {
+            EmitterNode node = this.pendingAdds.poll();
+            this.emitters.put(getPosHash(node.pos.x(), node.pos.y(), node.pos.z()), node);
+        }
+
+        if (this.emitters.isEmpty()) return;
+
+        var iterator = this.emitters.long2ObjectEntrySet().fastIterator();
+
+        while (iterator.hasNext()) {
+            Long2ObjectMap.Entry<EmitterNode> entry = iterator.next();
+            EmitterNode node = entry.getValue();
+            TilePos pos = node.pos;
+            ParticleEmitterRange type = node.type;
+
+            if (!world.isBlockLoaded(pos)) {
+                iterator.remove();
+                continue;
+            }
+
+            double dx = pos.x() - player.x;
+            double dy = pos.y() - player.y;
+            double dz = pos.z() - player.z;
+            double distSq = dx * dx + dy * dy + dz * dz;
+
+            if (distSq > type.getMaxDistanceSq(renderDistanceChunks)) continue;
+
+            int throttle = 4;
+            if (distSq > 4096) {
+                throttle = 64;
+            } else if (distSq > 1024) {
+                throttle = 16;
+            }
+
+            if (world.rand.nextInt(throttle) == 0) {
+                Block<?> block = world.getBlockType(pos);
+
+                if (block.getLogic().getParticleEmitterRange() != null) {
+                    block.animationTick(world, pos, world.rand);
+                } else {
+                    iterator.remove();
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/game/core/src/main/java/net/minecraft/core/world/pos/TilePos.java b/game/core/src/main/java/net/minecraft/core/world/pos/TilePos.java
index 58453ec8f..303fe2ce7 100644
--- a/game/core/src/main/java/net/minecraft/core/world/pos/TilePos.java
+++ b/game/core/src/main/java/net/minecraft/core/world/pos/TilePos.java
@@ -115,6 +115,20 @@ public final class TilePos implements Externalizable, Cloneable, TilePosc {
         return this;
     }
 
+    public @NotNull TilePos set(final double x, final double y, final double z) {
+        this.x = (int) Math.floor(x);
+        this.y = (int) Math.floor(y);
+        this.z = (int) Math.floor(z);
+        return this;
+    }
+
+    public @NotNull TilePos set(final float x, final float y, final float z) {
+        this.x = (int) Math.floor(x);
+        this.y = (int) Math.floor(y);
+        this.z = (int) Math.floor(z);
+        return this;
+    }
+
     public @NotNull TilePos set(final @NotNull TilePosc pos) {
         this.x = pos.x();
         this.y = pos.y();
diff --git a/game/core/src/main/resources/assets/minecraft/lang/en_US/gamerules.lang b/game/core/src/main/resources/assets/minecraft/lang/en_US/gamerules.lang
index e760fb69a..48e921839 100644
--- a/game/core/src/main/resources/assets/minecraft/lang/en_US/gamerules.lang
+++ b/game/core/src/main/resources/assets/minecraft/lang/en_US/gamerules.lang
@@ -22,6 +22,8 @@ gamerule.do_fire_spread.name=Fire Spread
 gamerule.do_fire_spread.desc=If disabled, fire will not spread.
 gamerule.instant_healing.name=Instant Healing from Food
 gamerule.instant_healing.desc=If enabled, food will heal the player instantly as it did in older versions of BTA!.
+gamerule.acid_melt_rate.name=Acid Melt Rate
+gamerule.acid_melt_rate.desc=Ticks between each acid bank tick, durability melt, and post-exit drain pulse. Default matches fire damage (every 20 ticks). Lower values run faster.
 gamerule.random_tick_speed.name=Random Tick Rate
 gamerule.random_tick_speed.desc=Determines how many blocks are randomly ticked per game tick.
 gamerule.dropped_item_despawn_delay.name=Dropped Item Despawn Delay
diff --git a/game/core/src/main/resources/assets/minecraft/lang/en_US/item.lang b/game/core/src/main/resources/assets/minecraft/lang/en_US/item.lang
index 939eabe0b..fe9875735 100644
--- a/game/core/src/main/resources/assets/minecraft/lang/en_US/item.lang
+++ b/game/core/src/main/resources/assets/minecraft/lang/en_US/item.lang
@@ -490,28 +490,23 @@ item.jar.butterfly.silver.name=Silver Butterfly Jar
 item.jar.butterfly.silver.desc=A glass jar containing a silver butterfly.
 
 item.statue.stone.name=Stone Statue
-item.statue.stone.desc=A decorative stone statue for you to hang your armor.
+item.statue.stone.desc=A decorative statue carved out of stone.
 item.statue.basalt.name=Basalt Statue
-item.statue.basalt.desc=A decorative basalt statue for you to hang your armor.
+item.statue.basalt.desc=A decorative statue carved out of basalt.
 item.statue.limestone.name=Limestone Statue
-item.statue.limestone.desc=A decorative limestone statue for you to hang your armor.
+item.statue.limestone.desc=A decorative statue carved out of limestone.
 item.statue.granite.name=Granite Statue
-item.statue.granite.desc=A decorative granite statue for you to hang your armor.
+item.statue.granite.desc=A decorative statue carved out of granite.
 item.statue.marble.name=Marble Statue
-item.statue.marble.desc=A decorative marble statue for you to hang your armor.
+item.statue.marble.desc=A decorative statue carved out of marble.
 item.statue.pigman.name=Pigman Statue
 item.statue.pigman.desc=An ancient marble statue found in a long-forgotten place.
 
 item.dough.name=Dough
 item.dough.desc=Uncooked dough. Can be baked into bread.
 
-item.rubyglass.crystal.cracked.name=Rubyglass Shards
-item.rubyglass.crystal.cracked.desc=Cracked and full of impurities. But still aesthetically pleasing.
-item.rubyglass.crystal.pure.name=Rubyglass
-item.rubyglass.crystal.pure.desc=A shard of charged Rubyglass crystal, brimming with energy.
-
-item.sulfur.name=Sulfur
-item.sulfur.desc=Highly flammable and yellow. Packs a punch.
+item.rubyglass.name=Rubyglass
+item.rubyglass.desc=A shard of charged rubyglass, brimming with energy.
 
 item.armor.wolf.leather.name=Leather Wolf Armor
 item.armor.wolf.leather.desc=Keep your best friend safe with this soft leather shell.
@@ -525,3 +520,62 @@ item.armor.wolf.gold.name=Gold Wolf Armor
 item.armor.wolf.gold.desc=Keep your best friend fabulous with this shiny gold shell.
 item.armor.wolf.steel.name=Steel Wolf Armor
 item.armor.wolf.steel.desc=Keep your best friend safe with this sturdy steel shell.
+
+
+item.paintbrush.black.name=Black Paintbrush
+item.paintbrush.black.desc=Loaded with black dye and ready for use!
+item.paintbrush.red.name=Red Paintbrush
+item.paintbrush.red.desc=Loaded with red dye and ready for use!
+item.paintbrush.green.name=Green Paintbrush
+item.paintbrush.green.desc=Loaded with green dye and ready for use!
+item.paintbrush.brown.name=Brown Paintbrush
+item.paintbrush.brown.desc=Loaded with brown dye and ready for use!
+item.paintbrush.blue.name=Blue Paintbrush
+item.paintbrush.blue.desc=Loaded with blue dye and ready for use!
+item.paintbrush.purple.name=Purple Paintbrush
+item.paintbrush.purple.desc=Loaded with purple dye and ready for use!
+item.paintbrush.cyan.name=Cyan Paintbrush
+item.paintbrush.cyan.desc=Loaded with cyan dye and ready for use!
+item.paintbrush.silver.name=Light Gray Paintbrush
+item.paintbrush.silver.desc=Loaded with light gray dye and ready for use!
+item.paintbrush.gray.name=Gray Paintbrush
+item.paintbrush.gray.desc=Loaded with gray dye and ready for use!
+item.paintbrush.pink.name=Pink Paintbrush
+item.paintbrush.pink.desc=Loaded with pink dye and ready for use!
+item.paintbrush.yellow.name=Yellow Paintbrush
+item.paintbrush.yellow.desc=Loaded with yellow dye and ready for use!
+item.paintbrush.lime.name=Lime Paintbrush
+item.paintbrush.lime.desc=Loaded with lime dye and ready for use!
+item.paintbrush.lightblue.name=Light Blue Paintbrush
+item.paintbrush.lightblue.desc=Loaded with light blue dye and ready for use!
+item.paintbrush.magenta.name=Magenta Paintbrush
+item.paintbrush.magenta.desc=Loaded with magenta dye and ready for use!
+item.paintbrush.orange.name=Orange Paintbrush
+item.paintbrush.orange.desc=Loaded with orange dye and ready for use!
+item.paintbrush.white.name=White Paintbrush
+item.paintbrush.white.desc=Loaded with white dye and ready for use!
+
+item.statue.slate.name=Slate Statue
+item.statue.slate.desc=A decorative statue carved from slate.
+
+item.statue.permafrost.name=Permafrost Statue
+item.statue.permafrost.desc=A decorative statue carved from freezing permafrost.
+
+item.statue.netherrack.name=Netherrack Statue
+item.statue.netherrack.desc=A decorative statue carved from netherrack.
+
+item.statue.gloomstone.name=Gloomstone Statue
+item.statue.gloomstone.desc=A decorative statue carved from gloomy stone.
+
+item.ammo.arrow.flaming.name=Flaming Arrow
+item.ammo.arrow.flaming.desc=An arrow tipped with highly combustible sulfur.
+
+item.sulfur.name=Sulfur
+item.sulfur.desc=A pungent, flammable powder sourced from acidic pools.
+
+item.bucket.iron.acid.name=Iron Acid Bucket
+item.bucket.iron.acid.desc=A sturdy bucket carefully filled with corrosive acid.
+
+item.bucket.steel.acid.name=Steel Acid Bucket
+item.bucket.steel.acid.desc=A large sturdy bucket carefully filled with corrosive acid.
+
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 1e57d8946..5ec26009f 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
@@ -268,6 +268,7 @@ options.hideDynamicTooltip=Hide
 options.hideHealthBar=Hide
 options.hideOxygenBar=Hide
 options.hideFireBar=Hide
+options.hideAcidBar=Hide
 options.hideVehicleBar=Hide
 
 options.hideHelmetBar=Hide
@@ -296,6 +297,7 @@ options.verticalHealthBar=Vertical
 options.verticalOxygenBar=Vertical
 options.verticalVehicleBar=Vertical
 options.verticalFireBar=Vertical
+options.verticalAcidBar=Vertical
 
 options.flipHeldItemCount=Flip
 options.flipHelmetBar=Flip
@@ -306,6 +308,7 @@ options.flipHealthBar=Flip
 options.flipOxygenBar=Flip
 options.flipVehicleBar=Flip
 options.flipFireBar=Flip
+options.flipAcidBar=Flip
 
 options.horizontalInfoTextAlignment=Horizontal Alignment
 options.horizontalInfoTextAlignment.left=Left
diff --git a/game/core/src/main/resources/assets/minecraft/lang/en_US/tile.lang b/game/core/src/main/resources/assets/minecraft/lang/en_US/tile.lang
index 1f2511f1a..0d97b30e8 100644
--- a/game/core/src/main/resources/assets/minecraft/lang/en_US/tile.lang
+++ b/game/core/src/main/resources/assets/minecraft/lang/en_US/tile.lang
@@ -54,6 +54,8 @@ tile.netherrack.carved.name=Carved Netherrack
 tile.netherrack.carved.desc=Polished Netherrack with lines hewn through the middle.
 tile.gloomstone.carved.name=Carved Gloomstone
 tile.gloomstone.carved.desc=Polished Gloomstone with lines hewn through the middle.
+tile.slate.carved.name=Carved Slate
+tile.slate.carved.desc=Two slabs of polished slate.
 
 tile.planks.oak.name=Wooden Planks
 tile.planks.oak.desc=Processed tree insides.
@@ -235,6 +237,8 @@ tile.brick.stone.desc=Stacked bricks of stone.
 tile.brick.permafrost.name=Permafrost Bricks
 tile.brick.permafrost.desc=Stacked bricks of permafrost.
 
+tile.brick.rubyglass.name=Rubyglass Bricks
+tile.brick.rubyglass.desc=Stacked bricks of rubyglass.
 tile.brick.diamond.name=Diamond Bricks
 tile.brick.diamond.desc=Stacked bricks of diamond.
 tile.brick.iron.name=Iron Bricks
@@ -260,6 +264,8 @@ tile.slab.brick.stone.polished.name=Polished Stone Brick Slab
 tile.slab.brick.stone.polished.desc=A slab of polished stone bricks.
 tile.slab.stone.carved.name=Polished Stone Slab
 tile.slab.stone.carved.desc=A slab of polished stone.
+tile.slab.slate.polished.name=Polished Slate Slab
+tile.slab.slate.polished.desc=A slab of polished slate.
 tile.slab.brick.marble.name=Marble Brick Slab
 tile.slab.brick.marble.desc=A slab of marble bricks.
 tile.slab.brick.clay.name=Clay Brick Slab
@@ -332,6 +338,8 @@ tile.slab.brick.sandstone.desc=A slab of sandstone bricks.
 tile.slab.brick.permafrost.name=Permafrost Brick Slab
 tile.slab.brick.permafrost.desc=A slab of permafrost bricks.
 
+tile.slab.brick.rubyglass.name=Rubyglass Brick Slab
+tile.slab.brick.rubyglass.desc=A slab of rubyglass bricks.
 tile.slab.brick.diamond.name=Diamond Brick Slab
 tile.slab.brick.diamond.desc=A slab of diamond bricks.
 tile.slab.brick.iron.name=Iron Brick Slab
@@ -382,6 +390,8 @@ tile.stairs.brick.sandstone.desc=Sandstone brick block used for rapid vertical m
 tile.stairs.brick.permafrost.name=Permafrost Brick Stairs
 tile.stairs.brick.permafrost.desc=Permafrost brick block used for rapid vertical movement.
 
+tile.stairs.brick.rubyglass.name=Rubyglass Brick Stairs
+tile.stairs.brick.rubyglass.desc=Rubyglass brick block used for rapid vertical movement.
 tile.stairs.brick.diamond.name=Diamond Brick Stairs
 tile.stairs.brick.diamond.desc=Diamond brick block used for rapid vertical movement.
 tile.stairs.brick.iron.name=Iron Brick Stairs
@@ -519,8 +529,6 @@ tile.log.thorn.name=Thorn Log
 tile.log.thorn.desc=A log from a thorn tree.
 tile.log.palm.name=Palm Log
 tile.log.palm.desc=A log from a palm tree.
-tile.log.petrified.name=Petrified Pillar
-tile.log.petrified.desc=A pillar of carbon, encased in hardened ash.
 
 tile.leaves.oak.name=Oak Leaves
 tile.leaves.oak.desc=Leaves from an oak tree.
@@ -1069,19 +1077,19 @@ tile.cobble.netherrack.desc=Dark red rock that burns forever.
 tile.magma.name=Magma
 tile.magma.desc=Lava that's been partially cooled. Melts in extreme heat.
 tile.cobble.netherrack.crystalline.name=Crystalline Netherrack
-tile.cobble.netherrack.crystalline.desc=Netherrack, covered in a web of Rubyglass.
+tile.cobble.netherrack.crystalline.desc=Netherrack, covered in a web of rubyglass.
 tile.netherrack.name=Netherrack
 tile.netherrack.desc=Dark red rock, smoothed by heat and time.
 tile.brick.netherrack.name=Netherrack Bricks
-tile.brick.netherrack.desc=Stacked bricks of Netherrack.
+tile.brick.netherrack.desc=Stacked bricks of netherrack.
 tile.slab.netherrack.carved.name=Polished Netherrack Slab
-tile.slab.netherrack.carved.desc=A slab of polished Netherrack.
+tile.slab.netherrack.carved.desc=A slab of polished netherrack.
 tile.slab.cobble.netherrack.name=Cobbled Netherrack Slab
-tile.slab.cobble.netherrack.desc=A slab of cobbled Netherrack.
+tile.slab.cobble.netherrack.desc=A slab of cobbled netherrack.
 tile.slab.brick.netherrack.name=Netherrack Brick Slab
-tile.slab.brick.netherrack.desc=A slab of Netherrack bricks.
+tile.slab.brick.netherrack.desc=A slab of netherrack bricks.
 tile.stairs.cobble.netherrack.name=Cobbled Netherrack Stairs
-tile.stairs.cobble.netherrack.desc=Cobbled Netherrack block used for rapid vertical movement.
+tile.stairs.cobble.netherrack.desc=Cobbled netherrack block used for rapid vertical movement.
 tile.stairs.brick.netherrack.name=Netherrack Brick Stairs
 tile.stairs.brick.netherrack.desc=Netherrack brick block used for rapid vertical movement.
 
@@ -1433,25 +1441,23 @@ tile.rope.name=Rope
 tile.rope.desc=Rope, rope, rope your boat.
 
 tile.rubyglass.column.name=Rubyglass Column
-tile.rubyglass.column.desc=A column of tightly packed Rubyglass.
-
+tile.rubyglass.column.desc=A column of tightly packed rubyglass.
 tile.rubyglass.node.name=Rubyglass Node
-tile.rubyglass.node.desc=A dense outcrop of Rubyglass. Crystals harvested from this node are purer and more powerful than usual.
-
+tile.rubyglass.node.desc=A dense outcrop yeilding pure rubyglass.
 tile.block.rubyglass.name=Block of Rubyglass
-tile.block.rubyglass.desc=A translucent glass-like crystal brimming with energy.
-
-tile.rubyglass.sprout.name=Rubyglass Bud
-tile.rubyglass.sprout.desc=A small bud of rubyglass, branching out in many directions.
-
+tile.block.rubyglass.desc=A bright pink crystal brimming with energy.
 tile.rubyglass.conduit.name=Conduit
-tile.rubyglass.conduit.desc=Netherrack etched with lines of rubyglass. It resonates with latent power.
+tile.rubyglass.conduit.desc=Netherrack powered by lines of etched rubyglass using redstone as a catalyst.
+tile.rubyglass.growth.name=Rubyglass Growth
+tile.rubyglass.growth.desc=A squared, crystalline formation of rubyglass.
+tile.rubyglass.crystal.name=Rubyglass Crystal
+tile.rubyglass.crystal.desc=Thin glass crystal with the crimson hue of rubyglass.
 
-tile.vent.name=Chimney
-tile.vent.desc=A rocky column that emits a stream of smoke.
+tile.vent.name=Thermal Vent
+tile.vent.desc=An explosive, geothermal column amplified by its length.
 
 tile.bone_pile.name=Bone Pile
-tile.bone_pile.desc=A pile of solid calcium.
+tile.bone_pile.desc=A pile of bones.
 tile.soul_catcher.name=Soul Catcher
 tile.soul_catcher.desc=Tendrils of soul sand that seemingly move on their own. Hard to walk through.
 
@@ -1465,34 +1471,78 @@ tile.jar.butterfly.silver.name=Silver Butterfly Jar
 tile.jar.butterfly.silver.desc=A glass jar containing a silver butterfly.
 
 tile.statue.stone.lower.name=Stone Statue (Lower)
-tile.statue.stone.lower.desc=
+tile.statue.stone.lower.desc=How?
 tile.statue.stone.upper.name=Stone Statue (Upper)
-tile.statue.stone.upper.desc=
+tile.statue.stone.upper.desc=How?
 tile.statue.basalt.lower.name=Basalt Statue (Lower)
-tile.statue.basalt.lower.desc=
+tile.statue.basalt.lower.desc=How?
 tile.statue.basalt.upper.name=Basalt Statue (Upper)
-tile.statue.basalt.upper.desc=
+tile.statue.basalt.upper.desc=How?
 tile.statue.limestone.lower.name=Limestone Statue (Lower)
-tile.statue.limestone.lower.desc=
+tile.statue.limestone.lower.desc=How?
 tile.statue.limestone.upper.name=Limestone Statue (Upper)
-tile.statue.limestone.upper.desc=
+tile.statue.limestone.upper.desc=How?
 tile.statue.granite.lower.name=Granite Statue (Lower)
-tile.statue.granite.lower.desc=
+tile.statue.granite.lower.desc=How?
 tile.statue.granite.upper.name=Granite Statue (Upper)
-tile.statue.granite.upper.desc=
+tile.statue.granite.upper.desc=How?
 tile.statue.marble.lower.name=Marble Statue (Lower)
-tile.statue.marble.lower.desc=
+tile.statue.marble.lower.desc=How?
 tile.statue.marble.upper.name=Marble Statue (Upper)
-tile.statue.marble.upper.desc=
+tile.statue.marble.upper.desc=How?
 tile.statue.pigman.lower.name=Pigman Statue (Lower)
-tile.statue.pigman.lower.desc=
+tile.statue.pigman.lower.desc=How?
 tile.statue.pigman.upper.name=Pigman Statue (Upper)
-tile.statue.pigman.upper.desc=
+tile.statue.pigman.upper.desc=How?
 
 tile.matcher.name=Matcher
 tile.matcher.desc=Matches the block in front to the block behind.
 tile.matcher.active.name=Matcher
 tile.matcher.desc=Matches the block in front to the block behind.
 
-tile.flowstone.name=Flowstone
-tile.flowstone.desc=A rocky growth formed over millennia.
\ No newline at end of file
+tile.log.scorched.name=Scorched Log
+tile.log.scorched.desc=A severely burnt log, charred black by intense heat.
+
+tile.brimstone.name=Brimstone
+tile.brimstone.desc=Compacted brimsand.
+
+tile.slab.brimstone.name=Brimstone Slab
+tile.slab.brimstone.desc=A slab of brimstone.
+
+tile.stairs.brimstone.name=Brimstone Stairs
+tile.stairs.brimstone.desc=Brimstone block used for rapid vertical movement.
+
+tile.brick.brimstone.name=Brimstone Bricks
+tile.brick.brimstone.desc=Stacked bricks of Brimstone.
+
+tile.slab.brick.brimstone.name=Brimstone Brick Slab
+tile.slab.brick.brimstone.desc=A slab of brimstone bricks.
+
+tile.stairs.brick.brimstone.name=Brimstone Brick Stairs
+tile.stairs.brick.brimstone.desc=Brimstone brick block used for rapid vertical movement.
+
+tile.sulfur.name=Block of Sulfur
+tile.sulfur.desc=A foul-smelling block of concentrated sulfur.
+
+tile.sulfur_hardened.name=Hardened Block of Sulfur
+tile.sulfur_hardened.desc=A rough, foul-smelling block of concentrated sulfur.
+
+tile.chain.iron.large.name=Iron Chain
+tile.chain.iron.large.desc=A heavy iron chain.
+tile.chain.steel.name=Steel Chain
+tile.chain.steel.desc=A durable steel chain.
+
+tile.boulder.magmatic.name=Magmatic Boulder
+tile.boulder.magmatic.desc=A large magmatic rock.
+
+tile.boulder.sulfuric.name=Sulfuric Boulder
+tile.boulder.sulfuric.desc=A large sulfuric rock.
+
+tile.brimthaw.name=Brimthaw
+tile.brimthaw.desc=Brimstone laced with glowing rock.
+
+tile.ember.name=Ember
+tile.ember.desc=Smoldering, explosive remnants of a forgotten world.
+
+tile.acid.name=Acid
+tile.acid.desc=A highly corrosive, sulfuric fluid.
\ No newline at end of file
diff --git a/game/core/src/main/resources/assets/minecraft/lang/en_US/tips.txt b/game/core/src/main/resources/assets/minecraft/lang/en_US/tips.txt
index e19432969..cec08b9fd 100644
--- a/game/core/src/main/resources/assets/minecraft/lang/en_US/tips.txt
+++ b/game/core/src/main/resources/assets/minecraft/lang/en_US/tips.txt
@@ -14,7 +14,7 @@ Beds need blankets! Use cloth to craft them.
 Quivers provide a convenient way to store a lot of arrows.
 Olivine is a decent substitute for Nether coal.
 Outback soil is rich with minerals.
-There are 4 main types of rock! See which one you like best.
+There are 5 main types of rock in the overworld! See which one you like best.
 Fancy a splash of color? Try painting wooden planks!
 Press %key.hotbar.switch to change your active hotbar.
 Use a compass to see your coordinates.
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 0aad36ab1..5181f4807 100644
--- a/game/core/src/main/resources/assets/minecraft/sounds/sounds.json
+++ b/game/core/src/main/resources/assets/minecraft/sounds/sounds.json
@@ -892,6 +892,57 @@
       }
     ]
   },
+  "tile.acidbubble": {
+    "subtitle": "subtitles.tile.acidbubble",
+    "sounds": [
+      "tile/bubble_1.ogg",
+      "tile/bubble_2.ogg",
+      "tile/bubble_3.ogg",
+      "tile/bubble_4.ogg"
+    ]
+  },
+  "step.crystal": {
+    "subtitle": "subtitles.step.crystal",
+    "sounds": [
+      "step/crystal_1.ogg",
+      "step/crystal_2.ogg",
+      "step/crystal_3.ogg",
+      "step/crystal_4.ogg",
+      "step/crystal_5.ogg",
+      "step/crystal_6.ogg"
+    ]
+  },
+  "tile.deepboom": {
+    "subtitle": "subtitles.tile.deepboom",
+    "sounds": [
+      "tile/deepboom.ogg"
+    ]
+  },
+  "tile.deepquietboom": {
+    "subtitle": "subtitles.tile.deepquietboom",
+    "sounds": [
+      "tile/deepquietboom.ogg"
+    ]
+  },
+  "tile.crumble": {
+    "subtitle": "subtitles.tile.crumble",
+    "sounds": [
+      "tile/crumble_1.ogg",
+      "tile/crumble_2.ogg"
+    ]
+  },
+  "tile.deepcrumble": {
+    "subtitle": "subtitles.tile.deepcrumble",
+    "sounds": [
+      "tile/deepcrumble.ogg"
+    ]
+  },
+  "random.scrape": {
+    "subtitle": "subtitles.random.scrape",
+    "sounds": [
+      "random/scrape.ogg"
+    ]
+  },
   "record.mall": {
     "sounds": [
       {
diff --git a/game/core/src/test/java/TestFlammables.java b/game/core/src/test/java/TestFlammables.java
index c662cff05..73c5baf96 100644
--- a/game/core/src/test/java/TestFlammables.java
+++ b/game/core/src/test/java/TestFlammables.java
@@ -24,10 +24,16 @@ public class TestFlammables {
                 Block.hasLogicClass(block, BlockLogicLog.class) ||
                 Block.hasLogicClass(block, BlockLogicLeavesBase.class) ||
                 Block.hasLogicClass(block, BlockLogicMoss.class);
+
             boolean shouldSmelt =
                 Block.hasLogicClass(block, BlockLogicLog.class) ||
                 Block.hasLogicClass(block, BlockLogicSaplingBase.class);
 
+            if (block == Blocks.LOG_SCORCHED){
+                shouldCombust = false;
+                shouldSmelt = false;
+            }
+
             if (shouldCombust) {
                 Assertions.assertTrue(BlockLogicFire.canBurn(block.id()), () -> "Block '" + block.namespaceId() + "' of class '" + block.getLogic().getClass() + "' should probably be flammable!");
             }
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 5c4ecdee4..96b7562d4 100644
--- a/util/datagen/src/main/java/net/minecraft/datagen/SmeltingGenerator.java
+++ b/util/datagen/src/main/java/net/minecraft/datagen/SmeltingGenerator.java
@@ -28,7 +28,7 @@ public class SmeltingGenerator {
 
         sharedRecipe("raw_iron_to_iron_ingot", Items.ORE_RAW_IRON.getDefaultStack(), Items.INGOT_IRON.getDefaultStack());
         sharedRecipe("raw_gold_to_gold_ingot", Items.ORE_RAW_GOLD.getDefaultStack(), Items.INGOT_GOLD.getDefaultStack());
-        
+
         furnaceRecipe("dough_to_bread", Items.DOUGH.getDefaultStack(), Items.FOOD_BREAD.getDefaultStack());
         furnaceRecipe("fish_raw_to_fish_cooked", Items.FOOD_FISH_RAW.getDefaultStack(), Items.FOOD_FISH_COOKED.getDefaultStack());
         furnaceRecipe("porkchop_raw_to_porkchop_cooked", Items.FOOD_PORKCHOP_RAW.getDefaultStack(), Items.FOOD_PORKCHOP_COOKED.getDefaultStack());
@@ -42,16 +42,30 @@ public class SmeltingGenerator {
         furnaceRecipe("cobble_permafrost_to_permafrost", Blocks.COBBLE_PERMAFROST.getDefaultStack(), Blocks.PERMAFROST.getDefaultStack());
         furnaceRecipe("cobble_gloomstone_to_gloomstone", Blocks.COBBLE_GLOOMSTONE.getDefaultStack(), Blocks.GLOOMSTONE.getDefaultStack());
 
+        blastRecipe("cobble_stone_to_slate", Blocks.COBBLE_STONE.getDefaultStack(), Blocks.SLATE.getDefaultStack());
+        blastRecipe("cobble_stone_to_stone", Blocks.COBBLE_STONE.getDefaultStack(), Blocks.COBBLE_STONE.getDefaultStack(), Blocks.STONE.getDefaultStack());
+
         blastRecipe("cobble_basalt_to_olivine", Blocks.COBBLE_BASALT.getDefaultStack(), Items.OLIVINE.getDefaultStack());
+        blastRecipe("cobble_basalt_to_basalt", Blocks.COBBLE_BASALT.getDefaultStack(), Blocks.COBBLE_BASALT.getDefaultStack(), Blocks.BASALT.getDefaultStack());
+
         blastRecipe("cobble_granite_to_quartz", Blocks.COBBLE_GRANITE.getDefaultStack(), Items.QUARTZ.getDefaultStack());
+        blastRecipe("cobble_granite_to_granite", Blocks.COBBLE_GRANITE.getDefaultStack(), Blocks.COBBLE_GRANITE.getDefaultStack(), Blocks.GRANITE.getDefaultStack());
+
         blastRecipe("cobble_limestone_to_marble", Blocks.COBBLE_LIMESTONE.getDefaultStack(), Blocks.MARBLE.getDefaultStack());
+        blastRecipe("cobble_limestone_to_limestone", Blocks.COBBLE_LIMESTONE.getDefaultStack(), Blocks.COBBLE_LIMESTONE.getDefaultStack(), Blocks.LIMESTONE.getDefaultStack());
+
+        blastRecipe("cobble_netherrack_to_magma", Blocks.COBBLE_NETHERRACK.getDefaultStack(), Blocks.MAGMA.getDefaultStack());
+        blastRecipe("cobble_netherrack_to_netherrack", Blocks.COBBLE_NETHERRACK.getDefaultStack(), Blocks.COBBLE_NETHERRACK.getDefaultStack(), Blocks.NETHERRACK.getDefaultStack());
+
+        blastRecipe("brimstone_to_brimthaw", Blocks.BRIMSTONE.getDefaultStack(), Blocks.BRIMTHAW.getDefaultStack());
+
         blastRecipe("cobble_permafrost_to_permafrost", Blocks.COBBLE_PERMAFROST.getDefaultStack(), Blocks.PERMAFROST.getDefaultStack());
-        blastRecipe("cobble_stone_to_slate", Blocks.COBBLE_STONE.getDefaultStack(), Blocks.SLATE.getDefaultStack());
+        blastRecipe("cobble_gloomstone_to_gloomstone", Blocks.COBBLE_GLOOMSTONE.getDefaultStack(), Blocks.GLOOMSTONE.getDefaultStack());
+
         blastRecipe("iron_ingot_and_coal_to_crude_steel", Items.INGOT_IRON.getDefaultStack(), Items.COAL.getDefaultStack(), Items.INGOT_STEEL_CRUDE.getDefaultStack());
         blastRecipe("iron_ingot_and_charcoal_to_crude_steel", Items.INGOT_IRON.getDefaultStack(), new ItemStack(Items.COAL, 1, 1), Items.INGOT_STEEL_CRUDE.getDefaultStack());
-        blastRecipe("cobble_netherrack_to_magma", Blocks.COBBLE_NETHERRACK.getDefaultStack(), Blocks.MAGMA.getDefaultStack());
-        blastRecipe("logs_to_petrified_pillar", "minecraft:logs", Blocks.BLOCK_ASH.getDefaultStack(), Blocks.LOG_PETRIFIED.getDefaultStack());
 
+        blastRecipe("log_to_scorched_log", "minecraft:logs", Blocks.BLOCK_ASH.getDefaultStack(), Blocks.LOG_SCORCHED.getDefaultStack());
     }
 
     private static void furnaceRecipe(String id, ItemStack input, ItemStack output) {
diff --git a/util/datagen/src/main/java/net/minecraft/datagen/TrommelGenerator.java b/util/datagen/src/main/java/net/minecraft/datagen/TrommelGenerator.java
index 1190892b3..095655ba5 100644
--- a/util/datagen/src/main/java/net/minecraft/datagen/TrommelGenerator.java
+++ b/util/datagen/src/main/java/net/minecraft/datagen/TrommelGenerator.java
@@ -78,19 +78,30 @@ public class TrommelGenerator {
             .addEntry(new WeightedRandomLootObject(new ItemStack(Items.ORE_RAW_GOLD), 1), 2)
             .addEntry(new WeightedRandomLootObject(new ItemStack(Items.ORE_RAW_IRON), 1), 1)
             .addEntry(new WeightedRandomLootObject(new ItemStack(Items.DUST_GLOWSTONE), 1, 6), 5)
-//            .addEntry(new WeightedRandomLootObject(new ItemStack(Items.RUBYGLASS_DUST), 1, 3), 5)
             .addEntry(new WeightedRandomLootObject(new ItemStack(Items.NETHERCOAL), 1), 2)
+            .addEntry(new WeightedRandomLootObject(new ItemStack(Items.AMMO_PEBBLE), 1, 2), 15)
             .create("soul_sand");
 
         RecipeBuilder.Trommel(RecipesGenerator.CORE_NAMESPACE)
-                .setInput(Blocks.BRIMSAND)
-                .addEntry(new WeightedRandomLootObject(new ItemStack(Items.GUNPOWDER), 1, 2), 25)
-                .addEntry(new WeightedRandomLootObject(new ItemStack(Items.BONE), 1, 2), 10)
-                .addEntry(new WeightedRandomLootObject(new ItemStack(Items.DUST_GLOWSTONE), 1, 4), 5)
-                .addEntry(new WeightedRandomLootObject(new ItemStack(Items.FLINT), 1, 2), 5)
-                .addEntry(new WeightedRandomLootObject(new ItemStack(Items.OLIVINE), 1), 5)
-                .addEntry(new WeightedRandomLootObject(new ItemStack(Items.ORE_RAW_GOLD), 1), 0.5)
-                .create("brimsand");
+            .setInput(Blocks.BRIMSAND)
+            .addEntry(new WeightedRandomLootObject(new ItemStack(Items.AMMO_PEBBLE), 2, 3), 35)
+            .addEntry(new WeightedRandomLootObject(new ItemStack(Items.GUNPOWDER), 1, 2), 25)
+            .addEntry(new WeightedRandomLootObject(new ItemStack(Items.BONE), 1, 2), 10)
+            .addEntry(new WeightedRandomLootObject(new ItemStack(Items.DUST_GLOWSTONE), 1, 2), 10)
+            .addEntry(new WeightedRandomLootObject(new ItemStack(Items.FLINT), 1, 2), 5)
+            .addEntry(new WeightedRandomLootObject(new ItemStack(Items.SULFUR), 1, 2), 5)
+            .addEntry(new WeightedRandomLootObject(new ItemStack(Items.NETHERCOAL), 1), 5)
+            .addEntry(new WeightedRandomLootObject(new ItemStack(Items.ORE_RAW_GOLD), 1), 0.5)
+            .create("brimsand");
+
+        RecipeBuilder.Trommel(RecipesGenerator.CORE_NAMESPACE)
+            .setInput(Blocks.SULFUR)
+            .addEntry(new WeightedRandomLootObject(new ItemStack(Items.SULFUR), 2, 5), 30)
+            .addEntry(new WeightedRandomLootObject(new ItemStack(Items.DUST_GLOWSTONE), 1, 3), 20)
+            .addEntry(new WeightedRandomLootObject(new ItemStack(Items.BONE), 1, 2), 10)
+            .addEntry(new WeightedRandomLootObject(new ItemStack(Items.FLINT), 1, 2), 5)
+            .addEntry(new WeightedRandomLootObject(new ItemStack(Items.ORE_RAW_IRON), 1, 2), 4)
+            .create("sulfur");
 
     }
 }
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 82a1a4cab..50656deab 100644
--- a/util/datagen/src/main/java/net/minecraft/datagen/WorkbenchGenerator.java
+++ b/util/datagen/src/main/java/net/minecraft/datagen/WorkbenchGenerator.java
@@ -62,6 +62,9 @@ class WorkbenchGenerator {
         generateDyeingRecipes();
 
         generateStatueRecipes();
+        generateAcidConversions();
+
+        generateMiscNewRecipes();
 
         RecipeBuilder.Shaped(CORE_NAMESPACE)
                 .setShape(
@@ -151,11 +154,11 @@ class WorkbenchGenerator {
                 "###",
                 "CSR",
                 "###")
-            .addInput('#', Blocks.COBBLE_NETHERRACK)
+            .addInput('#', Blocks.NETHERRACK)
             .addInput('S', Blocks.SOULSAND)
             .addInput('C', Blocks.CONDUIT)
             .addInput('R', Items.DUST_REDSTONE)
-            .create("activator", Blocks.ACTIVATOR_COBBLE_NETHERRACK.getDefaultStack());
+            .create("activator", Blocks.ACTIVATOR.getDefaultStack());
 
         RecipeBuilder.Shaped(CORE_NAMESPACE)
             .setShape(
@@ -254,7 +257,7 @@ class WorkbenchGenerator {
         shape22.addInput('#', Items.CLAY).create("clay_block", Blocks.BLOCK_CLAY.getDefaultStack());
         shape22.addInput('#', Items.AMMO_SNOWBALL).create("snow_block", Blocks.BLOCK_SNOW.getDefaultStack());
         shape22.addInput('#', Items.STRING).create("string_to_wool", Blocks.WOOL.getDefaultStack());
-        shape22.addInput('#', Items.AMMO_PEBBLE).create("pebbles_to_cobblestone", Blocks.COBBLE_STONE.getDefaultStack());
+        shape22.addInput('#', Items.AMMO_PEBBLE).create("pebbles_to_cobblestone", new ItemStack(Blocks.COBBLE_STONE, 2));
         shape22.addInput('#', Blocks.SAND).create("sandstone", new ItemStack(Blocks.SANDSTONE, 2));
         shape22.addInput('#', "minecraft:planks").create("workbench", Blocks.WORKBENCH.getDefaultStack());
 
@@ -429,7 +432,59 @@ class WorkbenchGenerator {
             .addInput(new ItemStack(Items.DYE, 1, DyeColor.BLACK.itemMeta))
             .addInput(Items.STRING)
             .create("blank_label", Items.LABEL.getDefaultStack());
+
+        RecipeBuilder.Shapeless(CORE_NAMESPACE)
+            .addInput(Items.BONE)
+            .addInput(Items.BONE)
+            .create("bone_pile", Blocks.BONE_PILE.getDefaultStack());
+
+        RecipeBuilder.Shaped(CORE_NAMESPACE)
+            .setShape(
+                "GSG",
+                "SCS",
+                "GSG")
+            .addInput('C', Items.NETHERCOAL)
+            .addInput('G', Items.GUNPOWDER)
+            .addInput('S', Items.SULFUR)
+            .create("ember", Blocks.EMBER.getDefaultStack());
+
+        RecipeBuilder.Shapeless(CORE_NAMESPACE)
+            .addInput(Items.SULFUR)
+            .addInput(Items.COAL)
+            .addInput(Items.PAPER)
+            .create("gunpowder_with_coal", new ItemStack(Items.GUNPOWDER, 2));
+
+        RecipeBuilder.Shapeless(CORE_NAMESPACE)
+            .addInput(Items.SULFUR)
+            .addInput(new ItemStack(Items.COAL, 1, 1))
+            .addInput(Items.PAPER)
+            .create("gunpowder_with_charcoal", new ItemStack(Items.GUNPOWDER, 2));
+
+        RecipeBuilder.Shapeless(CORE_NAMESPACE)
+            .addInput(Items.SULFUR)
+            .addInput(Items.NETHERCOAL)
+            .addInput(Items.PAPER)
+            .create("gunpowder_with_nethercoal", new ItemStack(Items.GUNPOWDER, 4));
+
+        RecipeBuilder.Shapeless(CORE_NAMESPACE)
+            .addInput(Blocks.RUBYGLASS_COLUMN)
+            .create("ruby_column_to_growth", new ItemStack(Blocks.RUBYGLASS_GROWTH, 4));
+
+        RecipeBuilder.Shapeless(CORE_NAMESPACE)
+            .addInput(Blocks.RUBYGLASS_GROWTH)
+            .addInput(Blocks.RUBYGLASS_GROWTH)
+            .addInput(Blocks.RUBYGLASS_GROWTH)
+            .addInput(Blocks.RUBYGLASS_GROWTH)
+            .create("ruby_growth_to_column", new ItemStack(Blocks.RUBYGLASS_COLUMN, 1));
+
+        RecipeBuilder.Shapeless(CORE_NAMESPACE)
+            .addInput(Items.SULFUR)
+            .addInput(Items.CLAY)
+            .addInput(new ItemStack(Items.DYE, 1, DyeColor.WHITE.itemMeta))
+            .create("dirt", new ItemStack(Blocks.DIRT, 2));
     }
+
+
     private static void generatePickaxes() {
         RecipeBuilderShaped pickaxeTemplate = RecipeBuilder.Shaped(CORE_NAMESPACE)
             .setShape(
@@ -732,7 +787,7 @@ class WorkbenchGenerator {
         block.addInput('X', Items.DIAMOND).create("block_of_diamond", Blocks.BLOCK_DIAMOND.getDefaultStack());
         block.addInput('X', Items.QUARTZ).create("block_of_quartz", Blocks.BLOCK_QUARTZ.getDefaultStack());
         block.addInput('X', Items.SUGARCANE).create("block_of_sugarcane", Blocks.BLOCK_SUGARCANE.getDefaultStack());
-        block.addInput('X', Items.RUBYGLASS_CRYSTAL).create("block_of_rubyglass", Blocks.BLOCK_RUBYGLASS.getDefaultStack());
+        block.addInput('X', Items.RUBYGLASS).create("block_of_rubyglass", Blocks.BLOCK_RUBYGLASS.getDefaultStack());
 
         blockFuel.addInput('X', Items.COAL, 0).create("block_of_coal", Blocks.BLOCK_COAL.getDefaultStack());
         blockFuel.addInput('X', Items.COAL, 1).create("block_of_charcoal", Blocks.BLOCK_CHARCOAL.getDefaultStack());
@@ -748,7 +803,7 @@ class WorkbenchGenerator {
         RecipeBuilder.Shapeless(CORE_NAMESPACE).addInput(Blocks.BLOCK_REDSTONE).create("block_of_redstone_to_redstone", new ItemStack(Items.DUST_REDSTONE, 9));
         RecipeBuilder.Shapeless(CORE_NAMESPACE).addInput(Blocks.BLOCK_DIAMOND).create("block_of_diamond_to_diamond", new ItemStack(Items.DIAMOND, 9));
         RecipeBuilder.Shapeless(CORE_NAMESPACE).addInput(Blocks.BLOCK_QUARTZ).create("block_of_quartz_to_quartz", new ItemStack(Items.QUARTZ, 9));
-        RecipeBuilder.Shapeless(CORE_NAMESPACE).addInput(Blocks.BLOCK_RUBYGLASS).create("block_of_rubyglass_to_rubyglass", new ItemStack(Items.RUBYGLASS_CRYSTAL, 9));
+        RecipeBuilder.Shapeless(CORE_NAMESPACE).addInput(Blocks.BLOCK_RUBYGLASS).create("block_of_rubyglass_to_rubyglass", new ItemStack(Items.RUBYGLASS, 9));
         RecipeBuilder.Shapeless(CORE_NAMESPACE).addInput(Blocks.BLOCK_SUGARCANE).create("block_of_sugarcane_to_sugarcane", new ItemStack(Items.SUGARCANE, 9));
 
         RecipeBuilder.Shapeless(CORE_NAMESPACE).addInput(Blocks.BLOCK_COAL).create("block_of_coal_to_coal", new ItemStack(Items.COAL, 8, 0));
@@ -765,6 +820,7 @@ class WorkbenchGenerator {
             );
 
         final int stackSize = 4;
+        bricks.addInput('X', Items.RUBYGLASS).create("rubyglass_bricks", new ItemStack(Blocks.BRICK_RUBYGLASS, stackSize));
         bricks.addInput('X', Items.DIAMOND).create("diamond_bricks", new ItemStack(Blocks.BRICK_DIAMOND, stackSize));
         bricks.addInput('X', Items.BRICK_CLAY).create("clay_bricks", new ItemStack(Blocks.BRICK_CLAY, stackSize));
         bricks.addInput('X', Items.INGOT_GOLD).create("golden_bricks", new ItemStack(Blocks.BRICK_GOLD, stackSize));
@@ -830,6 +886,7 @@ class WorkbenchGenerator {
         slabs.addInput('X', Blocks.BRICK_NETHERRACK).create("netherrack_brick_slab", new ItemStack(Blocks.SLAB_BRICK_NETHERRACK, stackSize));
         slabs.addInput('X', Blocks.BRICK_GLOOMSTONE).create("gloomstone_brick_slab", new ItemStack(Blocks.SLAB_BRICK_GLOOMSTONE, stackSize));
 
+        slabs.addInput('X', Blocks.BRICK_RUBYGLASS).create("rubyglass_brick_slab", new ItemStack(Blocks.SLAB_BRICK_RUBYGLASS, stackSize));
         slabs.addInput('X', Blocks.BRICK_DIAMOND).create("diamond_brick_slab", new ItemStack(Blocks.SLAB_BRICK_DIAMOND, stackSize));
         slabs.addInput('X', Blocks.BRICK_IRON).create("iron_brick_slab", new ItemStack(Blocks.SLAB_BRICK_IRON, stackSize));
         slabs.addInput('X', Blocks.BRICK_GOLD).create("gold_brick_slab", new ItemStack(Blocks.SLAB_BRICK_GOLD, stackSize));
@@ -880,6 +937,7 @@ class WorkbenchGenerator {
         stairs.addInput('X', Blocks.BRICK_NETHERRACK).create("netherrack_brick_stairs", new ItemStack(Blocks.STAIRS_BRICK_NETHERRACK, stackSize));
         stairs.addInput('X', Blocks.BRICK_GLOOMSTONE).create("gloomstone_brick_stairs", new ItemStack(Blocks.STAIRS_BRICK_GLOOMSTONE, stackSize));
 
+        stairs.addInput('X', Blocks.BRICK_RUBYGLASS).create("rubyglass_brick_stairs", new ItemStack(Blocks.STAIRS_BRICK_RUBYGLASS, stackSize));
         stairs.addInput('X', Blocks.BRICK_DIAMOND).create("diamond_brick_stairs", new ItemStack(Blocks.STAIRS_BRICK_DIAMOND, stackSize));
         stairs.addInput('X', Blocks.BRICK_IRON).create("iron_brick_stairs", new ItemStack(Blocks.STAIRS_BRICK_IRON, stackSize));
         stairs.addInput('X', Blocks.BRICK_GOLD).create("gold_brick_stairs", new ItemStack(Blocks.STAIRS_BRICK_GOLD, stackSize));
@@ -1384,7 +1442,7 @@ class WorkbenchGenerator {
                 "#C#",
                 "#Q#"
             )
-            .addInput('#', Blocks.COBBLE_NETHERRACK)
+            .addInput('#', Blocks.NETHERRACK)
             .addInput('Q', Items.QUARTZ)
             .addInput('C', Blocks.CONDUIT)
             .create("matcher", new ItemStack(Blocks.MATCHER, 1));
@@ -1395,8 +1453,8 @@ class WorkbenchGenerator {
                         "ORO",
                         "XOX")
                 .addInput('R', Items.DUST_REDSTONE)
-                .addInput('O', Items.RUBYGLASS_CRYSTAL)
-                .addInput('X', Blocks.COBBLE_NETHERRACK)
+                .addInput('O', Items.RUBYGLASS)
+                .addInput('X', Blocks.NETHERRACK)
                 .create("conduit", new ItemStack(Blocks.CONDUIT, 1));
 
         RecipeBuilder.Shaped(CORE_NAMESPACE)
@@ -1431,18 +1489,14 @@ class WorkbenchGenerator {
     }
 
     private static void generateStatueRecipes() {
-        final String[] statueNames = new String[]{"stone", "basalt", "limestone", "granite", "marble"};
-        final Item[] statueItems = new Item[]{Items.STATUE_STONE, Items.STATUE_BASALT, Items.STATUE_LIMESTONE, Items.STATUE_GRANITE, Items.STATUE_MARBLE};
-        final Block<?>[] statueBodyBlocks = new Block[]{Blocks.STONE, Blocks.BASALT, Blocks.LIMESTONE, Blocks.GRANITE, Blocks.MARBLE};
-        final Block<?>[] statueBaseBlocks = new Block[]{Blocks.SLAB_STONE_POLISHED, Blocks.SLAB_BASALT_POLISHED, Blocks.SLAB_LIMESTONE_POLISHED, Blocks.SLAB_GRANITE_POLISHED, Blocks.SLAB_CAPSTONE_MARBLE};
+        final String[] statueNames = new String[]{"stone", "basalt", "limestone", "granite", "marble", "slate", "permafrost", "netherrack", "gloomstone"};
+        final Item[] statueItems = new Item[]{Items.STATUE_STONE, Items.STATUE_BASALT, Items.STATUE_LIMESTONE, Items.STATUE_GRANITE, Items.STATUE_MARBLE, Items.STATUE_SLATE, Items.STATUE_PERMAFROST, Items.STATUE_NETHERRACK, Items.STATUE_GLOOMSTONE};
+        final Block<?>[] statueBodyBlocks = new Block[]{Blocks.STONE, Blocks.BASALT, Blocks.LIMESTONE, Blocks.GRANITE, Blocks.MARBLE, Blocks.SLATE, Blocks.PERMAFROST, Blocks.NETHERRACK, Blocks.GLOOMSTONE};
+        final Block<?>[] statueBaseBlocks = new Block[]{Blocks.SLAB_STONE_POLISHED, Blocks.SLAB_BASALT_POLISHED, Blocks.SLAB_LIMESTONE_POLISHED, Blocks.SLAB_GRANITE_POLISHED, Blocks.SLAB_CAPSTONE_MARBLE, Blocks.SLAB_SLATE_POLISHED, Blocks.SLAB_PERMAFROST_POLISHED, Blocks.SLAB_NETHERRACK_POLISHED, Blocks.SLAB_GLOOMSTONE_POLISHED};
 
         for (int i = 0; i < statueItems.length; i++) {
             RecipeBuilder.Shaped(CORE_NAMESPACE)
-                .setShape(
-                    "#",
-                    "#",
-                    "_"
-                )
+                    .setShape("#", "#", "_")
                 .addInput('#', statueBodyBlocks[i])
                 .addInput('_', statueBaseBlocks[i])
                 .create("statue_" + statueNames[i], new ItemStack(statueItems[i], 1));
@@ -1484,4 +1538,64 @@ class WorkbenchGenerator {
         Registries.RECIPES.WORKBENCH.register(String.format(format, "buttons_planks"), new RecipeEntryDyeing(new RecipeSymbol("minecraft:button_planks"), new ItemStack(Blocks.BUTTON_PLANKS_PAINTED), true, false));
         Registries.RECIPES.WORKBENCH.register(String.format(format, "pressureplates_planks"), new RecipeEntryDyeing(new RecipeSymbol("minecraft:pressureplates_planks"), new ItemStack(Blocks.PRESSURE_PLATE_PLANKS_OAK_PAINTED), true, false));
     }
+
+    private static void generateMiscNewRecipes() {
+        // Carved Stones
+        RecipeBuilderShaped templateCarvedStone = RecipeBuilder.Shaped(CORE_NAMESPACE).setShape("SS", "SS");
+        templateCarvedStone.addInput('S', Blocks.STONE_POLISHED).create("carved_stone", new ItemStack(Blocks.STONE_CARVED, 2));
+        templateCarvedStone.addInput('S', Blocks.BASALT_POLISHED).create("carved_basalt", new ItemStack(Blocks.BASALT_CARVED, 2));
+        templateCarvedStone.addInput('S', Blocks.LIMESTONE_POLISHED).create("carved_limestone", new ItemStack(Blocks.LIMESTONE_CARVED, 2));
+        templateCarvedStone.addInput('S', Blocks.GRANITE_POLISHED).create("carved_granite", new ItemStack(Blocks.GRANITE_CARVED, 2));
+        templateCarvedStone.addInput('S', Blocks.PERMAFROST_POLISHED).create("carved_permafrost", new ItemStack(Blocks.PERMAFROST_CARVED, 2));
+        templateCarvedStone.addInput('S', Blocks.MARBLE).create("marble_capstone", new ItemStack(Blocks.CAPSTONE_MARBLE, 2));
+        templateCarvedStone.addInput('S', Blocks.SLATE_POLISHED).create("carved_slate", new ItemStack(Blocks.SLATE_CARVED, 2));
+        templateCarvedStone.addInput('S', Blocks.NETHERRACK_POLISHED).create("carved_netherrack", new ItemStack(Blocks.NETHERRACK_CARVED, 2));
+        templateCarvedStone.addInput('S', Blocks.GLOOMSTONE_POLISHED).create("carved_gloomstone", new ItemStack(Blocks.GLOOMSTONE_CARVED, 2));
+
+        // Brimstone Formatting
+        RecipeBuilder.Shaped(CORE_NAMESPACE).setShape("SS", "SS").addInput('S', Blocks.BRIMSAND).create("brimstone", new ItemStack(Blocks.BRIMSTONE, 2));
+        RecipeBuilder.Shaped(CORE_NAMESPACE).setShape("SSS").addInput('S', Blocks.BRIMSTONE).create("brimstone_slab", new ItemStack(Blocks.SLAB_BRIMSTONE, 6));
+        RecipeBuilder.Shaped(CORE_NAMESPACE).setShape("S  ", "SS ", "SSS").addInput('S', Blocks.BRIMSTONE).create("brimstone_stairs", new ItemStack(Blocks.STAIRS_BRIMSTONE, 6));
+        RecipeBuilder.Shaped(CORE_NAMESPACE).setShape("SS", "SS").addInput('S', Blocks.BRIMSTONE).create("brimstone_bricks", new ItemStack(Blocks.BRICK_BRIMSTONE, 4));
+        RecipeBuilder.Shaped(CORE_NAMESPACE).setShape("SSS").addInput('S', Blocks.BRICK_BRIMSTONE).create("brick_brimstone_slab", new ItemStack(Blocks.SLAB_BRICK_BRIMSTONE, 6));
+        RecipeBuilder.Shaped(CORE_NAMESPACE).setShape("S  ", "SS ", "SSS").addInput('S', Blocks.BRICK_BRIMSTONE).create("brick_brimstone_stairs", new ItemStack(Blocks.STAIRS_BRICK_BRIMSTONE, 6));
+
+        // 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.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));
+    }
+
+    private static void generateAcidConversions() {
+        // Each pair: { input, output }
+        final Block<?>[][] pairs = {
+            { Blocks.COBBLE_STONE,         Blocks.STONE       },
+            { Blocks.COBBLE_BASALT,        Blocks.BASALT      },
+            { Blocks.COBBLE_LIMESTONE,     Blocks.LIMESTONE   },
+            { Blocks.COBBLE_GRANITE,       Blocks.GRANITE     },
+            { Blocks.COBBLE_PERMAFROST,    Blocks.PERMAFROST  },
+            { Blocks.COBBLE_NETHERRACK,    Blocks.NETHERRACK  },
+            { Blocks.COBBLE_GLOOMSTONE,    Blocks.GLOOMSTONE  },
+            { Blocks.SAND,                 Blocks.SANDSTONE   },
+            { Blocks.BRIMSAND,             Blocks.BRIMSTONE   }
+        };
+
+        for (Block<?>[] pair : pairs) {
+            Block<?> cobble = pair[0];
+            Block<?> smooth = pair[1];
+            String cobbleName = cobble.getKey().replace(".", "_").replace(":", "_");
+
+            RecipeBuilder.Shaped(CORE_NAMESPACE)
+                .setShape(
+                    "CCC",
+                    "CAC",
+                    "CCC")
+                .addInput('C', cobble)
+                .addInput('A', "minecraft:acid_buckets")
+                .create("acid_conversion_" + cobbleName, new ItemStack(smooth, 4));
+        }
+    }
 }
diff --git a/util/seed-viewer/src/main/java/org/useless/seedviewer/bta/BTABiome.java b/util/seed-viewer/src/main/java/org/useless/seedviewer/bta/BTABiome.java
index cb9b399df..bd6bb7962 100644
--- a/util/seed-viewer/src/main/java/org/useless/seedviewer/bta/BTABiome.java
+++ b/util/seed-viewer/src/main/java/org/useless/seedviewer/bta/BTABiome.java
@@ -1,6 +1,5 @@
 package org.useless.seedviewer.bta;
 
-import net.minecraft.core.data.registry.Registries;
 import net.minecraft.core.lang.I18n;
 import net.minecraft.core.util.HardIllegalArgumentException;
 import net.minecraft.core.util.collection.NamespaceID;
@@ -19,8 +18,12 @@ public class BTABiome implements ViewerBiome {
 
     @Override
     public NamespaceID getID() {
+        final String key = this.biome.getRegistryKey();
+        if (key == null) {
+            throw new IllegalArgumentException("Unregistered biome: " + this.biome.translationKey);
+        }
         try {
-            return NamespaceID.fromPool(Registries.BIOMES.getKey(this.biome)).copyUnpooled();
+            return NamespaceID.fromPool(key).copyUnpooled();
         } catch (HardIllegalArgumentException e) {
             throw new IllegalArgumentException(e);
         }
