diff --git a/game/client/build.gradle.kts b/game/client/build.gradle.kts
index 2a8647a40..1c85d76a4 100644
--- a/game/client/build.gradle.kts
+++ b/game/client/build.gradle.kts
@@ -292,34 +292,34 @@ fun generateMcPatchesFile(inputDir: File, outputDir: File, sbom: File) {
     patchMC.writeText(gson.toJson(json))
 }
 
-val testShaders by tasks.registering {
-    outputs.upToDateWhen { false }
-
-    val shaderDir = project.layout.projectDirectory.dir("/src/main/resources/shaders/").asFile
-
-    shaderDir.walkTopDown().forEach { file ->
-        run {
-            if (file.isFile) {
-                val type = when(file.extension) {
-                    "fsh" -> "frag"
-                    "gsh" -> "geom"
-                    "vsh" -> "vert"
-                    else -> throw RuntimeException("Unknown shader type '${file.extension}' in shader '${file.canonicalPath}'!")
-                }
-                val command = "glslangValidator ${file.canonicalPath} -S $type"
-                val process = command.runCommand(shaderDir)
-                process.waitFor(60, TimeUnit.MINUTES)
-                if (process.exitValue() != 0) {
-                    System.err.println("Failed to compile shader file:///${file.canonicalPath.replace("\\", "/")}")
-                    System.out.print(process.inputStream.bufferedReader().readText())
-                    throw RuntimeException(process.errorStream.bufferedReader().readText())
-                } else{
-                    print(process.inputStream.bufferedReader().readText())
-                }
-            }
-        }
-    }
-}
+//val testShaders by tasks.registering {
+//    outputs.upToDateWhen { false }
+//
+//    val shaderDir = project.layout.projectDirectory.dir("/src/main/resources/shaders/").asFile
+//
+//    shaderDir.walkTopDown().forEach { file ->
+//        run {
+//            if (file.isFile) {
+//                val type = when(file.extension) {
+//                    "fsh" -> "frag"
+//                    "gsh" -> "geom"
+//                    "vsh" -> "vert"
+//                    else -> throw RuntimeException("Unknown shader type '${file.extension}' in shader '${file.canonicalPath}'!")
+//                }
+//                val command = "glslangValidator ${file.canonicalPath} -S $type"
+//                val process = command.runCommand(shaderDir)
+//                process.waitFor(60, TimeUnit.MINUTES)
+//                if (process.exitValue() != 0) {
+//                    System.err.println("Failed to compile shader file:///${file.canonicalPath.replace("\\", "/")}")
+//                    System.out.print(process.inputStream.bufferedReader().readText())
+//                    throw RuntimeException(process.errorStream.bufferedReader().readText())
+//                } else{
+//                    print(process.inputStream.bufferedReader().readText())
+//                }
+//            }
+//        }
+//    }
+//}
 
 tasks.cyclonedxBom {
     setIncludeConfigs(listOf("runtimeClasspath"))
diff --git a/game/client/src/main/java/net/minecraft/client/discord/RichPresenceHandlerThread.java b/game/client/src/main/java/net/minecraft/client/discord/RichPresenceHandlerThread.java
index 03eff517c..744f0ece3 100644
--- a/game/client/src/main/java/net/minecraft/client/discord/RichPresenceHandlerThread.java
+++ b/game/client/src/main/java/net/minecraft/client/discord/RichPresenceHandlerThread.java
@@ -54,7 +54,13 @@ public class RichPresenceHandlerThread extends Thread {
         biomeNamesMap.put(Biomes.OVERWORLD_GLACIER, "a glacier");
         biomeNamesMap.put(Biomes.OVERWORLD_TUNDRA, "a tundra");
         biomeNamesMap.put(Biomes.OVERWORLD_MEADOW, "a meadow");
-        biomeNamesMap.put(Biomes.NETHER_NETHER, "Hell itself");
+        biomeNamesMap.put(Biomes.NETHER_CRAG, "the crag");
+        biomeNamesMap.put(Biomes.NETHER_CRYSTAL_FOREST, "a crystal forest");
+        biomeNamesMap.put(Biomes.NETHER_CRYSTAL_FIELD, "a crystal field");
+        biomeNamesMap.put(Biomes.NETHER_SULFUR_POOLS, "the sulfur pools");
+        biomeNamesMap.put(Biomes.NETHER_VOLCANIC_ISLANDS, "a volcanic island");
+        biomeNamesMap.put(Biomes.NETHER_OLD_WORLD, "the crag (cold)");
+        biomeNamesMap.put(Biomes.NETHER_SHELF, "the shelf");
         biomeNamesMap.put(Biomes.DRIFT_DRIFT, "the skies");
         biomeNamesMap.put(Biomes.OVERWORLD_BIRCH_FOREST, "a birch forest");
         biomeNamesMap.put(Biomes.OVERWORLD_RETRO, "a world long forgotten");
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 0caf8c889..8940c6dfa 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
@@ -134,9 +134,16 @@ public class FogManager {
             this.fogBlue = out[2];
         }
         if (CameraUtil.isUnderLiquid(this.mc.activeCamera, world, Materials.WATER, partialTick)) {
-            this.fogRed = 0.02F;
-            this.fogGreen = 0.02F;
-            this.fogBlue = 0.2F;
+            if(this.mc.currentWorld != null && this.mc.currentWorld.dimension == Dimension.NETHER) {
+                this.fogRed = 0.2F;
+                this.fogGreen = 0.02F;
+                this.fogBlue = 0.02F;
+            } else {
+                this.fogRed = 0.02F;
+                this.fogGreen = 0.02F;
+                this.fogBlue = 0.2F;
+            }
+
             if (GameSettings.BIOME_WATER.value) {
                 TilePos cameraTile = this.mc.activeCamera.getTilePos();
                 double temp = world.getBlockTemperature(cameraTile);
diff --git a/game/client/src/main/java/net/minecraft/client/render/block/color/BlockColorCrystal.java b/game/client/src/main/java/net/minecraft/client/render/block/color/BlockColorCrystal.java
deleted file mode 100644
index f77bac05a..000000000
--- a/game/client/src/main/java/net/minecraft/client/render/block/color/BlockColorCrystal.java
+++ /dev/null
@@ -1,60 +0,0 @@
-package net.minecraft.client.render.block.color;
-
-import net.minecraft.client.render.colorizer.Colorizer;
-import net.minecraft.core.world.WorldSource;
-import net.minecraft.core.world.noise.ImprovedPerlinNoise;
-import net.minecraft.core.world.pos.TilePosc;
-import org.jetbrains.annotations.NotNull;
-
-import java.util.Random;
-
-public class BlockColorCrystal extends BlockColorCustom{
-
-    public BlockColorCrystal(Colorizer colorizer) {
-        super(colorizer);
-    }
-    protected static ImprovedPerlinNoise crystalPerlinNoise = new ImprovedPerlinNoise(new Random(0));
-
-    @Override
-    public int getFallbackColor(int meta, int tintIndex) {
-        return 0xFFc40e26;
-    }
-
-    @Override
-    public int getWorldColor(@NotNull WorldSource source, @NotNull TilePosc tilePos, int tintIndex) {
-
-        int color = 0xFFc40e26;
-//        float offsetR = (float) (crystalPerlinNoise.getValue(x / 10f, z / 10f)) * 0.5f;
-//
-//        float r = ((color & 0xFF0000) >> 16) / 255F;
-//        float g = ((color & 0x00FF00) >> 8) / 255F;
-//        float b = (color & 0x0000FF) / 255F;
-//
-//        float newR = r + offsetR;
-//        float newG = g + offsetR;
-//        float newB = b + offsetR;
-//
-//        if(newR > 1.0f)
-//            newR = 1.0f;
-//        if(newR < 0.0f)
-//            newR = 0.0f;
-//
-//        if(newG > 1.0f)
-//            newG = 1.0f;
-//        if(newG < 0.0f)
-//            newG = 0.0f;
-//
-//        if(newB > 1.0f)
-//            newB = 1.0f;
-//        if(newB < 0.0f)
-//            newB = 0.0f;
-//
-//        int finalR = ((int) (newR * 255) & 0xFF) << 16;
-//        int finalG = ((int) (newG * 255) & 0xFF) << 8;
-//        int finalB = ((int) (newB * 255) & 0xFF);
-//
-//        color = finalR | finalG | finalB;
-
-        return color;
-    }
-}
diff --git a/game/client/src/main/java/net/minecraft/client/render/block/color/BlockColorDispatcher.java b/game/client/src/main/java/net/minecraft/client/render/block/color/BlockColorDispatcher.java
index bf48ba4b2..70dab5980 100644
--- a/game/client/src/main/java/net/minecraft/client/render/block/color/BlockColorDispatcher.java
+++ b/game/client/src/main/java/net/minecraft/client/render/block/color/BlockColorDispatcher.java
@@ -1,6 +1,5 @@
 package net.minecraft.client.render.block.color;
 
-import net.minecraft.client.render.block.model.BlockModel;
 import net.minecraft.client.render.colorizer.Colorizers;
 import net.minecraft.client.util.dispatch.Dispatcher;
 import net.minecraft.core.block.Block;
@@ -49,9 +48,9 @@ public final class BlockColorDispatcher
         addDispatch(Blocks.WIRE_REDSTONE, new BlockColorWireRedstone());
         addDispatch(Blocks.TALLGRASS, new BlockColorCustom(Colorizers.grass));
         addDispatch(Blocks.TALLGRASS_FERN, new BlockColorCustom(Colorizers.grass));
-        addDispatch(Blocks.CRYSTAL, new BlockColorCrystal(Colorizers.crystal));
-        addDispatch(Blocks.CRYSTAL_STALK, new BlockColorCrystal(Colorizers.crystal));
-        addDispatch(Blocks.NETHERRACK_CRYSTALLINE, new BlockColorCrystal(Colorizers.crystal));
+        addDispatch(Blocks.RUBYGLASS, new BlockColorRubyglass(Colorizers.crystal));
+        addDispatch(Blocks.RUBYGLASS_PURE, new BlockColorRubyglass(Colorizers.crystal));
+        addDispatch(Blocks.RUBYGLASS_SPROUT, new BlockColorRubyglass(Colorizers.crystal));
     }
 
 
diff --git a/game/client/src/main/java/net/minecraft/client/render/block/color/BlockColorRubyglass.java b/game/client/src/main/java/net/minecraft/client/render/block/color/BlockColorRubyglass.java
new file mode 100644
index 000000000..0d4b712e7
--- /dev/null
+++ b/game/client/src/main/java/net/minecraft/client/render/block/color/BlockColorRubyglass.java
@@ -0,0 +1,28 @@
+package net.minecraft.client.render.block.color;
+
+import net.minecraft.client.render.colorizer.Colorizer;
+import net.minecraft.core.world.WorldSource;
+import net.minecraft.core.world.noise.ImprovedPerlinNoise;
+import net.minecraft.core.world.pos.TilePosc;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Random;
+
+public class BlockColorRubyglass extends BlockColorCustom{
+
+    public BlockColorRubyglass(Colorizer colorizer) {
+        super(colorizer);
+    }
+
+    @Override
+    public int getFallbackColor(int meta, int tintIndex) {
+        return 0xFFC40E26;
+    }
+
+    @Override
+    public int getWorldColor(@NotNull WorldSource source, @NotNull TilePosc tilePos, int tintIndex) {
+
+        int color = 0xFFC40E26;
+        return color;
+    }
+}
diff --git a/game/client/src/main/java/net/minecraft/client/render/block/model/BlockModelCrossedSquaresRotatable.java b/game/client/src/main/java/net/minecraft/client/render/block/model/BlockModelCrossedSquaresRotatable.java
new file mode 100644
index 000000000..67b58fea4
--- /dev/null
+++ b/game/client/src/main/java/net/minecraft/client/render/block/model/BlockModelCrossedSquaresRotatable.java
@@ -0,0 +1,133 @@
+package net.minecraft.client.render.block.model;
+
+import net.minecraft.client.render.LightmapHelper;
+import net.minecraft.client.render.block.color.BlockColorDispatcher;
+import net.minecraft.client.render.tessellator.Tessellator;
+import net.minecraft.client.render.tessellator.TessellatorGeneral;
+import net.minecraft.client.render.tessellator.TessellatorTerrainImpl;
+import net.minecraft.client.render.texture.stitcher.IconCoordinate;
+import net.minecraft.core.block.*;
+import net.minecraft.core.util.helper.Direction;
+import net.minecraft.core.util.helper.MathHelper;
+import net.minecraft.core.util.helper.Side;
+import net.minecraft.core.util.phys.Vec3;
+import net.minecraft.core.world.WorldSource;
+import net.minecraft.core.world.pos.TilePosc;
+import org.jetbrains.annotations.NotNull;
+
+public class BlockModelCrossedSquaresRotatable<T extends BlockLogic> extends BlockModelCrossedSquares<T> {
+    public BlockModelCrossedSquaresRotatable(Block<T> block) {
+        super(block);
+    }
+
+    @Override
+    public boolean render(@NotNull TessellatorGeneral tessellator, @NotNull WorldSource worldSource, @NotNull TilePosc tilePos) {
+        float brightness = 1.0f;
+        tessellator.setLightmapCoord1i(block.getLightIndex(worldSource, tilePos));
+
+        int color = BlockColorDispatcher.getInstance().getDispatch(block).getWorldColor(worldSource, tilePos, 0);
+        float r = (float) (color >> 16 & 0xff) / 255F;
+        float g = (float) (color >> 8 & 0xff) / 255F;
+        float b = (float) (color & 0xff) / 255F;
+        tessellator.setColorOpaque3f(brightness * r, brightness * g, brightness * b);
+
+        int metadata = worldSource.getBlockData(tilePos);
+        Direction dir = BlockLogicRotatable.getDirectionFromMeta(metadata);
+
+        IconCoordinate texIndex = getBlockTextureFromSideAndMetadata(Side.BOTTOM, metadata);
+        if (renderBlocks.overrideBlockTexture != null) {
+            texIndex = renderBlocks.overrideBlockTexture;
+        }
+
+        final double minX = -0.45D;
+        final double maxX =  0.45D;
+        final double minY = -0.5D;
+        final double maxY =  0.5D;
+        final double minZ = -0.45D;
+        final double maxZ =  0.45D;
+
+        final double minU = texIndex.getIconUMin();
+        final double maxU = texIndex.getIconUMax();
+        final double minV = texIndex.getIconVMin();
+        final double maxV = texIndex.getIconVMax();
+
+        final boolean flipUVs = dir == Direction.WEST || dir == Direction.SOUTH || dir == Direction.DOWN;
+
+        Vec3[] vertices = new Vec3[8];
+
+        vertices[0] = Vec3.getTempVec3(minX, maxY, minZ);
+        vertices[1] = Vec3.getTempVec3(minX, minY, minZ);
+        vertices[2] = Vec3.getTempVec3(maxX, minY, maxZ);
+        vertices[3] = Vec3.getTempVec3(maxX, maxY, maxZ);
+        vertices[4] = Vec3.getTempVec3(minX, maxY, maxZ);
+        vertices[5] = Vec3.getTempVec3(minX, minY, maxZ);
+        vertices[6] = Vec3.getTempVec3(maxX, minY, minZ);
+        vertices[7] = Vec3.getTempVec3(maxX, maxY, minZ);
+
+
+        for (int vi = 0; vi < 8; vi++) {
+            switch (dir){
+                case NORTH:
+                    vertices[vi].rotateAroundX(-MathHelper.PI / 2f);
+                    break;
+                case SOUTH:
+                    vertices[vi].rotateAroundX(MathHelper.PI / 2f);
+                    break;
+                case EAST:
+                    vertices[vi].rotateAroundZ(-MathHelper.PI / 2f);
+                    break;
+                case WEST:
+                    vertices[vi].rotateAroundZ(MathHelper.PI / 2f);
+                    break;
+                case DOWN:
+                    vertices[vi].rotateAroundX(MathHelper.PI);
+                    break;
+            }
+
+            vertices[vi].rotateAroundY(MathHelper.PI);
+            vertices[vi].x += (float) tilePos.x() + 0.5D;
+            vertices[vi].y += (float) tilePos.y() + 0.5D;
+            vertices[vi].z += (float) tilePos.z() + 0.5D;
+        }
+
+        for (int side = 0; side < 4; side++) {
+
+            Vec3 v1;
+            Vec3 v2;
+            Vec3 v3;
+            Vec3 v4;
+            if (side == 0) {
+                v1 = vertices[0]; // minX, maxY, minZ
+                v2 = vertices[1]; // minX, minY, minZ
+                v3 = vertices[2]; // maxX, minY, maxZ
+                v4 = vertices[3]; // maxX, maxY, maxZ
+            } else if (side == 1) {
+                v1 = vertices[3]; // maxX, maxY, maxZ
+                v2 = vertices[2]; // maxX, minY, maxZ
+                v3 = vertices[1]; // minX, minY, minZ
+                v4 = vertices[0]; // minX, maxY, minZ
+            } else if (side == 2) {
+                v1 = vertices[4]; // minX, maxY, maxZ
+                v2 = vertices[5]; // minX, minY, maxZ
+                v3 = vertices[6]; // maxX, minY, minZ
+                v4 = vertices[7]; // maxX, maxY, minZ
+            } else {
+                v1 = vertices[7]; // maxX, maxY, minZ
+                v2 = vertices[6]; // maxX, minY, minZ
+                v3 = vertices[5]; // minX, minY, maxZ
+                v4 = vertices[4]; // minX, maxY, maxZ
+            }
+            tessellator.addVertexWithUV(v1.x, v1.y, v1.z, minU, minV);
+            tessellator.addVertexWithUV(v2.x, v2.y, v2.z, minU, maxV);
+            tessellator.addVertexWithUV(v3.x, v3.y, v3.z, maxU, maxV);
+            tessellator.addVertexWithUV(v4.x, v4.y, v4.z, maxU, minV);
+        }
+
+        return true;
+    }
+
+    @Override
+    public boolean shouldItemRender3d() {
+        return false;
+    }
+}
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 07f27b380..9b15b77c5 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
@@ -2,62 +2,7 @@ package net.minecraft.client.render.block.model;
 
 import com.mojang.logging.LogUtils;
 import net.minecraft.client.Minecraft;
-import net.minecraft.client.render.block.model.generic.BlockModelGeneric;
-import net.minecraft.client.render.block.model.generic.BlockModelGenericActivator;
-import net.minecraft.client.render.block.model.generic.BlockModelGenericAxis;
-import net.minecraft.client.render.block.model.generic.BlockModelGenericBasket;
-import net.minecraft.client.render.block.model.generic.BlockModelGenericBed;
-import net.minecraft.client.render.block.model.generic.BlockModelGenericBrazier;
-import net.minecraft.client.render.block.model.generic.BlockModelGenericButton;
-import net.minecraft.client.render.block.model.generic.BlockModelGenericButtonPainted;
-import net.minecraft.client.render.block.model.generic.BlockModelGenericCake;
-import net.minecraft.client.render.block.model.generic.BlockModelGenericChest;
-import net.minecraft.client.render.block.model.generic.BlockModelGenericChestPainted;
-import net.minecraft.client.render.block.model.generic.BlockModelGenericCropsPumpkin;
-import net.minecraft.client.render.block.model.generic.BlockModelGenericDoor;
-import net.minecraft.client.render.block.model.generic.BlockModelGenericDoorPainted;
-import net.minecraft.client.render.block.model.generic.BlockModelGenericFarmland;
-import net.minecraft.client.render.block.model.generic.BlockModelGenericFence;
-import net.minecraft.client.render.block.model.generic.BlockModelGenericFenceGate;
-import net.minecraft.client.render.block.model.generic.BlockModelGenericFenceGatePainted;
-import net.minecraft.client.render.block.model.generic.BlockModelGenericFencePainted;
-import net.minecraft.client.render.block.model.generic.BlockModelGenericFenceThin;
-import net.minecraft.client.render.block.model.generic.BlockModelGenericFlowerStackable;
-import net.minecraft.client.render.block.model.generic.BlockModelGenericFullyRotatable;
-import net.minecraft.client.render.block.model.generic.BlockModelGenericFurnace;
-import net.minecraft.client.render.block.model.generic.BlockModelGenericGrass;
-import net.minecraft.client.render.block.model.generic.BlockModelGenericLayer;
-import net.minecraft.client.render.block.model.generic.BlockModelGenericLever;
-import net.minecraft.client.render.block.model.generic.BlockModelGenericMatcher;
-import net.minecraft.client.render.block.model.generic.BlockModelGenericPebbles;
-import net.minecraft.client.render.block.model.generic.BlockModelGenericPie;
-import net.minecraft.client.render.block.model.generic.BlockModelGenericPiston;
-import net.minecraft.client.render.block.model.generic.BlockModelGenericPistonHead;
-import net.minecraft.client.render.block.model.generic.BlockModelGenericPortal;
-import net.minecraft.client.render.block.model.generic.BlockModelGenericPressurePlate;
-import net.minecraft.client.render.block.model.generic.BlockModelGenericPressurePlatePainted;
-import net.minecraft.client.render.block.model.generic.BlockModelGenericRope;
-import net.minecraft.client.render.block.model.generic.BlockModelGenericRotatable;
-import net.minecraft.client.render.block.model.generic.BlockModelGenericJar;
-import net.minecraft.client.render.block.model.generic.BlockModelGenericLamp;
-import net.minecraft.client.render.block.model.generic.BlockModelGenericLantern;
-import net.minecraft.client.render.block.model.generic.BlockModelGenericLeaves;
-import net.minecraft.client.render.block.model.generic.BlockModelGenericLeavesCacao;
-import net.minecraft.client.render.block.model.generic.BlockModelGenericLeavesCherryFlowering;
-import net.minecraft.client.render.block.model.generic.BlockModelGenericRepeater;
-import net.minecraft.client.render.block.model.generic.BlockModelGenericShifted;
-import net.minecraft.client.render.block.model.generic.BlockModelGenericPlanksPainted;
-import net.minecraft.client.render.block.model.generic.BlockModelGenericSlab;
-import net.minecraft.client.render.block.model.generic.BlockModelGenericSlabPainted;
-import net.minecraft.client.render.block.model.generic.BlockModelGenericSpikes;
-import net.minecraft.client.render.block.model.generic.BlockModelGenericStairs;
-import net.minecraft.client.render.block.model.generic.BlockModelGenericStairsPainted;
-import net.minecraft.client.render.block.model.generic.BlockModelGenericTorch;
-import net.minecraft.client.render.block.model.generic.BlockModelGenericTrapdoor;
-import net.minecraft.client.render.block.model.generic.BlockModelGenericTrapdoorPainted;
-import net.minecraft.client.render.block.model.generic.BlockModelGenericVeryRotatable;
-import net.minecraft.client.render.block.model.generic.BlockModelGenericCropsWheat;
-import net.minecraft.client.render.block.model.generic.BlockModelGenericWool;
+import net.minecraft.client.render.block.model.generic.*;
 import net.minecraft.client.render.texture.stitcher.IconCoordinate;
 import net.minecraft.client.util.dispatch.Dispatcher;
 import net.minecraft.core.block.Block;
@@ -277,10 +222,11 @@ public final class BlockModelDispatcher
 
         addDispatch(new BlockModelGeneric<>(Blocks.SAND, loadDataModel("minecraft:block/sand")));
         addDispatch(new BlockModelGeneric<>(Blocks.GRAVEL, loadDataModel("minecraft:block/gravel")));
-        addDispatch(new BlockModelGeneric<>(Blocks.SULFUR, loadDataModel("minecraft:block/sulfur")));
+        addDispatch(new BlockModelGeneric<>(Blocks.BRIMSAND, loadDataModel("minecraft:block/brimsand")));
 
         addDispatch(new BlockModelGeneric<>(Blocks.BEDROCK, loadDataModel("minecraft:block/bedrock")));
-        addDispatch(new BlockModelGenericFullyRotatable<>(Blocks.BONESHALE, loadDataModel("minecraft:block/bone_shale")));
+        addDispatch(new BlockModelGenericFullyRotatable<>(Blocks.BONESHALE,
+                loadDataModel("minecraft:block/bone_shale")));
 
         addDispatch(new BlockModelFluid<>(Blocks.FLUID_WATER_FLOWING)
             .onRenderLayer(1)
@@ -335,6 +281,9 @@ public final class BlockModelDispatcher
         addDispatch(new BlockModelGenericShifted<>(Blocks.SPINIFEX, loadDataModel("minecraft:block/spinifex")).render3D(false));
         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 BlockModelGenericFlowerStackable<>(Blocks.FLOWER_YELLOW, "minecraft:block/flower_yellow/").render3D(false));
         addDispatch(new BlockModelGenericFlowerStackable<>(Blocks.FLOWER_RED, "minecraft:block/flower_red/").render3D(false));
         addDispatch(new BlockModelGenericFlowerStackable<>(Blocks.FLOWER_PINK, "minecraft:block/flower_pink/").render3D(false));
@@ -387,9 +336,11 @@ public final class BlockModelDispatcher
         addDispatch(new BlockModelGeneric<>(Blocks.ORE_DIAMOND_GRANITE, loadDataModel("minecraft:block/ore/diamond/granite")));
         addDispatch(new BlockModelGeneric<>(Blocks.ORE_DIAMOND_PERMAFROST, loadDataModel("minecraft:block/ore/diamond/permafrost")));
 
-        addDispatch(new BlockModelGeneric<>(Blocks.CRYSTAL, loadDataModel("minecraft:block/nether_crystal/crystal")));
-        addDispatch(new BlockModelGeneric<>(Blocks.CRYSTAL_STALK, loadDataModel("minecraft:block/nether_crystal/stalk")).render3D(false));
-        addDispatch(new BlockModelGeneric<>(Blocks.NETHERRACK_CRYSTALLINE, loadDataModel("minecraft:block/nether_crystal/crystal_netherrack")));
+        addDispatch(new BlockModelGeneric<>(Blocks.RUBYGLASS, loadDataModel("minecraft:block/rubyglass/block")));
+        addDispatch(new BlockModelGeneric<>(Blocks.RUBYGLASS_PURE, loadDataModel("minecraft:block/rubyglass/block_pure")).forceCullSelf(true));
+        addDispatch(new BlockModelCrystalBud<>(Blocks.RUBYGLASS_SPROUT, loadDataModel("minecraft:block/rubyglass/sprout")));
+        addDispatch(new BlockModelGenericRubyglassBud<>(Blocks.RUBYGLASS_BUD));
+        addDispatch(new BlockModelGeneric<>(Blocks.RUBYGLASS_CIRCUIT, loadDataModel("minecraft:block/rubyglass/circuit")));
 
         addDispatch(new BlockModelGeneric<>(Blocks.ORE_NETHERCOAL_NETHERRACK, loadDataModel("minecraft:block/ore/nethercoal/netherrack")));
 
@@ -537,10 +488,9 @@ public final class BlockModelDispatcher
         addDispatch(new BlockModelGenericVeryRotatable<>(Blocks.PUMPKIN_CARVED_ACTIVE, loadDataModel("minecraft:block/pumpkin_carved_lit")));
         addDispatch(new BlockModelGenericVeryRotatable<>(Blocks.PUMPKIN_REDSTONE, loadDataModel("minecraft:block/pumpkin_redstone")));
 
-
         addDispatch(new BlockModelGeneric<>(Blocks.COBBLE_NETHERRACK, loadDataModel("minecraft:block/cobbled_netherrack/normal")));
-        addDispatch(new BlockModelGeneric<>(Blocks.COBBLE_NETHERRACK_IGNEOUS, loadDataModel("minecraft:block/cobbled_netherrack/igneous")));
-        addDispatch(new BlockModelGeneric<>(Blocks.COBBLE_NETHERRACK_MOSSY, loadDataModel("minecraft:block/cobbled_netherrack/mossy")));
+        addDispatch(new BlockModelGeneric<>(Blocks.MAGMA, loadDataModel("minecraft:block/magma")));
+        addDispatch(new BlockModelGeneric<>(Blocks.COBBLE_NETHERRACK_CRYSTALLINE, loadDataModel("minecraft:block/cobbled_netherrack/crystalline")));
         addDispatch(new BlockModelGeneric<>(Blocks.NETHERRACK, loadDataModel("minecraft:block/netherrack")));
         addDispatch(new BlockModelGeneric<>(Blocks.BRICK_NETHERRACK, loadDataModel("minecraft:block/brick_netherrack")));
         addDispatch(new BlockModelGenericSlab<>(Blocks.SLAB_NETHERRACK_POLISHED, loadDataModel("minecraft:block/slab/polished_netherrack/lower"), loadDataModel("minecraft:block/slab/polished_netherrack/upper"), loadDataModel("minecraft:block/slab/polished_netherrack/full")));
@@ -600,6 +550,11 @@ public final class BlockModelDispatcher
 
         addDispatch(new BlockModelGenericMatcher<>(Blocks.MATCHER, "minecraft:block/matcher/"));
         addDispatch(new BlockModelGenericMatcher<>(Blocks.MATCHER_ACTIVE, "minecraft:block/matcher/active"));
+
+        addDispatch(new BlockModelGeneric<>(Blocks.THERMAL_VENT, loadDataModel("minecraft:block/thermal_vent")));
+
+        addDispatch(new BlockModelGeneric<>(Blocks.GLOOMSTONE, loadDataModel("minecraft:block/gloomstone")));
+        addDispatch(new BlockModelGeneric<>(Blocks.COBBLE_GLOOMSTONE, loadDataModel("minecraft:block/cobbled_gloomstone")));
     }
 }
 
diff --git a/game/client/src/main/java/net/minecraft/client/render/block/model/generic/BlockModelCrystalBud.java b/game/client/src/main/java/net/minecraft/client/render/block/model/generic/BlockModelCrystalBud.java
new file mode 100644
index 000000000..a636a5e0f
--- /dev/null
+++ b/game/client/src/main/java/net/minecraft/client/render/block/model/generic/BlockModelCrystalBud.java
@@ -0,0 +1,50 @@
+package net.minecraft.client.render.block.model.generic;
+
+import net.minecraft.client.render.tessellator.TessellatorGeneral;
+import net.minecraft.client.render.texture.stitcher.IconCoordinate;
+import net.minecraft.core.block.Block;
+import net.minecraft.core.block.BlockLogic;
+import net.minecraft.core.block.BlockLogicRotatable;
+import net.minecraft.core.util.helper.Direction;
+import net.minecraft.core.world.WorldSource;
+import net.minecraft.core.world.pos.TilePosc;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.useless.dragonfly.data.block.BlockModelData;
+import org.useless.dragonfly.models.block.StaticBlockModel;
+
+public class BlockModelCrystalBud<T extends BlockLogic> extends BlockModelGeneric<T> {
+    public BlockModelCrystalBud(@NotNull Block<T> block, @NotNull StaticBlockModel staticModel) {
+        super(block, staticModel);
+    }
+
+    public BlockModelCrystalBud(@NotNull Block<T> block, @NotNull BlockModelData staticModel) {
+        super(block, staticModel);
+    }
+
+    @Override
+    public boolean renderAttached(@NotNull TessellatorGeneral tessellator, @NotNull WorldSource worldSource, @NotNull TilePosc tilePos, boolean cullFaces, @Nullable IconCoordinate overrideTexture) {
+        Direction direction = BlockLogicRotatable.getDirectionFromMeta(worldSource.getBlockData(tilePos));
+        switch (direction) {
+            case UP -> {
+                return getModel(worldSource, tilePos).renderAttached(this, tessellator, worldSource, tilePos, 0, 0, 0, 0, 0, 0, false, cullFaces, overrideTexture);
+            }
+            case DOWN -> {
+                return getModel(worldSource, tilePos).renderAttached(this, tessellator, worldSource, tilePos, 2, 0, 0, 0, 0, 0, false, cullFaces, overrideTexture);
+            }
+            case NORTH -> {
+                return getModel(worldSource, tilePos).renderAttached(this, tessellator, worldSource, tilePos, -1, 0, 0, 0, 0, 0, false, cullFaces, overrideTexture);
+            }
+            case WEST -> {
+                return getModel(worldSource, tilePos).renderAttached(this, tessellator, worldSource, tilePos, 0, 0, 1, 0, 0, 0, false, cullFaces, overrideTexture);
+            }
+            case EAST -> {
+                return getModel(worldSource, tilePos).renderAttached(this, tessellator, worldSource, tilePos, 0, 0, -1, 0, 0, 0, false, cullFaces, overrideTexture);
+            }
+            case SOUTH -> {
+                return getModel(worldSource, tilePos).renderAttached(this, tessellator, worldSource, tilePos, 1, 0, 0, 0, 0, 0, false, cullFaces, overrideTexture);
+            }
+        }
+        return getModel(worldSource, tilePos).renderAttached(this, tessellator, worldSource, tilePos, 0, 0, 0, 0, 0, 0, false, cullFaces, overrideTexture);
+    }
+}
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
new file mode 100644
index 000000000..e8ab93388
--- /dev/null
+++ b/game/client/src/main/java/net/minecraft/client/render/block/model/generic/BlockModelGenericRubyglassBud.java
@@ -0,0 +1,23 @@
+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/dynamictexture/DynamicTextureWaterFlow.java b/game/client/src/main/java/net/minecraft/client/render/dynamictexture/DynamicTextureWaterFlow.java
index 94af132cc..17c8a725f 100644
--- a/game/client/src/main/java/net/minecraft/client/render/dynamictexture/DynamicTextureWaterFlow.java
+++ b/game/client/src/main/java/net/minecraft/client/render/dynamictexture/DynamicTextureWaterFlow.java
@@ -3,6 +3,7 @@ package net.minecraft.client.render.dynamictexture;
 import net.minecraft.client.Minecraft;
 import net.minecraft.client.option.GameSettings;
 import net.minecraft.client.render.texture.stitcher.IconCoordinate;
+import net.minecraft.core.world.Dimension;
 import org.jetbrains.annotations.NotNull;
 
 public class DynamicTextureWaterFlow extends DynamicTexture {
@@ -77,11 +78,24 @@ public class DynamicTextureWaterFlow extends DynamicTexture {
 			}
 
 			float var13 = bright * bright;
-			int r = (int) (32.0F + var13 * 32.0F);
-			int g = (int) (50.0F + var13 * 64.0F);
-			int b = 255;
+
+			int colorMax = 255;
+			int colorMid = (int) (50.0F + var13 * 64.0F);
+			int colorMin = (int) (32.0F + var13 * 32.0F);
+
+			int r = colorMin;
+			int g = colorMid;
+			int b = colorMax;
 			int a = (int) (146.0F + var13 * 50.0F);
 
+			if (this.mc.currentWorld != null && this.mc.currentWorld.dimension == Dimension.NETHER) {
+				float brightness = 0.75F;
+				r = (int) (255 * brightness);
+				g = (int) (colorMin * brightness);
+				b = (int) (colorMin * brightness);
+				a = (int) (225.0F + var13 * 25.0F);
+			}
+
 			if (GameSettings.BIOME_WATER.value && !(this.mc.currentWorld != null && this.mc.currentWorld.isRetro())) {
 				r = g = b = (r + g + b) / 3;
 			}
diff --git a/game/client/src/main/java/net/minecraft/client/render/dynamictexture/DynamicTextureWaterStill.java b/game/client/src/main/java/net/minecraft/client/render/dynamictexture/DynamicTextureWaterStill.java
index 07c381ba6..c0298615b 100644
--- a/game/client/src/main/java/net/minecraft/client/render/dynamictexture/DynamicTextureWaterStill.java
+++ b/game/client/src/main/java/net/minecraft/client/render/dynamictexture/DynamicTextureWaterStill.java
@@ -3,6 +3,7 @@ package net.minecraft.client.render.dynamictexture;
 import net.minecraft.client.Minecraft;
 import net.minecraft.client.option.GameSettings;
 import net.minecraft.client.render.texture.stitcher.IconCoordinate;
+import net.minecraft.core.world.Dimension;
 
 public class DynamicTextureWaterStill extends DynamicTexture {
 	
@@ -76,11 +77,24 @@ public class DynamicTextureWaterStill extends DynamicTexture {
 			}
 
 			float var13 = bright * bright;
-			int r = (int) (32.0F + var13 * 32.0F);
-			int g = (int) (50.0F + var13 * 64.0F);
-			int b = 255;
+
+			int colorMax = 255;
+			int colorMid = (int) (50.0F + var13 * 64.0F);
+			int colorMin = (int) (32.0F + var13 * 32.0F);
+
+			int r = colorMin;
+			int g = colorMid;
+			int b = colorMax;
 			int a = (int) (146.0F + var13 * 50.0F);
 
+			if (this.mc.currentWorld != null && this.mc.currentWorld.dimension == Dimension.NETHER) {
+				float brightness = 0.75F;
+				r = (int) (255 * brightness);
+				g = (int) (colorMin * brightness);
+				b = (int) (colorMin * brightness);
+				a = (int) (225.0F + var13 * 25.0F);
+			}
+
 			if (GameSettings.BIOME_WATER.value && !(this.mc.currentWorld != null && this.mc.currentWorld.isRetro())) {
 				r = g = b = (r + g + b) / 3;
 			}
diff --git a/game/client/src/main/java/net/minecraft/client/render/entity/EntityRendererLightningBolt.java b/game/client/src/main/java/net/minecraft/client/render/entity/EntityRendererLightningBolt.java
index 96dc05415..1cd8084a9 100644
--- a/game/client/src/main/java/net/minecraft/client/render/entity/EntityRendererLightningBolt.java
+++ b/game/client/src/main/java/net/minecraft/client/render/entity/EntityRendererLightningBolt.java
@@ -17,81 +17,110 @@ public class EntityRendererLightningBolt extends EntityRenderer<EntityLightning>
     }
 
     @Override
-    public void render(@NotNull TessellatorGeneral tessellator, @NotNull EntityLightning lightning, double x, double y, double z,
-                       float yaw, float partialTick) {
+    public void render(
+        final @NotNull TessellatorGeneral tessellator,
+        final @NotNull EntityLightning lightning,
+        final double x, final double y, final double z,
+        final float yaw, final float partialTick
+    ) {
         GLRenderer.pushFrame();
+
         GLRenderer.setShader(Shaders.COLOR_WORLD);
         GLRenderer.globalSetLightEnabled(false);
+
         GLRenderer.enableState(State.BLEND);
         GLRenderer.setBlendFunc(BlendFactor.SRC_ALPHA, BlendFactor.ONE);
-        double[] ad = new double[8];
-        double[] ad1 = new double[8];
-        double d3 = 0.0D;
-        double d4 = 0.0D;
-        Random random = new Random(lightning.seed);
-        for (int j = 7; j >= 0; j--) {
-            ad[j] = d3;
-            ad1[j] = d4;
-            d3 += random.nextInt(11) - 5;
-            d4 += random.nextInt(11) - 5;
+
+        final double @NotNull [] xOffs = new double[8];
+        final double @NotNull [] zOffs = new double[8];
+        double xOff = 0.0D;
+        double zOff = 0.0D;
+        final @NotNull Random random = new Random(lightning.seed);
+
+        for (int h = 7; h >= 0; h--) {
+            xOffs[h] = xOff;
+            zOffs[h] = zOff;
+            xOff += random.nextInt(11) - 5;
+            zOff += random.nextInt(11) - 5;
         }
 
-        for (int i = 0; i < 4; i++) {
+        // r = radius
+        for (int r = 0; r < 4; r++) {
             random.setSeed(lightning.seed);
-            for (int k = 0; k < 3; k++) {
-                int l = 7;
-                int i1 = 0;
-                if (k > 0) {
-                    l = 7 - k;
-                }
-                if (k > 0) {
-                    i1 = l - 2;
+
+            // p = beam number
+            for (int p = 0; p < 3; p++) {
+                int hs = 7;
+                int ht = 0;
+                if (p > 0) {
+                    hs = 7 - p;
+                    ht = hs - 2;
                 }
-                double d5 = ad[l] - d3;
-                double d6 = ad1[l] - d4;
-                for (int j1 = l; j1 >= i1; j1--) {
-                    double d7 = d5;
-                    double d8 = d6;
-                    if (k == 0) {
-                        d5 += random.nextInt(11) - 5;
-                        d6 += random.nextInt(11) - 5;
+
+                double xo0 = xOffs[hs] - xOff;
+                double zo0 = zOffs[hs] - zOff;
+
+                // h = segment
+                for (int h = hs; h >= ht; h--) {
+                    final double xo1 = xo0;
+                    final double zo1 = zo0;
+                    if (p == 0) {
+                        xo0 += random.nextInt(11) - 5;
+                        zo0 += random.nextInt(11) - 5;
                     } else {
-                        d5 += random.nextInt(31) - 15;
-                        d6 += random.nextInt(31) - 15;
+                        xo0 += random.nextInt(31) - 15;
+                        zo0 += random.nextInt(31) - 15;
                     }
+
                     tessellator.startDrawing(DrawMode.TRIANGLE_STRIP);
-                    float f2 = 0.5F;
-                    tessellator.setColor4f(0.9F * f2, 0.9F * f2, f2, 0.3F);
-                    double d9 = 0.1D + (double) i * 0.2D;
-                    if (k == 0) {
-                        d9 *= (double) j1 * 0.1D + 1.0D;
+                    final float br = 0.5F;
+                    tessellator.setColor4f(lightning.r * br, lightning.g * br, lightning.b * br, 0.3F);
+
+                    double rr1 = 0.1D + (double) r * 0.2D;
+                    if (p == 0) {
+                        rr1 *= (double) h * 0.1D + 1.0D;
                     }
-                    double d10 = 0.1D + (double) i * 0.2D;
-                    if (k == 0) {
-                        d10 *= (double) (j1 - 1) * 0.1D + 1.0D;
+
+                    double rr2 = 0.1D + (double) r * 0.2D;
+                    if (p == 0) {
+                        rr2 *= (double) (h - 1) * 0.1D + 1.0D;
                     }
-                    for (int k1 = 0; k1 < 5; k1++) {
-                        double d11 = (x + 0.5D) - d9;
-                        double d12 = (z + 0.5D) - d9;
-                        if (k1 == 1 || k1 == 2) {
-                            d11 += d9 * 2D;
+
+                    for (int s = 0; s < 5; s++) {
+                        double xos1 = 0.5D - rr1;
+                        double zos1 = 0.5D - rr1;
+                        if (s == 1 || s == 2) {
+                            xos1 += rr1 * 2D;
                         }
-                        if (k1 == 2 || k1 == 3) {
-                            d12 += d9 * 2D;
+                        if (s == 2 || s == 3) {
+                            zos1 += rr1 * 2D;
                         }
-                        double d13 = (x + 0.5D) - d10;
-                        double d14 = (z + 0.5D) - d10;
-                        if (k1 == 1 || k1 == 2) {
-                            d13 += d10 * 2D;
+
+                        double xos2 = 0.5D - rr2;
+                        double zos2 = 0.5D - rr2;
+                        if (s == 1 || s == 2) {
+                            xos2 += rr2 * 2D;
+                        }
+                        if (s == 2 || s == 3) {
+                            zos2 += rr2 * 2D;
                         }
-                        if (k1 == 2 || k1 == 3) {
-                            d14 += d10 * 2D;
+
+                        if (lightning.flipped) {
+                            tessellator.addVertex(xos2 + xo0, ((h - hs) * 16), zos2 + zo0);
+                            tessellator.addVertex(xos1 + xo1, ((h - hs + 1) * 16), zos1 + zo1);
+                        } else {
+                            tessellator.addVertex(xos2 + xo0, (h * 16), zos2 + zo0);
+                            tessellator.addVertex(xos1 + xo1, ((h + 1) * 16), zos1 + zo1);
                         }
-                        tessellator.addVertex(d13 + d5, y + (double) (j1 * 16), d14 + d6);
-                        tessellator.addVertex(d11 + d7, y + (double) ((j1 + 1) * 16), d12 + d8);
                     }
 
+                    GLRenderer.pushFrame();
+                    GLRenderer.modelM4f().translate((float) x, (float) y, (float) z);
+                    GLRenderer.modelM4f().rotate(lightning.yaw, 0.0f, 1.0f, 0.0f);
+                    GLRenderer.modelM4f().rotate(lightning.pitch, 1.0f, 0.0f, 0.0f);
+                    GLRenderer.modelM4f().scale(lightning.scale, lightning.scale, lightning.scale);
                     tessellator.draw();
+                    GLRenderer.popFrame();
                 }
 
             }
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 f06b4670c..d134b8853 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
@@ -424,6 +424,8 @@ 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_CRYSTAL_CRACKED, "minecraft"));
+        addDispatch(new ItemModelStandard(Items.RUBYGLASS_CRYSTAL_PURE, "minecraft"));
 
         addDispatch(new ItemModelStandard(Items.DOUGH, "minecraft"));
 
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 69a295c2c..ec06a71e3 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
@@ -232,5 +232,12 @@ public final class ParticleDispatcher extends Dispatcher<String, ParticleEntry>
                 return new ParticleAsh(world, x, y, z, 0, 0, 0);
             }
         });
+
+        addDispatch("ventsmoke", new ParticleEntry() {
+            @Override
+            public Particle newParticle(@NotNull World world, double x, double y, double z, double motionX, double motionY, double motionZ, int data) {
+                return new ParticleVentSmoke(world, x, y, z, motionX, motionY, motionX, 2.5f, data);
+            }
+        });
     }
 }
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
new file mode 100644
index 000000000..05518fc88
--- /dev/null
+++ b/game/client/src/main/java/net/minecraft/client/render/particle/ParticleVentSmoke.java
@@ -0,0 +1,75 @@
+package net.minecraft.client.render.particle;
+
+import net.minecraft.client.render.tessellator.TessellatorParticle;
+import net.minecraft.client.render.texture.stitcher.TextureRegistry;
+import net.minecraft.core.world.World;
+import org.jetbrains.annotations.NotNull;
+
+public class ParticleVentSmoke extends Particle {
+    private final float oSize;
+
+    public ParticleVentSmoke(World world, double x, double y, double z, double xa, double ya, double za, float scale, int data) {
+        super(world, x, y, z, 0.0D, 0.0D, 0.0D);
+        this.xd *= 0.1D;
+        this.yd *= 0.1D;
+        this.zd *= 0.1D;
+        this.xd += xa;
+        this.yd += ya;
+        this.zd += za;
+        if (data == 1) {
+            this.rCol = this.gCol = this.bCol = (float) (1.0 - (Math.random() * 0.2));
+        } else {
+            this.rCol = this.gCol = this.bCol = (float) (Math.random() * 0.3D);
+        }
+        this.size *= 1F;
+        this.size *= scale;
+        this.oSize = this.size;
+        this.lifetime = (int) (20D / (Math.random() * 0.8D + 0.2D));
+        this.lifetime *= (int) scale;
+        this.noPhysics = false;
+    }
+
+    @Override
+    public void render(@NotNull TessellatorParticle t, float partialTick) {
+        float size = ((this.age + partialTick) / this.lifetime) * 32F;
+        if (size < 0.0F) {
+            size = 0.0F;
+        }
+        if (size > 1.0F) {
+            size = 1.0F;
+        }
+        this.size = this.oSize * size;
+        super.render(t, partialTick);
+    }
+
+    @Override
+    public void tick() {
+        this.xo = this.x;
+        this.yo = this.y;
+        this.zo = this.z;
+        if (this.age++ >= this.lifetime) {
+            this.remove();
+        }
+        int val = (14 - ((this.age * 14) / this.lifetime));
+        if(val > 7)
+            val = 7;
+        if (val >= 0) {
+            this.tex = TextureRegistry.getTexture("minecraft:particle/puff_" + val);
+        } else {
+            this.tex = null;
+        }
+        this.yd += 0.004D;
+        this.move(this.xd, this.yd, this.zd);
+        if (this.y == this.yo) {
+            this.xd *= 1.1D;
+            this.zd *= 1.1D;
+        }
+        this.xd *= 0.96D;
+        this.yd *= 0.96D;
+        this.zd *= 0.96D;
+        if (this.onGround) {
+            this.xd *= 0.7D;
+            this.zd *= 0.7D;
+        }
+    }
+}
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 904c206ca..d9e3c6d3a 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
@@ -7,7 +7,6 @@ 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.biome.provider.BiomeProviderNether;
 import net.minecraft.core.world.pos.TilePos;
 import net.minecraft.core.world.type.WorldType;
 import org.jetbrains.annotations.NotNull;
@@ -51,14 +50,15 @@ public class WorldTypeFXNether extends WorldTypeFX {
         return new Vector3f(r, g, b);
     }
 
+
     @Override
     public void playerAnimationTick(@NotNull World world, @NotNull Player player) {
         final int radius = 12;
 
         TilePos tilePos = new TilePos(player);
         Biome localBiome = world.getBlockBiome(tilePos);
-        if (localBiome == Biomes.NETHER_ASH_PLAINS || localBiome == Biomes.NETHER_BONEYARD) {
-            for (int i = 0; i < 5; i++) {
+        if (localBiome == Biomes.NETHER_OLD_WORLD) {
+            for (int i = 0; i < 3; i++) {
                 double rx = (player.x - radius) + (world.rand.nextDouble() * radius * 2);
                 double ry = (player.y - radius) + (world.rand.nextDouble() * radius * 2);
                 double rz = (player.z - radius) + (world.rand.nextDouble() * radius * 2);
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/bone_pile.json b/game/client/src/main/resources/assets/minecraft/models/block/bone_pile.json
new file mode 100644
index 000000000..e84e059bd
--- /dev/null
+++ b/game/client/src/main/resources/assets/minecraft/models/block/bone_pile.json
@@ -0,0 +1,6 @@
+{
+  "parent": "minecraft:block/cross",
+  "textures": {
+    "cross": "minecraft:block/bone_pile"
+  }
+}
\ No newline at end of file
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/cobbled_netherrack/mossy.json b/game/client/src/main/resources/assets/minecraft/models/block/brimsand.json
similarity index 52%
rename from game/client/src/main/resources/assets/minecraft/models/block/cobbled_netherrack/mossy.json
rename to game/client/src/main/resources/assets/minecraft/models/block/brimsand.json
index efb81e928..f518ad666 100644
--- a/game/client/src/main/resources/assets/minecraft/models/block/cobbled_netherrack/mossy.json
+++ b/game/client/src/main/resources/assets/minecraft/models/block/brimsand.json
@@ -1,6 +1,6 @@
 {
   "parent": "minecraft:block/cube_all",
   "textures": {
-    "all": "minecraft:block/cobbled_netherrack/mossy"
+    "all": "minecraft:block/brimsand"
   }
 }
\ No newline at end of file
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/cobbled_netherrack/igneous.json b/game/client/src/main/resources/assets/minecraft/models/block/cobbled_gloomstone.json
similarity index 52%
rename from game/client/src/main/resources/assets/minecraft/models/block/cobbled_netherrack/igneous.json
rename to game/client/src/main/resources/assets/minecraft/models/block/cobbled_gloomstone.json
index 7a79c2035..8c546490d 100644
--- a/game/client/src/main/resources/assets/minecraft/models/block/cobbled_netherrack/igneous.json
+++ b/game/client/src/main/resources/assets/minecraft/models/block/cobbled_gloomstone.json
@@ -1,6 +1,6 @@
 {
   "parent": "minecraft:block/cube_all",
   "textures": {
-    "all": "minecraft:block/cobbled_netherrack/igneous"
+    "all": "minecraft:block/cobbled_gloomstone"
   }
 }
\ No newline at end of file
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/cobbled_netherrack/crystalline.json b/game/client/src/main/resources/assets/minecraft/models/block/cobbled_netherrack/crystalline.json
new file mode 100644
index 000000000..dde0fc70b
--- /dev/null
+++ b/game/client/src/main/resources/assets/minecraft/models/block/cobbled_netherrack/crystalline.json
@@ -0,0 +1,6 @@
+{
+  "parent": "minecraft:block/cube_all",
+  "textures": {
+    "all": "minecraft:block/cobbled_netherrack/crystalline"
+  }
+}
\ No newline at end of file
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/gloomstone.json b/game/client/src/main/resources/assets/minecraft/models/block/gloomstone.json
new file mode 100644
index 000000000..c383871c2
--- /dev/null
+++ b/game/client/src/main/resources/assets/minecraft/models/block/gloomstone.json
@@ -0,0 +1,6 @@
+{
+  "parent": "minecraft:block/cube_all",
+  "textures": {
+    "all": "minecraft:block/gloomstone"
+  }
+}
\ No newline at end of file
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/magma.json b/game/client/src/main/resources/assets/minecraft/models/block/magma.json
new file mode 100644
index 000000000..b9678ef15
--- /dev/null
+++ b/game/client/src/main/resources/assets/minecraft/models/block/magma.json
@@ -0,0 +1,6 @@
+{
+  "parent": "minecraft:block/cube_all",
+  "textures": {
+    "all": "minecraft:block/magma"
+  }
+}
\ No newline at end of file
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/nether_crystal/stalk.json b/game/client/src/main/resources/assets/minecraft/models/block/nether_crystal/stalk.json
deleted file mode 100644
index 7da83b822..000000000
--- a/game/client/src/main/resources/assets/minecraft/models/block/nether_crystal/stalk.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
-  "parent": "minecraft:block/tinted_cross",
-  "textures": {
-    "cross": "minecraft:block/nether_crystal/stalk"
-  }
-}
\ No newline at end of file
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/nether_crystal/crystal.json b/game/client/src/main/resources/assets/minecraft/models/block/rubyglass/block.json
similarity index 56%
rename from game/client/src/main/resources/assets/minecraft/models/block/nether_crystal/crystal.json
rename to game/client/src/main/resources/assets/minecraft/models/block/rubyglass/block.json
index f54a7d0c0..8347c9b0c 100644
--- a/game/client/src/main/resources/assets/minecraft/models/block/nether_crystal/crystal.json
+++ b/game/client/src/main/resources/assets/minecraft/models/block/rubyglass/block.json
@@ -1,6 +1,6 @@
 {
   "parent": "minecraft:block/tinted_cube_all",
   "textures": {
-    "all": "minecraft:block/nether_crystal/crystal"
+    "all": "minecraft:block/rubyglass/block"
   }
 }
\ No newline at end of file
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/rubyglass/block_pure.json b/game/client/src/main/resources/assets/minecraft/models/block/rubyglass/block_pure.json
new file mode 100644
index 000000000..09878b374
--- /dev/null
+++ b/game/client/src/main/resources/assets/minecraft/models/block/rubyglass/block_pure.json
@@ -0,0 +1,7 @@
+{
+  "parent": "minecraft:block/tinted_cube_all",
+  "renderlayer": 1,
+  "textures": {
+    "all": "minecraft:block/rubyglass/block_pure"
+  }
+}
\ No newline at end of file
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/rubyglass/circuit.json b/game/client/src/main/resources/assets/minecraft/models/block/rubyglass/circuit.json
new file mode 100644
index 000000000..84ef01a62
--- /dev/null
+++ b/game/client/src/main/resources/assets/minecraft/models/block/rubyglass/circuit.json
@@ -0,0 +1,6 @@
+{
+  "parent": "minecraft:block/cube_all",
+  "textures": {
+    "all": "minecraft:block/rubyglass/circuit"
+  }
+}
\ No newline at end of file
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/nether_crystal/crystal_netherrack.json b/game/client/src/main/resources/assets/minecraft/models/block/rubyglass/crystal_netherrack.json
similarity index 100%
rename from game/client/src/main/resources/assets/minecraft/models/block/nether_crystal/crystal_netherrack.json
rename to game/client/src/main/resources/assets/minecraft/models/block/rubyglass/crystal_netherrack.json
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/rubyglass/sprout.json
new file mode 100644
index 000000000..d976ff3d8
--- /dev/null
+++ b/game/client/src/main/resources/assets/minecraft/models/block/rubyglass/sprout.json
@@ -0,0 +1,6 @@
+{
+  "parent": "minecraft:block/cross",
+  "textures": {
+    "cross": "minecraft:block/rubyglass/sprout"
+  }
+}
\ No newline at end of file
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/rubyglass_bud/stage0.json
new file mode 100644
index 000000000..a0f63fe67
--- /dev/null
+++ b/game/client/src/main/resources/assets/minecraft/models/block/rubyglass_bud/stage0.json
@@ -0,0 +1,6 @@
+{
+  "parent": "minecraft:block/cross",
+  "textures": {
+    "cross": "minecraft:block/rubyglass_bud/stage0"
+  }
+}
\ 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/rubyglass_bud/stage1.json
new file mode 100644
index 000000000..df2924802
--- /dev/null
+++ b/game/client/src/main/resources/assets/minecraft/models/block/rubyglass_bud/stage1.json
@@ -0,0 +1,6 @@
+{
+  "parent": "minecraft:block/cross",
+  "textures": {
+    "cross": "minecraft:block/rubyglass_bud/stage1"
+  }
+}
\ 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_bud/stage2.json
new file mode 100644
index 000000000..58ea9f65c
--- /dev/null
+++ b/game/client/src/main/resources/assets/minecraft/models/block/rubyglass_bud/stage2.json
@@ -0,0 +1,6 @@
+{
+  "parent": "minecraft:block/cross",
+  "textures": {
+    "cross": "minecraft:block/rubyglass_bud/stage2"
+  }
+}
\ 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
new file mode 100644
index 000000000..2904f9da3
--- /dev/null
+++ b/game/client/src/main/resources/assets/minecraft/models/block/rubyglass_bud/stage3.json
@@ -0,0 +1,6 @@
+{
+  "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/soul_catcher.json b/game/client/src/main/resources/assets/minecraft/models/block/soul_catcher.json
new file mode 100644
index 000000000..b2cc4640d
--- /dev/null
+++ b/game/client/src/main/resources/assets/minecraft/models/block/soul_catcher.json
@@ -0,0 +1,6 @@
+{
+  "parent": "minecraft:block/cross",
+  "textures": {
+    "cross": "minecraft:block/soul_catcher"
+  }
+}
\ No newline at end of file
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/thermal_vent.json b/game/client/src/main/resources/assets/minecraft/models/block/thermal_vent.json
new file mode 100644
index 000000000..01d5c93f5
--- /dev/null
+++ b/game/client/src/main/resources/assets/minecraft/models/block/thermal_vent.json
@@ -0,0 +1,8 @@
+{
+  "parent": "minecraft:block/cube_bottom_top",
+  "textures": {
+    "top": "minecraft:block/thermal_vent/top",
+    "bottom": "minecraft:block/thermal_vent/bottom",
+    "side": "minecraft:block/thermal_vent/side"
+  }
+}
\ No newline at end of file
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/basalt.png b/game/client/src/main/resources/assets/minecraft/textures/block/basalt.png
index 5ba015ce2..ffc34d179 100644
Binary files a/game/client/src/main/resources/assets/minecraft/textures/block/basalt.png and b/game/client/src/main/resources/assets/minecraft/textures/block/basalt.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 42c4727bc..f0ca0bf7a 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/bone_pile.png b/game/client/src/main/resources/assets/minecraft/textures/block/bone_pile.png
new file mode 100644
index 000000000..43732fdf8
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/block/bone_pile.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/brimsand.png b/game/client/src/main/resources/assets/minecraft/textures/block/brimsand.png
new file mode 100644
index 000000000..eaa56b101
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/block/brimsand.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 7c581deea..7b815594f 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_gloomstone.png b/game/client/src/main/resources/assets/minecraft/textures/block/cobbled_gloomstone.png
new file mode 100644
index 000000000..5cb75f869
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/block/cobbled_gloomstone.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/cobbled_netherrack/mossy.png b/game/client/src/main/resources/assets/minecraft/textures/block/cobbled_netherrack/crystalline.png
similarity index 100%
rename from game/client/src/main/resources/assets/minecraft/textures/block/cobbled_netherrack/mossy.png
rename to game/client/src/main/resources/assets/minecraft/textures/block/cobbled_netherrack/crystalline.png
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/cobbled_netherrack/igneous.emiss.png b/game/client/src/main/resources/assets/minecraft/textures/block/cobbled_netherrack/igneous.emiss.png
deleted file mode 100644
index cae60c73b..000000000
Binary files a/game/client/src/main/resources/assets/minecraft/textures/block/cobbled_netherrack/igneous.emiss.png and /dev/null differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/cobbled_netherrack/igneous.png b/game/client/src/main/resources/assets/minecraft/textures/block/cobbled_netherrack/igneous.png
deleted file mode 100644
index c6abcb44b..000000000
Binary files a/game/client/src/main/resources/assets/minecraft/textures/block/cobbled_netherrack/igneous.png and /dev/null differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/gloomstone.png b/game/client/src/main/resources/assets/minecraft/textures/block/gloomstone.png
new file mode 100644
index 000000000..b2086c1d2
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/block/gloomstone.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
new file mode 100644
index 000000000..d6e363303
Binary files /dev/null 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
new file mode 100644
index 000000000..642956f96
Binary files /dev/null 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/nether_crystal/crystal.emiss.png b/game/client/src/main/resources/assets/minecraft/textures/block/nether_crystal/crystal.emiss.png
deleted file mode 100644
index c382195c5..000000000
Binary files a/game/client/src/main/resources/assets/minecraft/textures/block/nether_crystal/crystal.emiss.png and /dev/null differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/nether_crystal/crystal.png b/game/client/src/main/resources/assets/minecraft/textures/block/nether_crystal/crystal.png
deleted file mode 100644
index c4c4aaae5..000000000
Binary files a/game/client/src/main/resources/assets/minecraft/textures/block/nether_crystal/crystal.png and /dev/null differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/nether_crystal/crystal_netherrack.png b/game/client/src/main/resources/assets/minecraft/textures/block/nether_crystal/crystal_netherrack.png
deleted file mode 100644
index 3a3cd1ba2..000000000
Binary files a/game/client/src/main/resources/assets/minecraft/textures/block/nether_crystal/crystal_netherrack.png and /dev/null differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/nether_crystal/overlay.emiss.png b/game/client/src/main/resources/assets/minecraft/textures/block/nether_crystal/overlay.emiss.png
deleted file mode 100644
index a967e867b..000000000
Binary files a/game/client/src/main/resources/assets/minecraft/textures/block/nether_crystal/overlay.emiss.png and /dev/null differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/nether_crystal/overlay.png b/game/client/src/main/resources/assets/minecraft/textures/block/nether_crystal/overlay.png
deleted file mode 100644
index 20ca0a50f..000000000
Binary files a/game/client/src/main/resources/assets/minecraft/textures/block/nether_crystal/overlay.png and /dev/null differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/nether_crystal/stalk.png b/game/client/src/main/resources/assets/minecraft/textures/block/nether_crystal/stalk.png
deleted file mode 100644
index acdf73430..000000000
Binary files a/game/client/src/main/resources/assets/minecraft/textures/block/nether_crystal/stalk.png and /dev/null differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/nether_crystal/top.png b/game/client/src/main/resources/assets/minecraft/textures/block/nether_crystal/top.png
deleted file mode 100644
index 0ce796538..000000000
Binary files a/game/client/src/main/resources/assets/minecraft/textures/block/nether_crystal/top.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/ore/ignium/basalt.png
similarity index 74%
rename from game/client/src/main/resources/assets/minecraft/textures/block/sulfur.png
rename to game/client/src/main/resources/assets/minecraft/textures/block/ore/ignium/basalt.png
index 439247795..083177f04 100644
Binary files a/game/client/src/main/resources/assets/minecraft/textures/block/sulfur.png and b/game/client/src/main/resources/assets/minecraft/textures/block/ore/ignium/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
new file mode 100644
index 000000000..bd13a0e2f
Binary files /dev/null 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..394983571
--- /dev/null
+++ b/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/block.emiss.png.mcmeta
@@ -0,0 +1,19 @@
+{
+    "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.png b/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/block.png
new file mode 100644
index 000000000..bdd770de5
Binary files /dev/null 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_pure.emiss.png b/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/block_pure.emiss.png
new file mode 100644
index 000000000..bd13a0e2f
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/block_pure.emiss.png 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
new file mode 100644
index 000000000..394983571
--- /dev/null
+++ b/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/block_pure.emiss.png.mcmeta
@@ -0,0 +1,19 @@
+{
+    "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
new file mode 100644
index 000000000..b2bffdea0
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/block_pure.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/nether_crystal/stalk.emiss.png b/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/circuit.emiss.png
similarity index 66%
rename from game/client/src/main/resources/assets/minecraft/textures/block/nether_crystal/stalk.emiss.png
rename to game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/circuit.emiss.png
index 6960c5c15..4e1cbce88 100644
Binary files a/game/client/src/main/resources/assets/minecraft/textures/block/nether_crystal/stalk.emiss.png and b/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/circuit.emiss.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/circuit.png b/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/circuit.png
new file mode 100644
index 000000000..2e0638446
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/circuit.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/growth.emiss.png b/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/growth.emiss.png
new file mode 100644
index 000000000..b0c887cea
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/growth.emiss.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/growth.png b/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/growth.png
new file mode 100644
index 000000000..84818536a
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/growth.png 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
new file mode 100644
index 000000000..7b76ce5ec
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass/sprout.png 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
new file mode 100644
index 000000000..ea878ac2a
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass_bud/stage0.png 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
new file mode 100644
index 000000000..729a7f644
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass_bud/stage1.png 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
new file mode 100644
index 000000000..a0a3ef8ef
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass_bud/stage2.png 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
new file mode 100644
index 000000000..e3b7520dc
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/block/rubyglass_bud/stage3.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/soul_catcher.png b/game/client/src/main/resources/assets/minecraft/textures/block/soul_catcher.png
new file mode 100644
index 000000000..1a3fdc7dc
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/block/soul_catcher.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/thermal_vent/bottom.png b/game/client/src/main/resources/assets/minecraft/textures/block/thermal_vent/bottom.png
new file mode 100644
index 000000000..a3fe2298a
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/block/thermal_vent/bottom.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/thermal_vent/side.png b/game/client/src/main/resources/assets/minecraft/textures/block/thermal_vent/side.png
new file mode 100644
index 000000000..a3fe2298a
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/block/thermal_vent/side.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/thermal_vent/top.png b/game/client/src/main/resources/assets/minecraft/textures/block/thermal_vent/top.png
new file mode 100644
index 000000000..c20680a00
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/block/thermal_vent/top.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/colormap/fog/nether.png b/game/client/src/main/resources/assets/minecraft/textures/colormap/fog/nether.png
new file mode 100644
index 000000000..0a4fd1533
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/colormap/fog/nether.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/item/ingot_ignium.png b/game/client/src/main/resources/assets/minecraft/textures/item/ingot_ignium.png
new file mode 100644
index 000000000..ec1c04afa
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/item/ingot_ignium.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/item/ore_raw_ignium.png b/game/client/src/main/resources/assets/minecraft/textures/item/ore_raw_ignium.png
new file mode 100644
index 000000000..30fef8614
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/item/ore_raw_ignium.png differ
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
new file mode 100644
index 000000000..2e320ce07
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/item/rubyglass_crystal_cracked.png 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
new file mode 100644
index 000000000..3670407e6
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/item/rubyglass_crystal_pure.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/item/shard_rubyglass.png b/game/client/src/main/resources/assets/minecraft/textures/item/shard_rubyglass.png
new file mode 100644
index 000000000..10248c4c6
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/item/shard_rubyglass.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
new file mode 100644
index 000000000..073de48ea
Binary files /dev/null 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/puff_large_0.png b/game/client/src/main/resources/assets/minecraft/textures/particle/puff_large_0.png
new file mode 100644
index 000000000..75b9fd79d
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/particle/puff_large_0.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/particle/puff_large_1.png b/game/client/src/main/resources/assets/minecraft/textures/particle/puff_large_1.png
new file mode 100644
index 000000000..0e87d6336
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/particle/puff_large_1.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/particle/puff_large_2.png b/game/client/src/main/resources/assets/minecraft/textures/particle/puff_large_2.png
new file mode 100644
index 000000000..dc830b207
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/particle/puff_large_2.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/particle/puff_large_3.png b/game/client/src/main/resources/assets/minecraft/textures/particle/puff_large_3.png
new file mode 100644
index 000000000..f93b11d8e
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/particle/puff_large_3.png differ
diff --git a/game/client/src/main/resources/credits.txt b/game/client/src/main/resources/credits.txt
index 9caf055fd..b75d92c95 100644
--- a/game/client/src/main/resources/credits.txt
+++ b/game/client/src/main/resources/credits.txt
@@ -42,6 +42,7 @@ Zaraz7
 ph
 Rikai
 Celeste
+gregwoods
 
 !Thank you for playing
 !Better than Adventure!
\ No newline at end of file
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
new file mode 100644
index 000000000..00bc1c4fc
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicBonepile.java
@@ -0,0 +1,44 @@
+package net.minecraft.core.block;
+
+import net.minecraft.core.block.entity.TileEntity;
+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 BlockLogicBonepile
+    extends BlockLogicFlower {
+
+    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);
+    }
+
+    @Override
+    public boolean canStay(final @NotNull World world, final @NotNull TilePosc tilePos) {
+        return this.mayPlaceOn(world.getBlockType(tilePos.down(new TilePos())));
+    }
+
+    @Override
+    public boolean mayPlaceOn(final @NotNull Block<?> block) {
+        return block.isSolidRender();
+    }
+
+    @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) };
+            }
+        };
+    }
+}
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicCrystal.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicCrystal.java
index d9d4c4e65..e69de29bb 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicCrystal.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicCrystal.java
@@ -1,11 +0,0 @@
-package net.minecraft.core.block;
-
-import net.minecraft.core.block.material.Materials;
-import org.jetbrains.annotations.NotNull;
-
-public class BlockLogicCrystal extends BlockLogic {
-    public BlockLogicCrystal(final @NotNull Block<?> block) {
-        super(block, Materials.STONE);
-    }
-
-}
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicNetherrackIgneous.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicMagma.java
similarity index 95%
rename from game/core/src/main/java/net/minecraft/core/block/BlockLogicNetherrackIgneous.java
rename to game/core/src/main/java/net/minecraft/core/block/BlockLogicMagma.java
index f5d14676d..33229309b 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicNetherrackIgneous.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicMagma.java
@@ -16,8 +16,8 @@ import org.joml.primitives.AABBdc;
 
 import java.util.Random;
 
-public class BlockLogicNetherrackIgneous extends BlockLogic {
-    public BlockLogicNetherrackIgneous(@NotNull Block<?> block) {
+public class BlockLogicMagma extends BlockLogic {
+    public BlockLogicMagma(@NotNull Block<?> block) {
         super(block, Materials.NETHERRACK);
         block.setTicking(true);
     }
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicRubyglass.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicRubyglass.java
new file mode 100644
index 000000000..0f5b1f45c
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicRubyglass.java
@@ -0,0 +1,109 @@
+package net.minecraft.core.block;
+
+import net.minecraft.core.block.entity.TileEntity;
+import net.minecraft.core.block.material.Materials;
+import net.minecraft.core.entity.EntityLightning;
+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.MathHelper;
+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 java.util.Random;
+
+public class BlockLogicRubyglass extends BlockLogic
+{
+
+    public BlockLogicRubyglass(Block<?> block)
+    {
+        super(block, Materials.STONE);
+        block.setTicking(true);
+    }
+
+    @Override
+    public ItemStack[] getBreakResult(@NotNull World world, @NotNull EnumDropCause dropCause, int meta, TileEntity tileEntity)
+    {
+        switch (dropCause)
+        {
+            case PICK_BLOCK:
+            case SILK_TOUCH:
+                return new ItemStack[] { new ItemStack(this) };
+            default:
+                return new ItemStack[] { new ItemStack(Items.RUBYGLASS_CRYSTAL_CRACKED, world.rand.nextInt(3)) };
+        }
+    }
+
+    @Override
+    public float getAmbientOcclusionStrength(final @NotNull WorldSource source, final @NotNull TilePosc tilePos) {
+        return 0.0f;
+    }
+
+    @Override
+    public void updateTick(final @NotNull World world, final @NotNull TilePosc tilePos, final @NotNull Random rand, final boolean isRandomTick) {
+        if (world.isClientSide) {
+            return;
+        }
+
+        if (world.dimension != Dimension.NETHER) {
+            return;
+        }
+
+        if (rand.nextInt(10) == 0) {
+            final int dir = rand.nextInt(6);
+            final @NotNull Direction direction = Direction.getDirectionById(dir);
+
+            final @NotNull TilePos checkPos = tilePos.add(direction, new TilePos());
+            final @NotNull Block<?> checkBlock = world.getBlockType(checkPos);
+            if (checkBlock == Blocks.AIR) {
+                final float pitch, yaw;
+                switch (dir) {
+                    case 0 -> { // Down
+                        pitch = 0.0f;
+                        yaw = 0.0f;
+                    }
+                    case 1 -> { // Up
+                        pitch = MathHelper.PI;
+                        yaw = 0.0f;
+                    }
+                    case 2 -> { // North
+                        pitch = MathHelper.PI / 2.0f;
+                        yaw = 0.0f;
+                    }
+                    case 3 -> { // South
+                        pitch = -MathHelper.PI / 2.0f;
+                        yaw = 0.0f;
+                    }
+                    case 4 -> { // West
+                        pitch = MathHelper.PI / 2.0f;
+                        yaw = MathHelper.PI / 2.0f;
+                    }
+                    case 5 -> { // East
+                        pitch = MathHelper.PI / 2.0f;
+                        yaw = -MathHelper.PI / 2.0f;
+                    }
+                    default -> {
+                        return;
+                    }
+                }
+
+                final @NotNull EntityLightning lightning = new EntityLightning(world, checkPos.x, checkPos.y, checkPos.z);
+                lightning.r = 1.0f;
+                lightning.g = 0.3f;
+                lightning.b = 0.1f;
+                lightning.pitch = pitch;
+                lightning.yaw = yaw;
+                lightning.silent = true;
+                lightning.scale = 0.125f;
+                lightning.flipped = true;
+
+                world.addWeatherEffect(lightning);
+            }
+        }
+    }
+}
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
new file mode 100644
index 000000000..5535bbe5d
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicRubyglassCrop.java
@@ -0,0 +1,156 @@
+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.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 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.dimension == Dimension.NETHER) {
+            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_CRYSTAL_CRACKED) };
+        } else {
+            return new ItemStack[] { new ItemStack(Items.RUBYGLASS_CRYSTAL_PURE) };
+        }
+    }
+
+
+    public static int metaToGrowthStage(int meta) {
+        return meta & MASK_GROWTH_STAGE;
+    }
+
+}
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicRubyglassGrowth.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicRubyglassGrowth.java
new file mode 100644
index 000000000..6c0b4c963
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicRubyglassGrowth.java
@@ -0,0 +1,187 @@
+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.data.tag.Tag;
+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 BlockLogicRubyglassGrowth extends BlockLogic {
+    public BlockLogicRubyglassGrowth(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.getDirection()));
+        } 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.getDirection();
+        }
+        if(world.isBlockNormalCube(tilePos.east(temp)))
+        {
+            return Side.WEST.getDirection();
+        }
+        if(world.isBlockNormalCube(tilePos.south(temp)))
+        {
+            return Side.NORTH.getDirection();
+        }
+        if (world.isBlockNormalCube(tilePos.north(temp))){
+            return  Side.SOUTH.getDirection();
+        }
+        if (world.isBlockNormalCube(tilePos.down(temp))){
+            return  Side.TOP.getDirection();
+        }
+        if (world.isBlockNormalCube(tilePos.up(temp))){
+            return  Side.BOTTOM.getDirection();
+        }
+        return Side.TOP.getDirection();
+    }
+
+    @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.getId());
+    }
+
+    @Override
+    public ItemStack[] getBreakResult(@NotNull World world, @NotNull EnumDropCause dropCause, int meta, TileEntity tileEntity) {
+        return new ItemStack[] { new ItemStack(Items.RUBYGLASS_CRYSTAL_CRACKED) };
+    }
+
+    public static Direction metaToDirection(int meta) {
+        return Direction.getDirectionById(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
new file mode 100644
index 000000000..8d616a0f7
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicSoulcatcher.java
@@ -0,0 +1,45 @@
+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.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 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);
+    }
+
+    @Override
+    public void onEntityCollision(final @NotNull World world, final @NotNull TilePosc tilePos, final @NotNull Entity entity) {
+        entity.xd *= 0.4D;
+        entity.zd *= 0.4D;
+    }
+
+    @Override
+    public boolean canStay(final @NotNull World world, final @NotNull TilePosc tilePos) {
+        return this.mayPlaceOn(world.getBlockType(tilePos.down(new TilePos())));
+    }
+
+    @Override
+    public boolean mayPlaceOn(final @NotNull Block<?> block) {
+        return block.isSolidRender();
+    }
+
+    @Override
+    public @NotNull ItemStack @Nullable [] getBreakResult(final @NotNull World world, final @NotNull EnumDropCause dropCause, final int meta, final @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/BlockLogicSponge.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicSponge.java
index ab07439c0..4880f4825 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicSponge.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicSponge.java
@@ -64,7 +64,7 @@ public class BlockLogicSponge extends BlockLogic {
         for (int i = 0; i < Direction.directions.length; i++) {
             Direction dir = Direction.directions[i];
             Block<?> b = world.getBlockType(tilePos.add(dir, queryPos));
-            if (b == Blocks.COBBLE_NETHERRACK_IGNEOUS || Blocks.hasTag(b, BlockTags.IS_LAVA)) return true;
+            if (b == Blocks.MAGMA || Blocks.hasTag(b, BlockTags.IS_LAVA)) return true;
         }
         return false;
     }
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
new file mode 100644
index 000000000..2f1fcc15b
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicThermalVent.java
@@ -0,0 +1,41 @@
+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.world.World;
+import net.minecraft.core.world.pos.TilePos;
+import net.minecraft.core.world.pos.TilePosc;
+import org.jetbrains.annotations.NotNull;
+import org.joml.primitives.AABBd;
+
+import java.util.List;
+import java.util.Random;
+
+public class BlockLogicThermalVent
+    extends BlockLogic {
+
+    public BlockLogicThermalVent(final @NotNull Block<?> block) {
+        super(block, Materials.STONE);
+
+    }
+
+    @Override
+    public void animationTick(final @NotNull World world, final @NotNull TilePosc tilePos, final @NotNull Random rand) {
+
+        final double xPos = (double) tilePos.x() + 0.5D;
+        final double yPos = (double) tilePos.y() + 1.125D;
+        final double zPos = (double) tilePos.z() + 0.5D;
+
+        TilePos queryPos = new TilePos();
+        tilePos.up(queryPos);
+
+        if(world.isAirBlock(queryPos)){
+            world.spawnParticle("ventsmoke", xPos, yPos, zPos, 0.0D, 0.005D, 0.0D, 1, 32);
+        }
+
+
+    }
+
+
+}
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 a4d30ebe9..17940f66e 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
@@ -411,9 +411,9 @@ public final class Blocks {
     public static final @NotNull Block<?> GRAVEL = register("gravel", "minecraft:block/gravel", 251, BlockLogicGravel::new)
         .withSound(BlockSounds.GRAVEL).withHardness(0.6F)
         .withTags(BlockTags.MINEABLE_BY_SHOVEL);
-    public static final @NotNull Block<?> SULFUR = register("sulfur", "minecraft:block/sulfur", 252, (b) -> new BlockLogic(b, Materials.SAND))
+    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);
+        .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()
@@ -1067,13 +1067,13 @@ public final class Blocks {
 
     public static final @NotNull Block<?> COBBLE_NETHERRACK = register("cobble.netherrack", "minecraft:block/cobble_netherrack", 800, (b) -> new BlockLogicCobble(b, Materials.NETHERRACK, null))
         .withSound(BlockSounds.STONE).withHardness(0.4F)
-        .withTags(BlockTags.MINEABLE_BY_PICKAXE, BlockTags.CHAINLINK_FENCES_CONNECT, BlockTags.INFINITE_BURN, BlockTags.CAVES_CUT_THROUGH, BlockTags.NETHER_MOBS_SPAWN);
-    public static final @NotNull Block<?> COBBLE_NETHERRACK_IGNEOUS = register("cobble.netherrack.igneous", "minecraft:block/cobble_netherrack_igneous", 801, BlockLogicNetherrackIgneous::new)
+        .withTags(BlockTags.MINEABLE_BY_PICKAXE, BlockTags.INFINITE_BURN, BlockTags.CAVES_CUT_THROUGH, BlockTags.CHAINLINK_FENCES_CONNECT, BlockTags.NETHER_MOBS_SPAWN, BlockTags.NETHER_SURFACE_BLOCK, BlockTags.GROWS_RUBYGLASS);
+    public static final @NotNull Block<?> MAGMA = register("magma", "minecraft:block/magma", 801, BlockLogicMagma::new)
         .withSound(BlockSounds.STONE).withHardness(1.25F).withBlastResistance(7F).withLightEmission(10)
-        .withTags(BlockTags.MINEABLE_BY_PICKAXE, BlockTags.CHAINLINK_FENCES_CONNECT);
-    public static final @NotNull Block<?> COBBLE_NETHERRACK_MOSSY = register("cobble.netherrack.mossy", "minecraft:block/cobble_netherrack_mossy", 802, (b) -> new BlockLogicCobble(b, Materials.NETHERRACK, null))
+        .withTags(BlockTags.MINEABLE_BY_PICKAXE);
+    public static final Block<?> COBBLE_NETHERRACK_CRYSTALLINE = register("cobble.netherrack.crystalline", "minecraft:block/cobble_netherrack_mossy", 802, (b) -> new BlockLogicCobble(b, Materials.NETHERRACK, null))
         .withSound(BlockSounds.STONE).withHardness(0.4F)
-        .withTags(BlockTags.MINEABLE_BY_PICKAXE, BlockTags.CHAINLINK_FENCES_CONNECT, BlockTags.GROWS_FLOWERS, BlockTags.INFINITE_BURN);
+        .withTags(BlockTags.MINEABLE_BY_PICKAXE, BlockTags.CHAINLINK_FENCES_CONNECT, BlockTags.GROWS_FLOWERS, BlockTags.INFINITE_BURN, BlockTags.GROWS_RUBYGLASS);
     public static final @NotNull Block<?> NETHERRACK = register("netherrack", "minecraft:block/netherrack", 803, (b) -> new BlockLogicStone(b, Blocks.COBBLE_NETHERRACK, Materials.NETHERRACK))
         .withSound(BlockSounds.STONE).withHardness(0.32F)
         .withTags(BlockTags.MINEABLE_BY_PICKAXE, BlockTags.CHAINLINK_FENCES_CONNECT, BlockTags.INFINITE_BURN, BlockTags.CAVES_CUT_THROUGH, BlockTags.NETHER_MOBS_SPAWN);
@@ -1105,17 +1105,24 @@ 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.CHAINLINK_FENCES_CONNECT);
-    public static final @NotNull Block<?> CRYSTAL = register("crystal", "minecraft:block/crystal", 821, (b) -> new BlockLogicCrystal(b))
-        .withSound(BlockSounds.GLASS).withLightEmission(0.8F)
         .withTags(BlockTags.MINEABLE_BY_PICKAXE);
-    public static final @NotNull Block<?> CRYSTAL_STALK = register("crystal.stalk", "minecraft:block/crystal_stalk", 822, (b) -> new BlockLogicStalk(b))
+    public static final Block<?> RUBYGLASS = register("rubyglass", "minecraft:block/rubyglass", 821, (b) -> new BlockLogicRubyglass(b))
+        .withSound(BlockSounds.GLASS).withHardness(0.3F)
+        .withTags(BlockTags.MINEABLE_BY_PICKAXE);
+    public static final Block<?> RUBYGLASS_PURE = register("rubyglass.pure", "minecraft:block/rubyglass_pure", 822, (b) -> new BlockLogicTransparent(b, Materials.STONE))
+        .withSound(BlockSounds.GLASS).withHardness(0.3F)
+        .withTags(BlockTags.MINEABLE_BY_PICKAXE);
+    public static final Block<?> RUBYGLASS_SPROUT = register("rubyglass.growth", "minecraft:block/rubyglass_growth", 823, (b) -> new BlockLogicRubyglassGrowth(b))
         .withSound(BlockSounds.GLASS).withDisabledNeighborNotifyOnMetadataChange().withLightEmission(0.8F)
         .withTags(BlockTags.MINEABLE_BY_PICKAXE);
-    public static final @NotNull Block<?> NETHERRACK_CRYSTALLINE = register("netherrack.crystalline", "minecraft:block/netherrack_crystalline", 823, (b) -> new BlockLogic(b, Materials.STONE))
-        .withSound(BlockSounds.STONE).withDisabledNeighborNotifyOnMetadataChange()
+    public static final Block<?> RUBYGLASS_CIRCUIT = register("rubyglass.circuit", "minecraft:block/rubyglass_circuit", 824, (b) -> new BlockLogic(b, Materials.STONE))
+        .withSound(BlockSounds.STONE).withDisabledNeighborNotifyOnMetadataChange().withHardness(0.5F)
         .withTags(BlockTags.MINEABLE_BY_PICKAXE, BlockTags.INFINITE_BURN, BlockTags.CAVES_CUT_THROUGH, BlockTags.NETHER_MOBS_SPAWN);
 
+    public static final Block<?> RUBYGLASS_BUD = register("rubyglass.bud", "minecraft:block/rubyglass_bud", 825, (b) -> new BlockLogicRubyglassCrop(b))
+            .withSound(BlockSounds.GLASS).withDisabledNeighborNotifyOnMetadataChange().withLightEmission(0.8F)
+            .withTags(BlockTags.MINEABLE_BY_PICKAXE, BlockTags.NOT_IN_CREATIVE_MENU);
+
     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);
@@ -1298,6 +1305,24 @@ public final class Blocks {
         .withSound(BlockSounds.STONE).withHardness(1.5F)
         .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);
+
+    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.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)
+            .withSound(BlockSounds.GRAVEL).withHardness(1.0F).withOverrideColor(MaterialColor.sand)
+            .withTags(BlockTags.BROKEN_BY_FLUIDS, BlockTags.PLACE_OVERWRITES, BlockTags.SHEARS_DO_SILK_TOUCH, BlockTags.PLANTABLE_IN_JAR);
+
+    public static final Block<?> GLOOMSTONE = register("gloomstone", "minecraft:block/gloomstone", 1032, (b) -> new BlockLogicStone(b, Blocks.COBBLE_GLOOMSTONE, Materials.BASALT))
+            .withSound(BlockSounds.STONE).withHardness(1.5F).withBlastResistance(10F)
+            .withTags(BlockTags.CAVES_CUT_THROUGH, BlockTags.MINEABLE_BY_PICKAXE);
+    public static final Block<?> COBBLE_GLOOMSTONE = register("cobble.gloomstone", "minecraft:block/cobble_gloomstone", 1033, (b) -> new BlockLogicCobble(b, Materials.BASALT, () -> null))
+            .withSound(BlockSounds.STONE).withHardness(2.0F).withBlastResistance(10F)
+            .withTags(BlockTags.MINEABLE_BY_PICKAXE);
+
+
     public static final @NotNull Block<BlockLogic> MATCHER = register("matcher", "minecraft:block/matcher", 1040, (b) -> new BlockLogicMatcher(b, false))
         .withSound(BlockSounds.STONE).withHardness(3.5F).withDisabledNeighborNotifyOnMetadataChange()
         .withTags(BlockTags.MINEABLE_BY_PICKAXE);
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 bf4a80095..47d0f217d 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
@@ -40,19 +40,25 @@ public class FluidLava
     public boolean checkForHarden(final @NotNull BlockLogicFluid logicFluid, final @NotNull World world, final @NotNull TilePosc tilePos, final @NotNull Material encountered) {
         if (encountered == Materials.WATER) {
             final int data = world.getBlockData(tilePos) & 0x0F;
-            if (data == 0) {
-                if (world.dimension == Dimension.NETHER) {
-                    world.setBlockTypeNotify(tilePos, Blocks.COBBLE_NETHERRACK_IGNEOUS);
+
+            if (world.dimension == Dimension.NETHER) {
+                if (data == 0) {
+                    world.setBlockTypeNotify(tilePos, Blocks.MAGMA);
                 } else {
-                    world.setBlockTypeNotify(tilePos, Blocks.OBSIDIAN);
+                    world.setBlockTypeNotify(tilePos, Blocks.NETHERRACK);
                 }
-            } else if (data <= 2) {
-                world.setBlockTypeNotify(tilePos, Blocks.COBBLE_GRANITE);
-            } else if (data <= 4) {
-                world.setBlockTypeNotify(tilePos, Blocks.COBBLE_STONE);
             } else {
-                world.setBlockTypeNotify(tilePos, Blocks.COBBLE_BASALT);
+                if (data == 0) {
+                    world.setBlockTypeNotify(tilePos, Blocks.OBSIDIAN);
+                } else if (data <= 2) {
+                    world.setBlockTypeNotify(tilePos, Blocks.COBBLE_GRANITE);
+                } else if (data <= 4) {
+                    world.setBlockTypeNotify(tilePos, Blocks.COBBLE_STONE);
+                } else {
+                    world.setBlockTypeNotify(tilePos, Blocks.COBBLE_BASALT);
+                }
             }
+
             BlockLogicFluid.fizz(world, tilePos);
             return true;
         }
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 3ce94ffda..dc72e0456 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
@@ -46,9 +46,13 @@ public class FluidWater
         if (encountered == Materials.LAVA) {
             final int data = world.getBlockData(tilePos) & 0x0F;
             if (data == 0) {
-                world.setBlockTypeNotify(tilePos, Blocks.COBBLE_LIMESTONE);
-                BlockLogicFluid.fizz(world, tilePos);
+                if (world.dimension == Dimension.NETHER) {
+                    world.setBlockTypeNotify(tilePos, Blocks.NETHERRACK);
+                } else {
+                    world.setBlockTypeNotify(tilePos, Blocks.COBBLE_LIMESTONE);
+                }
             }
+            BlockLogicFluid.fizz(world, tilePos);
             return true;
         }
         return false;
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 9b7f2e1b7..7bcd0ea07 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
@@ -149,7 +149,7 @@ public abstract class BlockTags
      */
     public static final Tag<Block<?>> INFINITE_BURN = Tag.of("infinite_burn");
     /**
-     * Block allows fire to burn forever on it {@link BlockFire BlockFire}
+     * Block allows fire to burn forever on it {@link BlockLogicFire BlockFire}
      */
     public static final Tag<Block<?>> INFINITE_BURN_COLD = Tag.of("infinite_burn_cold");
     /**
@@ -185,6 +185,16 @@ public abstract class BlockTags
      */
     public static final Tag<Block<?>> OVERRIDE_FRICTION = Tag.of("override_friction");
 
+    /**
+     * Determines what blocks features like sulfur pools and thermal vents can replace on generation.
+     */
+    public static final Tag<Block<?>> NETHER_SURFACE_BLOCK = Tag.of("nether_surface_block");
+
+    /**
+     * Determines what blocks rubyglass buds can be grown on.
+     */
+    public static final Tag<Block<?>> GROWS_RUBYGLASS = Tag.of("grows_rubyglass");
+
     public static List<Tag<Block<?>>> TAG_LIST;
 
     static
diff --git a/game/core/src/main/java/net/minecraft/core/data/legacy/LegacyBiomes.java b/game/core/src/main/java/net/minecraft/core/data/legacy/LegacyBiomes.java
index b83ef34f1..31915e014 100644
--- a/game/core/src/main/java/net/minecraft/core/data/legacy/LegacyBiomes.java
+++ b/game/core/src/main/java/net/minecraft/core/data/legacy/LegacyBiomes.java
@@ -4,13 +4,9 @@ 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 it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
 import net.minecraft.core.world.biome.Biome;
 import net.minecraft.core.world.biome.Biomes;
 
-import java.util.HashMap;
-import java.util.Map;
-
 public abstract class LegacyBiomes
 {
     private static final Int2ObjectMap<Biome> idToBiomeMap = new Int2ObjectArrayMap<>();
@@ -48,7 +44,7 @@ public abstract class LegacyBiomes
         register(Biomes.OVERWORLD_GLACIER, 11);
         register(Biomes.OVERWORLD_TUNDRA, 12);
         register(Biomes.OVERWORLD_MEADOW, 13);
-        register(Biomes.NETHER_NETHER, 14);
+        register(Biomes.NETHER_CRAG, 14);
         register(Biomes.DRIFT_DRIFT, 15);
         register(Biomes.OVERWORLD_BIRCH_FOREST, 16);
         register(Biomes.OVERWORLD_RETRO, 17);
diff --git a/game/core/src/main/java/net/minecraft/core/entity/EntityLightning.java b/game/core/src/main/java/net/minecraft/core/entity/EntityLightning.java
index 3a87f4948..1733c2818 100644
--- a/game/core/src/main/java/net/minecraft/core/entity/EntityLightning.java
+++ b/game/core/src/main/java/net/minecraft/core/entity/EntityLightning.java
@@ -18,6 +18,15 @@ public class EntityLightning extends Entity {
     public long seed;
     private int flashes;
 
+    public float pitch = 0.0f;
+    public float yaw = 0.0f;
+    public float r = 0.9f;
+    public float g = 0.9f;
+    public float b = 1.0f;
+    public float scale = 1.0f;
+    public boolean silent = false;
+    public boolean flipped = false;
+
     public EntityLightning(World world) {
         this(world, 0, 0, 0);
     }
@@ -34,7 +43,7 @@ public class EntityLightning extends Entity {
     @Override
     public void tick() {
         super.tick();
-        if (this.life == 2) {
+        if (this.life == 2 && !this.silent) {
             this.world.playSoundEffect(null, SoundCategory.WEATHER_SOUNDS, this.x, this.y, this.z, "ambient.weather.thunder", 10000F, 0.8F + this.random.nextFloat() * 0.2F);
             this.world.playSoundEffect(null, SoundCategory.WEATHER_SOUNDS, this.x, this.y, this.z, "random.explode", 2.0F, 0.5F + this.random.nextFloat() * 0.2F);
             for (int i = 0; i < this.world.entities.size(); i++) {
@@ -52,7 +61,7 @@ public class EntityLightning extends Entity {
                 this.flashes--;
                 this.life = 1;
                 this.seed = this.random.nextLong();
-                if (this.world.areBlocksLoaded(MathHelper.floor(this.x), MathHelper.floor(this.y), MathHelper.floor(this.z), 10)) {
+                if (!this.silent && this.world.areBlocksLoaded(MathHelper.floor(this.x), MathHelper.floor(this.y), MathHelper.floor(this.z), 10)) {
                     int i = MathHelper.floor(this.x);
                     int j = MathHelper.floor(this.y);
                     int k = MathHelper.floor(this.z);
@@ -62,7 +71,7 @@ public class EntityLightning extends Entity {
                 }
             }
         }
-        if (this.life >= 0) {
+        if (!this.silent && this.life >= 0) {
             double radius = 3D;
             List<Entity> list = this.world.getEntitiesWithinAABBExcludingEntity(this, new AABBd(this.x - radius, this.y - radius, this.z - radius, this.x + radius, this.y + 6D + radius, this.z + radius));
             for (Entity entity : list) {
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 41e12751f..bcb6319a7 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
@@ -4,6 +4,7 @@ import com.b100.utils.StringUtils;
 import com.mojang.nbt.tags.CompoundTag;
 import net.minecraft.core.Global;
 import net.minecraft.core.block.material.Materials;
+import net.minecraft.core.entity.vehicle.EntityBoat;
 import net.minecraft.core.item.ItemFood;
 import net.minecraft.core.util.phys.HitResult;
 import net.minecraft.core.WeightedRandomLootObject;
@@ -22,6 +23,7 @@ import net.minecraft.core.util.helper.DamageType;
 import net.minecraft.core.util.helper.Direction;
 import net.minecraft.core.util.helper.MathHelper;
 import net.minecraft.core.util.helper.Side;
+import net.minecraft.core.world.Dimension;
 import net.minecraft.core.world.World;
 import net.minecraft.core.world.pos.TilePos;
 import org.jetbrains.annotations.NotNull;
@@ -880,6 +882,17 @@ public abstract class Mob extends Entity {
         return false;
     }
 
+    public boolean hurtByNetherWater() {
+        return true;
+    }
+
+    @Override
+    public void waterHurt() {
+        if (this.hurtByNetherWater()) {
+            super.waterHurt();
+        }
+    }
+
     public void onLivingUpdate() {
         if (this.newPosRotationIncrements > 0) {
             double d = this.x + (this.newPosX - this.x) / (double) this.newPosRotationIncrements;
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 4ddad70a8..d38f53965 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
@@ -438,7 +438,13 @@ public class MobFireflyCluster extends MobFlying
         },
             new float[]{0.67f, 1f, 0.99f}));
         public static FireflyColor RED = register(new FireflyColor(3, "fireflyRed", new Biome[]{
-            Biomes.NETHER_NETHER
+            Biomes.NETHER_CRAG,
+            Biomes.NETHER_SHELF,
+            Biomes.NETHER_SULFUR_POOLS,
+            Biomes.NETHER_VOLCANIC_ISLANDS,
+            Biomes.NETHER_CRYSTAL_FOREST,
+            Biomes.NETHER_CRYSTAL_FIELD,
+            Biomes.NETHER_OLD_WORLD
         },
             new float[]{1f, 0.25f, 0.25f}));
 
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 4488db962..89fdb342f 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
@@ -6,9 +6,11 @@ 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.item.Items;
 import net.minecraft.core.util.helper.LightIndexHelper;
 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;
@@ -209,6 +211,16 @@ public class MobGhast extends MobFlying
         }
     }
 
+    @Override
+    public boolean canBreatheUnderwater() {
+        return true;
+    }
+
+    @Override
+    public boolean hurtByNetherWater() {
+        return false;
+    }
+
     @Override
     public String getLivingSound() {
         return "mob.ghast.moan";
diff --git a/game/core/src/main/java/net/minecraft/core/entity/monster/MobZombiePig.java b/game/core/src/main/java/net/minecraft/core/entity/monster/MobZombiePig.java
index 297204471..80250b69e 100644
--- a/game/core/src/main/java/net/minecraft/core/entity/monster/MobZombiePig.java
+++ b/game/core/src/main/java/net/minecraft/core/entity/monster/MobZombiePig.java
@@ -163,4 +163,14 @@ public class MobZombiePig extends MobZombie {
 
         return drops;
     }
+
+    @Override
+    public boolean canBreatheUnderwater() {
+        return true;
+    }
+
+    @Override
+    public boolean hurtByNetherWater() {
+        return false;
+    }
 }
diff --git a/game/core/src/main/java/net/minecraft/core/item/ItemCrystalBud.java b/game/core/src/main/java/net/minecraft/core/item/ItemCrystalBud.java
new file mode 100644
index 000000000..f68293d96
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/item/ItemCrystalBud.java
@@ -0,0 +1,54 @@
+package net.minecraft.core.item;
+
+import net.minecraft.core.block.Block;
+import net.minecraft.core.block.BlockLogicFarmland;
+import net.minecraft.core.block.BlockLogicRotatable;
+import net.minecraft.core.block.Blocks;
+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.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;
+
+public class ItemCrystalBud extends Item {
+
+    public ItemCrystalBud(@NotNull String name, @NotNull String namespaceId, int id) {
+        super(name, namespaceId, id);
+    }
+
+    @Override
+    public boolean onUseOnBlock(@NotNull ItemStack selfStack, @NotNull World world, @Nullable Player player, @NotNull TilePosc blockPos, @NotNull Side side, double xHit, double yHit) {
+        if (!world.canPlaceInsideBlock(blockPos)) {
+            blockPos = blockPos.add(side.getDirection(), new TilePos());
+        }
+
+        TilePos queryPos = new TilePos();
+
+        if (world.getBlockType(blockPos.down(queryPos)).hasTag(BlockTags.GROWS_RUBYGLASS)) {
+            if (world.setBlockType(blockPos, Blocks.RUBYGLASS_BUD)) {
+                world.playBlockSoundEffect(player, blockPos.x() + 0.5F, blockPos.y() + 0.5F, blockPos.z() + 0.5F, Blocks.RUBYGLASS_BUD, EnumBlockSoundEffectType.PLACE);
+                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) {
+        blockPos = blockPos.add(direction, new TilePos());
+        Block<?> b = world.getBlockType(blockPos);
+        boolean canGrowRubyglass = world.getBlockType(blockPos).hasTag(BlockTags.GROWS_RUBYGLASS);
+        if (b == null || canGrowRubyglass || BlockTags.PLACE_OVERWRITES.appliesTo(b)) {
+            onUseOnBlock(selfStack, world, null, blockPos.add(0, canGrowRubyglass ? 1 : 0, 0, new TilePos()), direction.getSide(), 0.5, 0.5);
+        }
+    }
+}
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 3a1e03ea1..c01f9a317 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
@@ -205,6 +205,9 @@ public final class Items {
     public static Item SIGN_PAINTED;
     public static Item DOUGH;
     public static Item WAND_NBT;
+    public static Item RUBYGLASS_CRYSTAL_CRACKED;
+    public static Item RUBYGLASS_CRYSTAL_PURE;
+
 
     private static boolean hasInit = false;
     public static void init(){
@@ -430,5 +433,7 @@ 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_CRYSTAL_CRACKED = (new ItemCrystalBud("rubyglass.crystal.cracked", "minecraft:item/rubyglass_crystal_cracked", 16554));
+        RUBYGLASS_CRYSTAL_PURE = (new ItemCrystalBud("rubyglass.crystal.pure", "minecraft:item/rubyglass_crystal_pure", 16555));
     }
 }
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 e42bbc5d1..81198680a 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
@@ -33,12 +33,14 @@ public class Biomes
     public static final Biome OVERWORLD_OUTBACK_GRASSY = register("minecraft:overworld.outback.grassy", new BiomeOutback("overworld.outback.grassy").setColor(0xf09037).setBlockedWeathers(Weathers.OVERWORLD_SNOW).setTopBlock(Blocks.GRASS_SCORCHED.id()).setFillerBlock(Blocks.DIRT_SCORCHED.id()).setDefaults(0.825f, 0.1f, 1.0f));
     public static final Biome OVERWORLD_CAATINGA = register("minecraft:overworld.caatinga", new BiomeCaatinga("overworld.caatinga").setColor(0xdecda6).setTopBlock(Blocks.MUD_BAKED.id()).setFillerBlock(Blocks.MUD_BAKED.id()).setBlockedWeathers(Weathers.OVERWORLD_SNOW).setDefaults(0.95f, 0.015f, 0.5f));
     public static final Biome OVERWORLD_CAATINGA_PLAINS = register("minecraft:overworld.caatinga.plains", new Biome("overworld.caatinga.plains").setColor(0xa19477).setTopBlock(Blocks.MUD_BAKED.id()).setFillerBlock(Blocks.MUD_BAKED.id()).setBlockedWeathers(Weathers.OVERWORLD_SNOW).setDefaults(0.95f, 0.015f, 0.0f));
-    public static final Biome NETHER_NETHER = register("minecraft:nether.nether", new BiomeNether("nether.nether").setColor(0xDD0000).setBlockedWeathers(Weathers.OVERWORLD_RAIN, Weathers.OVERWORLD_SNOW, Weathers.OVERWORLD_STORM, Weathers.OVERWORLD_FOG).setTopBlock(Blocks.COBBLE_NETHERRACK.id()).setFillerBlock(Blocks.COBBLE_NETHERRACK.id()));
-    public static final Biome NETHER_VOLCANIC_ISLANDS = register("minecraft:nether.volcanic_islands", new BiomeVolcanoIslands("nether.volcanic_islands").setColor(0xDD0000).setBlockedWeathers(Weathers.OVERWORLD_RAIN, Weathers.OVERWORLD_SNOW, Weathers.OVERWORLD_STORM, Weathers.OVERWORLD_FOG).setTopBlock(Blocks.COBBLE_NETHERRACK.id()).setFillerBlock(Blocks.COBBLE_NETHERRACK.id()));
-    public static final Biome NETHER_SULFUR_POOLS = register("minecraft:nether.sulfur_pools", new BiomeSulfurPools("nether.sulfur_pools").setColor(0xDD0000).setBlockedWeathers(Weathers.OVERWORLD_RAIN, Weathers.OVERWORLD_SNOW, Weathers.OVERWORLD_STORM, Weathers.OVERWORLD_FOG).setTopBlock(Blocks.COBBLE_NETHERRACK.id()).setFillerBlock(Blocks.COBBLE_NETHERRACK.id()));
+
+    public static final Biome NETHER_CRAG = register("minecraft:nether.crag", new BiomeNether("nether.crag").setColor(0x633e3d).setBlockedWeathers(Weathers.OVERWORLD_RAIN, Weathers.OVERWORLD_SNOW, Weathers.OVERWORLD_STORM, Weathers.OVERWORLD_FOG).setTopBlock(Blocks.COBBLE_NETHERRACK.id()).setFillerBlock(Blocks.COBBLE_NETHERRACK.id()));
+    public static final Biome NETHER_VOLCANIC_ISLANDS = register("minecraft:nether.volcanic_islands", new BiomeVolcanoIslands("nether.volcanic_islands").setColor(0xffa333).setBlockedWeathers(Weathers.OVERWORLD_RAIN, Weathers.OVERWORLD_SNOW, Weathers.OVERWORLD_STORM, Weathers.OVERWORLD_FOG).setTopBlock(Blocks.BRIMSAND.id()).setFillerBlock(Blocks.BRIMSAND.id()));
+    public static final Biome NETHER_SULFUR_POOLS = register("minecraft:nether.sulfur_pools", new BiomeSulfurPools("nether.sulfur_pools").setColor(0xa39940).setBlockedWeathers(Weathers.OVERWORLD_RAIN, Weathers.OVERWORLD_SNOW, Weathers.OVERWORLD_STORM, Weathers.OVERWORLD_FOG).setTopBlock(Blocks.BRIMSAND.id()).setFillerBlock(Blocks.BRIMSAND.id()));
+    public static final Biome NETHER_CRYSTAL_FIELD = register("minecraft:nether.crystal_field", new BiomeCrystalForest("nether.crystal_field").setColor(0xAA0000).setBlockedWeathers(Weathers.OVERWORLD_RAIN, Weathers.OVERWORLD_SNOW, Weathers.OVERWORLD_STORM, Weathers.OVERWORLD_FOG).setTopBlock(Blocks.COBBLE_NETHERRACK.id()).setFillerBlock(Blocks.COBBLE_NETHERRACK.id()));
     public static final Biome NETHER_CRYSTAL_FOREST = register("minecraft:nether.crystal_forest", new BiomeCrystalForest("nether.crystal_forest").setColor(0xDD0000).setBlockedWeathers(Weathers.OVERWORLD_RAIN, Weathers.OVERWORLD_SNOW, Weathers.OVERWORLD_STORM, Weathers.OVERWORLD_FOG).setTopBlock(Blocks.COBBLE_NETHERRACK.id()).setFillerBlock(Blocks.COBBLE_NETHERRACK.id()));
-    public static final Biome NETHER_ASH_PLAINS = register("minecraft:nether.ash_plains", new BiomeAshPlains("nether.ash_plains").setColor(0xDD0000).setBlockedWeathers(Weathers.OVERWORLD_RAIN, Weathers.OVERWORLD_SNOW, Weathers.OVERWORLD_STORM, Weathers.OVERWORLD_FOG).setTopBlock(Blocks.SLATE.id()).setFillerBlock(Blocks.SLATE.id()));
-    public static final Biome NETHER_BONEYARD = register("minecraft:nether.boneyard", new BiomeBoneyard("nether.boneyard").setColor(0xDD0000).setBlockedWeathers(Weathers.OVERWORLD_RAIN, Weathers.OVERWORLD_SNOW, Weathers.OVERWORLD_STORM, Weathers.OVERWORLD_FOG).setTopBlock(Blocks.SOULSAND.id()).setFillerBlock(Blocks.SOULSAND.id()));
+    public static final Biome NETHER_OLD_WORLD = register("minecraft:nether.old_world", new BiomeNether("nether.old_world").setColor(0x636480).setSurfaceSnow().setBlockedWeathers(Weathers.OVERWORLD_RAIN, Weathers.OVERWORLD_SNOW, Weathers.OVERWORLD_STORM, Weathers.OVERWORLD_FOG).setTopBlock(Blocks.COBBLE_NETHERRACK.id()).setFillerBlock(Blocks.COBBLE_NETHERRACK.id()));
+    public static final Biome NETHER_SHELF = register("minecraft:nether.shelf", new BiomeNether("nether.shelf").setColor(0x968a6f).setBlockedWeathers(Weathers.OVERWORLD_RAIN, Weathers.OVERWORLD_SNOW, Weathers.OVERWORLD_STORM, Weathers.OVERWORLD_FOG).setTopBlock(Blocks.COBBLE_NETHERRACK.id()).setFillerBlock(Blocks.COBBLE_NETHERRACK.id()));
 
     public static final Biome OVERWORLD_LEGACY_RAINFOREST = register("minecraft:overworld.legacy.rainforest", new BiomeRainforestLegacy("overworld.legacy.rainforest").setColor(0xd9e023).setBlockedWeathers(Weathers.OVERWORLD_SNOW).setDefaults(0.985f,  0.95f, 0.5f));
     public static final Biome OVERWORLD_LEGACY_SWAMPLAND = register("minecraft:overworld.legacy.swampland", new BiomeSwampLegacy("overworld.legacy.swampland").setColor(0x07f9b2).setDefaults(0.75f, 0.4f, 0.5f));
diff --git a/game/core/src/main/java/net/minecraft/core/world/biome/nether/BiomeAshPlains.java b/game/core/src/main/java/net/minecraft/core/world/biome/nether/BiomeAshPlains.java
deleted file mode 100644
index e2dd96b1e..000000000
--- a/game/core/src/main/java/net/minecraft/core/world/biome/nether/BiomeAshPlains.java
+++ /dev/null
@@ -1,22 +0,0 @@
-package net.minecraft.core.world.biome.nether;
-
-import net.minecraft.core.entity.SpawnListEntry;
-import net.minecraft.core.entity.monster.MobGhast;
-import net.minecraft.core.entity.monster.MobZombiePig;
-import net.minecraft.core.world.biome.Biome;
-
-public class BiomeAshPlains
-    extends Biome
-{
-
-    public BiomeAshPlains(String key) {
-        super(key);
-        spawnableMonsterList.clear();
-        spawnableCreatureList.clear();
-        spawnableWaterCreatureList.clear();
-        spawnableAmbientCreatureList.clear();
-        spawnableMonsterList.add(new SpawnListEntry(MobGhast.class, 10));
-        spawnableMonsterList.add(new SpawnListEntry(MobZombiePig.class, 10));
-
-    }
-}
diff --git a/game/core/src/main/java/net/minecraft/core/world/biome/nether/BiomeBoneyard.java b/game/core/src/main/java/net/minecraft/core/world/biome/nether/BiomeBoneyard.java
deleted file mode 100644
index 715278b4f..000000000
--- a/game/core/src/main/java/net/minecraft/core/world/biome/nether/BiomeBoneyard.java
+++ /dev/null
@@ -1,20 +0,0 @@
-package net.minecraft.core.world.biome.nether;
-
-import net.minecraft.core.entity.SpawnListEntry;
-import net.minecraft.core.entity.monster.MobGhast;
-import net.minecraft.core.entity.monster.MobZombiePig;
-import net.minecraft.core.world.biome.Biome;
-
-public class BiomeBoneyard extends Biome {
-
-    public BiomeBoneyard(String key) {
-        super(key);
-        spawnableMonsterList.clear();
-        spawnableCreatureList.clear();
-        spawnableWaterCreatureList.clear();
-        spawnableAmbientCreatureList.clear();
-        spawnableMonsterList.add(new SpawnListEntry(MobGhast.class, 10));
-        spawnableMonsterList.add(new SpawnListEntry(MobZombiePig.class, 10));
-
-    }
-}
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 8d0508689..f2c6f85bc 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
@@ -15,25 +15,26 @@ public final class BiomeProviderNether
     extends BiomeProvider
 {
     public static final Biome[] BIOMES_ORDER = {
-            Biomes.NETHER_BONEYARD,
-            Biomes.NETHER_ASH_PLAINS,
+            Biomes.NETHER_OLD_WORLD,
+            Biomes.NETHER_CRYSTAL_FIELD,
             Biomes.NETHER_CRYSTAL_FOREST,
+            Biomes.NETHER_SHELF,
+            Biomes.NETHER_CRAG,
             Biomes.NETHER_SULFUR_POOLS,
-            Biomes.NETHER_NETHER,
-            Biomes.NETHER_VOLCANIC_ISLANDS
+            Biomes.NETHER_VOLCANIC_ISLANDS,
     };
 
     private final FractalNoise2D<?> temperatureNoise;
     private final FractalNoise2D<?> fuzzinessNoise;
 
-    private double temperatureXScale = 0.0125;
-    private double temperatureZScale = 0.0125;
-    private double temperatureLacunarity = 0.25;
-    private double temperatureFuzzPercentage = 0.01;
+    private double temperatureXScale = 0.025;
+    private double temperatureZScale = 0.025;
+    private double temperatureLacunarity = 0.3;
+    private double temperatureFuzzPercentage = 0.02;
 
-    private double fuzzinessXScale = 0.25;
-    private double fuzzinessZScale = 0.25;
-    private double fuzzinessLacunarity = 1.0;
+    private double fuzzinessXScale = 0.2;
+    private double fuzzinessZScale = 0.2;
+    private double fuzzinessLacunarity = 0.25;
 
     public BiomeProviderNether(final @NotNull World world)
     {
@@ -94,15 +95,17 @@ public final class BiomeProviderNether
 
         double fuzzPctg = temperatureFuzzPercentage;
         double valPctg = 1.0 - fuzzPctg;
-        double temperature = (tnResult[0] * 0.15 + 0.7) * valPctg + fuzziness * fuzzPctg;
+        double temperature = (tnResult[0]) * valPctg + fuzziness * fuzzPctg;
+
+        temperature = 1.0 - (1.0 - temperature) * (1.0 - temperature);
 
         if (temperature < 0.0)
         {
-            temperature = 0.0;
+            temperature = -temperature;
         }
         if (temperature > 1.0)
         {
-            temperature = 1.0;
+            temperature = -temperature;
         }
 
         return temperature;
@@ -126,7 +129,7 @@ public final class BiomeProviderNether
 
                 double fuzzPctg = temperatureFuzzPercentage;
                 double valPctg = 1.0 - fuzzPctg;
-                double temperature = (tnResult[dx * zSize + dz] * 0.15 + 0.7) * valPctg + fuzziness * fuzzPctg;
+                double temperature = (tnResult[dx * zSize + dz] * 0.1 + 0.5) * valPctg + fuzziness * fuzzPctg;
 
                 if (temperature < 0.0)
                 {
@@ -184,7 +187,7 @@ public final class BiomeProviderNether
             }
         }
 
-        return Biomes.NETHER_NETHER;
+        return Biomes.NETHER_CRAG;
     }
 
     public static Biome[] allBiomes() {
diff --git a/game/core/src/main/java/net/minecraft/core/world/generate/LavaFloeLargeFeature.java b/game/core/src/main/java/net/minecraft/core/world/generate/LavaFloeLargeFeature.java
new file mode 100644
index 000000000..13b0dcf71
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/world/generate/LavaFloeLargeFeature.java
@@ -0,0 +1,102 @@
+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.chunk.Chunk;
+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 LavaFloeLargeFeature
+    extends LargeFeature {
+    private @Nullable WorleyNoise chunkNoise = null;
+    private @Nullable Noise2D gapNoise = null;
+    private @Nullable Noise2D heightNoise = null;
+    private @Nullable Noise2D scaleNoise = null;
+    private @Nullable World lastWorld = null;
+
+    @Override
+    protected void doGeneration(
+        @NotNull final World world,
+        @NotNull final Random random,
+        final int chunkX, final int chunkZ,
+        final int baseChunkX, final int baseChunkZ,
+        @NotNull final ChunkGeneratorResult result
+    ) {
+        if (this.chunkNoise == null || this.gapNoise == null || this.heightNoise == 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.heightNoise = new FractalNoise2D<>(ImprovedPerlinNoise.genOctaves(world.getRandomSeed(), 4));
+            this.scaleNoise = new Normalize2D<>(new FractalNoise2D<>(ImprovedPerlinNoise.genOctaves(world.getRandomSeed(), 2)));
+            this.lastWorld = world;
+        }
+
+        final @NotNull WorldType worldType = world.getWorldType();
+        final int oceanY = worldType.getMinY(world) + worldType.getOceanY();
+
+        final @NotNull TilePos tilePos = new TilePos();
+        final @NotNull WorleyNoise.Result worleyResult = new WorleyNoise.Result();
+
+        for (int x = 0; x < Chunk.CHUNK_SIZE_X; x++) {
+            for (int z = 0; z < Chunk.CHUNK_SIZE_Z; z++) {
+                tilePos.set(Chunk.CHUNK_SIZE_X * baseChunkX + x, oceanY, Chunk.CHUNK_SIZE_Z * baseChunkZ + z);
+                final @NotNull Biome biome = world.getBlockBiome(tilePos);
+                if (biome == Biomes.NETHER_VOLCANIC_ISLANDS) {
+                    final float worleyScaleScale = 50.0f;
+                    final float worleyScale = 1.0f + (0.0625f - (float) (this.scaleNoise.getValue((baseChunkX * Chunk.CHUNK_SIZE_X + x) / worleyScaleScale, (baseChunkZ * Chunk.CHUNK_SIZE_Z + z) / worleyScaleScale))) * 0.125f;
+                    this.chunkNoise.getValue(
+                            (baseChunkX + ((float) x / (float) Chunk.CHUNK_SIZE_X)) * worleyScale,
+                            (baseChunkZ + ((float) z / (float) Chunk.CHUNK_SIZE_Z)) * worleyScale,
+                            worleyResult
+                    );
+                    final float lineScale = 5.0f;
+                    final float lineWidth = (float) (this.gapNoise.getValue((baseChunkX * Chunk.CHUNK_SIZE_X + x) / lineScale, (baseChunkZ * Chunk.CHUNK_SIZE_Z + z) / lineScale) / 2.0 + 0.5);
+
+                    int heightMod = worleyResult.getRandom().nextInt(3) + 1;
+
+                    final float height = (float) (this.heightNoise.getValue((baseChunkX * Chunk.CHUNK_SIZE_X + x) / 5F, (baseChunkZ * Chunk.CHUNK_SIZE_Z + z) / 5F) / 2.0 + 0.5) * heightMod;
+
+                    if (MathHelper.unsignedMod(worleyResult.index, 15) != 0) {
+                        if (worleyResult.distance > 0.9f + lineWidth * 0.01f) {
+                            continue;
+                        }
+                        for (int y = oceanY - 3; y < oceanY + (height - 2); y++) {
+                            if(random.nextInt(20) == 0) {
+                                this.trySetBlock(result, x, y, z, Blocks.PUMICE_WET.id());
+                            } else {
+                                this.trySetBlock(result, x, y, z, Blocks.COBBLE_NETHERRACK.id());
+                            }
+                        }
+                        for (int y = oceanY + (int) (height - 2); y < oceanY + (height + 1); y++) {
+                            this.trySetBlock(result, x, y, z, Blocks.BRIMSAND.id());
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    private void trySetBlock(final @NotNull ChunkGeneratorResult result, final int x, final int y, final int z, final int id) {
+        final int current = result.getBlock(x, y, z);
+        if (current == Blocks.FLUID_LAVA_STILL.id() || current == 0) {
+            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/NetherBonesLargeFeature.java b/game/core/src/main/java/net/minecraft/core/world/generate/NetherBonesLargeFeature.java
deleted file mode 100644
index 4d5881d00..000000000
--- a/game/core/src/main/java/net/minecraft/core/world/generate/NetherBonesLargeFeature.java
+++ /dev/null
@@ -1,174 +0,0 @@
-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.Biomes;
-import net.minecraft.core.world.chunk.Chunk;
-import net.minecraft.core.world.chunk.ChunkSection;
-import net.minecraft.core.world.generate.chunk.ChunkGeneratorResult;
-import org.jetbrains.annotations.NotNull;
-
-import java.util.Random;
-
-public class NetherBonesLargeFeature extends LargeFeature {
-    private final int minY;
-    private final int maxY;
-
-    public NetherBonesLargeFeature() {
-        this.minY = 0;
-        this.maxY = Chunk.CHUNK_SECTIONS * ChunkSection.SECTION_SIZE_Y;
-    }
-
-    public NetherBonesLargeFeature(int minY, int maxY) {
-        this.minY = minY;
-        this.maxY = maxY;
-    }
-
-    protected void generateHubRoom(@NotNull World world, @NotNull Random random, long seed, int baseChunkX, int baseChunkZ, ChunkGeneratorResult result, double blockX, double blockY, double blockZ) {
-        generateCave(world, random, seed, baseChunkX, baseChunkZ, result, blockX, blockY, blockZ, 1.0F + random.nextFloat() * 6F, 0.0F, 0.0F, -1, -1, 0.5D);
-    }
-
-    protected void generateCave(@NotNull World world, @NotNull Random random, long seed, int baseChunkX, int baseChunkZ, @NotNull ChunkGeneratorResult result, double blockX, double blockY,
-                                double blockZ, float initialRadius, float yRot, float xRot, int startPos, int endPos,
-                                double heightMod) {
-        double chunkMiddleX = baseChunkX * Chunk.CHUNK_SIZE_X + Chunk.CHUNK_SIZE_X / 2;
-        double chunkMiddleZ = baseChunkZ * Chunk.CHUNK_SIZE_Z + Chunk.CHUNK_SIZE_Z / 2;
-        float rotHorOffset = 0.0F;
-        float rotVerOffset = 0.0F;
-        Random r = new Random(seed);
-        if (endPos <= 0) {
-            int maxLength = getRadiusChunk() * 16 - 16;
-            endPos = maxLength - r.nextInt(maxLength / 4);
-        }
-        boolean noBranches = false;
-        if (startPos == -1) {
-            startPos = endPos / 2;
-            noBranches = true;
-        }
-        boolean reversed = r.nextBoolean();
-        int branchPos = r.nextInt(endPos / 2) + endPos / 4;
-        for (; startPos < endPos; startPos++) {
-            double width = 1.5D + (MathHelper.sin((startPos * MathHelper.PI) / endPos) * initialRadius * 1.0F);
-            double height = width * heightMod;
-            float xzScale = MathHelper.cos(xRot);
-            float xOffset = MathHelper.sin(xRot);
-            blockX += xOffset;
-            blockY -= MathHelper.cos(yRot) * xzScale;
-            if (reversed) {
-                blockZ -= MathHelper.sin(yRot) * xzScale;
-            } else {
-                blockZ += MathHelper.sin(yRot) * xzScale;
-            }
-            yRot *= 0.92F;
-            xRot += rotVerOffset * 0.1F;
-            yRot += rotHorOffset * 0.1F;
-            rotVerOffset *= 0.75F;
-            rotHorOffset *= 0.9F;
-//            rotVerOffset += (random.nextFloat() - random.nextFloat()) * random.nextFloat() * 2.0F;
-//            rotHorOffset += (random.nextFloat() - random.nextFloat()) * random.nextFloat() * 4F;
-
-            // Do adjacent branches if necessary
-//            if (false) {
-//                generateCave(world, random.nextLong(), baseChunkX, baseChunkZ, result, blockX, blockY, blockZ, random.nextFloat() * 0.5F + 0.5F, yRot - MathHelper.PI / 2F, xRot / 3F, startPos, endPos, 1.0D);
-//                generateCave(world, random.nextLong(), baseChunkX, baseChunkZ, result, blockX, blockY, blockZ, random.nextFloat() * 0.5F + 0.5F, yRot + MathHelper.PI / 2F, xRot / 3F, startPos, endPos, 1.0D);
-//                return;
-//            }
-
-            // Check if we've gone far enough, and stop if so
-            double dxFromMiddle = blockX - chunkMiddleX;
-            double dzFromMiddle = blockZ - chunkMiddleZ;
-            double length = endPos - startPos;
-            double maxRadius = initialRadius + 2.0F + 16F;
-            if ((dxFromMiddle * dxFromMiddle + dzFromMiddle * dzFromMiddle) - length * length > maxRadius * maxRadius) {
-                return;
-            }
-            if (blockX < chunkMiddleX - Chunk.CHUNK_SIZE_X - width * 2D || blockZ < chunkMiddleZ - Chunk.CHUNK_SIZE_Z - width * 2D || blockX > chunkMiddleX + Chunk.CHUNK_SIZE_X + width * 2D || blockZ > chunkMiddleZ + Chunk.CHUNK_SIZE_Z + width * 2D) {
-                continue;
-            }
-
-            // Determine sizes of current section
-            int minX = MathHelper.floor(blockX - width) - baseChunkX * Chunk.CHUNK_SIZE_X - 1;
-            int maxX = (MathHelper.floor(blockX + width) - baseChunkX * Chunk.CHUNK_SIZE_X) + 1;
-            int minY = MathHelper.floor(blockY - height) - 1;
-            int maxY = MathHelper.floor(blockY + height) + 1;
-            int minZ = MathHelper.floor(blockZ - width) - baseChunkZ * Chunk.CHUNK_SIZE_Z - 1;
-            int maxZ = (MathHelper.floor(blockZ + width) - baseChunkZ * Chunk.CHUNK_SIZE_Z) + 1;
-            if (minX < 0) {
-                minX = 0;
-            }
-            if (maxX > Chunk.CHUNK_SIZE_X) {
-                maxX = Chunk.CHUNK_SIZE_X;
-            }
-            if (minY < this.minY + 1) {
-                minY = this.minY + 1;
-            }
-            if (maxY > this.maxY - (ChunkSection.SECTION_SIZE_Y / 2)) {
-                maxY = this.maxY - (ChunkSection.SECTION_SIZE_Y / 2);
-            }
-            if (minZ < 0) {
-                minZ = 0;
-            }
-            if (maxZ > Chunk.CHUNK_SIZE_Z) {
-                maxZ = Chunk.CHUNK_SIZE_Z;
-            }
-
-            // Place blocks in section
-            for (int x = minX; x < maxX; x++) {
-                double xPercentage = (((x + baseChunkX * Chunk.CHUNK_SIZE_X) + 0.5D) - blockX) / width;
-                zLabel:
-                for (int z = minZ; z < maxZ; z++) {
-                    double zPercentage = (((z + baseChunkZ * Chunk.CHUNK_SIZE_Z) + 0.5D) - blockZ) / width;
-                    int yIndex = maxY;
-                    if (xPercentage * xPercentage + zPercentage * zPercentage >= 1.0D) {
-                        continue;
-                    }
-                    int y = maxY - 1;
-                    do {
-                        if (y < minY) {
-                            continue zLabel;
-                        }
-                        double yPercentage = ((y + 0.5D) - blockY) / height;
-                        if (yPercentage > -0.7D && xPercentage * xPercentage + yPercentage * yPercentage + zPercentage * zPercentage < 1.0D) {
-                            int blockId = result.getBlock(x, yIndex, z);
-                            if (blockId == 0) {
-                                result.setBlock(x, yIndex, z, Blocks.BONESHALE.id());
-                            }
-                        }
-                        yIndex--;
-                        y--;
-                    } while (true);
-                }
-            }
-
-            if (noBranches) {
-                break;
-            }
-        }
-    }
-
-    @Override
-    protected void doGeneration(@NotNull World world, @NotNull Random random, int chunkX, int chunkZ, int baseChunkX, int baseChunkZ, @NotNull ChunkGeneratorResult result) {
-        if (world.getBlockBiome(chunkX * Chunk.CHUNK_SIZE_X, 0, chunkZ * Chunk.CHUNK_SIZE_Z) != Biomes.NETHER_BONEYARD) {
-            return;
-        }
-
-        int cavesToGenerate = random.nextInt(random.nextInt(random.nextInt(10) + 1) + 1);
-//        if (rand.nextInt(12) != 0) {
-//            cavesToGenerate = 0;
-//        }
-
-        final int yRange = this.maxY - this.minY;
-
-        for (int i = 0; i < cavesToGenerate; i++) {
-            double blockX = chunkX * Chunk.CHUNK_SIZE_X + random.nextInt(Chunk.CHUNK_SIZE_X);
-            double blockY = this.minY + random.nextInt(random.nextInt(yRange - (ChunkSection.SECTION_SIZE_Y / 2)) + (ChunkSection.SECTION_SIZE_Y / 2));
-            double blockZ = chunkZ * Chunk.CHUNK_SIZE_Z + random.nextInt(Chunk.CHUNK_SIZE_Z);
-
-            float yRot = random.nextFloat() * MathHelper.PI * 2.0F; // 0 - 2Pi
-            float xRot = ((random.nextFloat() - 0.5F) * 2.0F) / 8F; // -0.125 - 0.125
-            float initialRadius = random.nextFloat() + random.nextFloat(); // 0 - 3
-            generateCave(world, random, random.nextLong(), baseChunkX, baseChunkZ, result, blockX, blockY, blockZ, initialRadius, yRot, xRot, 0, 0, 1.0f);
-        }
-    }
-}
\ No newline at end of file
diff --git a/game/core/src/main/java/net/minecraft/core/world/generate/ShelfLargeFeature.java b/game/core/src/main/java/net/minecraft/core/world/generate/ShelfLargeFeature.java
new file mode 100644
index 000000000..7051d3134
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/world/generate/ShelfLargeFeature.java
@@ -0,0 +1,84 @@
+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.chunk.Chunk;
+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 ShelfLargeFeature
+    extends LargeFeature {
+    private @Nullable WorleyNoise chunkNoise = null;
+    private @Nullable Noise2D gapNoise = null;
+    private @Nullable Noise2D scaleNoise = null;
+    private @Nullable World lastWorld = null;
+
+    @Override
+    protected void doGeneration(
+        @NotNull final World world,
+        @NotNull final Random random,
+        final int chunkX, final int chunkZ,
+        final int baseChunkX, final int baseChunkZ,
+        @NotNull final 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;
+        }
+
+        final @NotNull WorldType worldType = world.getWorldType();
+        final int oceanY = worldType.getMinY(world) + worldType.getOceanY();
+
+        final @NotNull TilePos tilePos = new TilePos();
+        final @NotNull WorleyNoise.Result worleyResult = new WorleyNoise.Result();
+
+        for (int x = 0; x < Chunk.CHUNK_SIZE_X; x++) {
+            for (int z = 0; z < Chunk.CHUNK_SIZE_Z; z++) {
+                tilePos.set(Chunk.CHUNK_SIZE_X * baseChunkX + x, oceanY, Chunk.CHUNK_SIZE_Z * baseChunkZ + z);
+                final @NotNull Biome biome = world.getBlockBiome(tilePos);
+                if (biome == Biomes.NETHER_SHELF) {
+                    final float worleyScaleScale = 50.0f;
+                    final float worleyScale = 1.0f + (0.0625f - (float) (this.scaleNoise.getValue((baseChunkX * Chunk.CHUNK_SIZE_X + x) / worleyScaleScale, (baseChunkZ * Chunk.CHUNK_SIZE_Z + z) / worleyScaleScale))) * 0.125f;
+                    this.chunkNoise.getValue(
+                        (baseChunkX + ((float) x / (float) Chunk.CHUNK_SIZE_X)) * worleyScale,
+                        (baseChunkZ + ((float) z / (float) Chunk.CHUNK_SIZE_Z)) * worleyScale,
+                        worleyResult
+                    );
+
+                    int heightMod = worleyResult.getRandom().nextInt(5) + 1;
+
+                    for (int y = oceanY - 20; y < oceanY + heightMod; y++) {
+                        this.trySetBlock(result, x, y, z, Blocks.BRIMSAND.id());
+                    }
+                }
+            }
+        }
+    }
+
+    private void trySetBlock(final @NotNull ChunkGeneratorResult result, final int x, final int y, final int z, final int id) {
+        final int current = result.getBlock(x, y, z);
+        if (current == Blocks.FLUID_WATER_STILL.id() || current == 0) {
+            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/WarrensCavesLargeFeature.java b/game/core/src/main/java/net/minecraft/core/world/generate/WarrensCavesLargeFeature.java
deleted file mode 100644
index 1ecb1eb49..000000000
--- a/game/core/src/main/java/net/minecraft/core/world/generate/WarrensCavesLargeFeature.java
+++ /dev/null
@@ -1,223 +0,0 @@
-package net.minecraft.core.world.generate;
-
-import net.minecraft.core.block.Blocks;
-import net.minecraft.core.block.tag.BlockTags;
-import net.minecraft.core.util.helper.MathHelper;
-import net.minecraft.core.world.World;
-import net.minecraft.core.world.chunk.Chunk;
-import net.minecraft.core.world.chunk.ChunkSection;
-import net.minecraft.core.world.generate.chunk.ChunkGeneratorResult;
-import org.jetbrains.annotations.NotNull;
-
-import java.util.Random;
-
-public class WarrensCavesLargeFeature extends LargeFeature {
-    private final int minY;
-    private final int maxY;
-
-    public WarrensCavesLargeFeature() {
-        this.minY = 0;
-        this.maxY = Chunk.CHUNK_SECTIONS * ChunkSection.SECTION_SIZE_Y;
-    }
-
-    public WarrensCavesLargeFeature(int minY, int maxY) {
-        this.minY = minY;
-        this.maxY = maxY;
-    }
-
-    protected void generateHubRoom(@NotNull World world, @NotNull Random random, long seed, int baseChunkX, int baseChunkZ, ChunkGeneratorResult result, double blockX, double blockY, double blockZ) {
-        generateCave(world, random, seed, baseChunkX, baseChunkZ, result, blockX, blockY, blockZ, 1.0F + random.nextFloat() * 6F, 0.0F, 0.0F, -1, -1, 0.5D);
-    }
-
-    protected void generateCave(@NotNull World world, @NotNull Random random, long seed, int baseChunkX, int baseChunkZ, ChunkGeneratorResult result, double blockX, double blockY,
-                                double blockZ, float initialRadius, float yRot, float xRot, int startPos, int endPos,
-                                double heightMod) {
-        double chunkMiddleX = baseChunkX * Chunk.CHUNK_SIZE_X + Chunk.CHUNK_SIZE_X / 2;
-        double chunkMiddleZ = baseChunkZ * Chunk.CHUNK_SIZE_Z + Chunk.CHUNK_SIZE_Z / 2;
-        float rotHorOffset = 0.0F;
-        float rotVerOffset = 0.0F;
-        Random r = new Random(seed);
-        if (endPos <= 0) {
-            int maxLength = getRadiusChunk() * 24 - 16;
-            endPos = maxLength - r.nextInt(maxLength / 8);
-        }
-        final boolean noRotate = r.nextInt(32) == 0;
-        if (noRotate) {
-            yRot = (yRot / (MathHelper.PI * 2)) * 4;
-            yRot = Math.round(yRot);
-            yRot = yRot * (MathHelper.PI * 2) / 4;
-//            xRot = (xRot / (MathHelper.PI * 2)) * 4;
-//            xRot = Math.round(xRot);
-//            xRot = xRot * (MathHelper.PI * 2) / 4;
-        }
-        int branchPos = r.nextInt(endPos / 2) + endPos / 4;
-        boolean sharpRotVer = r.nextInt(6) == 0;
-        for (; startPos < endPos; startPos++) {
-            double width = 1.5D + (MathHelper.sin((startPos * MathHelper.PI) / endPos) * initialRadius * 1.0F);
-            double height = width * heightMod;
-            float xzScale = MathHelper.cos(xRot);
-            float yOffset = MathHelper.sin(xRot);
-            blockX += MathHelper.cos(yRot) * xzScale;
-            blockY += yOffset;
-            blockZ += MathHelper.sin(yRot) * xzScale;
-
-            if (!noRotate) {
-                if (sharpRotVer) {
-                    xRot *= 0.92F;
-                } else {
-                    xRot *= 0.7F;
-                }
-                xRot += rotVerOffset * 0.1F;
-                yRot += rotHorOffset * 0.1F;
-                rotVerOffset *= 0.9F;
-                rotHorOffset *= 0.75F;
-                rotVerOffset += (r.nextFloat() - r.nextFloat()) * r.nextFloat() * 2.0F;
-                rotHorOffset += (r.nextFloat() - r.nextFloat()) * r.nextFloat() * 4F;
-
-                final int flip = r.nextInt(5);
-                if (flip == 0) {
-                    yRot = -yRot;
-                }
-//                } else if (flip == 1) {
-//                    xRot = -xRot;
-//                }
-                final int addRandom = r.nextInt(4);
-                if (addRandom == 0) {
-                    yRot += r.nextFloat() * MathHelper.PI - MathHelper.PI / 2;
-                }
-//                } else if (addRandom == 1) {
-//                    xRot += random.nextFloat() * MathHelper.PI - MathHelper.PI / 2;
-//                }
-            }
-
-            // Do adjacent branches if necessary
-            if (startPos == branchPos && initialRadius > 1.0F) {
-                generateCave(world, random, r.nextLong(), baseChunkX, baseChunkZ, result, blockX, blockY, blockZ, r.nextFloat() * 0.5F + 0.5F, yRot - MathHelper.PI / 2F, xRot / 3F, startPos, endPos, 1.0D);
-                generateCave(world, random, r.nextLong(), baseChunkX, baseChunkZ, result, blockX, blockY, blockZ, r.nextFloat() * 0.5F + 0.5F, yRot + MathHelper.PI / 2F, xRot / 3F, startPos, endPos, 1.0D);
-                return;
-            }
-
-            // Randomly skip a section
-            if (r.nextInt(4) == 0) {
-                continue;
-            }
-
-            // Check if we've gone far enough, and stop if so
-            double dxFromMiddle = blockX - chunkMiddleX;
-            double dzFromMiddle = blockZ - chunkMiddleZ;
-            double length = endPos - startPos;
-            double maxRadius = initialRadius + 2.0F + 16F;
-            if ((dxFromMiddle * dxFromMiddle + dzFromMiddle * dzFromMiddle) - length * length > maxRadius * maxRadius) {
-                return;
-            }
-            if (blockX < chunkMiddleX - Chunk.CHUNK_SIZE_X - width * 2D || blockZ < chunkMiddleZ - Chunk.CHUNK_SIZE_Z - width * 2D || blockX > chunkMiddleX + Chunk.CHUNK_SIZE_X + width * 2D || blockZ > chunkMiddleZ + Chunk.CHUNK_SIZE_Z + width * 2D) {
-                continue;
-            }
-
-            // Determine sizes of current section
-            int minX = MathHelper.floor(blockX - width) - baseChunkX * Chunk.CHUNK_SIZE_X - 1;
-            int maxX = (MathHelper.floor(blockX + width) - baseChunkX * Chunk.CHUNK_SIZE_X) + 1;
-            int minY = MathHelper.floor(blockY - height) - 1;
-            int maxY = MathHelper.floor(blockY + height) + 1;
-            int minZ = MathHelper.floor(blockZ - width) - baseChunkZ * Chunk.CHUNK_SIZE_Z - 1;
-            int maxZ = (MathHelper.floor(blockZ + width) - baseChunkZ * Chunk.CHUNK_SIZE_Z) + 1;
-            if (minX < 0) {
-                minX = 0;
-            }
-            if (maxX > Chunk.CHUNK_SIZE_X) {
-                maxX = Chunk.CHUNK_SIZE_X;
-            }
-            if (minY < this.minY + 1) {
-                minY = this.minY + 1;
-            }
-            if (maxY > this.maxY - (ChunkSection.SECTION_SIZE_Y / 2)) {
-                maxY = this.maxY - (ChunkSection.SECTION_SIZE_Y / 2);
-            }
-            if (minZ < 0) {
-                minZ = 0;
-            }
-            if (maxZ > Chunk.CHUNK_SIZE_Z) {
-                maxZ = Chunk.CHUNK_SIZE_Z;
-            }
-
-            // Check if we've hit the ocean, and if so, stop generating
-            boolean hasHitWater = false;
-            for (int x = minX; !hasHitWater && x < maxX; x++) {
-                for (int z = minZ; !hasHitWater && z < maxZ; z++) {
-                    for (int y = maxY + 1; !hasHitWater && y >= minY - 1; y--) {
-                        int blockId = result.getBlock(x, y, z);
-
-                        if (y < this.minY || y >= this.maxY) {
-                            continue;
-                        }
-                        if (Blocks.hasTag(blockId, BlockTags.IS_WATER)) {
-                            hasHitWater = true;
-                        }
-                        if (y != minY - 1 && x != minX && x != maxX - 1 && z != minZ && z != maxZ - 1) {
-                            y = minY;
-                        }
-                    }
-                }
-            }
-            if (hasHitWater) {
-                continue;
-            }
-
-            // Delete blocks in section
-            for (int x = minX; x < maxX; x++) {
-                double xPercentage = (((x + baseChunkX * Chunk.CHUNK_SIZE_X) + 0.5D) - blockX) / width;
-                zLabel:
-                for (int z = minZ; z < maxZ; z++) {
-                    double zPercentage = (((z + baseChunkZ * Chunk.CHUNK_SIZE_Z) + 0.5D) - blockZ) / width;
-                    int yIndex = maxY;
-                    if (xPercentage * xPercentage + zPercentage * zPercentage >= 1.0D) {
-                        continue;
-                    }
-                    int y = maxY - 1;
-                    do {
-                        if (y < minY) {
-                            continue zLabel;
-                        }
-                        double yPercentage = ((y + 0.5D) - blockY) / height;
-                        if (yPercentage > -0.7D && xPercentage * xPercentage + yPercentage * yPercentage + zPercentage * zPercentage < 1.0D) {
-                            int blockId = result.getBlock(x, yIndex, z);
-                            if (blockId == Blocks.SOULSCHIST.id()) {
-                                result.setBlock(x, yIndex, z, 0);
-                            }
-                        }
-                        yIndex--;
-                        y--;
-                    } while (true);
-                }
-            }
-        }
-    }
-
-    @Override
-    protected void doGeneration(@NotNull World world, @NotNull Random random, int chunkX, int chunkZ, int baseChunkX, int baseChunkZ, @NotNull ChunkGeneratorResult result) {
-        int cavesToGenerate = random.nextInt(random.nextInt(random.nextInt(100) + 1) + 1);
-        if (random.nextInt(15) != 0) {
-            cavesToGenerate = 0;
-        }
-
-        final int yRange = this.maxY - this.minY;
-
-        for (int i = 0; i < cavesToGenerate; i++) {
-            double blockX = chunkX * Chunk.CHUNK_SIZE_X + random.nextInt(Chunk.CHUNK_SIZE_X);
-            double blockY = this.minY + random.nextInt(random.nextInt(yRange - (ChunkSection.SECTION_SIZE_Y / 2)) + (ChunkSection.SECTION_SIZE_Y / 2));
-            double blockZ = chunkZ * Chunk.CHUNK_SIZE_Z + random.nextInt(Chunk.CHUNK_SIZE_Z);
-
-            int numBranches = 1;
-            if (random.nextInt(4) == 0) {
-                generateHubRoom(world, random, random.nextLong(), baseChunkX, baseChunkZ, result, blockX, blockY, blockZ);
-                numBranches += random.nextInt(4);
-            }
-            for (int l1 = 0; l1 < numBranches; l1++) {
-                float yRot = random.nextFloat() * MathHelper.PI * 2.0F; // 0 - 2Pi
-                float xRot = ((random.nextFloat() - 0.5F) * 2.0F) / 8F; // -0.125 - 0.125
-                float initialRadius = random.nextFloat() * 1.5f + random.nextFloat(); // 0 - 3
-                generateCave(world, random, random.nextLong(), baseChunkX, baseChunkZ, result, blockX, blockY, blockZ, initialRadius, yRot, xRot, 0, 0, 1.0f);
-            }
-        }
-    }
-}
\ 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 111a7145c..d66c6bc05 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
@@ -8,8 +8,8 @@ import net.minecraft.core.world.biome.Biomes;
 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.generate.feature.tree.WorldFeatureBoneshaleTree;
 import net.minecraft.core.world.noise.FractalNoise2D;
-import net.minecraft.core.world.noise.FractalNoise3D;
 import net.minecraft.core.world.noise.ImprovedPerlinNoise;
 import net.minecraft.core.world.noise.Noise2D;
 import net.minecraft.core.world.pos.TilePos;
@@ -45,68 +45,123 @@ public class ChunkDecoratorNether
 
         final @NotNull TilePos ti = new TilePos();
 
-        if (biome == Biomes.NETHER_CRYSTAL_FOREST) {
+        if (biome == Biomes.NETHER_CRYSTAL_FOREST || biome == Biomes.NETHER_CRYSTAL_FIELD ) {
             double noiseScale = 0.5;
 
-            int density = (int) ((this.crystalDensityNoise.getValue(x * noiseScale, z * noiseScale) - 6D + 36D) / 6D);
+            int density = (int) ((this.crystalDensityNoise.getValue(x * noiseScale, z * noiseScale) - 6D + 30D) / 6D);
 
-            for (int i = 0; i < density - 5; i++) {
-                int xf = x + rand.nextInt(16) + 8;
-                int yf = this.world.getWorldType().getOceanY() + rand.nextInt(5);
-                int zf = z + rand.nextInt(16) + 8;
-                boolean isCeiling = rand.nextInt(5) == 0;
-                new WorldFeatureCrystalChunk(isCeiling).place(this.world, rand, xf, yf, zf);
+            if (biome == Biomes.NETHER_CRYSTAL_FOREST) {
+                for (int i = 0; i < density + 4; i++) {
+                    ti.set(
+                            x + rand.nextInt(16) + 8,
+                            rangeY / 2 + rand.nextInt(rangeY - 8) + 4,
+                            z + rand.nextInt(16) + 8
+                    );
+
+                    boolean isCeiling = rand.nextInt(5) == 0;
+                    new WorldFeatureRubyglassCrystal(isCeiling).place(this.world, rand, ti);
+                }
+
+
+                for (int i = 0; i < density + 6; i++) {
+                    ti.set(
+                            x + rand.nextInt(16) + 8,
+                            rangeY / 2 + rand.nextInt(rangeY - 8) + 4,
+                            z + rand.nextInt(16) + 8
+                    );
+
+                    new WorldFeatureRubyglassCrystal(false, 25, 50).place(world, rand, ti);
+                }
             }
 
+            ti.set(
+                    x + rand.nextInt(16) + 8,
+                    rangeY / 2 + rand.nextInt(rangeY - 8) + 4,
+                    z + rand.nextInt(16) + 8
+            );
+
+            new WorldFeatureRubyglassCrystal(false, 20, 30).place(world, rand, ti);
+
 
-            for (int i = 0; i < density - 5; i++) {
+            for (int i = 0; i < 20; i++) {
                 int xf = x + rand.nextInt(16) + 8;
                 int yf = this.world.getWorldType().getOceanY() + rand.nextInt(rangeY - 8) + 4;
                 int zf = z + rand.nextInt(16) + 8;
-                new WorldFeatureCrystalChunk(false, 15, 30, true).place(this.world, rand, xf, yf, zf);
-
+                (new WorldFeatureRubyglassGrowthPatch()).place(world, rand, xf, yf, zf);
             }
 
-            for (int i = 0; i < 40; i++) {
+            for(int l18 = 0; l18 < 30; l18++)
+            {
+                int l21 = x + rand.nextInt(16) + 8;
+                int k23 = minY + rand.nextInt(rand.nextInt(rangeY - (rangeY / 16)) + (rangeY / 16));
+                int l24 = z + rand.nextInt(16) + 8;
+                (new WorldFeatureNetherLava(Blocks.FLUID_WATER_FLOWING.id())).place(world, rand, l21, k23, l24);
+            }
+            if (rand.nextInt(4) == 0) {
                 int xf = x + rand.nextInt(16) + 8;
-                int yf = this.world.getWorldType().getOceanY() + rand.nextInt(rangeY - 8) + 4;
+                int yf = rangeY / 2 + rand.nextInt(rangeY - 8) + 4;
                 int zf = z + rand.nextInt(16) + 8;
-                (new WorldFeatureDeadBush(Blocks.CRYSTAL_STALK.id())).place(this.world, rand, xf, yf, zf);
+                new WorldFeaturePillar(Blocks.NETHERRACK.id(), true).place(world, rand, xf, yf, zf);
             }
+        }
 
-            for (int l18 = 0; l18 < 50; l18++) {
+        if (biome == Biomes.NETHER_VOLCANIC_ISLANDS) {
+            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));
                 int l24 = z + rand.nextInt(16) + 8;
-                (new WorldFeatureLiquid(Blocks.FLUID_WATER_FLOWING.id())).place(this.world, rand, l21, k23, l24);
+                (new WorldFeatureNetherLava(Blocks.FLUID_LAVA_FLOWING.id())).place(world, rand, l21, k23, l24);
             }
 
-        }
-//
-//        if (biome == Biomes.NETHER_SULFUR_POOLS) {
-//            for (int i = 0; i < 2; i++) {
-//                int xf = x + rand.nextInt(16) + 8;
-//                int yf = world.getWorldType().getOceanY() + rand.nextInt(rangeY - 8) + 4;
-//                int zf = z + rand.nextInt(16) + 8;
-//                new WorldFeaturePillar(Blocks.COBBLE_NETHERRACK.id, true).place(world, rand, xf, yf, zf);
-//            }
-//        }
-
-        if (biome == Biomes.NETHER_ASH_PLAINS) {
-            if (rand.nextInt(2) == 0) {
+            if (rand.nextInt(4) == 0) {
                 int xf = x + rand.nextInt(16) + 8;
-                int yf = this.world.getWorldType().getOceanY() + rand.nextInt(rangeY - 8) + 4;
+                int yf = rangeY / 2 + rand.nextInt(rangeY - 8) + 4;
                 int zf = z + rand.nextInt(16) + 8;
-                new WorldFeaturePillar(Blocks.SLATE.id(), false).place(this.world, rand, xf, yf, zf);
+                new WorldFeaturePillar(Blocks.COBBLE_BASALT.id(), true).place(world, rand, xf, yf, zf);
+            }
+
+            for (int i = 0; i < 2; i++) {
+                int xf1 = x + rand.nextInt(16) + 8;
+                int yf1 = world.getWorldType().getOceanY() + rand.nextInt(rangeY - 8) + 4;
+                int zf1 = z + rand.nextInt(16) + 8;
+                new WorldFeatureMagmaBoulder().place(world, rand, xf1, yf1, zf1);
             }
+
         }
 
-        if (biome != Biomes.NETHER_ASH_PLAINS && biome != Biomes.NETHER_BONEYARD) {
-            for (int i = 0; i < 8; i++) {
+        if (biome == Biomes.NETHER_SULFUR_POOLS) {
+            if (rand.nextInt(8) != 0) {
+                new WorldFeatureTerrace(Blocks.FLUID_WATER_STILL, Blocks.BRIMSAND).place(world, rand, x, 0, z);
+            }
+
+            if (rand.nextInt(8) == 0) {
                 int xf = x + rand.nextInt(16) + 8;
                 int yf = rangeY / 2 + rand.nextInt(rangeY - 8) + 4;
                 int zf = z + rand.nextInt(16) + 8;
-                (new WorldFeatureNetherLava(Blocks.FLUID_LAVA_FLOWING.id())).place(this.world, rand, xf, yf, zf);
+                new WorldFeaturePillar(Blocks.COBBLE_BASALT.id(), true).place(world, rand, xf, yf, zf);
+            }
+
+            for(int l18 = 0; l18 < 30; l18++)
+            {
+                int l21 = x + rand.nextInt(16) + 8;
+                int k23 = minY + rand.nextInt(rand.nextInt(rangeY - (rangeY / 16)) + (rangeY / 16));
+                int l24 = z + rand.nextInt(16) + 8;
+                (new WorldFeatureNetherLava(Blocks.FLUID_LAVA_FLOWING.id())).place(world, rand, l21, k23, l24);
+            }
+
+            for (int i = 0; i < 1; i++) {
+                int xf1 = x + rand.nextInt(16) + 8;
+                int yf1 = world.getWorldType().getOceanY() + rand.nextInt(rangeY - 8) + 4;
+                int zf1 = z + rand.nextInt(16) + 8;
+                new WorldFeatureThermalSpire().place(world, rand, xf1, yf1, zf1);
+            }
+
+            for (int i = 0; i < 2; i++) {
+                int xf1 = x + rand.nextInt(16) + 8;
+                int yf1 = world.getWorldType().getOceanY() + rand.nextInt(rangeY - 8) + 4;
+                int zf1 = z + rand.nextInt(16) + 8;
+                new WorldFeatureMagmaBoulder().place(world, rand, xf1, yf1, zf1);
             }
         }
 
@@ -118,27 +173,6 @@ public class ChunkDecoratorNether
         }
 
         int max;
-        if (biome != Biomes.NETHER_ASH_PLAINS && biome != Biomes.NETHER_BONEYARD) {
-            max = rand.nextInt(rand.nextInt(10) + 1);
-            for (int i = 0; i < max; i++) {
-                int xf = x + rand.nextInt(16) + 8;
-                int yf = this.world.getWorldType().getOceanY() + rand.nextInt(rangeY - 8) + 4;
-                int zf = z + rand.nextInt(16) + 8;
-                (new WorldFeatureFire()).place(this.world, rand, xf, yf, zf);
-            }
-        }
-
-        if (biome == Biomes.NETHER_BONEYARD) {
-            max = rand.nextInt(20) + 5;
-            for (int i = 0; i < max; i++) {
-                ti.set(
-                    x + rand.nextInt(16) + 8,
-                    this.world.getWorldType().getOceanY() + rand.nextInt(rangeY - 8) + 4,
-                    z + rand.nextInt(16) + 8
-                );
-                (new WorldFeatureColdFire()).place(this.world, rand, ti);
-            }
-        }
 
         max = rand.nextInt(rand.nextInt(10) + 1);
         for (int i = 0; i < max; i++) {
@@ -155,19 +189,72 @@ public class ChunkDecoratorNether
             new WorldFeatureGlowstoneB().place(this.world, rand, xf, yf, zf);
         }
 
-        if (biome != Biomes.NETHER_ASH_PLAINS && biome != Biomes.NETHER_BONEYARD) {
-            if (rand.nextInt(8) == 0) {
-                int xf = x + rand.nextInt(16) + 8;
-                int yf = rangeY / 2 + rand.nextInt(rangeY - 16) + 8;
-                int zf = z + rand.nextInt(16) + 8;
-                (new WorldFeatureLake(Blocks.FLUID_LAVA_STILL.id())).place(this.world, rand, xf, yf, zf);
-            }
 
-            for (int i = 0; i < /*20*/80; i++) {
+        if (biome == Biomes.NETHER_VOLCANIC_ISLANDS) {
+            for (int i = 0; i < 20; i++) {
                 int xf = x + rand.nextInt(16);
-                int yf = rangeY / 2 + rand.nextInt(rangeY);
+                int yf = rangeY / 2 + rand.nextInt(rangeY - 8) + 4;
                 int zf = z + rand.nextInt(16);
-                (new WorldFeaturePumice(32)).place(this.world, rand, xf, yf, zf);
+                new WorldFeatureOre(Blocks.ORE_NETHERCOAL_NETHERRACK.id(), 16).place(this.world, rand, xf, yf, zf);
+            }
+        }
+
+        max = 4;
+
+        if(biome == Biomes.NETHER_VOLCANIC_ISLANDS || biome == Biomes.NETHER_SULFUR_POOLS) {
+            max = 8;
+        }
+        if(biome == Biomes.NETHER_CRYSTAL_FIELD || biome == Biomes.NETHER_CRYSTAL_FOREST) {
+            max = 0;
+        }
+
+        for(int i = 0; i < max; i++)
+        {
+            int xf = x + rand.nextInt(16) + 8;
+            int yf = this.world.getWorldType().getOceanY() + rand.nextInt(rangeY - 8) + 4;
+            int zf = z + rand.nextInt(16) + 8;
+            (new WorldFeatureFire()).place(this.world, rand, xf, yf, zf);
+        }
+
+        max = 2;
+
+        if(biome == Biomes.NETHER_OLD_WORLD) {
+            max = 8;
+        }
+
+        for(int i = 0; i < max; i++)
+        {
+            int xf = x + rand.nextInt(16) + 8;
+            int yf = this.world.getWorldType().getOceanY() + rand.nextInt(rangeY - 8) + 4;
+            int zf = z + rand.nextInt(16) + 8;
+            (new WorldFeatureNetherPatch(Blocks.BONE_PILE.id())).place(this.world, rand, xf, yf, zf);
+        }
+
+        max = 4;
+
+        if(biome == Biomes.NETHER_OLD_WORLD) {
+            max = 2;
+        }
+
+        for(int i = 0; i < max; i++)
+        {
+            int xf = x + rand.nextInt(16) + 8;
+            int yf = this.world.getWorldType().getOceanY() + rand.nextInt(rangeY - 8) + 4;
+            int zf = z + rand.nextInt(16) + 8;
+            (new WorldFeatureNetherPatch(Blocks.SOULCATCHER.id())).place(this.world, rand, xf, yf, zf);
+        }
+
+        if(biome == Biomes.NETHER_OLD_WORLD) {
+            double noiseScale = 0.5;
+
+            int density = (int) ((this.crystalDensityNoise.getValue(x * noiseScale, z * noiseScale) - 6D + 20D) / 6D);
+
+            for(int i = 0; i < density + 4; i++)
+            {
+                int xf = x + rand.nextInt(16) + 8;
+                int yf = this.world.getWorldType().getOceanY() + rand.nextInt(rangeY - 8) + 4;
+                int zf = z + rand.nextInt(16) + 8;
+                (new WorldFeatureBoneshaleTree(0, Blocks.BONESHALE.id())).place(this.world, rand, xf, yf, zf);
             }
         }
 
@@ -182,8 +269,8 @@ public class ChunkDecoratorNether
                 dy++;
                 Biome localBiome = this.world.getBlockBiome(dx, dy, dz);
 
-                if ((localBiome == Biomes.NETHER_ASH_PLAINS || localBiome == Biomes.NETHER_BONEYARD) && dy > 0 && dy < this.world.getHeightBlocks()) {
-                    if (this.world.isAirBlock(dx, dy, dz) && this.world.getBlockMaterial(dx, dy - 1, dz).blocksMotion() && this.world.getBlockId(dx, dy - 1, dz) != Blocks.OBSIDIAN.id()) {
+                if (localBiome != null && localBiome.hasSurfaceSnow() && (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_ASH.id());
                     }
                 }
@@ -191,7 +278,6 @@ public class ChunkDecoratorNether
 
         }
 
-
         BlockLogicSand.fallInstantly = false;
 
     }
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 5f0a9c863..159e90c92 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
@@ -1,10 +1,7 @@
 package net.minecraft.core.world.generate.chunk.perlin.nether;
 
 import net.minecraft.core.world.World;
-import net.minecraft.core.world.generate.CavesLargeFeature;
-import net.minecraft.core.world.generate.LargeFeature;
-import net.minecraft.core.world.generate.NetherBonesLargeFeature;
-import net.minecraft.core.world.generate.WarrensCavesLargeFeature;
+import net.minecraft.core.world.generate.*;
 import net.minecraft.core.world.generate.chunk.perlin.ChunkGeneratorPerlin;
 import org.jetbrains.annotations.NotNull;
 
@@ -19,8 +16,8 @@ public class ChunkGeneratorNether
             new LargeFeature[]
                 {
                     new CavesLargeFeature(64, 256),
-                    new WarrensCavesLargeFeature(0, 128),
-                    new NetherBonesLargeFeature(64, 256)
+                    new LavaFloeLargeFeature(),
+                    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 5f991a5d2..adcdae007 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
@@ -1,24 +1,15 @@
 package net.minecraft.core.world.generate.chunk.perlin.nether;
 
-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.biome.provider.BiomeProviderNether;
 import net.minecraft.core.world.chunk.Chunk;
 import net.minecraft.core.world.generate.chunk.perlin.DensityGenerator;
 import net.minecraft.core.world.noise.*;
 import net.minecraft.core.world.type.WorldType;
 import org.jetbrains.annotations.NotNull;
 
-import java.util.HashMap;
-import java.util.Map;
-
 public class DensityGeneratorNether
     implements DensityGenerator {
-    private interface BiomeDensityGenerator {
-        double[] generateDensityMap(Chunk chunk, int terrainHeight, int xSize, int ySize, int zSize, int x, int y, int z);
-    }
 
     public static class BiomeOverlapPair {
         public boolean hasOverlap;
@@ -43,8 +34,6 @@ public class DensityGeneratorNether
 
     private final @NotNull FractalNoise2D<?> islandNoise;
 
-    private final @NotNull Map<@NotNull Biome, @NotNull BiomeDensityGenerator> densityGeneratorMap = new HashMap<>();
-
     public DensityGeneratorNether(World world) {
         this.world = world;
 
@@ -60,13 +49,6 @@ public class DensityGeneratorNether
         this.ravineNoise2 = new FractalNoise3D<>(SimplexNoise.genOctaves(seed, 4, 6));
         this.poolNoiseOver = new FractalNoise3D<>(SimplexNoise.genOctaves(seed, 4, 10));
         this.poolNoiseUnder = new FractalNoise3D<>(SimplexNoise.genOctaves(seed, 1, 14));
-
-        this.densityGeneratorMap.put(Biomes.NETHER_NETHER, this::generateDensityMapBiomeNether);
-        this.densityGeneratorMap.put(Biomes.NETHER_VOLCANIC_ISLANDS, this::generateDensityMapBiomeNether);
-        this.densityGeneratorMap.put(Biomes.NETHER_SULFUR_POOLS, this::generateDensityMapBiomeNether);
-        this.densityGeneratorMap.put(Biomes.NETHER_CRYSTAL_FOREST, this::generateDensityMapBiomeNether);
-        this.densityGeneratorMap.put(Biomes.NETHER_ASH_PLAINS, this::generateDensityMapBiomeNether);
-        this.densityGeneratorMap.put(Biomes.NETHER_BONEYARD, this::generateDensityMapBiomeNether);
     }
 
     @Override
@@ -85,52 +67,13 @@ public class DensityGeneratorNether
         int threeQuarterYSize = ((int) (terrainHeight * 0.75f) / 8) + 1;
 
         double[] densityMapArray = new double[xSize * ySize * zSize];
-        Map<Biome, double[]> perBiomeDensityMaps = new HashMap<>();
-
-        BiomeOverlapPair[] overlapPairs = new BiomeOverlapPair[xSize * zSize];
-
-        // Cache density maps for biome(s)
-        int xSizeScale = 16 / xSize;
-        for (int dx = 0; dx < xSize; dx++) {
-            int ix = dx * xSizeScale + xSizeScale / 2;
-            for (int dz = 0; dz < zSize; dz++) {
-                int iz = dz * xSizeScale + xSizeScale / 2;
-                double temperature = chunk.temperature[ix * 16 + iz];
-                BiomeOverlapPair overlapPair = overlapPairs[dx * xSize + dz] = new BiomeOverlapPair();
-                this.getOverlap(temperature, overlapPair);
-
-                if (!perBiomeDensityMaps.containsKey(overlapPair.biomeA)) {
-                    perBiomeDensityMaps.put(overlapPair.biomeA, this.densityGeneratorMap.get(overlapPair.biomeA).generateDensityMap(chunk, (int) (terrainHeight * 0.75f), xSize, threeQuarterYSize, zSize, x, y, z));
-                }
-                if (overlapPair.hasOverlap) {
-                    if (!perBiomeDensityMaps.containsKey(overlapPair.biomeB)) {
-                        perBiomeDensityMaps.put(overlapPair.biomeB, this.densityGeneratorMap.get(overlapPair.biomeB).generateDensityMap(chunk, (int) (terrainHeight * 0.75f), xSize, threeQuarterYSize, zSize, x, y, z));
-                    }
-                }
-            }
-        }
+        double[] biomeDensityMap = this.generateDensityMap(xSize, threeQuarterYSize, zSize, x, y, z);
 
-        // Merge density maps
         for (int dx = 0; dx < xSize; dx++) {
             for (int dz = 0; dz < zSize; dz++) {
-                BiomeOverlapPair overlapPair = overlapPairs[dx * xSize + dz];
-                double[] densityMapA = perBiomeDensityMaps.get(overlapPair.biomeA);
-                double[] densityMapB = null;
-                if (overlapPair.hasOverlap) {
-                    densityMapB = perBiomeDensityMaps.get(overlapPair.biomeB);
-                }
-
-                if (densityMapB != null) {
-                    for (int dy = 0; dy < threeQuarterYSize; dy++) {
-                        double densityA = densityMapA[(dx * zSize * threeQuarterYSize) + (dz * threeQuarterYSize) + dy];
-                        double densityB = densityMapB[(dx * zSize * threeQuarterYSize) + (dz * threeQuarterYSize) + dy];
-                        densityMapArray[(dx * zSize * ySize) + (dz * ySize) + (dy + quarterYSize - 1)] = MathHelper.lerp(densityA, densityB, overlapPair.biomeBPercentage);
-                    }
-                } else {
-                    for (int dy = 0; dy < threeQuarterYSize; dy++) {
-                        double densityA = densityMapA[(dx * zSize * threeQuarterYSize) + (dz * threeQuarterYSize) + dy];
-                        densityMapArray[(dx * zSize * ySize) + (dz * ySize) + (dy + quarterYSize - 1)] = densityA;
-                    }
+                for (int dy = 0; dy < threeQuarterYSize; dy++) {
+                    double density = biomeDensityMap[(dx * zSize * threeQuarterYSize) + (dz * threeQuarterYSize) + dy];
+                    densityMapArray[(dx * zSize * ySize) + (dz * ySize) + (dy + quarterYSize - 1)] = density;
                 }
             }
         }
@@ -138,7 +81,7 @@ public class DensityGeneratorNether
         return densityMapArray;
     }
 
-    private double[] generateDensityMapBiomeNether(Chunk chunk, int terrainHeight, int xSize, int ySize, int zSize, int x, int y, int z) {
+    private double[] generateDensityMap(int xSize, int ySize, int zSize, int x, int y, int z) {
         double[] densityMapArray = new double[xSize * ySize * zSize];
 
         final double mainNoiseScaleX = 80.0;
@@ -202,664 +145,4 @@ public class DensityGeneratorNether
 
         return densityMapArray;
     }
-
-    private double[] generateDensityMapFractured(Chunk chunk, int terrainHeight, int xSize, int ySize, int zSize, int x, int y, int z) {
-        double[] densityMapArray = new double[xSize * ySize * zSize];
-
-        final double mainNoiseScaleX = 80;
-        final double mainNoiseScaleY = 160;
-        final double mainNoiseScaleZ = 80;
-
-        final double scaleNoiseScaleX = 1.121;
-        final double scaleNoiseScaleZ = 1.121;
-
-        final double depthNoiseScaleX = 200;
-        final double depthNoiseScaleZ = 200;
-
-        final double depthBaseSize = 3d;
-
-        final double coordScale = 684.412D;
-        final double heightScale = 684.412D;
-
-        final double heightStretch = 12;
-
-        final double upperLimitScale = 256;
-        final double lowerLimitScale = 512;
-
-        double ravineNoiseScale = 0.03D;
-
-        double[] scaleArray = this.scaleNoise.getRegion(null, x, z, xSize, zSize, scaleNoiseScaleX, scaleNoiseScaleZ);
-        double[] depthArray = this.depthNoise.getRegion(null, x, z, xSize, zSize, depthNoiseScaleX, depthNoiseScaleZ);
-        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);
-
-        double[] ravineNoise = this.ravineNoise2.setLacunarity(3.0).setPersistence(3.0).getRegion(null, x, z, xSize, zSize, ravineNoiseScale, ravineNoiseScale);
-//        double[] ravineMod = ravineNoise2.getValue(null, x, z, xSize, zSize, ravineNoiseScale, ravineNoiseScale, 0.25D, 0.6D);
-
-        int mainIndex = 0;
-
-        int depthScaleIndex = 0;
-        for (int dx = 0; dx < xSize; dx++) {
-            for (int dz = 0; dz < zSize; dz++) {
-
-                // Calculate scale
-                double scale = (scaleArray[depthScaleIndex] + 256D) / 512D;
-                scale *= 0.5D;
-                if (scale > 1.0D) {
-                    scale = 1.0D;
-                }
-
-                // Calculate depth
-                double depth = depthArray[depthScaleIndex] / 2000D;
-                if (depth < 0.0D) {
-                    depth = -depth * 0.3D;
-                }
-                depth = depth * 3D - 2D;
-
-                if (depth < 0.0D) {
-                    depth /= 2D;
-                    if (depth < -1D) {
-                        depth = -1D;
-                    }
-                    depth /= 1.4D;
-                    depth /= 2D;
-                    scale = 0.0D;
-                } else {
-                    if (depth > 1.0D) {
-                        depth = 1.0D;
-                    }
-                    depth /= 8D;
-                }
-
-                if (scale < 0.0D) {
-                    scale = 0.0D;
-                }
-
-                scale += 0.5D;
-                depth = (depth * (depthBaseSize * 2)) / 16D;
-
-                double offsetY = depthBaseSize + depth * 4D;
-                depthScaleIndex++;
-
-                for (int dy = 0; dy < ySize; dy++) {
-                    double density;
-                    double densityOffset = ((((double) dy - offsetY) * heightStretch) / scale);
-                    if (densityOffset < 0.0D) {
-                        densityOffset *= 4D;
-                    }
-                    double minDensity = minLimitArray[mainIndex] / upperLimitScale;
-                    double maxDensity = maxLimitArray[mainIndex] / lowerLimitScale;
-                    double mainDensity = (mainNoiseArray[mainIndex] / 10D + 1.0D) / 2D;
-
-                    // Constrain density
-                    if (mainDensity < 0.0D) {
-                        density = minDensity;
-                    } else if (mainDensity > 1.0D) {
-                        density = maxDensity;
-                    } else {
-                        density = minDensity + (maxDensity - minDensity) * mainDensity;
-                    }
-                    density -= densityOffset;
-
-                    double densityMod = (1.0f - (3F - dy)) / 3F;
-
-                    float val = (float) ravineNoise[dx * zSize + dz] * 60;
-//                    float mod = (float) ravineMod[dx * zSize + dz];
-                    if (val < 0.3) {
-                        val = 0;
-                    }
-
-                    if (dy > 3) {
-                        density += (val * 6d);
-                    } else {
-                        density += (val * 4d) * (dy / (ySize / 2d)) * (dy / (ySize / 2d));
-
-                    }
-
-                    if (dy > ySize - 7) {
-                        densityMod = (float) (dy - (ySize - 7)) / 6F;
-                        density = density * (1.0D - densityMod) + 300D * densityMod;
-                    }
-
-                    densityMapArray[mainIndex] = density;
-                    mainIndex++;
-                }
-            }
-        }
-        return densityMapArray;
-    }
-
-    private double[] generateDensityMapOcean(Chunk chunk, int terrainHeight, int xSize, int ySize, int zSize, int x, int y, int z) {
-        double[] densityMapArray = new double[xSize * ySize * zSize];
-
-        final double mainNoiseScaleX = 80;
-        final double mainNoiseScaleY = 160;
-        final double mainNoiseScaleZ = 80;
-
-        final double scaleNoiseScaleX = 1.121;
-        final double scaleNoiseScaleZ = 1.121;
-
-        final double depthNoiseScaleX = 200;
-        final double depthNoiseScaleZ = 200;
-
-        final double depthBaseSize = 2.5D;
-
-        final double coordScale = 684.412D;
-        final double heightScale = 684.412D;
-
-        final double heightStretch = 12;
-
-        final double upperLimitScale = 512;
-        final double lowerLimitScale = 512;
-
-        final double noiseScale = 0.02;
-
-        double[] scaleArray = this.scaleNoise.getRegion(null, x, z, xSize, zSize, scaleNoiseScaleX, scaleNoiseScaleZ);
-        double[] depthArray = this.depthNoise.getRegion(null, x, z, xSize, zSize, depthNoiseScaleX, depthNoiseScaleZ);
-        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);
-        double[] islandNoiseArray = this.islandNoise.setLacunarity(2.0).setPersistence(2.0).getRegion(null, x, z, xSize, zSize, noiseScale, noiseScale);
-
-        int mainIndex = 0;
-
-        int depthScaleIndex = 0;
-        for (int dx = 0; dx < xSize; dx++) {
-            for (int dz = 0; dz < zSize; dz++) {
-                // Calculate scale
-                double scale = (scaleArray[depthScaleIndex] + 256D) / 2048D;
-                if (scale > 1.0D) {
-                    scale = 1.0D;
-                }
-
-                // Calculate depth
-                double depth = depthArray[depthScaleIndex] / 2000D;
-                if (depth < 0.0D) {
-                    depth = -depth * 0.3D;
-                }
-                depth = depth * 3D - 2D;
-
-                if (depth < 0.0D) {
-                    depth /= 2D;
-                    if (depth < -1D) {
-                        depth = -1D;
-                    }
-                    depth /= 1.4D;
-                    depth /= 2D;
-                    scale = 0.0D;
-                } else {
-                    if (depth > 1.0D) {
-                        depth = 1.0D;
-                    }
-                    depth /= 8D;
-                }
-
-                if (scale < 0.0D) {
-                    scale = 0.0D;
-                }
-
-                scale += 0.5D;
-                depth = (depth * (depthBaseSize * 2)) / 16D;
-
-                double offsetY = depthBaseSize + depth * 4D;
-                depthScaleIndex++;
-                for (int dy = 0; dy < ySize; dy++) {
-                    double density;
-                    double densityOffset = ((((double) dy - offsetY) * heightStretch) / scale);
-                    if (densityOffset < 0.0D) {
-                        densityOffset *= 4D;
-                    }
-                    double minDensity = minLimitArray[mainIndex] / upperLimitScale;
-                    double maxDensity = maxLimitArray[mainIndex] / lowerLimitScale;
-                    double mainDensity = (mainNoiseArray[mainIndex] / 10D + 1.0D) / 2D;
-
-                    // Constrain density
-                    if (mainDensity < 0.0D) {
-                        density = minDensity;
-                    } else if (mainDensity > 1.0D) {
-                        density = maxDensity;
-                    } else {
-                        density = minDensity + (maxDensity - minDensity) * mainDensity;
-                    }
-                    density -= densityOffset;
-
-                    //Generate islands
-                    float isl = (float) islandNoiseArray[dx * zSize + dz];
-                    if (isl > 0.5) isl = isl * 100;
-                    if (isl < -0.2) isl = -isl * 100;
-
-                    if (dy > ySize - 9) {
-                        double densityMod = (float) (dy - (ySize - 10)) / 6F;
-                        density = density * (1.0D - densityMod) + 100D * densityMod;
-                    }
-
-
-                    density += isl;
-
-
-                    densityMapArray[mainIndex] = density;
-                    mainIndex++;
-                }
-            }
-        }
-        return densityMapArray;
-    }
-
-    private double[] generateDensityMapBiomeTest(Chunk chunk, int terrainHeight, int xSize, int ySize, int zSize, int x, int y, int z) {
-        double[] densityMapArray = new double[xSize * ySize * zSize];
-
-        final double mainNoiseScaleX = 80;
-        final double mainNoiseScaleY = 160;
-        final double mainNoiseScaleZ = 80;
-
-        final double scaleNoiseScaleX = 1.121;
-        final double scaleNoiseScaleZ = 1.121;
-
-        final double depthNoiseScaleX = 200;
-        final double depthNoiseScaleZ = 200;
-
-        final double depthBaseSize = 4D;
-
-        final double coordScale = 684.412D;
-        final double heightScale = 684.412D;
-
-        final double heightStretch = 12;
-
-        final double upperLimitScale = 512;
-        final double lowerLimitScale = 512;
-
-        double[] scaleArray = this.scaleNoise.getRegion(null, x, z, xSize, zSize, scaleNoiseScaleX, scaleNoiseScaleZ);
-        double[] depthArray = this.depthNoise.getRegion(null, x, z, xSize, zSize, depthNoiseScaleX, depthNoiseScaleZ);
-        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);
-        int mainIndex = 0;
-
-        int depthScaleIndex = 0;
-        for (int dx = 0; dx < xSize; dx++) {
-            for (int dz = 0; dz < zSize; dz++) {
-                // Calculate scale
-                double scale = (scaleArray[depthScaleIndex] + 256D) / 2048D;
-                if (scale > 1.0D) {
-                    scale = 1.0D;
-                }
-
-                // Calculate depth
-                double depth = depthArray[depthScaleIndex] / 2000D;
-                if (depth < 0.0D) {
-                    depth = -depth * 0.3D;
-                }
-                depth = depth * 3D - 2D;
-
-                if (depth < 0.0D) {
-                    depth /= 2D;
-                    if (depth < -1D) {
-                        depth = -1D;
-                    }
-                    depth /= 1.4D;
-                    depth /= 2D;
-                    scale = 0.0D;
-                } else {
-                    if (depth > 1.0D) {
-                        depth = 1.0D;
-                    }
-                    depth /= 8D;
-                }
-
-                if (scale < 0.0D) {
-                    scale = 0.0D;
-                }
-
-                scale += 0.5D;
-                depth = (depth * (depthBaseSize * 2)) / 16D;
-
-                double offsetY = depthBaseSize + depth * 4D;
-                depthScaleIndex++;
-                for (int dy = 0; dy < ySize; dy++) {
-                    double density;
-                    double densityOffset = ((((double) dy - offsetY) * heightStretch) / scale);
-                    if (densityOffset < 0.0D) {
-                        densityOffset *= 4D;
-                    }
-                    double minDensity = minLimitArray[mainIndex] / upperLimitScale;
-                    double maxDensity = maxLimitArray[mainIndex] / lowerLimitScale;
-                    double mainDensity = (mainNoiseArray[mainIndex] / 10D + 1.0D) / 2D;
-
-                    // Constrain density
-                    if (mainDensity < 0.0D) {
-                        density = minDensity;
-                    } else if (mainDensity > 1.0D) {
-                        density = maxDensity;
-                    } else {
-                        density = minDensity + (maxDensity - minDensity) * mainDensity;
-                    }
-                    density -= densityOffset;
-
-                    if (dy > ySize - 7) {
-                        double densityMod = (float) (dy - (ySize - 7)) / 6F;
-                        density = density * (1.0D - densityMod) + 300D * densityMod;
-                    }
-
-                    densityMapArray[mainIndex] = density;
-                    mainIndex++;
-                }
-            }
-        }
-
-        return densityMapArray;
-    }
-
-    private double[] generateDensityMapBiomeSulphurPools(Chunk chunk, int terrainHeight, int xSize, int ySize, int zSize, int x, int y, int z) {
-        double[] densityMapArray = new double[xSize * ySize * zSize];
-
-        final double mainNoiseScaleX = 8;
-        final double mainNoiseScaleY = 160;
-        final double mainNoiseScaleZ = 8;
-
-        final double scaleNoiseScaleX = 1.121 * 8192;
-        final double scaleNoiseScaleZ = 1.121 * 8192;
-
-        final double poolNoiseScale = 0.2;
-
-        final double depthNoiseScaleX = 200;
-        final double depthNoiseScaleZ = 200;
-
-        final double depthBaseSize = 3.5D;
-
-        final double coordScale = 6844.412D;
-        final double heightScale = 684.412D;
-
-        final double heightStretch = 512;
-
-        final double upperLimitScale = 512;
-        final double lowerLimitScale = 512;
-
-        double[] scaleArray = this.scaleNoise.getRegion(null, x, z, xSize, zSize, scaleNoiseScaleX, scaleNoiseScaleZ);
-        double[] depthArray = this.depthNoise.getRegion(null, x, z, xSize, zSize, depthNoiseScaleX, depthNoiseScaleZ);
-        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);
-        double[] poolOverArray = this.poolNoiseOver.setLacunarity(1.0).setPersistence(0.2).getRegion(null, x, z, xSize, zSize, poolNoiseScale, poolNoiseScale);
-        double[] poolUnderArray = this.poolNoiseUnder.setLacunarity(0.01).setPersistence(1.0).getRegion(null, x, z, xSize, zSize, poolNoiseScale, poolNoiseScale);
-        int mainIndex = 0;
-
-        int depthScaleIndex = 0;
-        for (int dx = 0; dx < xSize; dx++) {
-            for (int dz = 0; dz < zSize; dz++) {
-                // Calculate scale
-                double scale = (scaleArray[depthScaleIndex] + 256D) / 2048D;
-                if (scale > 1.0D) {
-                    scale = 1.0D;
-                }
-
-                // Calculate depth
-                double depth = depthArray[depthScaleIndex] / 2000D;
-                if (depth < 0.0D) {
-                    depth = -depth * 0.3D;
-                }
-                depth = depth * 3D - 2D;
-
-                if (depth < 0.0D) {
-                    depth /= 2D;
-                    if (depth < -1D) {
-                        depth = -1D;
-                    }
-                    depth /= 1.4D;
-                    depth /= 2D;
-                    scale = 0.0D;
-                } else {
-                    if (depth > 1.0D) {
-                        depth = 1.0D;
-                    }
-                    depth /= 8D;
-                }
-
-                if (scale < 0.0D) {
-                    scale = 0.0D;
-                }
-
-                scale += 0.5D;
-                depth = (depth * (depthBaseSize * 2)) / 16D;
-
-                double offsetY = depthBaseSize + depth * 4D;
-                depthScaleIndex++;
-                for (int dy = 0; dy < ySize; dy++) {
-                    double density;
-                    double densityOffset = ((((double) dy - offsetY) * heightStretch) / scale);
-                    if (densityOffset < 0.0D) {
-                        densityOffset *= 4D;
-                    }
-                    double minDensity = minLimitArray[mainIndex] / upperLimitScale;
-                    double maxDensity = maxLimitArray[mainIndex] / lowerLimitScale;
-                    double mainDensity = (mainNoiseArray[mainIndex] / 10D + 1.0D) / 2D;
-
-                    // Constrain density
-                    if (mainDensity < 0.0D) {
-                        density = minDensity;
-                    } else if (mainDensity > 1.0D) {
-                        density = maxDensity;
-                    } else {
-                        density = minDensity + (maxDensity - minDensity) * mainDensity;
-                    }
-                    density -= densityOffset;
-
-                    if (dy > ySize - 12) {
-                        double densityMod = (float) (dy - (ySize - 12)) / 11F;
-                        density = density * (1.0D - densityMod) + 8000D * densityMod;
-                    }
-                    if (dy < 5) {
-                        double densityMod = (1.0f - (4 - dy)) / 4f;
-                        float val = (float) poolOverArray[dx * zSize + dz];
-                        float mod = (float) poolUnderArray[dx * zSize + dz];
-                        if (val < -20 - (20 * mod)) val = val * 80;
-                        else if (val > 20 + (20 * mod)) val = -val * 80;
-                        else val = 0;
-                        density = density * (1.0 - densityMod) + val * densityMod;
-                    }
-
-                    densityMapArray[mainIndex] = density;
-                    mainIndex++;
-                }
-            }
-        }
-
-        return densityMapArray;
-    }
-
-    private double[] generateDensityMapBiomeVolcanicIslands(Chunk chunk, int terrainHeight, int xSize, int ySize, int zSize, int x, int y, int z) {
-        double[] densityMapArray = new double[xSize * ySize * zSize];
-
-        final double mainNoiseScaleX = 80;
-        final double mainNoiseScaleY = 160;
-        final double mainNoiseScaleZ = 80;
-
-        final double scaleNoiseScaleX = 1.121 * 512;
-        final double scaleNoiseScaleZ = 1.121 * 512;
-
-        final double depthNoiseScaleX = 200;
-        final double depthNoiseScaleZ = 200;
-
-        final double depthBaseSize = 3.5D;
-
-        final double coordScale = 6844.412D;
-        final double heightScale = 684.412D;
-
-        final double heightStretch = 64;
-
-        final double upperLimitScale = 512;
-        final double lowerLimitScale = 512;
-
-        double[] scaleArray = this.scaleNoise.getRegion(null, x, z, xSize, zSize, scaleNoiseScaleX, scaleNoiseScaleZ);
-        double[] depthArray = this.depthNoise.getRegion(null, x, z, xSize, zSize, depthNoiseScaleX, depthNoiseScaleZ);
-        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);
-        int mainIndex = 0;
-
-        int depthScaleIndex = 0;
-        for (int dx = 0; dx < xSize; dx++) {
-            for (int dz = 0; dz < zSize; dz++) {
-                // Calculate scale
-                double scale = (scaleArray[depthScaleIndex] + 256D) / 2048D;
-                if (scale > 1.0D) {
-                    scale = 1.0D;
-                }
-
-                // Calculate depth
-                double depth = depthArray[depthScaleIndex] / 2000D;
-                if (depth < 0.0D) {
-                    depth = -depth * 0.3D;
-                }
-                depth = depth * 3D - 2D;
-
-                if (depth < 0.0D) {
-                    depth /= 2D;
-                    if (depth < -1D) {
-                        depth = -1D;
-                    }
-                    depth /= 1.4D;
-                    depth /= 2D;
-                    scale = 0.0D;
-                } else {
-                    if (depth > 1.0D) {
-                        depth = 1.0D;
-                    }
-                    depth /= 8D;
-                }
-
-                if (scale < 0.0D) {
-                    scale = 0.0D;
-                }
-
-                scale += 0.5D;
-                depth = (depth * (depthBaseSize * 2)) / 16D;
-
-                double offsetY = depthBaseSize + depth * 4D;
-                depthScaleIndex++;
-                for (int dy = 0; dy < ySize; dy++) {
-                    double density;
-                    double densityOffset = ((((double) dy - offsetY) * heightStretch) / scale);
-                    if (densityOffset < 0.0D) {
-                        densityOffset *= 4D;
-                    }
-                    double minDensity = minLimitArray[mainIndex] / upperLimitScale;
-                    double maxDensity = maxLimitArray[mainIndex] / lowerLimitScale;
-                    double mainDensity = (mainNoiseArray[mainIndex] / 10D + 1.0D) / 2D;
-
-                    // Constrain density
-                    if (mainDensity < 0.0D) {
-                        density = minDensity;
-                    } else if (mainDensity > 1.0D) {
-                        density = maxDensity;
-                    } else {
-                        density = minDensity + (maxDensity - minDensity) * mainDensity;
-                    }
-                    density -= densityOffset;
-
-                    if (dy < 4) {
-                        double densityMod = (1.0f - (3 - dy)) / 3f;
-                        density = density * (1.0 - densityMod) + scaleArray[dx * zSize + dz] * densityMod;
-                    }
-                    if (dy > ySize - 8) {
-                        double densityMod = (float) (dy - (ySize - 8)) / 7F;
-                        density = density * (1.0D - densityMod) + 8000D * densityMod;
-                    }
-
-                    densityMapArray[mainIndex] = density;
-                    mainIndex++;
-                }
-            }
-        }
-
-        return densityMapArray;
-    }
-
-    public void getOverlap(double temperature, BiomeOverlapPair toUpdate) {
-        final float overlapAmount = 1f / (float)(BiomeProviderNether.BIOMES_ORDER.length * 4);
-
-        for (int i = 0; i < BiomeProviderNether.BIOMES_ORDER.length; i++) {
-            // The *strict* bounds of the biome, as used by this::lookupBiome()
-            final float minStrict = (1.0f / BiomeProviderNether.BIOMES_ORDER.length) * i;
-            final float maxStrict = (1.0f / BiomeProviderNether.BIOMES_ORDER.length) * (i + 1);
-            // The outer bounds of the biome, i.e. strict bounds plus overlap amount. If in this range, guaranteed overlap
-            final float minOuter = minStrict - overlapAmount;
-            final float maxOuter = maxStrict + overlapAmount;
-            // The inner bounds of the biome, i.e. strict bounds minus overlap amount. If in this range, guaranteed no overlap
-            final float minInner = minStrict + overlapAmount;
-            final float maxInner = maxStrict - overlapAmount;
-
-            // Edge cases
-            if ((i == 0 && temperature < minInner) ||
-                (i == BiomeProviderNether.BIOMES_ORDER.length - 1 && temperature >= maxInner)
-            ) {
-                toUpdate.hasOverlap = false;
-                toUpdate.biomeA = BiomeProviderNether.BIOMES_ORDER[i];
-                toUpdate.biomeAPercentage = 1.0f;
-                return;
-            }
-
-            // Check if in inner bounds
-            if (temperature >= minInner && temperature < maxInner) {
-                // In inner bounds - only one biome is present here
-                toUpdate.hasOverlap = false;
-                toUpdate.biomeA = BiomeProviderNether.BIOMES_ORDER[i];
-                toUpdate.biomeAPercentage = 1.0f;
-                return;
-            }
-
-            // Check if in outer bounds
-            if (temperature >= minOuter && temperature < maxOuter) {
-                // Check if in min outer bound
-                if (temperature < minInner) {
-                    toUpdate.hasOverlap = true;
-
-                    // Define biomes
-                    toUpdate.biomeA = BiomeProviderNether.BIOMES_ORDER[i - 1];
-                    toUpdate.biomeB = BiomeProviderNether.BIOMES_ORDER[i];
-
-                    // Define percentages
-                    final float range = minInner - minOuter;
-                    // minOuter <= temperature < minInner
-                    temperature -= minOuter; // 0 <= temperature < range
-                    temperature /= range; // 0 <= temperature < 1
-
-                    // Biome A goes to 0.0 as temperature goes to 1.0
-                    // Biome B goes to 1.0 as temperature goes to 1.0
-                    toUpdate.biomeAPercentage = 1.0f - (float) temperature;
-                    toUpdate.biomeBPercentage = (float) temperature;
-
-                    return;
-                }
-
-                // Check if in max outer bound
-                if (temperature >= maxInner) {
-                    toUpdate.hasOverlap = true;
-
-                    // Define biomes
-                    toUpdate.biomeA = BiomeProviderNether.BIOMES_ORDER[i];
-                    toUpdate.biomeB = BiomeProviderNether.BIOMES_ORDER[i + 1];
-
-                    // Define percentages
-                    final float range = maxOuter - maxInner;
-                    // maxInner <= temperature < maxOuter
-                    temperature -= maxInner; // 0 <= temperature < range
-                    temperature /= range; // 0 <= temperature < 1
-
-                    // Biome A goes to 0.0 as temperature goes to 1.0
-                    // Biome B goes to 1.0 as temperature goes to 1.0
-                    toUpdate.biomeAPercentage = 1.0f - (float) temperature;
-                    toUpdate.biomeBPercentage = (float) temperature;
-
-                    return;
-                }
-            }
-        }
-
-        // We should never get here!
-        toUpdate.hasOverlap = false;
-        toUpdate.biomeA = Biomes.NETHER_NETHER;
-        toUpdate.biomeAPercentage = 1.0f;
-        return;
-    }
 }
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 98a4889b0..a10760ca0 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
@@ -27,6 +27,13 @@ public class SurfaceGeneratorNether
         this.soilNoise = new FractalNoise3D<>(ImprovedPerlinNoise.genOctaves(world.getRandomSeed(), 4, 44));
     }
 
+    private boolean safeToPlacePool(ChunkGeneratorResult result, int x, int y, int z) {
+        if(x<0||x>15||z<0||z>15) return true;
+        if(y < world.getWorldType().getMinY(world) || y > world.getWorldType().getMaxY(world)) return false;
+        int id = result.getBlock(x, y, z);
+        return id != 0;
+    }
+
     @Override
     public void generateSurface(@NotNull Chunk chunk, @NotNull ChunkGeneratorResult result) {
         int oceanY = this.world.getWorldType().getOceanY();
@@ -44,10 +51,12 @@ public class SurfaceGeneratorNether
         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[] sandPatchNoise = this.soilNoise.getRegion(null, chunkX * 16, chunkZ * 16, 0.0D, 16, 16, 1, beachScale * 2D, beachScale * 2D, beachScale);
         for (int z = 0; z < 16; z++) {
             for (int x = 0; x < 16; x++) {
-                boolean generateSandBeach = sandBeachNoise[z + x * 16] + rand.nextDouble() * 0.2D > 0.0D;
+                boolean generateSoulSandBeach = sandBeachNoise[z + x * 16] + rand.nextDouble() * 0.2D > 0.0D;
                 boolean generateGravelBeach = gravelBeachNoise[z + x * 16] + rand.nextDouble() * 0.2D > 0.0D;
+                boolean generateSulfurPool = gravelBeachNoise[z + x * 16] / 3D + 3D + rand.nextDouble() * 0.5D > 3.D;
                 int soilThickness = (int) (soilThicknessNoise[z + x * 16] / 3D + 3D + rand.nextDouble() * 0.25D);
 
                 int currentLayerDepth = -1;
@@ -75,7 +84,7 @@ public class SurfaceGeneratorNether
                         continue;
                     }
 
-                    // will skip a generation loop if it encounters something other than netherrack
+                    // will skip a generation loop if it encounters something other than Netherrack (or basalt for the sulfur pools)
                     if (block != worldFillBlock) {
                         continue;
                     }
@@ -85,7 +94,7 @@ 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) worldFillBlock;
+                            fillerBlock = (short) Blocks.NETHERRACK.id();
                         } else {
                             boolean biomeGeneratesSulfur = biome == Biomes.NETHER_SULFUR_POOLS;
 
@@ -93,20 +102,17 @@ public class SurfaceGeneratorNether
                                 // Generate coastlines
                                 topBlock = biome.topBlock;
                                 fillerBlock = biome.fillerBlock;
-                                if (biomeGeneratesSulfur) {
-                                    topBlock = (short) Blocks.SULFUR.id();
-                                    fillerBlock = (short) Blocks.SULFUR.id();
-                                } else if (generateGravelBeach) {
+                                if (generateGravelBeach) {
                                     topBlock = (short) Blocks.GRAVEL.id();
-                                    fillerBlock = (short) Blocks.COBBLE_NETHERRACK.id();
-                                } else if (generateSandBeach) {
+                                    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.SULFUR.id();
-                                    fillerBlock = (short) Blocks.SULFUR.id();
+                                    topBlock = (short) Blocks.BRIMSAND.id();
+                                    fillerBlock = (short) Blocks.BRIMSAND.id();
                                 }
                             }
                         }
@@ -116,7 +122,7 @@ public class SurfaceGeneratorNether
                             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;
+                        currentLayerDepth = soilThickness+2;
 
                         // set block at index to designated surface block above ocean level
                         if (y >= minY + oceanY - 1) {
@@ -133,6 +139,14 @@ public class SurfaceGeneratorNether
                         currentLayerDepth--;
                         result.setBlock(x, y, z, fillerBlock);
                     }
+
+                    if(currentLayerDepth == 0)
+                    {
+                        if(biome == Biomes.NETHER_SULFUR_POOLS && fillerBlock == Blocks.BRIMSAND.id()) {
+                            currentLayerDepth = rand.nextInt(4) + 2;
+                            fillerBlock = (short)Blocks.BRIMSAND.id();
+                        }
+                    }
                 }
             }
         }
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 1e165ed96..28ffa3202 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
@@ -24,10 +24,6 @@ public class TerrainGeneratorNether
     private final int lastX = Integer.MIN_VALUE;
     private final int lastZ = Integer.MIN_VALUE;
     private @Nullable Biome lastBiome = null;
-    private @Nullable Biome lastBiomeNegX = null;
-    private @Nullable Biome lastBiomePosX = null;
-    private @Nullable Biome lastBiomeNegZ = null;
-    private @Nullable Biome lastBiomePosZ = null;
     private final Object2IntMap<Biome> fluidLookup = new Object2IntArrayMap<>();
     private int lastChunkX = Integer.MIN_VALUE;
     private int lastChunkZ = Integer.MIN_VALUE;
@@ -41,52 +37,29 @@ public class TerrainGeneratorNether
 
         this.densityGenerator = new DensityGeneratorNether(world);
 
-        this.fluidLookup.put(Biomes.NETHER_NETHER, Blocks.FLUID_LAVA_STILL.id());
         this.fluidLookup.put(Biomes.NETHER_VOLCANIC_ISLANDS, Blocks.FLUID_LAVA_STILL.id());
-        this.fluidLookup.put(Biomes.NETHER_SULFUR_POOLS, Blocks.FLUID_WATER_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_ASH_PLAINS, Blocks.OBSIDIAN.id());
-        this.fluidLookup.put(Biomes.NETHER_BONEYARD, Blocks.COBBLE_NETHERRACK.id());
+        this.fluidLookup.put(Biomes.NETHER_CRYSTAL_FIELD, Blocks.FLUID_WATER_STILL.id());
+        this.fluidLookup.put(Biomes.NETHER_OLD_WORLD, Blocks.FLUID_WATER_STILL.id());
 
         this.minY = world.getWorldType().getMinY(world);
         this.maxY = world.getWorldType().getMaxY(world);
+
     }
 
     @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);
-            if (x - 1 < 0) {
-                this.lastBiomeNegX = this.lastBiome;
-            } else {
-                this.lastBiomeNegX = chunk.getBlockBiome(x - 1, 0, z);
-            }
-            if (x + 1 >= Chunk.CHUNK_SIZE_X) {
-                this.lastBiomePosX = this.lastBiome;
-            } else {
-                this.lastBiomePosX = chunk.getBlockBiome(x + 1, 0, z);
-            }
-            if (z - 1 < 0) {
-                this.lastBiomeNegZ = this.lastBiome;
-            } else {
-                this.lastBiomeNegZ = chunk.getBlockBiome(x, 0, z - 1);
-            }
-            if (z + 1 >= Chunk.CHUNK_SIZE_Z) {
-                this.lastBiomePosZ = this.lastBiome;
-            } else {
-                this.lastBiomePosZ = chunk.getBlockBiome(x, 0, z + 1);
-            }
         }
+
         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);
-
-            float avg = 0;
-            for (int i = 0; i < this.cache.length; i++) {
-                avg += this.cache[i];
-            }
-            avg /= this.cache.length;
         }
 
         WorldType type = this.world.getWorldType();
@@ -96,45 +69,21 @@ public class TerrainGeneratorNether
             return 0;
         } else if (y >= maxY - this.rand.nextInt(5)) {
             return Blocks.BEDROCK.id();
-        }
-//        else if (y >= quarterHeight - rand.nextInt(5) && y <= quarterHeight + 5 - rand.nextInt(5))
-//        {
-//            return Blocks.BEDROCK.id();
-//        }
-//        else if (y <= type.getMinY() + rand.nextInt(5))
-//        {
-//            return Blocks.BEDROCK.id();
-//        }
-//        else if (y <= quarterHeight)
-//        {
-//            return Blocks.SOULSCHIST.id();
-//        }
-        else if (y <= quarterHeight + this.rand.nextInt(5)) {
+        } else if (y <= quarterHeight + this.rand.nextInt(5)) {
             return Blocks.BEDROCK.id();
         } else if (density > 0.0) {
-            if (this.lastBiome == Biomes.NETHER_CRYSTAL_FOREST) {
+            if (this.lastBiome == Biomes.NETHER_CRYSTAL_FOREST || this.lastBiome == Biomes.NETHER_CRYSTAL_FIELD) {
                 int sizeY = Chunk.CHUNK_SECTIONS * ChunkSection.SECTION_SIZE_Y;
                 // YZX ordering
                 final double d = ((this.cache[(x * Chunk.CHUNK_SIZE_Z * sizeY) + (z * sizeY) + y]));
-                if (d > 0 && d < 2) {
-                    return Blocks.CRYSTAL.id();
-                } else if (d > -1 && d < 3) {
-                    return Blocks.COBBLE_NETHERRACK_MOSSY.id();
+                if (d > -1 && d < 2) {
+                    return Blocks.COBBLE_NETHERRACK_CRYSTALLINE.id();
                 }
             }
-            return type.getFillerBlockId();
-        } else if (y >= quarterHeight && y < type.getOceanY()) {
-            int biomeFluid = this.fluidLookup.getOrDefault(this.lastBiome, 0);
-            int biomeFluidNegX = this.fluidLookup.getOrDefault(this.lastBiomeNegX, 0);
-            int biomeFluidPosX = this.fluidLookup.getOrDefault(this.lastBiomePosX, 0);
-            int biomeFluidNegZ = this.fluidLookup.getOrDefault(this.lastBiomeNegZ, 0);
-            int biomeFluidPosZ = this.fluidLookup.getOrDefault(this.lastBiomePosZ, 0);
-
-            if (biomeFluid == biomeFluidNegX && biomeFluid == biomeFluidPosX && biomeFluid == biomeFluidNegZ && biomeFluid == biomeFluidPosZ) {
-                return biomeFluid;
-            }
 
-            return Blocks.OBSIDIAN.id();
+            return type.getFillerBlockId();
+        } else if (y < type.getOceanY()) {
+            return this.fluidLookup.getOrDefault(this.lastBiome, 0);
         }
 
         return 0;
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/WorldFeatureChimney.java
new file mode 100644
index 000000000..c895b7b97
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureChimney.java
@@ -0,0 +1,109 @@
+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 java.util.Random;
+
+public class WorldFeatureChimney extends WorldFeature{
+
+    final double[][] RADIUS_MAPS = {
+            {1.5D, 1.5D, 1D, 1D, 0D, 0D},
+            {1.5D, 1D, 1D, 0D, 0D},
+            {1.5D, 1D, 1D, 1D, 0D, 0D},
+            {1D, 0D, 0D},
+            {1.5D}
+    };
+
+    @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(); }
+
+        double[] map = RADIUS_MAPS[random.nextInt(5)];
+        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);
+
+                for (int i = 0; i < map.length; 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++) {
+
+                        if (!world.isAirBlock(circlePos)) {
+                            if (random.nextInt(8) == 0)
+                                world.setBlockType(circlePos, Blocks.MAGMA);
+                            else if (random.nextInt(4) == 0)
+                                world.setBlockType(circlePos, Blocks.COBBLE_BASALT);
+                        }
+                    }
+                }
+            }
+        }
+
+
+        return true;
+    }
+
+    private Block<?> getBlockAt(Vector2i center, int x, int z){
+        if(x == center.x && z == center.y){
+            return Blocks.THERMAL_VENT;
+        }
+        return Blocks.COBBLE_BASALT;
+    }
+
+    private void plotBlocks(World world, Vector2i center, TilePos queryPos, int x, int y, int 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);
+        }
+
+        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);
+            world.setBlockType(queryPos.set(xx, y, center.y - x), block);
+        }
+    }
+
+    private void generateRadius(World world, Vector2i center, TilePos queryPos, double r, int y){
+        int x = 0;
+        int z = (int) r;
+        double d = 3 - 2 * r;
+        plotBlocks(world, center, queryPos, x, y, z);
+        while (z > x){
+            x++;
+            if (d > 0) {
+                z--;
+                d = d + 4 * (x - z) + 10;
+            }  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))
+                        return false;
+                }
+            }
+        }
+        return true;
+    }
+
+
+}
diff --git a/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureColdFire.java b/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureColdFire.java
deleted file mode 100644
index 35bf1e3e5..000000000
--- a/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureColdFire.java
+++ /dev/null
@@ -1,31 +0,0 @@
-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 WorldFeatureColdFire implements WorldFeatureInterface {
-    @Override
-    public boolean place(final @NotNull World world, final @NotNull Random random, final @NotNull TilePosc tilePos) {
-        final @NotNull TilePos ti = new TilePos();
-        final @NotNull TilePos tiDown = new TilePos();
-
-        for (int i = 0; i < 64; i++) {
-            ti.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)
-            );
-
-            if (world.isAirBlock(ti) && world.getBlockType(ti.down(tiDown)) == Blocks.SOULSAND) {
-                world.setBlockTypeNotify(ti, Blocks.FIRE_COLD);
-            }
-        }
-
-        return true;
-    }
-}
diff --git a/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureCrystal.java b/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureCrystal.java
deleted file mode 100644
index 7fab98906..000000000
--- a/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureCrystal.java
+++ /dev/null
@@ -1,105 +0,0 @@
-package net.minecraft.core.world.generate.feature;
-
-import net.minecraft.core.block.Blocks;
-import net.minecraft.core.world.World;
-
-import java.util.Random;
-
-public class WorldFeatureCrystal extends WorldFeature
-{
-
-    final double[] RADIUS_MAP_1 = {1.5D, 1D, 0D};
-    @Override
-    public boolean place(World world, Random random, int x, int y, int z) {
-
-        int yf = y;
-            int checksForGround = 0;
-
-
-
-            boolean canGenerate = false;
-            while (true) {
-                int blockBelow = world.getBlockId(x, yf - 1, x);
-
-                if (checksForGround > 64 || yf <= 0) {
-                    break;
-                }
-
-                if (Blocks.solid[blockBelow] && blockBelow == Blocks.COBBLE_NETHERRACK.id()) {
-                    canGenerate = true;
-                    break;
-                }
-
-                yf--;
-                checksForGround++;
-            }
-
-            if (canGenerate && world.isAirBlock(x, yf, z)) {
-//                for (int xi = x1 - 1; xi <= x1 + 1; xi++) {
-//                    for (int zi = z1 - 1; zi <= z1 + 1; zi++) {
-//                        for (int yi = yf; yi <= yf + height; yi++) {
-//                            world.setBlock(xi, yi, zi, Blocks.CRYSTAL.id);
-//                        }
-//                    }
-//                }
-//                //cap crystal
-//
-//                for (int i = 0; i < radiusMap.length; i++) {
-//                    generateRadius(world, x1, z1, radiusMap[i], yf + 1 + height + i);
-//                }
-
-                for (int xi = x - 5; xi < x + 5; xi++) {
-                    for (int zi = x - 5; zi < x + 5; zi++) {
-                        for (int yi = yf - 3; yi < yf + 3; yi++) {
-                            if (world.getBlockId(xi, yi, zi) == Blocks.COBBLE_NETHERRACK.id() && world.getBlockId(xi, yi + 1, zi) == 0 && random.nextInt(5) != 0)
-                                world.setBlock(xi, yi, zi, Blocks.NETHERRACK_CRYSTALLINE.id());
-                        }
-                    }
-                }
-            }
-
-
-        return true;
-    }
-
-    private void plotBlocks(World world, int id, int xc, int zc, int x, int y, int z){
-        for(int xx = xc - x; xx <= xc + x; xx++) {
-            world.setBlock(xx, y, zc + z ,id);
-            world.setBlock(xx, y, zc - z, id);
-        }
-
-        for(int xx = xc - z; xx <= xc + z; xx++) {
-            world.setBlock(xx, y, zc + x, id);
-            world.setBlock(xx, y, zc - x, id);
-        }
-    }
-
-    private void generateRadius(World world, int xc, int zc, double r, int y){
-        int x = 0;
-        int z = (int) r;
-        double d = 3 - 2 * r;
-        plotBlocks(world, Blocks.CRYSTAL.id(), xc, zc, x, y, z);
-        while (z > x){
-            x++;
-            if (d > 0) {
-                z--;
-                d = d + 4 * (x - z) + 10;
-            }  else
-                d = d + 4 * x + 6;
-
-            plotBlocks(world, Blocks.CRYSTAL.id(), xc, zc, x, y, z);
-        }
-    }
-
-    private boolean hasSpace(World world, int xc, int y, int zc){
-        for (int xi = xc - 1; xi < xc + 1; xi++) {
-            for (int zi = zc - 1; zi < zc + 1; zi++) {
-                if (world.getBlockId(xi, y-1, zi) != Blocks.COBBLE_NETHERRACK.id())
-                    return false;
-            }
-        }
-        return true;
-    }
-
-
-}
diff --git a/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureCrystalCeiling.java b/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureCrystalCeiling.java
deleted file mode 100644
index 863746d29..000000000
--- a/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureCrystalCeiling.java
+++ /dev/null
@@ -1,54 +0,0 @@
-package net.minecraft.core.world.generate.feature;
-
-import net.minecraft.core.block.Blocks;
-import net.minecraft.core.world.World;
-
-import java.util.Random;
-
-public class WorldFeatureCrystalCeiling extends WorldFeature
-{
-
-    @Override
-    public boolean place(World world, Random random, int x, int y, int z)
-    {
-        int x1 = (x + random.nextInt(2)) - random.nextInt(2);
-        int z1 = (z + random.nextInt(2)) - random.nextInt(2);
-        int yF = y;
-
-        double heightMod = 5D;
-
-        int checksForGround = 0;
-        boolean canGenerate = false;
-        while(true){
-            int blockBelow = world.getBlockId(x1, yF+1, z1);
-
-            if(checksForGround > 64 || yF >= world.getHeightBlocks()) {
-                break;
-            }
-
-            if(Blocks.solid[blockBelow]){
-                canGenerate = true;
-                break;
-            }
-
-            yF ++;
-            checksForGround++;
-        }
-
-        double currentHeight = heightMod + random.nextInt(10);
-
-        if(canGenerate && world.isAirBlock(x1, yF, z1))
-        {
-            for(int xi = x - 1; xi < x + 1; xi++){
-                for(int zi = z - 1; zi < z + 1; zi++){
-                    for(int yi = yF; yi > yF - currentHeight; yi--){
-                        world.setBlock(xi, yi, zi, Blocks.CRYSTAL.id());
-                    }
-                    currentHeight = (currentHeight * 0.25D) + random.nextInt(2);
-                }
-            }
-        }
-
-        return true;
-    }
-}
diff --git a/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureCrystalChunk.java b/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureCrystalChunk.java
deleted file mode 100644
index 7b7847676..000000000
--- a/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureCrystalChunk.java
+++ /dev/null
@@ -1,209 +0,0 @@
-package net.minecraft.core.world.generate.feature;
-
-import net.minecraft.core.block.Blocks;
-import net.minecraft.core.util.helper.MathHelper;
-import net.minecraft.core.world.World;
-
-import java.util.Random;
-
-import static net.minecraft.core.util.helper.MathHelper.cos;
-import static net.minecraft.core.util.helper.MathHelper.sin;
-
-public class WorldFeatureCrystalChunk extends WorldFeature {
-    private final boolean isCeiling;
-    private final int minHeight;
-    private final int heightMod;
-    private final boolean decorateFloor;
-
-    public WorldFeatureCrystalChunk(boolean isCeiling) {
-        this.isCeiling = isCeiling;
-        this.minHeight = 30;
-        this.heightMod = 50;
-        this.decorateFloor = false;
-    }
-
-    public WorldFeatureCrystalChunk(boolean isCeiling, int minHeight, int heightMod, boolean decorateFloor) {
-        this.isCeiling = isCeiling;
-        this.minHeight = minHeight;
-        this.heightMod = heightMod;
-        this.decorateFloor = decorateFloor;
-    }
-
-    @Override
-    public boolean place(World world, Random random, int x, int y, int z) {
-        int x1 = (x + random.nextInt(2)) - random.nextInt(2);
-        int z1 = (z + random.nextInt(2)) - random.nextInt(2);
-        int yF = y;
-
-        int checksForGround = 0;
-        boolean canGenerate = false;
-        while(true){
-            if(isCeiling)
-                yF ++;
-            else
-                yF --;
-
-            if(checksForGround > 64 || yF < 0 || yF >= world.getHeightBlocks()) {
-                break;
-            }
-
-            int blockId = world.getBlockId(x1, yF, z1);
-
-            if(Blocks.solid[blockId]){
-                canGenerate = true;
-                break;
-            }
-
-            checksForGround++;
-        }
-
-        int blockId = world.getBlockId(x1, yF, z1);
-        if(canGenerate && (blockId == Blocks.COBBLE_NETHERRACK.id() || blockId == Blocks.SOULSAND.id())) {
-            if (isCeiling) {
-                yF += 5;
-            } else {
-                yF -= 5;
-            }
-
-            placeCrystal(world, random, x1, yF, z1);
-
-            return true;
-        }
-
-        return false;
-    }
-
-    private void placeCrystal(World world, Random random, final int x, int y, final int z) {
-        float r = random.nextFloat();
-        int length = minHeight + MathHelper.floor(Math.pow(r, 4) * heightMod);
-        int base = length / 4;
-        float rx = 0;
-        float ry = 0;
-        float rz = 0;
-
-        y = isCeiling ? y + (base/2) : y - (base/2);
-
-        if(!hasSpace(world, x, y, z, base)){
-            return;
-        }
-
-        if (isCeiling) {
-            rx = MathHelper.PI;
-        }
-        rx += (float) ((random.nextDouble() - 0.5) * (Math.PI / 2)) * 0.8f;
-        ry += (float) (random.nextDouble() * 2.0 * Math.PI);
-
-        float rxSin = sin(rx), rxCos = cos(rx);
-        float rySin = sin(ry), ryCos = cos(ry);
-        float rzSin = sin(rz), rzCos = cos(rz);
-
-        float[][] rxMatrix = {
-            {1, 0, 0},
-            {0, rxCos, -rxSin},
-            {0, rxSin, rxCos}
-        };
-        float[][] ryMatrix = {
-            {ryCos, 0, rySin},
-            {0, 1, 0},
-            {-rySin, 0, ryCos}
-        };
-        float[][] rzMatrix = {
-            {rzCos, -rzSin, 0},
-            {rzSin, rzCos, 0},
-            {0, 0, 1}
-        };
-
-        float[][] matrix = mult(ryMatrix, rxMatrix);
-
-        int i = 0;
-        for (int dy = 0; dy < length; dy++) {
-            for (int dx = 0; dx < base; dx++) {
-                for (int dz = 0; dz < base; dz++) {
-
-                    if (dy <= base + random.nextInt(4)) {
-                        place(world, Blocks.NETHERRACK_CRYSTALLINE.id(), x, y, z, dx + i + 0.5f, dy + 0.5f, dz + i + 0.5f, matrix);
-                    } else {
-                        place(world, Blocks.CRYSTAL.id(), x, y, z, dx + i + 0.5f, dy + 0.5f, dz + i + 0.5f, matrix);
-                    }
-                }
-            }
-            if (random.nextFloat() > 0.95) {
-                base -= 2;
-                i += 1;
-                if (base <= 1) {
-                    break;
-                }
-            }
-        }
-        int yy = length;
-        while (true) {
-            base -= 2;
-            i += 1;
-            if (base <= 0) {
-                break;
-            }
-            for (int dx = 0; dx < base; dx++) {
-                for (int dz = 0; dz < base; dz++) {
-                    place(world, Blocks.CRYSTAL.id(), x, y, z, dx + i, yy, dz + i, matrix);
-                }
-            }
-            yy++;
-        }
-    }
-
-    private float[][] mult(float[][] ma, float[][] mb) {
-        float a = ma[0][0], b = ma[0][1], c = ma[0][2],
-              d = ma[1][0], e = ma[1][1], f = ma[1][2],
-              g = ma[2][0], h = ma[2][1], i = ma[2][2];
-        float j = mb[0][0], k = mb[0][1], l = mb[0][2],
-              m = mb[1][0], n = mb[1][1], o = mb[1][2],
-              p = mb[2][0], q = mb[2][1], r = mb[2][2];
-
-        return new float[][] {
-            {a * j + b * m + c * p, a * k + b * n + c * q, a * l + b * o + c * r},
-            {d * j + e * m + f * p, d * k + e * n + f * q, d * l + e * o + f * r},
-            {g * j + h * m + i * p, g * k + h * n + i * q, g * l + h * o + i * r}
-        };
-    }
-
-    private float[] mult(float[][] m, float[] v) {
-        return new float[] {
-            m[0][0] * v[0] + m[0][1] * v[1] + m[0][2] * v[2],
-            m[1][0] * v[0] + m[1][1] * v[1] + m[1][2] * v[2],
-            m[2][0] * v[0] + m[2][1] * v[1] + m[2][2] * v[2]
-        };
-    }
-
-    private void place(World world, int id, int ox, int oy, int oz, float dx, float dy, float dz, float[][] m) {
-        float[] v = {dx, dy, dz};
-        float[] t = mult(m, v);
-
-        int x = ox + MathHelper.floor(t[0]);
-        int y = oy + MathHelper.floor(t[1]);
-        int z = oz + MathHelper.floor(t[2]);
-
-        world.setBlock(x, y, z, id);
-        world.setBlock(x + 1, y, z, id);
-        world.setBlock(x, y, z + 1, id);
-        world.setBlock(x + 1, y, z + 1, id);
-
-        world.setBlock(x, y + 1, z, id);
-        world.setBlock(x + 1, y + 1, z, id);
-        world.setBlock(x, y + 1, z + 1, id);
-        world.setBlock(x + 1, y + 1, z + 1, id);
-
-        world.markBlocksDirty(x, y, z, x + 1, y + 1, z + 1);
-    }
-
-    private boolean hasSpace(World world, int x, int y, int z, int base){
-        for (int yi = y; yi < y + base; yi++) {
-            for (int xi = x - base; xi < x + base; xi++) {
-                for (int zi = z - base; zi < z + base; zi++) {
-                    if (world.getBlockId(xi, yi, zi) == Blocks.CRYSTAL.id())
-                        return false;
-                }
-            }
-        }
-        return true;
-    }
-}
diff --git a/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureCrystalCone.java b/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureCrystalCone.java
deleted file mode 100644
index 742866bed..000000000
--- a/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureCrystalCone.java
+++ /dev/null
@@ -1,167 +0,0 @@
-package net.minecraft.core.world.generate.feature;
-
-import net.minecraft.core.block.Blocks;
-import net.minecraft.core.util.helper.MathHelper;
-import net.minecraft.core.world.World;
-
-import java.util.Random;
-
-public class WorldFeatureCrystalCone extends WorldFeature
-{
-    private final boolean ceilingGen;
-
-    public WorldFeatureCrystalCone(boolean ceilingGen){
-        this.ceilingGen = ceilingGen;
-    }
-
-    protected void assignValue(int x, int y, int z, int id, int meta, World world) {
-        /* assign a block to the world */
-        world.setBlockAndMetadata(x, y, z, id, meta);
-    }
-
-    protected void crossSection(int[] center, double radius, int dirAxis, int id, int data, World world) {
-        int rad = MathHelper.floor(radius + 0.618);
-        int secidx1 = (dirAxis + 1) % 3;
-        int secidx2 = (dirAxis + 2) % 3;
-        int[] coord = new int[3];
-        for (int off1 = -rad; off1 < rad + 1; off1++) {
-            for (int off2 = -rad; off2 < rad + 1; off2++) {
-                double thisDist = Math.sqrt(Math.pow(Math.abs(off1) + 0.5, 2) + Math.pow(Math.abs(off2) + 0.5, 2));
-                if (thisDist > radius) {
-                    continue;
-                }
-                int pri = center[dirAxis];
-                int sec1 = center[secidx1] + off1;
-                int sec2 = center[secidx2] + off2;
-                coord[dirAxis] = pri;
-                coord[secidx1] = sec1;
-                coord[secidx2] = sec2;
-                assignValue(coord[0], coord[1], coord[2], id, data, world);
-            }
-        }
-    }
-
-    protected void taperedLimb(int[] start, int[] end, double startSize, double endSize, World world) {
-        // delta is the coordinate vector for the difference between
-        // start and end.
-        int[] delta = new int[] { end[0] - start[0], end[1] - start[1], end[2] - start[2] };
-        int maxDist = MathHelper.maxAbs(delta[0], MathHelper.maxAbs(delta[1], delta[2]));
-        if (maxDist == 0) {
-            return;
-        }
-        int primidx = 0;
-        for (int i = 0; i < 3; i++) {
-            if (delta[i] == maxDist) {
-                primidx = i;
-                break;
-            }
-        }
-        // secidx1 and secidx2 are the remaining indices out of [0,1,2].
-        int secidx1 = (primidx - 1) % 3;
-        if (secidx1 < 0) secidx1 += 3;
-        int secidx2 = (1 + primidx) % 3;
-        // primSign is the digit 1 or -1 depending on whether the limb is headed
-        // along the positive or negative primidx axis.
-        int primSign = delta[primidx] / Math.abs(delta[primidx]);
-        // secdelta1 and ...2 are the amount the associated values change
-        // for every step along the prime axis.
-        int secdelta1 = delta[secidx1];
-        float secfac1 = ((float)secdelta1) / delta[primidx];
-        int secdelta2 = delta[secidx2];
-        float secfac2 = ((float)secdelta2) / delta[primidx];
-        // Initialize coord
-        int[] coord = new int[3];
-        // Loop through each cross-section along the primary axis,
-        // from start to end.
-        int endOffset = delta[primidx] + primSign;
-        int endOffsetAbs = Math.abs(endOffset);
-        int iterator = Math.abs(primSign);
-        for (int primOffsetAbs = 0; primOffsetAbs < endOffsetAbs; primOffsetAbs += iterator) {
-            int primOffset = primOffsetAbs * primSign;
-            int primLoc = start[primidx] + primOffset;
-            int secloc1 = (int)(start[secidx1] + primOffset * secfac1);
-            int secloc2 = (int)(start[secidx2] + primOffset * secfac2);
-            coord[primidx] = primLoc;
-            coord[secidx1] = secloc1;
-            coord[secidx2] = secloc2;
-            int primDist = Math.abs(delta[primidx]);
-            double radius = endSize + (startSize - endSize) * Math.abs(delta[primidx] - primOffset) / primDist;
-            crossSection(coord, radius, primidx, Blocks.CRYSTAL.id(), 0, world);
-        }
-    }
-
-    @Override
-    public boolean place(World world, Random random, int x, int y, int z)
-    {
-        int x1 = (x + random.nextInt(2)) - random.nextInt(2);
-        int z1 = (z + random.nextInt(2)) - random.nextInt(2);
-        int yF = y;
-
-        int radius = 5;
-
-        int checksForGround = 0;
-        boolean canGenerate = false;
-        while(true){
-            int blockId;
-            if(ceilingGen)
-                blockId = world.getBlockId(x1, yF+1, z1);
-            else
-                blockId = world.getBlockId(x1, yF-1, z1);
-
-            if(checksForGround > 64 || yF <= 0 || yF >= world.getHeightBlocks()) {
-                break;
-            }
-
-            if(Blocks.solid[blockId]){
-                canGenerate = true;
-                break;
-            }
-
-            if(ceilingGen)
-                yF ++;
-            else
-                yF --;
-
-            checksForGround++;
-        }
-
-        if(canGenerate && world.isAirBlock(x1, yF, z1))
-        {
-            int rand = random.nextInt(4);
-            if(ceilingGen){
-                switch (rand) {
-                    case 0:
-                        taperedLimb(new int[]{x1, yF + 2, z1}, new int[]{x1 + 24, yF - 24, z1}, radius, 1, world);
-                        break;
-                    case 1:
-                        taperedLimb(new int[]{x1, yF + 2, z1}, new int[]{x1 - 24, yF - 24, z1}, radius, 1, world);
-                        break;
-                    case 2:
-                        taperedLimb(new int[]{x1, yF + 2, z1}, new int[]{x1, yF - 24, z1 + 24}, radius, 1, world);
-                        break;
-                    case 3:
-                        taperedLimb(new int[]{x1, yF + 2, z1}, new int[]{x1, yF - 24, z - 24}, radius, 1, world);
-                        break;
-                }
-            } else {
-                switch (rand) {
-                    case 0:
-                        taperedLimb(new int[]{x1, yF - 2, z1}, new int[]{x1 + 24, yF + 24, z1}, radius, 1, world);
-                        break;
-                    case 1:
-                        taperedLimb(new int[]{x1, yF - 2, z1}, new int[]{x1 - 24, yF + 24, z1}, radius, 1, world);
-                        break;
-                    case 2:
-                        taperedLimb(new int[]{x1, yF - 2, z1}, new int[]{x1, yF + 24, z1 + 24}, radius, 1, world);
-                        break;
-                    case 3:
-                        taperedLimb(new int[]{x1, yF - 2, z1}, new int[]{x1, yF + 24, z - 24}, radius, 1, world);
-                        break;
-                }
-            }
-
-        }
-
-        return true;
-    }
-}
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 f2506450d..8560a7512 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
@@ -15,14 +15,15 @@ public class WorldFeatureFire extends WorldFeature
     @Override
 	public boolean place(World world, Random random, int x, int y, int z)
     {
-        for(int l = 0; l < 64; l++)
+        for(int i = 0; i < 32; i++)
         {
-            int i1 = (x + random.nextInt(8)) - random.nextInt(8);
-            int j1 = (y + random.nextInt(4)) - random.nextInt(4);
-            int k1 = (z + random.nextInt(8)) - random.nextInt(8);
-            if(world.isAirBlock(i1, j1, k1) && world.getBlockId(i1, j1 - 1, k1) == Blocks.COBBLE_NETHERRACK.id())
+            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(i1, j1, k1, Blocks.FIRE.id());
+                world.setBlockWithNotify(x1, y1, z1, Blocks.FIRE.id());
+                world.setBlock(x1, y1 - 1, z1, Blocks.COBBLE_NETHERRACK.id());
             }
         }
 
diff --git a/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureLiquid.java b/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureLiquid.java
index 47d2a78bd..78134e604 100644
--- a/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureLiquid.java
+++ b/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureLiquid.java
@@ -16,32 +16,32 @@ public class WorldFeatureLiquid extends WorldFeature
     @Override
 	public boolean place(World world, Random random, int x, int y, int z)
     {
-        if(world.getBlockId(x, y + 1, z) != Blocks.STONE.id())
+        if(world.getBlockId(x, y + 1, z) != Blocks.STONE.id()|| world.getBlockId(x, y + 1, z) != Blocks.COBBLE_NETHERRACK.id())
         {
             return false;
         }
-        if(world.getBlockId(x, y - 1, z) != Blocks.STONE.id())
+        if(world.getBlockId(x, y - 1, z) != Blocks.STONE.id()|| 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.STONE.id())
+        if(world.getBlockId(x, y, z) != 0 && world.getBlockId(x, y, z) != Blocks.STONE.id()|| world.getBlockId(x, y, z) != Blocks.COBBLE_NETHERRACK.id())
         {
             return false;
         }
         int l = 0;
-        if(world.getBlockId(x - 1, y, z) == Blocks.STONE.id())
+        if(world.getBlockId(x - 1, y, z) == Blocks.STONE.id() || world.getBlockId(x - 1, y, z) == Blocks.COBBLE_NETHERRACK.id())
         {
             l++;
         }
-        if(world.getBlockId(x + 1, y, z) == Blocks.STONE.id())
+        if(world.getBlockId(x + 1, y, z) == Blocks.STONE.id()|| world.getBlockId(x + 1, y, z) == Blocks.COBBLE_NETHERRACK.id())
         {
             l++;
         }
-        if(world.getBlockId(x, y, z - 1) == Blocks.STONE.id())
+        if(world.getBlockId(x, y, z - 1) == Blocks.STONE.id()|| world.getBlockId(x, y, z - 1) == Blocks.COBBLE_NETHERRACK.id())
         {
             l++;
         }
-        if(world.getBlockId(x, y, z + 1) == Blocks.STONE.id())
+        if(world.getBlockId(x, y, z + 1) == Blocks.STONE.id()|| world.getBlockId(x, y, z + 1) == Blocks.COBBLE_NETHERRACK.id())
         {
             l++;
         }
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/WorldFeatureMagmaBoulder.java
new file mode 100644
index 000000000..d2912096f
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureMagmaBoulder.java
@@ -0,0 +1,103 @@
+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 java.util.Random;
+
+public class WorldFeatureMagmaBoulder extends WorldFeature{
+
+    final double[][] RADIUS_MAPS = {
+            {1.5D, 1.5D, 1D},
+            {1D, 1.5D, 1D},
+    };
+
+    @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(); }
+
+        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);
+
+            for (int i = 0; i < map.length; 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++) {
+
+                        if (!world.isAirBlock(circlePos)) {
+                            if (random.nextInt(16) == 0)
+                                world.setBlockType(circlePos, Blocks.MAGMA);
+                            else if (random.nextInt(4) == 0)
+                                world.setBlockType(circlePos, Blocks.COBBLE_BASALT);
+                        }
+                    }
+                }
+            }
+        }
+
+
+        return true;
+    }
+
+    private Block<?> getBlockAt(Vector2i center, int x, int z){
+        return Blocks.COBBLE_BASALT;
+    }
+
+    private void plotBlocks(World world, Vector2i center, TilePos queryPos, int x, int y, int 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);
+        }
+
+        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);
+            world.setBlockType(queryPos.set(xx, y, center.y - x), block);
+        }
+    }
+
+    private void generateRadius(World world, Vector2i center, TilePos queryPos, double r, int y){
+        int x = 0;
+        int z = (int) r;
+        double d = 3 - 2 * r;
+        plotBlocks(world, center, queryPos, x, y, z);
+        while (z > x){
+            x++;
+            if (d > 0) {
+                z--;
+                d = d + 4 * (x - z) + 10;
+            }  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))
+                        return false;
+                }
+            }
+        }
+        return true;
+    }
+
+
+}
\ 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
new file mode 100644
index 000000000..77eee77a1
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureNetherPatch.java
@@ -0,0 +1,34 @@
+package net.minecraft.core.world.generate.feature;
+
+import net.minecraft.core.block.Blocks;
+import net.minecraft.core.world.World;
+
+import java.util.Random;
+
+public class WorldFeatureNetherPatch extends WorldFeature
+{
+    private final int blockId;
+    @MethodParametersAnnotation(names = {"blockId"})
+    public WorldFeatureNetherPatch(int blockId)
+    {
+        this.blockId = blockId;
+    }
+
+    @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);
+            }
+        }
+
+        return true;
+    }
+}
diff --git a/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeaturePillar.java b/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeaturePillar.java
index fc3810948..947701048 100644
--- a/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeaturePillar.java
+++ b/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeaturePillar.java
@@ -13,7 +13,7 @@ public class WorldFeaturePillar
     private static int diameter(final int radius) {
         return radius * 2 + 1;
     }
-    private static final int CIRCLE_SCALE = 4;
+    private static final int CIRCLE_SCALE = 6;
 
     private final int blockId;
     private final boolean doLava;
@@ -25,17 +25,44 @@ public class WorldFeaturePillar
         this.doLava = doLava;
     }
 
+    private float[][] mult(float[][] ma, float[][] mb) {
+        float a = ma[0][0], b = ma[0][1], c = ma[0][2],
+                d = ma[1][0], e = ma[1][1], f = ma[1][2],
+                g = ma[2][0], h = ma[2][1], i = ma[2][2];
+        float j = mb[0][0], k = mb[0][1], l = mb[0][2],
+                m = mb[1][0], n = mb[1][1], o = mb[1][2],
+                p = mb[2][0], q = mb[2][1], r = mb[2][2];
+
+        return new float[][] {
+                {a * j + b * m + c * p, a * k + b * n + c * q, a * l + b * o + c * r},
+                {d * j + e * m + f * p, d * k + e * n + f * q, d * l + e * o + f * r},
+                {g * j + h * m + i * p, g * k + h * n + i * q, g * l + h * o + i * r}
+        };
+    }
+
+    private float[] mult(float[][] m, float[] v) {
+        return new float[] {
+                m[0][0] * v[0] + m[0][1] * v[1] + m[0][2] * v[2],
+                m[1][0] * v[0] + m[1][1] * v[1] + m[1][2] * v[2],
+                m[2][0] * v[0] + m[2][1] * v[1] + m[2][2] * v[2]
+        };
+    }
+
     @Override
     public boolean place(World world, Random random, int x, int y, int z) {
+        int x0 = random.nextInt(16) - 8 + x;
+        int z0 = random.nextInt(16) - 8 + z;
         int y0 = y;
 
-        while (isEmpty(world, x, y0, z)) {
+        while (isEmpty(world, x0, y0, z0)) {
             y0--;
         }
 
+        int x1 = random.nextInt(2) - 2 + x;
+        int z1 = random.nextInt(2) - 2 + z;
         int y1 = y;
 
-        while (isEmpty(world, x, y1, z)) {
+        while (isEmpty(world, x1, y1, z1)) {
             y1++;
         }
 
@@ -44,11 +71,42 @@ public class WorldFeaturePillar
 
         int length = y1 - y0;
 
+        if(length >= 128 || length <= 5)
+            return false;
+
+        int xd = (x1 - x0);
+        int yd = (y1 - y0);
+        int zd = (z1 - z0);
+
+        float dist = (float) Math.sqrt((xd * xd) + (yd * yd) + (zd * zd));
+
+        float xn = xd / dist;
+        float zn = zd / dist;
+
+        float[][] sxMatrix = {
+                {1, xn, 0},
+                {0, 1, 0},
+                {0, 0, 1}
+        };
+        float[][] szMatrix = {
+                {1, 0, 0},
+                {0, 1, 0},
+                {0, zn, 1}
+        };
+
+        float[][] matrix = mult(sxMatrix, szMatrix);
+
         float r = random.nextFloat();
-        int initRadiusTop = 1 + (int) (r * (MAX_RADIUS - 2));
+        int initRadiusTop = (int) (MAX_RADIUS - ((r * 4)+1));
         r = random.nextFloat();
-        int initRadiusBottom = initRadiusTop + (int) (r * (MAX_RADIUS - initRadiusTop));
-        int initRadiusMin = (int) (r * Math.min(initRadiusBottom, initRadiusTop) * 0.75f);
+        int initRadiusBottom = initRadiusTop;
+        int initRadiusMin = (int) (r * initRadiusBottom * 0.9f);
+
+
+        if(exposedBase(world, x1, y1+1, z1, initRadiusTop) || exposedBase(world, x0, y0-1, z0, initRadiusBottom)){
+            return false;
+        }
+
 
         int randomSkip = random.nextInt(8);
         if (randomSkip == 0) {
@@ -72,7 +130,7 @@ public class WorldFeaturePillar
             }
 
             float lerpedRadius = MathHelper.lerp(initRadiusBottom, initRadiusTop, p);
-            float radMod = (MathHelper.cos((2f * p) * MathHelper.PI) / 2 + 0.5f) * 0.75f + 0.25f;
+            float radMod = (MathHelper.cos((2f * p) * MathHelper.PI) / 2 + 0.5f) * 0.75f + 0.5f;
             float currentRadius = ((int) (lerpedRadius * radMod * CIRCLE_SCALE)) / (float) CIRCLE_SCALE;
             if (currentRadius < initRadiusMin) {
                 currentRadius = initRadiusMin;
@@ -82,10 +140,16 @@ public class WorldFeaturePillar
                 bufferedRadius = currentRadius;
             }
 
-            for (int dx = 0; dx < diameter(MAX_RADIUS); dx++) {
-                for (int dz = 0; dz < diameter(MAX_RADIUS); dz++) {
-                    if (getBufferPoint(dx, dz)) {
-                        world.setBlockWithNotify(x - MAX_RADIUS + dx, y0 + dy, z - MAX_RADIUS + dz, blockId);
+            for (int dx = -MAX_RADIUS; dx < MAX_RADIUS; dx++) {
+                for (int dz = -MAX_RADIUS; dz < MAX_RADIUS; dz++) {
+                    if (getBufferPoint(dx+MAX_RADIUS, dz+MAX_RADIUS)) {
+                        float[] v = {dx, dy, dz};
+                        float[] t = mult(matrix, v);
+
+                        int xW = x0 + MathHelper.floor(t[0]);
+                        int yW = y0 + MathHelper.floor(t[1]);
+                        int zW = z0 + MathHelper.floor(t[2]);
+                        world.setBlockWithNotify(xW, yW, zW, blockId);
                     }
                 }
             }
@@ -110,6 +174,17 @@ public class WorldFeaturePillar
         return true;
     }
 
+    private boolean exposedBase(World world, int x, int y, int z, int radius) {
+        for(int xi = -radius; xi < radius; xi++){
+            for(int zi = -radius; zi < radius; zi++){
+                if(!Blocks.solid[world.getBlockId(x + xi, y, z + zi)]){
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
     private boolean isEmpty(World world, int x, int y, int z) {
         if (y < 0 || y >= world.getHeightBlocks()) {
             return false;
diff --git a/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureRubyglassCrystal.java b/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureRubyglassCrystal.java
new file mode 100644
index 000000000..59bb9911e
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureRubyglassCrystal.java
@@ -0,0 +1,194 @@
+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 WorldFeatureRubyglassCrystal implements WorldFeatureInterface {
+    private static final float BASE_PCTG = 0.5f;
+
+    private final boolean isCeiling;
+    private final int minHeight;
+    private final int heightMod;
+
+    public WorldFeatureRubyglassCrystal(final boolean isCeiling) {
+        this(isCeiling, 20, 10);
+    }
+
+    public WorldFeatureRubyglassCrystal(final boolean isCeiling, final int minHeight, final int heightMod) {
+        this.isCeiling = isCeiling;
+        this.minHeight = minHeight;
+        this.heightMod = heightMod;
+    }
+
+    @Override
+    public boolean place(final @NotNull World world, final @NotNull Random random, final @NotNull TilePosc tilePos) {
+        final @NotNull 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;
+            }
+
+            final @NotNull 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;
+                } else if (groundDepth >= 6) {
+                    canGenerate = true;
+                    break;
+                }
+            }
+
+            checksForGround++;
+        }
+
+        final @NotNull Block<?> block = world.getBlockType(ti);
+        if (canGenerate && (block == Blocks.COBBLE_NETHERRACK || block == Blocks.SOULSAND)) {
+            if (this.isCeiling) {
+                floorY += 1;
+            } else {
+                floorY -= 1;
+            }
+
+            this.placeCrystal(world, random, new TilePos(ti.x, floorY, ti.z));
+
+            return true;
+        }
+
+        return false;
+    }
+
+    private float randomOffset(final int length, final @NotNull Random random) {
+        final int base = length / 16;
+        return random.nextFloat() * base;
+    }
+
+    private void placeCrystal(final @NotNull World world, final @NotNull Random random, final @NotNull TilePosc tilePos) {
+        final float r = random.nextFloat();
+        final int length = this.minHeight + MathHelper.floor(Math.pow(r, 4) * this.heightMod);
+        final int base = length / 4;
+        float rx = 0;
+        float ry = 0;
+
+        final @NotNull TilePos ti = new TilePos(tilePos);
+
+        ti.y = this.isCeiling ? ti.y + (base / 2) : ti.y - (base / 2);
+
+        if (!this.hasSpace(world, ti, base)) {
+            return;
+        }
+
+        final float minBase = -(base / 2.0f);
+        final float maxBase = base / 2.0f;
+
+        final float randX = this.randomOffset(length, random);
+        final float randZ = this.randomOffset(length, random);
+
+        final @NotNull Vector3f @NotNull [] pts = new Vector3f[] {
+            // Cuboid
+            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, length, minBase - randZ),
+            new Vector3f(maxBase + randX, length, minBase - randZ),
+            new Vector3f(minBase - randX, length, maxBase + randZ),
+            new Vector3f(maxBase + randX, length, maxBase + randZ),
+
+            // Pyramid top
+            new Vector3f(0.0f, length + (maxBase * 4 * (random.nextFloat() * 0.5f + 0.5f)), 0.0f)
+        };
+
+        if (this.isCeiling) {
+            rx = MathHelper.PI;
+        }
+        rx += (float) ((random.nextDouble() - 0.5) * (Math.PI / 2)) * 0.8f;
+        ry += (float) (random.nextDouble() * 2.0 * Math.PI);
+
+        final @NotNull Matrix4f matrix = Objects.requireNonNull(new Matrix4f().rotateY(ry).rotateX(rx));
+
+        final int @NotNull [] @NotNull [] faces = {
+            { 0, 1, 3 }, // bottom face
+            { 0, 1, 5 }, // lower front
+            { 1, 3, 7 }, // lower right
+            { 3, 2, 6 }, // lower back
+            { 2, 0, 4 }, // lower left
+            { 4, 5, 8 }, // pyramid front
+            { 5, 7, 8 }, // pyramid right
+            { 7, 6, 8 }, // pyramid back
+            { 6, 4, 8 }  // pyramid left
+        };
+
+        final @NotNull ConvexShapeVoxelizer voxelizer = new ConvexShapeVoxelizer(
+            pts,
+            faces,
+            matrix,
+            (worldPos, replacing, faceDistances) -> {
+                if (replacing == Blocks.BEDROCK) {
+                    return null;
+                }
+
+                final float distancePctg = faceDistances[0] / length;
+                if (distancePctg < BASE_PCTG) {
+                    final float innerDistancePctg = distancePctg / BASE_PCTG;
+                    if (innerDistancePctg < random.nextFloat()) {
+                        return Blocks.COBBLE_NETHERRACK_CRYSTALLINE;
+                    } else {
+                        return Blocks.RUBYGLASS;
+                    }
+                } else {
+                    return Blocks.RUBYGLASS;
+                }
+            }
+        );
+
+        voxelizer.voxelize(world, ti);
+    }
+
+    private boolean hasSpace(final @NotNull World world, final @NotNull TilePosc tilePos, final int base) {
+        final @NotNull 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)
+                        return false;
+                }
+            }
+        }
+        return true;
+    }
+}
\ No newline at end of file
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..03ea15cca
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureRubyglassGrowthPatch.java
@@ -0,0 +1,36 @@
+package net.minecraft.core.world.generate.feature;
+
+import net.minecraft.core.block.BlockLogicRubyglassGrowth;
+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 WorldFeatureRubyglassGrowthPatch extends WorldFeature
+{
+    private final int blockId;
+
+    public WorldFeatureRubyglassGrowthPatch()
+    {
+        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);
+                BlockLogicRubyglassGrowth.setDefaultDirection(world, new TilePos(x1, y1, z1));
+            }
+        }
+
+        return true;
+    }
+}
diff --git a/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureTerrace.java b/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureTerrace.java
new file mode 100644
index 000000000..944ddf5ad
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureTerrace.java
@@ -0,0 +1,90 @@
+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.util.helper.Direction;
+import net.minecraft.core.world.World;
+import net.minecraft.core.world.pos.TilePos;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Random;
+
+public class WorldFeatureTerrace extends WorldFeature
+{
+    private final @NotNull Block<?> fillBlock;
+    private final @NotNull Block<?> wallBlock;
+    private final int minRadius;
+    private final int radiusMod;
+    public WorldFeatureTerrace(@NotNull Block<?> fillBlock, @NotNull Block<?> wallBlock)
+    {
+        this.fillBlock = fillBlock;
+        this.wallBlock = wallBlock;
+        this.minRadius = 6;
+        this.radiusMod = 6;
+    }
+
+    @Override
+	public boolean place(World world, Random random, int x, int y, int z)
+    {
+        int radius = minRadius + random.nextInt(radiusMod);
+
+        TilePos centerPos = new TilePos(x + random.nextInt(8), y, z + random.nextInt(8));
+        TilePos cursor = new TilePos();
+
+        TilePos queryPos = new TilePos();
+
+        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();
+
+                float dist = xd * xd + zd * zd;
+
+                for(cursor.y = world.getWorldType().getMaxY(world); cursor.y > world.getWorldType().getOceanY() + 4; cursor.y--) {
+
+                    // Skip placement if cursor hasn't hit ground yet
+                    if(world.isAirBlock(cursor)) {
+                        continue;
+                    }
+
+                    if (dist <= (radius * radius) && canPlaceWall(cursor, queryPos, world)) {
+                        world.setBlockTypeNotify(cursor, wallBlock);
+                    }
+
+                    if (dist <= ((radius-1) * (radius-1)) && canPlacePool(cursor, queryPos, world)) {
+                        world.setBlockTypeNotify(cursor, fillBlock);
+                    }
+                }
+            }
+        }
+        return true;
+    }
+
+    private boolean canPlaceWall(TilePos cursor, TilePos query, World world) {
+        cursor.add(Direction.UP, query);
+        if (!world.isAirBlock(query)) {
+            return false;
+        }
+
+        return world.getBlockType(cursor).hasTag(BlockTags.NETHER_SURFACE_BLOCK);
+    }
+
+    private boolean canPlacePool(TilePos cursor, TilePos query, World world) {
+        if(!world.getBlockType(cursor).hasTag(BlockTags.NETHER_SURFACE_BLOCK)){
+            return false;
+        }
+
+        for (Direction dir : Direction.directions) {
+            cursor.add(dir, query);
+            if(dir == Direction.UP){
+                if (!world.isAirBlock(query)) {
+                    return false;
+                }
+            } else if(world.isAirBlock(query) || !(world.getBlockType(cursor).hasTag(BlockTags.NETHER_SURFACE_BLOCK))){
+                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
new file mode 100644
index 000000000..ad7bb23dd
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureThermalSpire.java
@@ -0,0 +1,63 @@
+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(4) + 2;
+                    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.THERMAL_VENT);
+            }
+        }
+
+
+        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/WorldFeatureThermalVents.java b/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureThermalVents.java
new file mode 100644
index 000000000..1597cd77b
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureThermalVents.java
@@ -0,0 +1,68 @@
+package net.minecraft.core.world.generate.feature;
+
+import net.minecraft.core.block.Blocks;
+import net.minecraft.core.block.tag.BlockTags;
+import net.minecraft.core.util.helper.Direction;
+import net.minecraft.core.world.World;
+import net.minecraft.core.world.pos.TilePos;
+
+import java.util.Random;
+
+public class WorldFeatureThermalVents extends WorldFeature
+{
+
+    private final int minRadius;
+    private final int radiusMod;
+
+
+    public WorldFeatureThermalVents(int minRadius, int radiusMod)
+    {
+        this.minRadius = minRadius;
+        this.radiusMod = radiusMod;
+    }
+
+    public WorldFeatureThermalVents()
+    {
+        this.minRadius = 4;
+        this.radiusMod = 6;
+    }
+
+    @Override
+	public boolean place(World world, Random random, int x, int y, int z)
+    {
+        int radius = minRadius + random.nextInt(radiusMod);
+
+        TilePos tilePos = new TilePos(x, y, z);
+        while(world.isAirBlock(tilePos) && tilePos.y - 1 > 0) {tilePos.down(); }
+
+        TilePos queryPos = new TilePos();
+        TilePos blockAbove = new TilePos();
+
+        for (int r = 0; r < 10; r++) {
+            int radModX = random.nextInt(radius) - random.nextInt(radius);
+            int radModY = random.nextInt(radius) - random.nextInt(radius);
+            int radModZ = random.nextInt(radius) - random.nextInt(radius);
+
+            int height = random.nextInt(5);
+
+            tilePos.add(radModX, radModY, radModZ, queryPos);
+            queryPos.add(0, 1, 0, blockAbove);
+            boolean canBlockSpawn = world.getBlockType(queryPos).hasTag(BlockTags.NETHER_SURFACE_BLOCK);
+
+            if (canBlockSpawn) {
+                for(int i = 1; i < height; i++){
+                    queryPos.add(0, i, 0, blockAbove);
+
+                    if(!world.isAirBlock(blockAbove)){
+                        break;
+                    }
+
+                    world.setBlockTypeNotify(blockAbove, Blocks.THERMAL_VENT);
+                }
+            }
+
+        }
+
+        return true;
+    }
+}
diff --git a/game/core/src/main/java/net/minecraft/core/world/generate/feature/tree/WorldFeatureBoneshaleTree.java b/game/core/src/main/java/net/minecraft/core/world/generate/feature/tree/WorldFeatureBoneshaleTree.java
new file mode 100644
index 000000000..95b96c0a3
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/world/generate/feature/tree/WorldFeatureBoneshaleTree.java
@@ -0,0 +1,431 @@
+package net.minecraft.core.world.generate.feature.tree;
+
+import net.minecraft.core.block.Blocks;
+import net.minecraft.core.block.tag.BlockTags;
+import net.minecraft.core.util.helper.MathHelper;
+import net.minecraft.core.world.World;
+import net.minecraft.core.world.generate.feature.MethodParametersAnnotation;
+import net.minecraft.core.world.generate.feature.WorldFeature;
+
+import java.util.Random;
+
+public class WorldFeatureBoneshaleTree extends WorldFeature
+{
+	protected int leavesID;
+	protected int logID;
+
+    static final byte[] axisConversionArray = { 2, 0, 0, 1, 2, 1 };
+    Random rnd;
+    World world;
+    int[] origin = { 0, 0, 0 };
+    int height;
+    int trunkHeight;
+    double trunkHeightScale;
+    double branchDensity;
+    double branchSlope;
+    double widthScale;
+    double foliageDensity;
+    int trunkWidth;
+    int heightVariance;
+    int foliageHeight;
+    int[][] foliageCoords;
+    int heightMod;
+
+    @MethodParametersAnnotation(names = {"leavesID", "logID"})
+    public WorldFeatureBoneshaleTree(int leavesID, int logID)
+    {
+        this(leavesID, logID, 0);
+    }
+
+    @MethodParametersAnnotation(names = {"leavesID", "logID", "heightMod"})
+    public WorldFeatureBoneshaleTree(int leavesID, int logID, int heightMod)
+    {
+        rnd = new Random();
+        height = 0;
+        trunkHeightScale = 0.6D;
+        branchDensity = 1.0D;
+        branchSlope = 0.4D;
+        widthScale = 1.0D;
+        foliageDensity = 1.0D;
+        trunkWidth = 1;
+        heightVariance = 12;
+        foliageHeight = 4;
+        this.leavesID = leavesID;
+        this.logID = logID;
+        this.heightMod = heightMod;
+    }
+
+    void prepare()
+    {
+        trunkHeight = (int)((double) height * trunkHeightScale);
+        if (trunkHeight >= height)
+        {
+            trunkHeight = height - 1;
+        }
+        int numOfClustersPerY = (int)(1.4D + Math.pow((foliageDensity * (double) height) / 13D, 2D));
+        if (numOfClustersPerY < 1)
+        {
+            numOfClustersPerY = 1;
+        }
+        int[][] foliageCoords = new int[numOfClustersPerY * height][4];
+        int yEnd = (origin[1] + height) - foliageHeight;
+        int foliageCoordsIndex = 1;
+        int topY = origin[1] + trunkHeight;
+        int y = yEnd - origin[1];
+        foliageCoords[0][0] = origin[0];
+        foliageCoords[0][1] = yEnd;
+        foliageCoords[0][2] = origin[2];
+        foliageCoords[0][3] = topY;
+        yEnd--;
+        while (y >= 0) {
+            float shapeFac = shapeFunc(y);
+            if (shapeFac < 0.0F) {
+                yEnd--;
+                y--;
+            } else {
+                double offset = 0.5D;
+                for (int i = 0; i < numOfClustersPerY; i++) {
+                    double r = widthScale * ((double)shapeFac * ((double) rnd.nextFloat() + 0.328D));
+                    double theta = (double) rnd.nextFloat() * 2D * Math.PI;
+                    int x = MathHelper.floor(r * Math.sin(theta) + (double) origin[0] + offset);
+                    int z = MathHelper.floor(r * Math.cos(theta) + (double) origin[2] + offset);
+                    int[] branchEnd = { x, yEnd, z };
+                    int[] branchTopOfTree = { x, yEnd + foliageHeight, z };
+                    if (distToMat(branchEnd, branchTopOfTree) != -1) {
+                        continue;
+                    }
+                    int[] branchStart = { origin[0], origin[1], origin[2] };
+                    double dist = Math.sqrt(Math.pow(Math.abs(origin[0] - branchEnd[0]), 2D) + Math.pow(Math.abs(origin[2] - branchEnd[2]), 2D));
+                    if ((double)branchEnd[1] - dist * branchSlope > (double)topY) {
+                        branchStart[1] = topY;
+                    }
+                    else {
+                        branchStart[1] = (int)((double)branchEnd[1] - dist * branchSlope);
+                    }
+                    if(distToMat(branchStart, branchEnd) == -1) {
+                        foliageCoords[foliageCoordsIndex][0] = x;
+                        foliageCoords[foliageCoordsIndex][1] = yEnd;
+                        foliageCoords[foliageCoordsIndex][2] = z;
+                        foliageCoords[foliageCoordsIndex][3] = branchStart[1];
+                        foliageCoordsIndex++;
+                    }
+                }
+
+                yEnd--;
+                y--;
+            }
+        }
+        this.foliageCoords = new int[foliageCoordsIndex][4];
+        System.arraycopy(foliageCoords, 0, this.foliageCoords, 0, foliageCoordsIndex);
+    }
+
+    void crossSection(int x, int y, int z, float radius, int blockId)
+    {
+        int rad = (int)(radius + 0.618D);
+
+        int[] center = { x, y, z };
+        int[] coord = { 0, 0, 0 };
+
+        coord[1] = center[1];
+        for(int off1 = -rad; off1 <= rad; off1++)
+        {
+            coord[0] = center[0] + off1;
+
+            for(int off2 = -rad; off2 <= rad; off2++)
+            {
+                coord[2] = center[2] + off2;
+
+                double thisDist = Math.sqrt(Math.pow((double) Math.abs(off1) + 0.5D, 2D) + Math.pow((double) Math.abs(off2) + 0.5D, 2D));
+                if(thisDist <= (double)radius)
+                {
+                    int id = world.getBlockId(coord[0], coord[1], coord[2]);
+                    if (id == 0 || id == leavesID)
+                    {
+                        world.setBlockWithNotify(coord[0], coord[1], coord[2], blockId);
+                    }
+                }
+            }
+        }
+    }
+
+    float shapeFunc(int y)
+    {
+        if (y < height * 0.3D)
+        {
+            return -1.618F;
+        }
+        float radius = height / 2.0F;
+        float adj = height / 2.0F - y;
+        float dist;
+        if (adj == 0.0F) {
+            dist = radius;
+        } else if (Math.abs(adj) >= radius) {
+            dist = 0.0F;
+        } else {
+            dist = (float) Math.sqrt(Math.pow(Math.abs(radius), 2D) - Math.pow(Math.abs(adj), 2D));
+        }
+        dist *= 0.5F;
+        return dist;
+    }
+
+    float getFoliageShape(int y)
+    {
+        if(y < 0 || y >= foliageHeight)
+        {
+            return -1F;
+        }
+        else if (y == 0 || y == foliageHeight - 1)
+        {
+            return 2F;
+        }
+        else
+        {
+            return 3F;
+        }
+    }
+
+    void foliageCluster(int x, int y, int z)
+    {
+        for(int leavesY = y; leavesY < y + foliageHeight; leavesY++)
+        {
+            float radius = getFoliageShape(leavesY - y);
+            crossSection(x, leavesY, z, radius, leavesID);
+        }
+    }
+
+    void makeFoliage()
+    {
+        for (int[] foliageCoord : foliageCoords)
+        {
+            int x = foliageCoord[0];
+            int y = foliageCoord[1];
+            int z = foliageCoord[2];
+            foliageCluster(x, y, z);
+        }
+    }
+
+    void makeBranch(int[] startPos, int[] endPos, int blockId)
+    {
+        int[] dimensions = { 0, 0, 0 };
+        int dim0 = 0;
+
+        // Find widest dimension and assign it to dim0
+        for(int i = 0; i < 3; i++)
+        {
+            dimensions[i] = endPos[i] - startPos[i];
+            if(Math.abs(dimensions[i]) > Math.abs(dimensions[dim0]))
+            {
+                dim0 = i;
+            }
+        }
+
+        if(dimensions[dim0] == 0)
+        {
+            return;
+        }
+
+        // Look up other two dimensions
+        byte dim1 = axisConversionArray[dim0];
+        byte dim2 = axisConversionArray[dim0 + 3];
+
+        byte delta;
+        if(dimensions[dim0] > 0)
+        {
+            delta = 1;
+        }
+        else
+        {
+            delta = -1;
+        }
+
+        // Determine delta scale for other two dimensions
+        double dim1DeltaScale = (double)dimensions[dim1] / (double)dimensions[dim0];
+        double dim2DeltaScale = (double)dimensions[dim2] / (double)dimensions[dim0];
+
+        // Draw line from startPos to endPos
+        for(int i = 0; i != dimensions[dim0] + delta; i += delta)
+        {
+            int[] pos = { 0, 0, 0 };
+            pos[dim0] = MathHelper.floor((double)startPos[dim0] + (double)i + 0.5D);
+            pos[dim1] = MathHelper.floor((double)startPos[dim1] + (double)i * dim1DeltaScale + 0.5D);
+            pos[dim2] = MathHelper.floor((double)startPos[dim2] + (double)i * dim2DeltaScale + 0.5D);
+            world.setBlockWithNotify(pos[0], pos[1], pos[2], blockId);
+        }
+    }
+
+    boolean isHighEnoughToBranch(int i)
+    {
+        return (double)i >= (double) height * 0.2D;
+    }
+
+    void makeTrunk()
+    {
+        int x = origin[0];
+        int minY = origin[1];
+        int maxY = origin[1] + trunkHeight;
+        int z = origin[2];
+        int[] startPos = { x, minY, z };
+        int[] endPos = { x, maxY, z };
+        makeBranch(startPos, endPos, logID);
+        if(trunkWidth == 2)
+        {
+            startPos[0]++;
+            endPos[0]++;
+            makeBranch(startPos, endPos, logID);
+            startPos[2]++;
+            endPos[2]++;
+            makeBranch(startPos, endPos, logID);
+            startPos[0]--;
+            endPos[0]--;
+            makeBranch(startPos, endPos, logID);
+        }
+    }
+
+    void makeBranches()
+    {
+        int[] startPos = { origin[0], origin[1], origin[2] };
+        for (int[] branchPoint : foliageCoords)
+        {
+            int[] endPos = { branchPoint[0], branchPoint[1], branchPoint[2] };
+            startPos[1] = branchPoint[3];
+            int dy = startPos[1] - origin[1];
+            if (isHighEnoughToBranch(dy))
+            {
+                makeBranch(startPos, endPos, logID);
+            }
+        }
+    }
+
+    int distToMat(int[] startPos, int[] endPos)
+    {
+        int[] dimensions = { 0, 0, 0 };
+        int dim0 = 0;
+
+        // Find widest dimension and assign it to dim0
+        for(byte i = 0; i < 3; i++)
+        {
+            dimensions[i] = endPos[i] - startPos[i];
+            if(Math.abs(dimensions[i]) > Math.abs(dimensions[dim0]))
+            {
+                dim0 = i;
+            }
+        }
+
+        if(dimensions[dim0] == 0)
+        {
+            return -1;
+        }
+
+        // Look up other two dimensions
+        byte dim1 = axisConversionArray[dim0];
+        byte dim2 = axisConversionArray[dim0 + 3];
+
+        byte delta;
+        if(dimensions[dim0] > 0)
+        {
+            delta = 1;
+        }
+        else
+        {
+            delta = -1;
+        }
+
+        // Determine delta scale for other two dimensions
+        double dim1DeltaScale = (double)dimensions[dim1] / (double)dimensions[dim0];
+        double dim2DeltaScale = (double)dimensions[dim2] / (double)dimensions[dim0];
+
+        // Check all blocks from startPos to endPos, breaking if interrupted
+        int linePos = 0;
+        int lineLength = dimensions[dim0] + delta;
+        while (linePos != lineLength)
+        {
+            int[] pos = { 0, 0, 0 };
+            pos[dim0] = startPos[dim0] + linePos;
+            pos[dim1] = MathHelper.floor((double)startPos[dim1] + (double)linePos * dim1DeltaScale);
+            pos[dim2] = MathHelper.floor((double)startPos[dim2] + (double)linePos * dim2DeltaScale);
+            int idAtPos = world.getBlockId(pos[0], pos[1], pos[2]);
+            if(idAtPos != 0 && idAtPos != leavesID)
+            {
+                break;
+            }
+            linePos += delta;
+        }
+
+        // Check if was interrupted; if so, return the distance we travelled, otherwise return -1
+        if(linePos == lineLength)
+        {
+            return -1;
+        }
+        else
+        {
+            return Math.abs(linePos);
+        }
+    }
+
+    boolean canGenerateTree()
+    {
+        int[] bottomPos = { origin[0], origin[1], origin[2] };
+        int[] topPos = { origin[0], (origin[1] + height) - 1, origin[2] };
+        int blockIdUnderneath = world.getBlockId(origin[0], origin[1] - 1, origin[2]);
+        if(blockIdUnderneath != Blocks.COBBLE_NETHERRACK.id())
+        {
+            return false;
+        }
+        int interruptedTrunkHeight = distToMat(bottomPos, topPos);
+        if(interruptedTrunkHeight == -1)
+        {
+            return true;
+        }
+        if(interruptedTrunkHeight < 6)
+        {
+            return false;
+        }
+        else
+        {
+            height = interruptedTrunkHeight;
+            return true;
+        }
+    }
+
+    @Override
+	public void init(double d, double d1, double d2)
+    {
+        heightVariance = (int)(d * (12D + heightMod));
+        if(d > 0.5D)
+        {
+            foliageHeight = 5;
+        }
+        widthScale = d1;
+        foliageDensity = d2;
+    }
+
+    @Override
+	public boolean place(World world, Random random, int x, int y, int z)
+    {
+        this.world = world;
+        long seed = random.nextLong();
+        rnd.setSeed(seed);
+        while(world.getBlockId(x, y, z) == 0 && y > 0) { y--; }
+
+        origin[0] = x;
+        origin[1] = y + 1;
+        origin[2] = z;
+        if(height == 0)
+        {
+            height = 5 + rnd.nextInt(heightVariance);
+        }
+
+        if(canGenerateTree())
+        {
+            prepare();
+            makeFoliage();
+            makeTrunk();
+            makeBranches();
+            return true;
+        }
+        else
+        {
+            return false;
+        }
+    }
+}
diff --git a/game/core/src/main/java/net/minecraft/core/world/generate/voxelizer/ConvexShapeVoxelizer.java b/game/core/src/main/java/net/minecraft/core/world/generate/voxelizer/ConvexShapeVoxelizer.java
new file mode 100644
index 000000000..c2e9ca9ef
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/world/generate/voxelizer/ConvexShapeVoxelizer.java
@@ -0,0 +1,150 @@
+package net.minecraft.core.world.generate.voxelizer;
+
+import net.minecraft.core.block.Block;
+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 org.jetbrains.annotations.Nullable;
+import org.joml.*;
+
+import java.util.Objects;
+
+public class ConvexShapeVoxelizer implements Voxelizer {
+    public interface BlockFetcher {
+        /**
+         * Fetches the block to be placed at the given position.
+         * @param worldPos The real-world position of the block to be placed.
+         * @param replacing The block to be replaced.
+         * @param faceDistances Unsigned distances from each face in provided order, in world coordinates.
+         * @return The block to insert, or null to skip.
+         */
+        @Nullable Block<?> getBlock(final @NotNull TilePosc worldPos, final @NotNull Block<?> replacing, final float @NotNull [] faceDistances);
+    }
+
+    private final @NotNull Vector3f @NotNull [] @NotNull [] faces;
+    private final @NotNull BlockFetcher fetcher;
+
+    public ConvexShapeVoxelizer(
+        final @NotNull Vector3f @NotNull [] points,
+        final int @NotNull [] @NotNull [] faces,
+        final @NotNull Matrix4f transformation,
+        final @NotNull BlockFetcher fetcher
+    ) {
+        this.fetcher = fetcher;
+
+        final @NotNull Vector3f @NotNull [] tPoints = new Vector3f[points.length];
+        for (int i = 0; i < points.length; i++) {
+            tPoints[i] = Objects.requireNonNull(new Vector4f(points[i], 1.0f).mul(transformation).xyz(new Vector3f()));
+        }
+
+        this.faces = new Vector3f[faces.length][];
+        for (int i = 0; i < faces.length; i++) {
+            this.faces[i] = new Vector3f[faces[i].length];
+
+            for (int j = 0; j < faces[i].length; j++) {
+                this.faces[i][j] = tPoints[faces[i][j]];
+            }
+        }
+    }
+
+    @Override
+    public void voxelize(final @NotNull World world, final @NotNull TilePosc origin) {
+        this.fillConvexSolid(world, origin, this.fetcher, this.faces);
+    }
+
+    private void fillConvexSolid(
+        final @NotNull World world,
+        final @NotNull TilePosc origin,
+        final @NotNull BlockFetcher fetcher,
+        final @NotNull Vector3f @NotNull [] @NotNull [] faces
+    ) {
+        // Compute planes for all faces
+        final @NotNull Plane @NotNull [] planes = new Plane[faces.length];
+        for (int i = 0; i < faces.length; i++) {
+            final @NotNull var f = faces[i];
+            planes[i] = new Plane(f[0], f[1], f[2]);
+        }
+
+        final @NotNull Vector3f min = new Vector3f(Float.POSITIVE_INFINITY);
+        final @NotNull Vector3f max = new Vector3f(Float.NEGATIVE_INFINITY);
+        for (final @NotNull var f : faces) {
+            for (final @NotNull var v : f) {
+                min.min(v);
+                max.max(v);
+            }
+        }
+
+        // Compute centroid to orient planes consistently
+        final @NotNull Vector3f centroid = new Vector3f();
+        int count = 0;
+        for (final @NotNull var f : faces) {
+            for (final @NotNull var v : f) {
+                centroid.add(v);
+                count++;
+            }
+        }
+        centroid.div(count);
+
+        // Flip planes so centroid is on inside side
+        for (final @NotNull var p : planes) {
+            if (p.signedDistance(centroid) > 0f) {
+                p.flip();
+            }
+        }
+
+        // Rasterize AABB with inside-plane tests
+        final @NotNull Vector3f p = new Vector3f();
+        final @NotNull TilePos ti = new TilePos();
+        final @NotNull TilePos temp = new TilePos();
+        final float @NotNull [] planeDistances = new float[planes.length];
+        for (ti.x = MathHelper.floor(min.x); ti.x <= MathHelper.floor(max.x); ti.x++) {
+            for (ti.y = MathHelper.floor(min.y); ti.y <= MathHelper.floor(max.y); ti.y++) {
+                for (ti.z = MathHelper.floor(min.z); ti.z <= MathHelper.floor(max.z); ti.z++) {
+                    p.set(ti.x + 0.5f, ti.y + 0.5f, ti.z + 0.5f);
+
+                    boolean inside = true;
+                    for (int i = 0; i < planes.length; i++) {
+                        planeDistances[i] = -planes[i].signedDistance(p);
+                        if (planeDistances[i] < 0.0f) {
+                            inside = false;
+                            break;
+                        }
+                    }
+
+                    if (inside) {
+                        ti.add(origin.x(), origin.y(), origin.z(), temp);
+                        final @NotNull Block<?> replacing = world.getBlockType(temp);
+                        final @Nullable Block<?> replacer = fetcher.getBlock(temp, replacing, planeDistances);
+
+                        if (replacer != null) {
+                            world.setBlockTypeNotify(temp, replacer);
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    private static class Plane {
+        final @NotNull Vector3f normal;
+        float d;
+
+        Plane(final @NotNull Vector3f a, final @NotNull Vector3f b, final @NotNull Vector3f c) {
+            final @NotNull Vector3f ab = Objects.requireNonNull(new Vector3f(b).sub(a));
+            final @NotNull Vector3f ac = Objects.requireNonNull(new Vector3f(c).sub(a));
+            this.normal = Objects.requireNonNull(Objects.requireNonNull(ab.cross(ac)).normalize());
+            this.d = -this.normal.dot(a);
+        }
+
+        float signedDistance(final @NotNull Vector3f p) {
+            return this.normal.dot(p) + this.d;
+        }
+
+        void flip() {
+            this.normal.negate();
+            this.d = -this.d;
+        }
+    }
+}
diff --git a/game/core/src/main/java/net/minecraft/core/world/generate/voxelizer/Voxelizer.java b/game/core/src/main/java/net/minecraft/core/world/generate/voxelizer/Voxelizer.java
new file mode 100644
index 000000000..6ad5f8950
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/world/generate/voxelizer/Voxelizer.java
@@ -0,0 +1,9 @@
+package net.minecraft.core.world.generate.voxelizer;
+
+import net.minecraft.core.world.World;
+import net.minecraft.core.world.pos.TilePosc;
+import org.jetbrains.annotations.NotNull;
+
+public interface Voxelizer {
+    void voxelize(final @NotNull World world, final @NotNull TilePosc origin);
+}
diff --git a/game/core/src/main/java/net/minecraft/core/world/noise/WorleyNoise.java b/game/core/src/main/java/net/minecraft/core/world/noise/WorleyNoise.java
index 08be1ac72..06e492ca3 100644
--- a/game/core/src/main/java/net/minecraft/core/world/noise/WorleyNoise.java
+++ b/game/core/src/main/java/net/minecraft/core/world/noise/WorleyNoise.java
@@ -41,6 +41,10 @@ public class WorleyNoise
     public static class Result {
         public double distance;
         public int index;
+
+        public @NotNull Random getRandom() {
+            return new Random(index);
+        }
     }
 
     private static final @NotNull Vector2dc RANGE = new Vector2d(0.5, 1.0);
diff --git a/game/core/src/main/java/net/minecraft/core/world/type/nether/WorldTypeNether.java b/game/core/src/main/java/net/minecraft/core/world/type/nether/WorldTypeNether.java
index 3ef04360f..274d9ef36 100644
--- a/game/core/src/main/java/net/minecraft/core/world/type/nether/WorldTypeNether.java
+++ b/game/core/src/main/java/net/minecraft/core/world/type/nether/WorldTypeNether.java
@@ -27,7 +27,7 @@ public class WorldTypeNether
 
     private static float[] getLightRamp() {
         float[] brightnessRamp = new float[32];
-        float f = 0.15F;
+        float f = 0.35F;
         for (int i = 0; i <= 15; i++) {
             float f1 = 1.0F - (float) i / 15F;
             brightnessRamp[i] = ((1.0F - f1) / (f1 * 3F + 1.0F)) * (1.0F - f) + f;
diff --git a/game/core/src/main/resources/lang/en_US/biomes.lang b/game/core/src/main/resources/lang/en_US/biomes.lang
index c32253e38..c63d7b317 100644
--- a/game/core/src/main/resources/lang/en_US/biomes.lang
+++ b/game/core/src/main/resources/lang/en_US/biomes.lang
@@ -21,12 +21,13 @@ biome.overworld.swampland.muddy=Muddy Swamp
 biome.overworld.outback.grassy=Grassy Outback
 biome.overworld.caatinga=Caatinga
 biome.overworld.caatinga.plains=Caatinga Plains
-biome.nether.nether=Nether
+biome.nether.crag=The Crag
+biome.nether.old_world=The Old World
 biome.nether.volcanic_islands=Volcanic Islands
 biome.nether.sulfur_pools=Sulfur Pools
 biome.nether.crystal_forest=Crystal Forest
-biome.nether.ash_plains=Ash Plains
-biome.nether.boneyard=Boneyard
+biome.nether.crystal_field=Crystal Field
+biome.nether.shelf=The Shelf
 biome.drift.drift=Drift
 
 biome.overworld.legacy.tundra=Beta Tundra
diff --git a/game/core/src/main/resources/lang/en_US/item.lang b/game/core/src/main/resources/lang/en_US/item.lang
index e27a281a7..5b98af2c2 100644
--- a/game/core/src/main/resources/lang/en_US/item.lang
+++ b/game/core/src/main/resources/lang/en_US/item.lang
@@ -490,3 +490,11 @@ 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=Cracked Rubyglass Crystal
+item.rubyglass.crystal.cracked.desc=Cracked open and full of impurities. Must planted in Netherrack to purify.
+item.rubyglass.crystal.pure.name=Rubyglass Crystal
+item.rubyglass.crystal.pure.desc=A shard of sanguine crystal, perfectly intact and slightly translucent.
+
+item.sulfur.name=Sulfur
+item.sulfur.desc=Highly flammable and yellow. Packs a punch.
\ No newline at end of file
diff --git a/game/core/src/main/resources/lang/en_US/tile.lang b/game/core/src/main/resources/lang/en_US/tile.lang
index 37c497bc0..99b9945ec 100644
--- a/game/core/src/main/resources/lang/en_US/tile.lang
+++ b/game/core/src/main/resources/lang/en_US/tile.lang
@@ -461,13 +461,15 @@ tile.sand.name=Sand
 tile.sand.desc=Gets everywhere.
 tile.gravel.name=Gravel
 tile.gravel.desc=A loose collection of pebbles.
-tile.sulfur.name=Sulfur
-tile.sulfur.desc=Yellow.
+tile.brimsand.name=Brimsand
+tile.brimsand.desc=Scorched sulfuric sand rich in gunpowder.
 
 tile.bedrock.name=Bedrock
 tile.bedrock.desc=The toughest substance known to man.
-tile.boneshale.name=Bone Shale
+tile.boneshale.name=Boneshale
 tile.boneshale.desc=An incredibly tough white rock torn from the deepest depths.
+tile.boneshale.cobbled.name=Fractured Boneshale
+tile.boneshale.cobbled.desc=Cobbles of boneshale brought to the surface by intense tectonic activity.
 
 tile.fluid.water.flowing.name=Flowing Water
 tile.fluid.water.flowing.desc=Water that flows when placed.
@@ -1033,10 +1035,10 @@ tile.pumpkin.redstone.desc=An orange squash with a spooky face, lit by a redston
 
 tile.cobble.netherrack.name=Cobbled Netherrack
 tile.cobble.netherrack.desc=Dark red rock that burns forever.
-tile.cobble.netherrack.igneous.name=Igneous Cobbled Netherrack
-tile.cobble.netherrack.igneous.desc=Extreme heat has made this rock glow from inside.
-tile.cobble.netherrack.mossy.name=Mossy Cobbled Netherrack
-tile.cobble.netherrack.mossy.desc=Cobbled netherrack, covered in something like moss.
+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.netherrack.name=Netherrack
 tile.netherrack.desc=Dark red rock, smoothed by heat and time.
 tile.brick.netherrack.name=Netherrack Bricks
@@ -1053,7 +1055,7 @@ tile.stairs.brick.netherrack.name=Netherrack Brick Stairs
 tile.stairs.brick.netherrack.desc=Netherrack brick block used for rapid vertical movement.
 
 tile.soulsand.name=Soul Sand
-tile.soulsand.desc=Squishy brown sand that's tough to walk through.
+tile.soulsand.desc=Finely grained ancient bones. The weight of their sins slows you down.
 tile.soulschist.name=Soul Schist
 tile.soulschist.desc=A hideous medley of ancient bones, fused through heat, pressure, and time.
 
@@ -1380,14 +1382,24 @@ tile.glass.steel.desc=Like glass, but without the fragility.
 tile.rope.name=Rope
 tile.rope.desc=Rope, rope, rope your boat.
 
-tile.crystal.name=Nether Crystal
-tile.crystal.desc=A bright, iridescent stone that grows under intense heat.
+tile.rubyglass.name=Block of Rubyglass
+tile.rubyglass.desc=A bright, iridescent crystal brimming with energy.
+tile.rubyglass.pure.name=Block of Pure Rubyglass
+tile.rubyglass.pure.desc=A bright, iridescent crystal brimming with energy. Contains fewer impurities than normal rubyglass.
 
-tile.crystal.stalk.name=Nether Crystal Stalk
-tile.crystal.stalk.desc=A stalk of crystal, branching out in many directions.
+tile.rubyglass.growth.name=Rubyglass Sprout
+tile.rubyglass.growth.desc=A small sprout of rubyglass, branching out in many directions.
 
-tile.netherrack.crystalline.name=Crystalline Netherrack
-tile.netherrack.crystalline.desc=Cobbled Netherrack, embedded with crystals.
+tile.rubyglass.circuit.name=Rubyglass Circuit
+tile.rubyglass.circuit.desc=Netherrack etched with lines of rubyglass. It resonates with latent power.
+
+tile.vent.name=Chimney
+tile.vent.desc=A rocky column that emits a stream of smoke.
+
+tile.bone_pile.name=Bone Pile
+tile.bone_pile.desc=A pile of solid calcium.
+tile.soul_catcher.name=Soul Catcher
+tile.soul_catcher.desc=Tendrils of soul sand that seemingly move on their own. Hard to walk through.
 
 tile.jar.butterfly.blue.name=Blue Butterfly Jar
 tile.jar.butterfly.blue.desc=A glass jar containing a blue butterfly.
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 4dc285a22..22cb2c065 100644
--- a/util/datagen/src/main/java/net/minecraft/datagen/SmeltingGenerator.java
+++ b/util/datagen/src/main/java/net/minecraft/datagen/SmeltingGenerator.java
@@ -46,7 +46,7 @@ public class SmeltingGenerator {
         blastRecipe("cobble_stone_to_slate", Blocks.COBBLE_STONE.getDefaultStack(), Blocks.SLATE.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_igneous_netherrack", Blocks.COBBLE_NETHERRACK.getDefaultStack(), Blocks.COBBLE_NETHERRACK_IGNEOUS.getDefaultStack());
+        blastRecipe("cobble_netherrack_to_magma", Blocks.COBBLE_NETHERRACK.getDefaultStack(), Blocks.MAGMA.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 1252820ff..0aa619ea1 100644
--- a/util/datagen/src/main/java/net/minecraft/datagen/TrommelGenerator.java
+++ b/util/datagen/src/main/java/net/minecraft/datagen/TrommelGenerator.java
@@ -46,7 +46,6 @@ public class TrommelGenerator {
             .addEntry(new WeightedRandomLootObject(new ItemStack(Items.AMMO_PEBBLE), 1, 4), 15)
             .addEntry(new WeightedRandomLootObject(new ItemStack(Items.ORE_RAW_IRON), 1, 2), 5)
             .addEntry(new WeightedRandomLootObject(new ItemStack(Items.DYE,1, DyeColor.BLUE.itemMeta), 2, 6), 5)
-            .addEntry(new WeightedRandomLootObject(new ItemStack(Items.GUNPOWDER), 1), 5)
             .addEntry(new WeightedRandomLootObject(new ItemStack(Items.QUARTZ), 1), 0.5)
             .create("gravel");
 
@@ -68,7 +67,6 @@ public class TrommelGenerator {
             .addEntry(new WeightedRandomLootObject(new ItemStack(Items.AMMO_PEBBLE), 1, 3), 15)
             .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.GUNPOWDER), 1), 5)
             .addEntry(new WeightedRandomLootObject(new ItemStack(Items.OLIVINE), 1), 5)
             .addEntry(new WeightedRandomLootObject(new ItemStack(Items.ORE_RAW_GOLD), 1), 0.5)
             .create("sand");
@@ -80,8 +78,19 @@ 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.QUARTZ), 1, 3), 5)
+            .addEntry(new WeightedRandomLootObject(new ItemStack(Items.RUBYGLASS_CRYSTAL_CRACKED), 1, 3), 5)
             .addEntry(new WeightedRandomLootObject(new ItemStack(Items.NETHERCOAL), 1), 2)
             .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");
+
     }
 }
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 a069def6b..85daba5cd 100644
--- a/util/datagen/src/main/java/net/minecraft/datagen/WorkbenchGenerator.java
+++ b/util/datagen/src/main/java/net/minecraft/datagen/WorkbenchGenerator.java
@@ -154,7 +154,7 @@ class WorkbenchGenerator {
                 "#R#")
             .addInput('#', Blocks.COBBLE_NETHERRACK)
             .addInput('S', Blocks.SOULSAND)
-            .addInput('N', Items.NETHERCOAL)
+            .addInput('N', Items.RUBYGLASS_CRYSTAL_PURE)
             .addInput('R', Items.DUST_REDSTONE)
             .create("activator", Blocks.ACTIVATOR_COBBLE_NETHERRACK.getDefaultStack());
 
@@ -1296,9 +1296,42 @@ class WorkbenchGenerator {
             )
             .addInput('#', Blocks.COBBLE_NETHERRACK)
             .addInput('Q', Items.QUARTZ)
-            .addInput('R', Items.DUST_REDSTONE)
+            .addInput('R', Items.RUBYGLASS_CRYSTAL_PURE)
             .addInput('S', Blocks.SOULSAND)
             .create("matcher", new ItemStack(Blocks.MATCHER, 1));
+
+        RecipeBuilder.Shaped(CORE_NAMESPACE)
+                .setShape(
+                        "XX",
+                        "XX")
+                .addInput('X', Items.RUBYGLASS_CRYSTAL_CRACKED)
+                .create("rubyglass", new ItemStack(Blocks.RUBYGLASS, 1));
+
+        RecipeBuilder.Shaped(CORE_NAMESPACE)
+                .setShape(
+                        "XX",
+                        "XX")
+                .addInput('X', Items.RUBYGLASS_CRYSTAL_PURE)
+                .create("rubyglass_pure", new ItemStack(Blocks.RUBYGLASS_PURE, 1));
+
+        RecipeBuilder.Shaped(CORE_NAMESPACE)
+                .setShape(
+                        "XOX",
+                        "OOO",
+                        "XOX")
+                .addInput('O', Items.RUBYGLASS_CRYSTAL_PURE)
+                .addInput('X', Blocks.COBBLE_NETHERRACK)
+                .create("rubyglass_circuit", new ItemStack(Blocks.RUBYGLASS_CIRCUIT, 1));
+
+        RecipeBuilder.Shaped(CORE_NAMESPACE)
+                .setShape(
+                        "###",
+                        "#RB",
+                        "###")
+                .addInput('#', Blocks.COBBLE_NETHERRACK)
+                .addInput('R', Items.RUBYGLASS_CRYSTAL_PURE)
+                .addInput('B', Items.BONE)
+                .create("motion_sensor", Blocks.MOTION_SENSOR_IDLE.getDefaultStack());
     }
 
     private static void generateScrapRecipes() {
diff --git a/util/seed-viewer/src/main/java/org/useless/seedviewer/bta/BTAChunkProvider.java b/util/seed-viewer/src/main/java/org/useless/seedviewer/bta/BTAChunkProvider.java
index f31b26326..610a57353 100644
--- a/util/seed-viewer/src/main/java/org/useless/seedviewer/bta/BTAChunkProvider.java
+++ b/util/seed-viewer/src/main/java/org/useless/seedviewer/bta/BTAChunkProvider.java
@@ -28,6 +28,7 @@ public class BTAChunkProvider implements ChunkProvider {
             Global.accessor = new DummyMinecraft();
             Blocks.init();
             Items.init();
+            Dimension.init();
             new Registries();
             Biomes.init();
             WorldTypes.init();
@@ -41,9 +42,7 @@ public class BTAChunkProvider implements ChunkProvider {
 
 
     public BTAChunkProvider(long seed) {
-//        this.biomeProvider = new BiomeProviderOverworld(seed, WorldTypes.OVERWORLD_EXTENDED);
-        // TODO: Fix
-        throw new IllegalArgumentException("This is currently broken!");
+          this.biomeProvider = WorldTypes.NETHER_DEFAULT.createBiomeProvider(new DummyWorld(Dimension.NETHER));
     }
 
     public BTAChunkProvider(File worldFolder, LevelData data) {
diff --git a/util/seed-viewer/src/main/java/org/useless/seedviewer/bta/DummyWorld.java b/util/seed-viewer/src/main/java/org/useless/seedviewer/bta/DummyWorld.java
index 0d485d500..53e7c5216 100644
--- a/util/seed-viewer/src/main/java/org/useless/seedviewer/bta/DummyWorld.java
+++ b/util/seed-viewer/src/main/java/org/useless/seedviewer/bta/DummyWorld.java
@@ -2,7 +2,6 @@ package org.useless.seedviewer.bta;
 
 import net.minecraft.core.world.Dimension;
 import net.minecraft.core.world.World;
-import net.minecraft.core.world.biome.Biome;
 import net.minecraft.core.world.chunk.provider.ChunkProvider;
 import net.minecraft.core.world.save.LevelStorage;
 import net.minecraft.core.world.save.LevelStorageDummy;
@@ -10,8 +9,8 @@ import net.minecraft.core.world.settings.WorldConfiguration;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 import org.useless.seedviewer.bta.worldloader.ChunkLoader;
+import org.useless.seedviewer.bta.worldloader.ChunkLoaderEmpty;
 
-import java.lang.reflect.Field;
 import java.util.Objects;
 
 public class DummyWorld extends World {
@@ -28,6 +27,6 @@ public class DummyWorld extends World {
 
     @Override
     protected @NotNull ChunkProvider createChunkProvider(final @NotNull LevelStorage levelStorage) {
-        return Objects.requireNonNull(CHUNK_LOADER);
+        return Objects.requireNonNullElseGet(CHUNK_LOADER, () -> new ChunkLoaderEmpty(this));
     }
 }
diff --git a/util/seed-viewer/src/main/java/org/useless/seedviewer/bta/worldloader/ChunkLoaderEmpty.java b/util/seed-viewer/src/main/java/org/useless/seedviewer/bta/worldloader/ChunkLoaderEmpty.java
new file mode 100644
index 000000000..235dce36b
--- /dev/null
+++ b/util/seed-viewer/src/main/java/org/useless/seedviewer/bta/worldloader/ChunkLoaderEmpty.java
@@ -0,0 +1,84 @@
+package org.useless.seedviewer.bta.worldloader;
+
+import net.minecraft.core.world.ProgressListener;
+import net.minecraft.core.world.World;
+import net.minecraft.core.world.chunk.Chunk;
+import net.minecraft.core.world.chunk.DisplayChunk;
+import net.minecraft.core.world.chunk.EmptyChunk;
+import net.minecraft.core.world.chunk.provider.ChunkProvider;
+import net.minecraft.core.world.chunk.reader.*;
+import net.minecraft.core.world.pos.ChunkPosc;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+
+public class ChunkLoaderEmpty implements ChunkProvider {
+    public final @NotNull Chunk blankChunk;
+    public final @NotNull Chunk displayChunk;
+
+    public ChunkLoaderEmpty(World world) {
+        this.blankChunk = new EmptyChunk(world, 0, 0);
+        this.displayChunk = new DisplayChunk(world, 0, 0);
+    }
+
+    @Override
+    public boolean isChunkLoaded(@NotNull ChunkPosc chunkPos) {
+        return true;
+    }
+
+    @Override
+    public @NotNull Chunk provideChunk(@NotNull ChunkPosc chunkPos, boolean priority) {
+        if (chunkPos.x() == 0 && chunkPos.z() == 0) {
+            return this.displayChunk;
+        }
+        return this.blankChunk;
+    }
+
+    @Override
+    public @NotNull Chunk prepareChunk(@NotNull ChunkPosc chunkPos, boolean priority) {
+        if (chunkPos.x() == 0 && chunkPos.z() == 0) {
+            return this.displayChunk;
+        }
+        return this.blankChunk;
+    }
+
+    @Override
+    public void regenerateChunk(@NotNull ChunkPosc chunkPos) {
+
+    }
+
+    @Override
+    public void populate(@NotNull ChunkPosc chunkPos) {
+
+    }
+
+    @Override
+    public boolean saveChunks(boolean saveImmediately, @Nullable ProgressListener progressListener) {
+        return false;
+    }
+
+    @Override
+    public boolean tick() {
+        return false;
+    }
+
+    @Override
+    public boolean canSave() {
+        return false;
+    }
+
+    @Override
+    public @NotNull String getInfoString() {
+        return "";
+    }
+
+    @Override
+    public void unloadAllChunks() {
+
+    }
+
+    @Override
+    public void setCurrentChunkOver(@NotNull ChunkPosc chunkPos) {
+
+    }
+}
