diff --git a/game/client/devPacks/inTestingSoundsPack/assets/minecraft/sounds/sounds.json b/game/client/devPacks/inTestingSoundsPack/assets/minecraft/sounds/sounds.json
index 606273548..a667a01cd 100644
--- a/game/client/devPacks/inTestingSoundsPack/assets/minecraft/sounds/sounds.json
+++ b/game/client/devPacks/inTestingSoundsPack/assets/minecraft/sounds/sounds.json
@@ -166,7 +166,7 @@
       },
       {
         "name": "btamusic/underground/untitled.ogg",
-        "title": "Untitled Cave Song",
+        "title": "Karst",
         "author": "ShignBright",
         "stream": true
       }
diff --git a/game/client/src/main/java/net/minecraft/client/Minecraft.java b/game/client/src/main/java/net/minecraft/client/Minecraft.java
index 26ab5ec5b..81f67a35c 100644
--- a/game/client/src/main/java/net/minecraft/client/Minecraft.java
+++ b/game/client/src/main/java/net/minecraft/client/Minecraft.java
@@ -2322,7 +2322,7 @@ public class Minecraft
             if (spawnCoordinates != null) {
                 bedSpawnCoordinates = Player.getValidBedSpawnCoordinates(this.currentWorld, spawnCoordinates);
                 if (bedSpawnCoordinates == null) {
-                    this.thePlayer.sendTranslatedChatMessage("bed.notValid");
+                    this.thePlayer.sendTranslatedChatMessage("messages.bed.notValid");
                 }
             }
         }
diff --git a/game/client/src/main/java/net/minecraft/client/gui/options/data/OptionsPages.java b/game/client/src/main/java/net/minecraft/client/gui/options/data/OptionsPages.java
index 4e3382389..bc100ff7b 100644
--- a/game/client/src/main/java/net/minecraft/client/gui/options/data/OptionsPages.java
+++ b/game/client/src/main/java/net/minecraft/client/gui/options/data/OptionsPages.java
@@ -45,6 +45,7 @@ import paulscode.sound.SoundSystem;
 import java.io.File;
 import java.io.IOException;
 import java.util.Collections;
+import java.util.List;
 
 
 public abstract class OptionsPages {
@@ -398,7 +399,7 @@ public abstract class OptionsPages {
                 .withComponent(new ShortcutComponent("gui.options.page.debug.button.open.biome_finder", () -> {
                     new Thread(() -> {
                         try {
-                            JavaProcess.exec(SeedViewerMain.class, Collections.emptyList());
+                            JavaProcess.exec(SeedViewerMain.class, List.of("-main", "false"));
                         } catch (final IOException | InterruptedException e) {
                             LogUtils.getLogger().error("", e);
                         }
diff --git a/game/client/src/main/java/net/minecraft/client/player/controller/PlayerController.java b/game/client/src/main/java/net/minecraft/client/player/controller/PlayerController.java
index f3594f92a..0b94d52bb 100644
--- a/game/client/src/main/java/net/minecraft/client/player/controller/PlayerController.java
+++ b/game/client/src/main/java/net/minecraft/client/player/controller/PlayerController.java
@@ -102,6 +102,8 @@ public abstract class PlayerController {
         }
 
         final Block<?> mineBlock = world.getBlockType(tilePos);
+        if (!mineBlock.canCollideCheck(world.getBlockData(tilePos), false)) return;
+
         this.syncCurrentPlayItem();
 
         final float mineStrength = mineBlock.getStrength(world, tilePos, side, player);
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 005acdcb5..e184b6351 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
@@ -346,10 +346,10 @@ public final class BlockModelDispatcher
         addDispatch(new BlockModelGeneric<>(Blocks.ORE_DIAMOND_PERMAFROST, loadDataModel("minecraft:block/ore/diamond/permafrost")));
 
         addDispatch(new BlockModelGeneric<>(Blocks.RUBYGLASS_COLUMN, loadDataModel("minecraft:block/rubyglass/column")));
-        addDispatch(new BlockModelGeneric<>(Blocks.RUBYGLASS_NODE, loadDataModel("minecraft:block/rubyglass/node")).forceCullSelf(true));
+        addDispatch(new BlockModelGeneric<>(Blocks.RUBYGLASS_NODE, loadDataModel("minecraft:block/rubyglass/node")));
         addDispatch(new BlockModelGeneric<>(Blocks.BRICK_RUBYGLASS, loadDataModel("minecraft:block/rubyglass/brick")));
-        addDispatch(new BlockModelGeneric<>(Blocks.BLOCK_RUBYGLASS, loadDataModel("minecraft:block/rubyglass/block")).forceCullSelf(true));
-        addDispatch(new BlockModelConduit<>(Blocks.CONDUIT).forceCullSelf(true));
+        addDispatch(new BlockModelGeneric<>(Blocks.BLOCK_RUBYGLASS, loadDataModel("minecraft:block/rubyglass/block")));
+        addDispatch(new BlockModelConduit<>(Blocks.CONDUIT));
         addDispatch(new BlockModelGenericRubyglassCrystal<>(Blocks.RUBYGLASS_CRYSTAL, loadDataModel("minecraft:block/rubyglass/crystal/0")).forceCullSelf(true));
         addDispatch(new BlockModelGenericRubyglassGrowth<>(Blocks.RUBYGLASS_GROWTH));
 
diff --git a/game/client/src/main/java/net/minecraft/client/render/block/model/generic/BlockModelGenericFurnace.java b/game/client/src/main/java/net/minecraft/client/render/block/model/generic/BlockModelGenericFurnace.java
index 56031bde0..07a71fb6b 100644
--- a/game/client/src/main/java/net/minecraft/client/render/block/model/generic/BlockModelGenericFurnace.java
+++ b/game/client/src/main/java/net/minecraft/client/render/block/model/generic/BlockModelGenericFurnace.java
@@ -6,6 +6,7 @@ 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.block.entity.TileEntityFurnace;
 import net.minecraft.core.block.entity.TileEntityFurnaceBlast;
 import net.minecraft.core.player.inventory.container.Container;
 import net.minecraft.core.util.helper.Direction;
@@ -54,9 +55,9 @@ public class BlockModelGenericFurnace<T extends BlockLogic> extends BlockModelGe
         if (source.getTileEntity(tilePosc) instanceof Container container) {
             boolean hasOutput;
             if (container instanceof TileEntityFurnaceBlast) {
-                hasOutput = container.getItem(3) != null;
+                hasOutput = container.getItem(TileEntityFurnaceBlast.SLOT_RESULT) != null;
             } else {
-                hasOutput = container.getItem(2) != null;
+                hasOutput = container.getItem(TileEntityFurnace.SLOT_RESULT) != null;
             }
             if (hasOutput) {
                 return this.filled;
diff --git a/game/client/src/main/java/net/minecraft/client/render/block/model/generic/BlockModelGenericRubyglassCrystal.java b/game/client/src/main/java/net/minecraft/client/render/block/model/generic/BlockModelGenericRubyglassCrystal.java
index 0a5e7a394..efe02e10b 100644
--- a/game/client/src/main/java/net/minecraft/client/render/block/model/generic/BlockModelGenericRubyglassCrystal.java
+++ b/game/client/src/main/java/net/minecraft/client/render/block/model/generic/BlockModelGenericRubyglassCrystal.java
@@ -3,12 +3,13 @@ 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.BlockLogicRubyglassCrystal;
 import org.jetbrains.annotations.NotNull;
 import org.useless.dragonfly.data.block.BlockModelData;
 import org.useless.dragonfly.models.block.StaticBlockModel;
 
 public class BlockModelGenericRubyglassCrystal<T extends BlockLogic> extends BlockModelGeneric<T> {
-	public final StaticBlockModel[] models = new StaticBlockModel[4];
+	public final StaticBlockModel[] models = new StaticBlockModel[BlockLogicRubyglassCrystal.BREAK_STAGES + 1];
 
 	public BlockModelGenericRubyglassCrystal(@NotNull Block<T> block, @NotNull StaticBlockModel staticModel) {
 		super(block, staticModel);
diff --git a/game/client/src/main/java/net/minecraft/client/render/entity/MobRendererSheep.java b/game/client/src/main/java/net/minecraft/client/render/entity/MobRendererSheep.java
index 67054d433..b44444a7a 100644
--- a/game/client/src/main/java/net/minecraft/client/render/entity/MobRendererSheep.java
+++ b/game/client/src/main/java/net/minecraft/client/render/entity/MobRendererSheep.java
@@ -95,8 +95,8 @@ public class MobRendererSheep extends MobRenderer<MobSheep> {
         else
         {
             // normal head rotations
-            head.rotX = headPitch * MathHelper.DEG_TO_RAD;
-            head.rotY = headYaw * MathHelper.DEG_TO_RAD;
+            head.rotX = headPitch;
+            head.rotY = headYaw;
         }
 
         return model;
diff --git a/game/client/src/main/java/net/minecraft/client/render/tileentity/TileEntityRendererJarButterfly.java b/game/client/src/main/java/net/minecraft/client/render/tileentity/TileEntityRendererJarButterfly.java
index 0b5f21272..789078fc5 100644
--- a/game/client/src/main/java/net/minecraft/client/render/tileentity/TileEntityRendererJarButterfly.java
+++ b/game/client/src/main/java/net/minecraft/client/render/tileentity/TileEntityRendererJarButterfly.java
@@ -11,8 +11,15 @@ public class TileEntityRendererJarButterfly extends TileEntityRenderer<TileEntit
     public void doRender(TessellatorGeneral tessellator, TileEntityJarButterfly tileEntity, double x, double y, double z, float partialTick) {
         int meta = tileEntity.getBlockMeta();
         boolean hanging = (meta & BlockLogicJarButterfly.MASK_HANGING) != 0;
-        MobRendererButterfly.drawButterfly(tessellator, tileEntity.ticksRan, 0.70f, 0,
-            x + 0.5, y + (hanging ? 6/16f : 0) - 0.125 + MathHelper.sin((tileEntity.ticksRan + partialTick)/20) * (0.0625), z + 0.5,
+        double realTick = tileEntity.ticksRan + partialTick;
+
+        MobRendererButterfly.drawButterfly(tessellator, tileEntity.ticksRan, 0.65f, (float) getYRot(tileEntity.ticksRan, partialTick),
+            x + 0.5, y + (hanging ? 6/16f : 0) - 0.125 + Math.sin(realTick/20) * (0.0625), z + 0.5,
             partialTick, this.renderDispatcher.textureManager, "/assets/minecraft/textures/entity/butterfly/" + tileEntity.color.name() + "/0_0.png");
     }
+
+    public double getYRot(int tick, float partialTick) {
+        double realTick = ((tick + partialTick)/500) % (Math.PI * 2);
+        return (Math.sin(10 * realTick) + 2 * Math.sin(10 * realTick / 3) - 5 * Math.sin(realTick) + 2 * Math.sin(10 * realTick / 3) - 3.464 * Math.sin(realTick/4)) * 37;
+    }
 }
diff --git a/game/client/src/main/java/org/useless/dragonfly/models/block/mojang/StaticBlockModelMojang.java b/game/client/src/main/java/org/useless/dragonfly/models/block/mojang/StaticBlockModelMojang.java
index 78971b1c4..5daf67d85 100644
--- a/game/client/src/main/java/org/useless/dragonfly/models/block/mojang/StaticBlockModelMojang.java
+++ b/game/client/src/main/java/org/useless/dragonfly/models/block/mojang/StaticBlockModelMojang.java
@@ -233,7 +233,7 @@ public class StaticBlockModelMojang implements StaticBlockModel {
                 }
 
                 final boolean useAO = this.compiled.data.ambientOcclusion && GameSettings.AMBIENT_OCCLUSION.value && element.shade;
-                final boolean isFullCube = depth <= FULL_CUBE_THRESHOLD;
+                final boolean isFullCube = depth <= FULL_CUBE_THRESHOLD && element.shade;
 
                 final float r;
                 final float g;
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/rubyglass/block.json b/game/client/src/main/resources/assets/minecraft/models/block/rubyglass/block.json
index 45c55adbc..61e5c9a68 100644
--- a/game/client/src/main/resources/assets/minecraft/models/block/rubyglass/block.json
+++ b/game/client/src/main/resources/assets/minecraft/models/block/rubyglass/block.json
@@ -1,6 +1,5 @@
 {
   "parent": "minecraft:block/cube_all",
-  "renderlayer": 1,
   "textures": {
     "all": "minecraft:block/rubyglass/block"
   }
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogic.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogic.java
index b710ae394..668fdc792 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogic.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogic.java
@@ -6,7 +6,6 @@ import net.minecraft.core.block.entity.TileEntityActivator;
 import net.minecraft.core.block.material.Material;
 import net.minecraft.core.block.material.MaterialColor;
 import net.minecraft.core.block.motion.CarriedBlock;
-import net.minecraft.core.block.piston.CarriedPistonHead;
 import net.minecraft.core.block.piston.IMovingBlock;
 import net.minecraft.core.block.piston.MovingLine;
 import net.minecraft.core.block.support.FullSupport;
@@ -15,7 +14,6 @@ import net.minecraft.core.block.tag.BlockTags;
 import net.minecraft.core.entity.Entity;
 import net.minecraft.core.entity.EntityItem;
 import net.minecraft.core.entity.Mob;
-import net.minecraft.core.entity.monster.MobHuman;
 import net.minecraft.core.entity.player.Player;
 import net.minecraft.core.enums.EnumDropCause;
 import net.minecraft.core.item.IItemConvertible;
@@ -23,7 +21,6 @@ import net.minecraft.core.item.Item;
 import net.minecraft.core.item.ItemStack;
 import net.minecraft.core.item.tool.ItemToolShears;
 import net.minecraft.core.util.collection.NamespaceID;
-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;
@@ -237,7 +234,9 @@ public class BlockLogic implements BlockInterface, IItemConvertible {
     // this is currently asymmetrical to onMoved, as part of the line might not have being converted to normal blocks
     // TODO: revisit with better abstraction in the future 
     @Override
-    public void onMoveComplete(@NotNull World world, @NotNull TilePosc tilePos) {}
+    public void onMoveComplete(@NotNull World world, @NotNull TilePosc tilePos) {
+        this.onNeighborChanged(world, tilePos, this.block);
+    }
 
     @Override
     public void onFlung(@NotNull World world, @NotNull TilePosc sourcePos, int data, @NotNull CarriedBlock block) { }
@@ -515,14 +514,54 @@ public class BlockLogic implements BlockInterface, IItemConvertible {
         return false;
     }
 
-    @Override
-    public boolean isEquivalent(final @NotNull World world, final @NotNull TilePosc thisPos, final @NotNull TilePosc thatPos) {
-        final @Nullable Block<?> bBlock = world.getBlockType(thatPos);
+    /**
+     * Metadata compared by the matcher. Override to ignore orientation while keeping visual state (e.g. powered).
+     */
+    protected int getMatcherEquivalenceData(final int data) {
+        return data;
+    }
+
+    /**
+     * Whether another block counts as the same type for matcher comparison (e.g. on/off variants).
+     */
+    protected boolean isSameBlockForMatcher(final @NotNull Block<?> other) {
+        return this.block == other;
+    }
+
+    public record MatcherSnapshot(@NotNull Block<?> block, int meta, @NotNull BlockLogic logic) {}
 
-        final int thisMeta = world.getBlockData(thisPos);
-        final int otherMeta = world.getBlockData(thatPos);
+    /**
+     * Resolves the block/meta/logic at this position for matcher comparison.
+     * <p>
+     * The default implementation returns the block actually stored in the world. Blocks
+     * that visually stand in for another block (e.g. the moving piston, which carries
+     * its real contents in the tile entity) should override this to delegate to that block.
+     */
+    public @NotNull MatcherSnapshot resolveForMatcher(final @NotNull World world, final @NotNull TilePosc tilePos) {
+        final @NotNull Block<?> block = world.getBlockType(tilePos);
+        return new MatcherSnapshot(block, world.getBlockData(tilePos), block.getLogic());
+    }
 
-        return this.block == bBlock && thisMeta == otherMeta;
+    public static @NotNull MatcherSnapshot resolveMatcherBlock(final @NotNull World world, final @NotNull TilePosc tilePos) {
+        return world.getBlockType(tilePos).getLogic().resolveForMatcher(world, tilePos);
+    }
+
+    public static boolean areBlocksEquivalentForMatcher(
+        final @NotNull World world,
+        final @NotNull TilePosc posA,
+        final @NotNull TilePosc posB
+    ) {
+        final @NotNull MatcherSnapshot a = resolveMatcherBlock(world, posA);
+        final @NotNull MatcherSnapshot b = resolveMatcherBlock(world, posB);
+        if (!a.logic().isSameBlockForMatcher(b.block()) || !b.logic().isSameBlockForMatcher(a.block())) {
+            return false;
+        }
+        return a.logic().getMatcherEquivalenceData(a.meta()) == b.logic().getMatcherEquivalenceData(b.meta());
+    }
+
+    @Override
+    public boolean isEquivalent(final @NotNull World world, final @NotNull TilePosc thisPos, final @NotNull TilePosc thatPos) {
+        return areBlocksEquivalentForMatcher(world, thisPos, thatPos);
     }
 
     @Override
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicActivator.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicActivator.java
index 6f3aead64..226d3e3a2 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicActivator.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicActivator.java
@@ -27,6 +27,11 @@ public class BlockLogicActivator
     extends BlockLogicVeryRotatable {
     public static final int MASK_POWERED = 0b0000_1000;
 
+    @Override
+    protected int getMatcherEquivalenceData(final int data) {
+        return data & MASK_POWERED;
+    }
+
     public BlockLogicActivator(final @NotNull Block<?> block) {
         super(block, Materials.NETHERRACK);
         block.withEntity(TileEntityActivator::new);
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicAxisAligned.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicAxisAligned.java
index 1f78a5629..6c4214482 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicAxisAligned.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicAxisAligned.java
@@ -17,6 +17,11 @@ public class BlockLogicAxisAligned
         super(block, material);
     }
 
+    @Override
+    protected int getMatcherEquivalenceData(final int data) {
+        return data & ~MASK_DIRECTION;
+    }
+
     @Override
     public void onPlacedByMob(final @NotNull World world, final @NotNull TilePosc tilePos, final @NotNull Side side, final @NotNull Mob mob, final double xHit, final double yHit) {
         final @NotNull Axis axis = mob.getPlacementDirection(side, PlacementMode.SIDE).axis();
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicBed.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicBed.java
index 90017d1ce..d29c1ef19 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicBed.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicBed.java
@@ -32,6 +32,11 @@ public class BlockLogicBed
         this.setBlockBounds(0.0F, 0.0F, 0.0F, 1.0F, 0.5625F, 1.0F);
     }
 
+    @Override
+    protected int getMatcherEquivalenceData(final int data) {
+        return data & ~DIRECTION.mask;
+    }
+
     @Override
     public boolean onInteracted(final @NotNull World world, final @NotNull TilePosc tilePos, final @NotNull Player player, final @Nullable Side side, final double xHit, final double yHit) {
         if (world.isClientSide) {
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicButton.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicButton.java
index b8add79b1..63d149c3f 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicButton.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicButton.java
@@ -41,6 +41,11 @@ public class BlockLogicButton
         block.setTicking(true);
     }
 
+    @Override
+    protected int getMatcherEquivalenceData(final int data) {
+        return data & MASK_POWERED;
+    }
+
     @Override
     public @Nullable AABBdc getCollisionAABB(final @NotNull WorldSource source, final @NotNull TilePosc tilePos) {
         return null;
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicChest.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicChest.java
index 9fadd0ef0..f5c36f7a2 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicChest.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicChest.java
@@ -25,6 +25,11 @@ public class BlockLogicChest
         block.withEntity(TileEntityChest::new);
     }
 
+    @Override
+    protected int getMatcherEquivalenceData(final int data) {
+        return data & typeBits;
+    }
+
     @Override
     public void onPlacedByMob(final @NotNull World world, final @NotNull TilePosc tilePos, final @NotNull Side side, final @NotNull Mob mob, final double xHit, final double yHit) {
         @NotNull Direction direction = mob.getHorizontalPlacementDirection(side).opposite();
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicConduit.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicConduit.java
index 38fa673b5..0909fb927 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicConduit.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicConduit.java
@@ -77,12 +77,12 @@ public class BlockLogicConduit extends BlockLogic {
 
     @Override
     public boolean isSolidRender() {
-        return false;
+        return true; // Setting to true. False allows redstone towering, true makes it handle culling properly. It is a full solid block so ideally is set to true, really redstone shouldn't depend on these at all though -Useless
     }
 
     @Override
     public boolean isCubeShaped() {
-        return false;
+        return false; // False makes it not able to be hard powered
     }
 
     @Override
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicDispenser.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicDispenser.java
index faacd85aa..55e368fea 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicDispenser.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicDispenser.java
@@ -27,6 +27,11 @@ import org.joml.Vector3dc;
 public class BlockLogicDispenser extends BlockLogicVeryRotatable {
     public static final int MASK_POWERED = 0b0000_1000;
 
+    @Override
+    protected int getMatcherEquivalenceData(final int data) {
+        return data & MASK_POWERED;
+    }
+
     public BlockLogicDispenser(final @NotNull Block<?> block) {
         super(block, Materials.STONE);
         block.withEntity(TileEntityDispenser::new);
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicDoor.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicDoor.java
index 86a67a49d..e07c01202 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicDoor.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicDoor.java
@@ -52,6 +52,19 @@ public class BlockLogicDoor
         }
     }
 
+    @Override
+    protected boolean isSameBlockForMatcher(final @NotNull Block<?> other) {
+        if (!(other.getLogic() instanceof BlockLogicDoor otherDoor)) {
+            return false;
+        }
+        return otherDoor.isTop == this.isTop;
+    }
+
+    @Override
+    protected int getMatcherEquivalenceData(final int data) {
+        return data & ~(MASK_ROTATION);
+    }
+
     @Override
     public void onPlacedOnSide(final @NotNull World world, final @NotNull TilePosc tilePos, final @NotNull Side side, final double xHit, final double yHit) {
         final int meta = world.getBlockData(tilePos);
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicFenceGate.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicFenceGate.java
index b803851d5..6a6f1d6de 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicFenceGate.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicFenceGate.java
@@ -32,6 +32,11 @@ public class BlockLogicFenceGate
         super(block, Materials.WOOD);
     }
 
+    @Override
+    protected int getMatcherEquivalenceData(final int data) {
+        return data & ~MASK_DIRECTION;
+    }
+
     @Override
     public @Nullable AABBdc getCollisionAABB(final @NotNull WorldSource source, final @NotNull TilePosc tilePos) {
         final int metadata = source.getBlockData(tilePos);
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicFullyRotatable.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicFullyRotatable.java
index 2313016d4..b80fefd85 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicFullyRotatable.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicFullyRotatable.java
@@ -14,6 +14,11 @@ public class BlockLogicFullyRotatable extends BlockLogic {
         super(block, material);
     }
 
+    @Override
+    protected int getMatcherEquivalenceData(final int data) {
+        return data & ~MASK_DIRECTION;
+    }
+
     @Override
     public void onPlacedByMob(@NotNull World world, @NotNull TilePosc tilePos, @NotNull Side side, @NotNull Mob mob, double xHit, double yHit) {
         final Direction direction = mob.getPlacementDirection(side).opposite();
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicLadder.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicLadder.java
index 3d43989b9..fb83841cb 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicLadder.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicLadder.java
@@ -22,6 +22,11 @@ public class BlockLogicLadder extends BlockLogic implements ISupportable {
         super(block, Materials.WOOD);
     }
 
+    @Override
+    protected int getMatcherEquivalenceData(final int data) {
+        return 0;
+    }
+
     @Override
     public boolean isSolidRender() {
         return false;
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicLamp.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicLamp.java
index 73b63fd20..a0286ec70 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicLamp.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicLamp.java
@@ -173,13 +173,12 @@ public class BlockLogicLamp extends BlockLogic implements IPainted {
     }
 
     @Override
-    public boolean isEquivalent(final @NotNull World world, final @NotNull TilePosc thisPos, final @NotNull TilePosc tilePos) {
-        Block<?> block = world.getBlockType(tilePos);
-
-        if (block.getLogic() instanceof BlockLogicLamp logicOfLamp) {
-            return this.isActive == logicOfLamp.isActive;
-        }
+    protected boolean isSameBlockForMatcher(final @NotNull Block<?> other) {
+        return other.getLogic() instanceof BlockLogicLamp;
+    }
 
-        return false;
+    @Override
+    protected int getMatcherEquivalenceData(final int data) {
+        return (this.isActive ? 1 : 0) | (data & 0x0F);
     }
 }
\ No newline at end of file
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicLever.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicLever.java
index b14ad73dd..e029e54be 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicLever.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicLever.java
@@ -39,6 +39,11 @@ public class BlockLogicLever extends BlockLogic implements ISupportable {
         super(block, Materials.DECORATION);
     }
 
+    @Override
+    protected int getMatcherEquivalenceData(final int data) {
+        return data & MASK_POWERED;
+    }
+
     @Override
     public @Nullable AABBdc getCollisionAABB(@NotNull WorldSource source, @NotNull TilePosc tilePos) {
         return null;
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicMatcher.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicMatcher.java
index 389110c9a..8c0b4510f 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicMatcher.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicMatcher.java
@@ -4,6 +4,7 @@ import net.minecraft.core.block.entity.TileEntity;
 import net.minecraft.core.block.material.Materials;
 import net.minecraft.core.block.piston.IMovingBlock;
 import net.minecraft.core.block.piston.MovingLine;
+import net.minecraft.core.block.piston.TileEntityMovingPistonBlock;
 import net.minecraft.core.entity.Mob;
 import net.minecraft.core.enums.EnumDropCause;
 import net.minecraft.core.enums.PlacementMode;
@@ -32,6 +33,16 @@ public class BlockLogicMatcher extends BlockLogicAxisAligned {
         this.isActive = isActive;
     }
 
+    @Override
+    protected boolean isSameBlockForMatcher(final @NotNull Block<?> other) {
+        return other.getLogic() instanceof BlockLogicMatcher;
+    }
+
+    @Override
+    protected int getMatcherEquivalenceData(final int data) {
+        return this.isActive ? 1 : 0;
+    }
+
     @Override
     public int tickDelay() {
         return 4;
@@ -65,12 +76,40 @@ public class BlockLogicMatcher extends BlockLogicAxisAligned {
 
     @Override
     public void onNeighborChanged(@NotNull World world, @NotNull TilePosc tilePos, final @NotNull Block<?> block) {
+        this.scheduleUpdate(world, tilePos);
+    }
+
+    private void scheduleUpdate(@NotNull World world, @NotNull TilePosc tilePos) {
         world.scheduleBlockUpdate(tilePos, this.block, tickDelay());
+        if (this.hasMovingPistonNeighbor(world, tilePos)) {
+            world.scheduleBlockUpdate(tilePos, this.block, TileEntityMovingPistonBlock.TICKS_TO_EXTEND + tickDelay());
+        }
+    }
+
+    private boolean hasMovingPistonNeighbor(@NotNull World world, @NotNull TilePosc tilePos) {
+        final Axis axis = BlockLogicAxisAligned.metaToAxis(world.getBlockData(tilePos) & BlockLogicAxisAligned.MASK_DIRECTION);
+        final TilePos front = new TilePos(tilePos);
+        final TilePos back = new TilePos(tilePos);
+        switch (axis) {
+            case X -> {
+                front.x += 1;
+                back.x -= 1;
+            }
+            case Y -> {
+                front.y += 1;
+                back.y -= 1;
+            }
+            default -> {
+                front.z += 1;
+                back.z -= 1;
+            }
+        }
+        return world.getBlockType(front) == Blocks.PISTON_MOVING || world.getBlockType(back) == Blocks.PISTON_MOVING;
     }
     
     @Override
     public void onMoved(@NotNull World world, @NotNull TilePosc sourcePos, @NotNull TilePosc targetPos, @NotNull IMovingBlock block, int indexInLine, @NotNull MovingLine line) {
-        world.scheduleBlockUpdate(targetPos, this.block, tickDelay());
+        this.scheduleUpdate(world, targetPos);
     }
 
     public void updateState(@NotNull World world, @NotNull TilePosc tilePos) {
@@ -95,9 +134,8 @@ public class BlockLogicMatcher extends BlockLogicAxisAligned {
         }
 
 
-        boolean backMatchesFront;
-        Block<?> fBlock = world.getBlockType(front);
-        backMatchesFront = fBlock.isEquivalent(world, front, back);
+        final @Nullable Block<?> frontBlock = world.getBlockType(front);
+        final boolean backMatchesFront = frontBlock.isEquivalent(world, front, back);
 
         if (!this.isActive && backMatchesFront) {
             world.setBlockTypeDataNotify(tilePos, Blocks.MATCHER_ACTIVE, world.getBlockData(tilePos));
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicNote.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicNote.java
index d889de532..44aa5147c 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicNote.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicNote.java
@@ -25,6 +25,11 @@ public class BlockLogicNote extends BlockLogic {
     public static final int MASK_NOTE = 0b0011_1111;
     public static final int MASK_POWERED = 0b1000_0000;
 
+    @Override
+    protected int getMatcherEquivalenceData(final int data) {
+        return data & MASK_POWERED;
+    }
+
     public static final byte MAX_NOTE = 24;
 
     public BlockLogicNote(@NotNull Block<?> block) {
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicPortal.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicPortal.java
index de4c866ce..c4e030e52 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicPortal.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicPortal.java
@@ -40,6 +40,11 @@ public class BlockLogicPortal extends BlockLogicTransparent implements IPainted
     public final @NotNull Block<?> portalFrame;
     public final @NotNull Block<?> portalTrigger;
 
+    @Override
+    protected int getMatcherEquivalenceData(final int data) {
+        return data & ~MASK_ROTATION;
+    }
+
     public BlockLogicPortal(@NotNull Block<?> block, @NotNull Dimension targetDimension, @NotNull Block<?> portalMaterial, @NotNull Block<?> portalTrigger) {
         super(block, Materials.PORTAL);
         this.targetDimension = targetDimension;
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicPressurePlate.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicPressurePlate.java
index e0725cb98..ddf3577e6 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicPressurePlate.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicPressurePlate.java
@@ -24,6 +24,11 @@ import java.util.Random;
 
 public class BlockLogicPressurePlate<T extends Entity> extends BlockLogic implements IPaintable, ISupportable {
     public static final int MASK_POWERED = 0b0000_0001;
+
+    @Override
+    protected int getMatcherEquivalenceData(final int data) {
+        return data & MASK_POWERED;
+    }
     public static final int MASK_SIDE = 0b0000_1110;
     private final @NotNull Class<T> triggerMobClass;
 
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicRail.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicRail.java
index 09d99f924..cca4f6fe7 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicRail.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicRail.java
@@ -38,6 +38,14 @@ public class BlockLogicRail extends BlockLogic implements ISupportable {
         setBlockBounds(0.0F, 0.0F, 0.0F, 1.0F, 0.125F, 1.0F);
     }
 
+    @Override
+    protected int getMatcherEquivalenceData(final int data) {
+        if (this.isPowered) {
+            return data & MASK_POWERED;
+        }
+        return data;
+    }
+
     public @NotNull RailDirection getRailDirection(@NotNull WorldSource world, @NotNull TilePosc tilePos) {
         int meta = world.getBlockData(tilePos);
         if (this.isPowered) {
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicRepeater.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicRepeater.java
index 1f8b0e775..caf9c3020 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicRepeater.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicRepeater.java
@@ -45,6 +45,16 @@ public class BlockLogicRepeater extends BlockLogic implements ISupportable {
         setBlockBounds(0.0F, 0.0F, 0.0F, 1.0F, 0.125F, 1.0F);
     }
 
+    @Override
+    protected boolean isSameBlockForMatcher(final @NotNull Block<?> other) {
+        return other.getLogic() instanceof BlockLogicRepeater;
+    }
+
+    @Override
+    protected int getMatcherEquivalenceData(final int data) {
+        return (this.isRepeaterPowered ? 0x10 : 0) | (data & MASK_TICK_DELAY);
+    }
+
     @Override
     public boolean isCubeShaped() {
         return false;
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicRotatable.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicRotatable.java
index df176e776..8e8075dc0 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicRotatable.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicRotatable.java
@@ -16,6 +16,11 @@ public abstract class BlockLogicRotatable extends BlockLogic {
         super(block, material);
     }
 
+    @Override
+    protected int getMatcherEquivalenceData(final int data) {
+        return data & ~MASK_DIRECTION;
+    }
+
     public static @NotNull Direction getDirectionFromMeta(int meta) {
         return Direction.fromId(meta & MASK_DIRECTION);
     }
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicRubyglassCrystal.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicRubyglassCrystal.java
index 514fced4c..4ce22dac6 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicRubyglassCrystal.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicRubyglassCrystal.java
@@ -21,6 +21,7 @@ import org.joml.primitives.AABBdc;
 import java.util.Random;
 
 public class BlockLogicRubyglassCrystal extends BlockLogicTransparent {
+	public static final int BREAK_STAGES = 3;
 	private final @NotNull AABBd checkAABB = new AABBd();
 	public BlockLogicRubyglassCrystal(@NotNull Block<?> block) {
 		super(block, Materials.ICE);
@@ -73,7 +74,7 @@ public class BlockLogicRubyglassCrystal extends BlockLogicTransparent {
 			int data = world.getBlockData(tilePos);
 			if (!standing.isEmpty()) {
 				int nextData = data + 1;
-				if (nextData >= 3) {
+				if (nextData > BREAK_STAGES) {
 					world.playBlockEvent(null, tilePos, LevelListener.EVENT_BLOCK_BREAK, block.id());
 					world.setBlockTypeNotify(tilePos, Blocks.AIR);
 				} else {
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicSign.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicSign.java
index dc87952e5..6b4afe098 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicSign.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicSign.java
@@ -40,6 +40,19 @@ public class BlockLogicSign extends BlockLogic implements IPaintable {
         block.withEntity(TileEntitySign::new);
     }
 
+    @Override
+    protected boolean isSameBlockForMatcher(final @NotNull Block<?> other) {
+        if (!(other.getLogic() instanceof BlockLogicSign otherSign)) {
+            return false;
+        }
+        return otherSign.isFreeStanding == this.isFreeStanding;
+    }
+
+    @Override
+    protected int getMatcherEquivalenceData(final int data) {
+        return 0;
+    }
+
     @Override
     public @Nullable AABBdc getCollisionAABB(@NotNull WorldSource source, @NotNull TilePosc tilePos) {
         return null;
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicSpikes.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicSpikes.java
index 10f062f76..f9b83120b 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicSpikes.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicSpikes.java
@@ -38,6 +38,11 @@ public class BlockLogicSpikes extends BlockLogic
         this.setBlockBounds(0.0F, 0.0F, 0.0F, 1.0F, thickness, 1.0F);
     }
 
+    @Override
+    protected int getMatcherEquivalenceData(final int data) {
+        return data & MASK_POWERED;
+    }
+
     @Override
     public void onPlacedByMob(final @NotNull World world, final @NotNull TilePosc tilePos, final @NotNull Side sidePlacedOnto, final @NotNull Mob mob, final double xHit, final double yHit) {
         if (world.isClientSide) {
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicStairs.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicStairs.java
index e0294b7a3..9d86f24b0 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicStairs.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicStairs.java
@@ -27,6 +27,11 @@ import org.joml.primitives.AABBd;
 import org.joml.primitives.AABBdc;
 
 public class BlockLogicStairs extends BlockLogic {
+    @Override
+    protected int getMatcherEquivalenceData(final int data) {
+        return data & ~MASK_ROTATION_HORIZONTAL;
+    }
+
     //    private static final Map<Integer, BoundingVolume> selectionVolumes = new HashMap<>();
 //
 //    static
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicThermalVent.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicThermalVent.java
index c04ab6df9..eb4e1250f 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicThermalVent.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicThermalVent.java
@@ -57,6 +57,11 @@ public class BlockLogicThermalVent extends BlockLogic {
         return Block.hasLogicClass(world.getBlockType(tilePos.down(new TilePos())), BlockLogicThermalVent.class);
     }
 
+    @Override
+    public boolean isEquivalent(final @NotNull World world, final @NotNull TilePosc thisPos, final @NotNull TilePosc thatPos) {
+        return Block.hasLogicClass(world.getBlockType(thatPos), BlockLogicThermalVent.class);
+    }
+
     @Override
     public void onNeighborChanged(final @NotNull World world, final @NotNull TilePosc tilePos, final @NotNull Block<?> block) {
         if (!hasVentBelow(world, tilePos) && getVentState(world.getBlockData(tilePos)) != DORMANT_STATE) {
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicTimer.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicTimer.java
index 21648dc61..068ddbb61 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicTimer.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicTimer.java
@@ -49,6 +49,11 @@ public class BlockLogicTimer extends BlockLogic implements ISupportable {
 
     public static final int MASK_TICK_DELAY = 0b0000_1100;
 
+    @Override
+    protected int getMatcherEquivalenceData(final int data) {
+        return data & (MASK_TICK_DELAY | ~MASK_DIRECTION);
+    }
+
     public BlockLogicTimer(@NotNull Block<?> block) {
         super(block, Materials.DECORATION);
         setBlockBounds(0.0F, 0.0F, 0.0F, 1.0F, 0.125F, 1.0F);
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicTorch.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicTorch.java
index 41b10f73c..2b7a5f1b5 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicTorch.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicTorch.java
@@ -34,6 +34,11 @@ public class BlockLogicTorch extends BlockLogic implements ISupportable {
         block.setTicking(true);
     }
 
+    @Override
+    protected int getMatcherEquivalenceData(final int data) {
+        return data & ~MASK_DIRECTION;
+    }
+
     @Override
     public @Nullable AABBdc getCollisionAABB(@NotNull WorldSource source, @NotNull TilePosc tilePos) {
         return null;
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicTorchRedstone.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicTorchRedstone.java
index 895b17f39..9c14af673 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicTorchRedstone.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicTorchRedstone.java
@@ -19,7 +19,16 @@ public class BlockLogicTorchRedstone extends BlockLogicTorch {
     public BlockLogicTorchRedstone(@NotNull Block<?> block, boolean isActive) {
         super(block);
         this.torchActive = isActive;
-        block.setTicking(true);
+    }
+
+    @Override
+    protected boolean isSameBlockForMatcher(final @NotNull Block<?> other) {
+        return other.getLogic() instanceof BlockLogicTorchRedstone;
+    }
+
+    @Override
+    protected int getMatcherEquivalenceData(final int data) {
+        return this.torchActive ? 1 : 0;
     }
 
     @Override
@@ -32,6 +41,7 @@ public class BlockLogicTorchRedstone extends BlockLogicTorch {
         if (world.getBlockData(tilePos) == SIDE_NONE) {
             super.onPlacedByWorld(world, tilePos);
         }
+        world.scheduleBlockUpdate(tilePos, this.block, tickDelay());
         world.notifyShellBlocksInRadiusOfNeighborChange(2, tilePos, this.block);
     }
 
@@ -70,8 +80,6 @@ public class BlockLogicTorchRedstone extends BlockLogicTorch {
 
     @Override
     public void updateTick(@NotNull World world, @NotNull TilePosc tilePos, @NotNull Random rand, boolean isRandomTick) {
-        if (isRandomTick) return;
-
         boolean isPowered = hasNeighborSignal(world, tilePos);
         if (this.torchActive) {
             if (isPowered) {
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicTrapDoor.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicTrapDoor.java
index 0855f0212..eb0b6a71b 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicTrapDoor.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicTrapDoor.java
@@ -43,6 +43,11 @@ public class BlockLogicTrapDoor extends BlockLogic implements IPaintable {
         setBlockBounds(0.5F - f, 0.0F, 0.5F - f, 0.5F + f, f1, 0.5F + f);
     }
 
+    @Override
+    protected int getMatcherEquivalenceData(final int data) {
+        return data & ~(MASK_DIRECTION | MASK_UPPER_HALF);
+    }
+
     @Override
     public void onPlacedOnSide(@NotNull World world, @NotNull TilePosc tilePos, @NotNull Side side, double xHit, double yHit) {
         int meta = world.getBlockData(tilePos);
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicVeryRotatable.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicVeryRotatable.java
index ef39e7d8e..0bfd57ee8 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicVeryRotatable.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicVeryRotatable.java
@@ -16,6 +16,11 @@ public abstract class BlockLogicVeryRotatable
         super(block, material);
     }
 
+    @Override
+    protected int getMatcherEquivalenceData(final int data) {
+        return data & ~MASK_DIRECTION;
+    }
+
     @Override
     public void onPlacedByMob(final @NotNull World world, final @NotNull TilePosc tilePos, final @NotNull Side side, final @NotNull Mob mob, final double xHit, final double yHit) {
         final @NotNull Direction direction = mob.getPlacementDirection(side).opposite();
diff --git a/game/core/src/main/java/net/minecraft/core/block/entity/TileEntityFurnace.java b/game/core/src/main/java/net/minecraft/core/block/entity/TileEntityFurnace.java
index 006d87b58..d000affae 100644
--- a/game/core/src/main/java/net/minecraft/core/block/entity/TileEntityFurnace.java
+++ b/game/core/src/main/java/net/minecraft/core/block/entity/TileEntityFurnace.java
@@ -370,10 +370,10 @@ public class TileEntityFurnace extends TileEntity
 
     @Override
     public Packet getDescriptionPacket() {
-        if (furnaceItemStacks[SLOT_RESULT] != null) {
+//        if (furnaceItemStacks[SLOT_RESULT] != null) { // Making this always send, furnaces aren't super big and this fixes the filled status not being synched in mp -Useless
             return new PacketTileEntityData(this);
-        }
-        return null;
+//        }
+//        return null;
     }
 
     @Override
diff --git a/game/core/src/main/java/net/minecraft/core/block/entity/TileEntityFurnaceBlast.java b/game/core/src/main/java/net/minecraft/core/block/entity/TileEntityFurnaceBlast.java
index 61cfb1c7b..87b75ad4a 100644
--- a/game/core/src/main/java/net/minecraft/core/block/entity/TileEntityFurnaceBlast.java
+++ b/game/core/src/main/java/net/minecraft/core/block/entity/TileEntityFurnaceBlast.java
@@ -328,10 +328,10 @@ public class TileEntityFurnaceBlast extends TileEntity
 
     @Override
     public Packet getDescriptionPacket() {
-        if (this.inventory[SLOT_RESULT] != null) {
+//        if (this.inventory[SLOT_RESULT] != null) { // Making this always send, blast furnaces aren't super big and this fixes the filled status not being synched in mp -Useless
             return new PacketTileEntityData(this);
-        }
-        return null;
+//        }
+//        return null;
     }
 
     @Override
diff --git a/game/core/src/main/java/net/minecraft/core/block/piston/BlockLogicPistonBase.java b/game/core/src/main/java/net/minecraft/core/block/piston/BlockLogicPistonBase.java
index 0b818cb64..931f38e8d 100644
--- a/game/core/src/main/java/net/minecraft/core/block/piston/BlockLogicPistonBase.java
+++ b/game/core/src/main/java/net/minecraft/core/block/piston/BlockLogicPistonBase.java
@@ -63,6 +63,16 @@ public class BlockLogicPistonBase extends BlockLogic {
         this.flingStrength = flingStrength;
     }
 
+    @Override
+    protected boolean isSameBlockForMatcher(final @NotNull Block<?> other) {
+        return other.getLogic() instanceof BlockLogicPistonBase otherBase && otherBase.typeId == this.typeId;
+    }
+
+    @Override
+    protected int getMatcherEquivalenceData(final int data) {
+        return data & ~DIRECTION.mask;
+    }
+
     // Piston updates:
     // =================
 
@@ -414,7 +424,7 @@ public class BlockLogicPistonBase extends BlockLogic {
     }
 
     private void retract(@NotNull World world, @NotNull TilePosc basePos, int data, int lineInfo) {
-        data = validateBaseData(data, this);
+        final int rData = IS_EXTENDED.set(validateBaseData(data, this), 0);
         final var dir = Direction.fromId(DIRECTION.get(data));
 
         final TilePos query1 = new TilePos(), query2 = new TilePos();
@@ -429,10 +439,9 @@ public class BlockLogicPistonBase extends BlockLogic {
         }
         world.setBlockTypeRaw(headPos, Blocks.AIR);
         world.removeTileEntity(headPos);
-
         final var moving = new TileEntityMovingPistonBlock(
             MovingType.RETRACTING_PISTON,
-            this.block, dir.id, null, dir);
+            this.block, rData, null, dir);
         world.setBlockTypeRaw(basePos, Blocks.AIR);
         world.setBlockTypeDataEntity(basePos, Blocks.PISTON_MOVING, data, moving);
 
@@ -588,10 +597,10 @@ public class BlockLogicPistonBase extends BlockLogic {
             static final ItemStack[] buf = new ItemStack[1];
         }
 
-        data = validateBaseData(data, this);
-        data = DIRECTION.set(data, 0) & IS_EXTENDED.set(data, 0);
+//        data = validateBaseData(data, this);
+//        data = DIRECTION.set(data, 0) & IS_EXTENDED.set(data, 0);
 
-        ctx.buf[0] = new ItemStack(this.block, 1, data);
+        ctx.buf[0] = new ItemStack(this.block, 1, /*data*/0); // Setting the item to just have metadata 0, the item doesn't really need to know the blocks metadata to work and it just complicates recipe logic when the metadata is there -Useless
         return ctx.buf;
     }
 
diff --git a/game/core/src/main/java/net/minecraft/core/block/piston/BlockLogicPistonHead.java b/game/core/src/main/java/net/minecraft/core/block/piston/BlockLogicPistonHead.java
index cb836acbe..e16017c0c 100644
--- a/game/core/src/main/java/net/minecraft/core/block/piston/BlockLogicPistonHead.java
+++ b/game/core/src/main/java/net/minecraft/core/block/piston/BlockLogicPistonHead.java
@@ -46,6 +46,11 @@ public class BlockLogicPistonHead extends BlockLogic implements ICarrySource {
         this.shaftThickness = shaftThickness;
     }
 
+    @Override
+    protected int getMatcherEquivalenceData(final int data) {
+        return data & ~DIRECTION.mask;
+    }
+
     @Override
     public boolean isSolidRender() {
         return false;
diff --git a/game/core/src/main/java/net/minecraft/core/block/piston/BlockLogicPistonMoving.java b/game/core/src/main/java/net/minecraft/core/block/piston/BlockLogicPistonMoving.java
index e8f2f9aba..63372dce4 100644
--- a/game/core/src/main/java/net/minecraft/core/block/piston/BlockLogicPistonMoving.java
+++ b/game/core/src/main/java/net/minecraft/core/block/piston/BlockLogicPistonMoving.java
@@ -133,6 +133,15 @@ public class BlockLogicPistonMoving extends BlockLogic {
             : super.getBoundsFromState(source, tilePos);
     }
 
+    @Override
+    public @NotNull MatcherSnapshot resolveForMatcher(@NotNull World world, @NotNull TilePosc tilePos) {
+        if (world.getTileEntity(tilePos) instanceof TileEntityMovingPistonBlock moving) {
+            final Block<?> block = moving.movingBlock();
+            return new MatcherSnapshot(block, moving.movingBlockData(), block.getLogic());
+        }
+        return super.resolveForMatcher(world, tilePos);
+    }
+
     @Override
     public int getPistonPushReaction(@NotNull World world, @NotNull TilePosc tilePos) {
         return Material.PISTON_CANT_PUSH;
diff --git a/game/core/src/main/java/net/minecraft/core/block/piston/MovingLine.java b/game/core/src/main/java/net/minecraft/core/block/piston/MovingLine.java
index fdb808614..d97eda610 100644
--- a/game/core/src/main/java/net/minecraft/core/block/piston/MovingLine.java
+++ b/game/core/src/main/java/net/minecraft/core/block/piston/MovingLine.java
@@ -119,8 +119,17 @@ public final class MovingLine {
         if (!this.isExtending) world.markBlockDirty(query);
 
         for (int i = 0; i < len; i++, query.add(this.direction)) {
-            world.setBlockTypeDataEntity(query, Blocks.PISTON_MOVING, this.data[i], this.createMoving(i));
+            world.setBlockTypeDataEntity(query, Blocks.PISTON_MOVING, Byte.toUnsignedInt(this.data[i]), this.createMoving(i));
         }
+
+        if (!this.isExtending) this.notifyLineEndNeighbors(query);
+    }
+
+    private void notifyLineEndNeighbors(@NotNull TilePos query) {
+        world.notifyBlocksOfNeighborChange(this.origin, null);
+        query.set(this.origin);
+        this.direction.offsetScaled(query, this.len);
+        world.notifyBlocksOfNeighborChange(query, null);
     }
 
     void notify(@NotNull TilePos query1, @NotNull TilePos query2) {
@@ -170,7 +179,7 @@ public final class MovingLine {
             if (world.hasDirectSignal(queryPos.set(tilePos).add(d2), d2.side())) return true;
         }
         if (block == Blocks.PUMPKIN_REDSTONE) {
-            final var pumpkinDir = Direction.fromId(this.data[index]);
+            final var pumpkinDir = Direction.fromId(Byte.toUnsignedInt(this.data[index]));
             if (pumpkinDir != d1 && world.hasDirectSignal(queryPos.set(tilePos).add(d1), d1.side())) return true;
             if (pumpkinDir != d2 && world.hasDirectSignal(queryPos.set(tilePos).add(d2), d2.side())) return true;
         }
@@ -281,6 +290,6 @@ public final class MovingLine {
         assert index >= 0 && index < this.len;
         return this.movingBlocks[index] = new TileEntityMovingPistonBlock(
             (this.isExtending) ? MovingType.EXTENDING : MovingType.RETRACTING,
-            this.blocks[index], this.data[index], this.entities[index], this.direction);
+            this.blocks[index], Byte.toUnsignedInt(this.data[index]), this.entities[index], this.direction);
     }
 }
diff --git a/game/core/src/main/java/net/minecraft/core/entity/EntityItem.java b/game/core/src/main/java/net/minecraft/core/entity/EntityItem.java
index 3e1a09e98..41355d1ae 100644
--- a/game/core/src/main/java/net/minecraft/core/entity/EntityItem.java
+++ b/game/core/src/main/java/net/minecraft/core/entity/EntityItem.java
@@ -151,6 +151,10 @@ public class EntityItem extends Entity
         }
     }
 
+    @Override
+    public void waterHurt() {
+    }
+
     @Override
     public boolean hurt(Entity entity, int i, DamageType type)
     {
diff --git a/game/core/src/main/java/net/minecraft/core/item/ItemBoat.java b/game/core/src/main/java/net/minecraft/core/item/ItemBoat.java
index 0d0fcfcc6..05821f209 100644
--- a/game/core/src/main/java/net/minecraft/core/item/ItemBoat.java
+++ b/game/core/src/main/java/net/minecraft/core/item/ItemBoat.java
@@ -5,7 +5,7 @@ import net.minecraft.core.entity.vehicle.EntityBoat;
 import net.minecraft.core.entity.player.Player;
 import net.minecraft.core.block.Blocks;
 import net.minecraft.core.util.helper.Direction;
-import net.minecraft.core.util.phys.HitResult;
+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;
@@ -22,24 +22,25 @@ public class ItemBoat extends Item {
     }
 
     @Override
-    public @Nullable ItemStack onUse(@NotNull ItemStack selfStack, @NotNull World world, @NotNull Player player) {
-        HitResult rayCast = player.rayCast(player.getGamemode().getBlockReachDistance(), 1f, true, false, false);
-        if (rayCast == null) {
-            return selfStack;
-        }
-        if (rayCast instanceof HitResult.Tile hitTile) {
-            TilePos pos = new TilePos(hitTile.tilePos);
-            if (!world.isClientSide) {
-                if (world.getBlockType(pos) == Blocks.LAYER_SNOW) {
-                    pos.y--;
-                }
-                EntityBoat boat = new EntityBoat(world, pos.x + 0.5F, pos.y + 1.0F, pos.z + 0.5F);
+    public boolean interactsWithFluid(@NotNull ItemStack selfStack) {
+        return true;
+    }
+
+    @Override
+    public boolean onUseOnBlock(@NotNull ItemStack selfStack, @NotNull World world, @Nullable Player player, @NotNull TilePosc blockPos, @NotNull Side side, double xHit, double yHit) {
+        TilePos pos = new TilePos(blockPos);
+        if (!world.isClientSide) {
+            if (world.getBlockType(pos) == Blocks.LAYER_SNOW) {
+                pos.y--;
+            }
+            EntityBoat boat = new EntityBoat(world, pos.x + 0.5F, pos.y + 1.0F, pos.z + 0.5F);
+            if (player != null) {
                 boat.setRot(player.yRot - 90, 0);
-                world.entityJoinedWorld(boat);
             }
-            selfStack.consumeItem(player);
+            world.entityJoinedWorld(boat);
         }
-        return selfStack;
+        selfStack.consumeItem(player);
+        return true;
     }
 
     @Override
diff --git a/game/core/src/main/java/net/minecraft/core/item/ItemBucket.java b/game/core/src/main/java/net/minecraft/core/item/ItemBucket.java
index 3dbc3b892..5ed5c8f78 100644
--- a/game/core/src/main/java/net/minecraft/core/item/ItemBucket.java
+++ b/game/core/src/main/java/net/minecraft/core/item/ItemBucket.java
@@ -17,12 +17,11 @@ import net.minecraft.core.util.HardIllegalArgumentException;
 import net.minecraft.core.util.collection.NamespaceID;
 import net.minecraft.core.util.helper.Direction;
 import net.minecraft.core.util.helper.Side;
-import net.minecraft.core.util.phys.HitResult;
 import net.minecraft.core.world.LevelListener;
 import net.minecraft.core.world.World;
-import net.minecraft.core.world.type.tag.WorldTypeTags;
 import net.minecraft.core.world.pos.TilePos;
 import net.minecraft.core.world.pos.TilePosc;
+import net.minecraft.core.world.type.tag.WorldTypeTags;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 import org.joml.primitives.AABBd;
@@ -275,10 +274,13 @@ public abstract class ItemBucket extends ItemFood implements IItemContainer {
 
         if (currentState.isEdible() && !currentState.canPlace()) return false;
 
-        final int oldSize = selfStack.stackSize;
-        @Nullable ItemStack newStack;
+        final @NotNull UseResult result;
+
+        interface ctx { @NotNull UseResult result = new UseResult(); }
+        interact: synchronized(ctx.result) {
+            ctx.result.used = false;
+            ctx.result.itemStack = null;
 
-        interact: {
             if (world.getBlockData(tilePos) == 0) {
                 final NamespaceID targetStateId = getFluidStateAt(world, tilePos);
     
@@ -287,7 +289,7 @@ public abstract class ItemBucket extends ItemFood implements IItemContainer {
                     currentStateId.equals(targetStateId) && getCharges(selfStack) < this.maxCharges;
     
                 if (targetStateId != null && (isEmptyScoop || isRefillScoop)) {
-                    newStack = tryPickupFluid(selfStack, world, player, tilePos, targetStateId);
+                    result = tryPickupFluid(selfStack, world, player, tilePos, targetStateId, ctx.result);
                     break interact;
                 }
             }
@@ -295,20 +297,20 @@ public abstract class ItemBucket extends ItemFood implements IItemContainer {
             if (currentState.canPlace()) {
                 final NamespaceID aimedFluid = getFluidStateAt(world, tilePos);
                 if (currentStateId.equals(aimedFluid) && getCharges(selfStack) < this.maxCharges) {
-                    newStack = selfStack;
+                    result = ctx.result;
                     break interact;
                 }
 
-                newStack = tryPlaceFluid(selfStack, world, player, tilePos, side);
+                result = tryPlaceFluid(selfStack, world, player, tilePos, side, ctx.result);
                 break interact;
             }
             return false;
         }
 
-        final boolean isDifferent = oldSize != newStack.stackSize || newStack != selfStack;
-        if (isDifferent) player.inventory.setItem(player.inventory.getCurrentSlot(), newStack);
-
-        return isDifferent;
+        if (result.used && result.itemStack != null) {
+            player.inventory.setItem(player.inventory.getCurrentSlot(), result.itemStack);
+        }
+        return result.used;
     }
 
     @Override
@@ -316,47 +318,59 @@ public abstract class ItemBucket extends ItemFood implements IItemContainer {
         return handleAirClick(itemStack, world, player);
     }
 
-    protected ItemStack tryPickupFluid(ItemStack itemStack, World world, Player player, TilePosc tilePos, NamespaceID targetState) {
-        if (!world.canMineBlock(player, tilePos)) return itemStack;
+    protected static class UseResult {
+        public boolean used = false;
+        public @Nullable ItemStack itemStack = null;
+    }
+
+    protected @NotNull UseResult tryPickupFluid(
+        @NotNull ItemStack itemStack, @NotNull World world, @NotNull Player player, @NotNull TilePosc tilePos, @NotNull NamespaceID targetState,
+        @NotNull UseResult result
+    ) {
+        if (!world.canMineBlock(player, tilePos)) return result;
 
         world.setBlockTypeNotify(tilePos, Blocks.AIR);
-        player.swingItem();
         world.playSoundEffect(player, SoundCategory.WORLD_SOUNDS, tilePos.x() + 0.5F, tilePos.y() + 0.5F, tilePos.z() + 0.5F, "item.pickup", 2.2f, 0.5f);
 
+        result.used = true;
         if (itemStack.stackSize > 1) {
-            return splitBucket(itemStack, player, targetState, world);
+            result.itemStack = splitBucket(itemStack, player, targetState, world);
+            return result;
+        }
+        final NamespaceID currentState = getState(itemStack);
+        if (STATE_EMPTY.equals(currentState)) {
+            setState(itemStack, targetState);
+            setCharges(itemStack, 1);
         } else {
-            NamespaceID currentState = getState(itemStack);
-            if (STATE_EMPTY.equals(currentState)) {
-                setState(itemStack, targetState);
-                setCharges(itemStack, 1);
-            } else {
-                setCharges(itemStack, getCharges(itemStack) + 1);
-            }
-            return itemStack;
+            setCharges(itemStack, getCharges(itemStack) + 1);
         }
+        result.itemStack = itemStack;
+        return result;
     }
 
-    protected ItemStack tryPlaceFluid(@NotNull ItemStack itemStack, @NotNull World world, @NotNull Player player, @NotNull TilePosc tilePos, @NotNull Side side) {
-        if (!world.canMineBlock(player, tilePos)) return itemStack;
+    protected @NotNull UseResult tryPlaceFluid(
+        @NotNull ItemStack itemStack, @NotNull World world, @NotNull Player player, @NotNull TilePosc tilePos, @NotNull Side side,
+        @NotNull UseResult result
+    ) {
+        if (!world.canMineBlock(player, tilePos)) return result;
 
         final NamespaceID stateId = getState(itemStack);
         final BucketState bucketState = getBucketState(stateId);
-        if (bucketState.fluidBlock() == null) return itemStack;
+        if (bucketState.fluidBlock() == null) return result;
 
         final var blockAtPos = world.getBlockType(tilePos);
         if (!blockAtPos.hasTag(BlockTags.PLACE_OVERWRITES) && !blockAtPos.hasTag(BlockTags.BROKEN_BY_FLUIDS)) {
             tilePos = tilePos.add(side, new TilePos());
         }
 
-        if (!world.canBlockIdBePlacedAt(bucketState.fluidBlock().id(), tilePos, true, side)) return itemStack;
+        if (!world.canBlockIdBePlacedAt(bucketState.fluidBlock().id(), tilePos, true, side)) return result;
 
         if (bucketState.placementAction() != null) bucketState.placementAction().accept(world, tilePos);
 
         final Block<?> replacedBlock = world.getBlockType(tilePos);
         final int replacedData = world.getBlockData(tilePos);
 
-        if (!world.setBlockTypeNotify(tilePos, bucketState.fluidBlock())) return itemStack;
+        if (!world.setBlockTypeNotify(tilePos, bucketState.fluidBlock())) return result;
 
         replacedBlock.dropWithCause(world, EnumDropCause.WORLD, tilePos, replacedData, null, null);
 
@@ -369,7 +383,9 @@ public abstract class ItemBucket extends ItemFood implements IItemContainer {
             world.playSoundEffect(player, SoundCategory.WORLD_SOUNDS, tilePos.x() + 0.5F, tilePos.y() + 0.5F, tilePos.z() + 0.5F, bucketState.soundPath(), 1f, 1f);
         }
 
-        return itemStack;
+        result.used = true;
+        result.itemStack = itemStack;
+        return result;
     }
 
     protected ItemStack handleAirClick(ItemStack itemStack, World world, Player player) {
diff --git a/game/core/src/main/java/net/minecraft/core/item/ItemPlaceablePair.java b/game/core/src/main/java/net/minecraft/core/item/ItemPlaceablePair.java
index 3827f3647..0b2c38152 100644
--- a/game/core/src/main/java/net/minecraft/core/item/ItemPlaceablePair.java
+++ b/game/core/src/main/java/net/minecraft/core/item/ItemPlaceablePair.java
@@ -116,12 +116,16 @@ public abstract class ItemPlaceablePair extends Item implements IPlaceable {
         
         isAIdentical = prevBlockA == this.blockA && prevDataA == dataA;
         isBIdentical = prevBlockB == this.blockB && prevDataB == dataB;
-        
+
+        if (isAIdentical && isBIdentical) return false;
+
         if (!isAIdentical) world.setBlockTypeDataRaw(posA, this.blockA, dataA);
         if (!isBIdentical) world.setBlockTypeDataRaw(posB, this.blockB, dataB);
         if (!isAIdentical) world.markBlockNeedsUpdate(posA);
         if (!isBIdentical) world.markBlockNeedsUpdate(posB);
 
+        selfStack.consumeItem(player);
+
         if (!isAIdentical) prevBlockA.onRemoved(world, posA, prevDataA);
         if (!isBIdentical) prevBlockB.onRemoved(world, posB, prevDataB);
         if (player == null) {
diff --git a/game/core/src/main/java/net/minecraft/core/item/ItemWandSpawner.java b/game/core/src/main/java/net/minecraft/core/item/ItemWandSpawner.java
index b37151054..2e4a7e2cd 100644
--- a/game/core/src/main/java/net/minecraft/core/item/ItemWandSpawner.java
+++ b/game/core/src/main/java/net/minecraft/core/item/ItemWandSpawner.java
@@ -4,7 +4,7 @@ import net.minecraft.core.block.entity.TileEntityActivator;
 import net.minecraft.core.entity.Mob;
 import net.minecraft.core.player.gamemode.Gamemodes;
 import net.minecraft.core.util.helper.Direction;
-import net.minecraft.core.util.phys.HitResult;
+import net.minecraft.core.util.helper.Side;
 import net.minecraft.core.entity.Entity;
 import net.minecraft.core.entity.EntityDispatcher;
 import net.minecraft.core.entity.player.Player;
@@ -20,6 +20,22 @@ public class ItemWandSpawner extends Item implements IDispensable
     public ItemWandSpawner(@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 (player == null) return false;
+        if (player.gamemode != Gamemodes.CREATIVE) return false;
+        if (player.isSneaking()) return false;
+
+        if (!world.isClientSide) {
+            final double x = blockPos.x() + 0.5 + side.offsetX() * 0.5;
+            final double y = blockPos.y() + 0.5 + side.offsetY() * 0.5;
+            final double z = blockPos.z() + 0.5 + side.offsetZ() * 0.5;
+            spawnEntity(selfStack, world, x, y, z);
+        }
+        return true;
+    }
+
     @Override
     public @Nullable ItemStack onUse(@NotNull ItemStack selfStack, @NotNull World world, @NotNull Player player) {
         if (player.gamemode != Gamemodes.CREATIVE){
@@ -27,18 +43,6 @@ public class ItemWandSpawner extends Item implements IDispensable
             return selfStack;
         }
 
-        if (!player.isSneaking()){
-            double reachDistance = player.getGamemode().getBlockReachDistance();
-            HitResult rayTraceResult = player.rayCast(reachDistance, 1f, false, false, false);
-
-            if (rayTraceResult != null){
-                if (!world.isClientSide){
-                    spawnEntity(selfStack, world, rayTraceResult.location.x(), rayTraceResult.location.y(), rayTraceResult.location.z());
-                }
-                return selfStack;
-            }
-        }
-
         player.displayWandMobPickerScreen(selfStack);
         return selfStack;
     }
diff --git a/game/core/src/main/java/net/minecraft/core/world/World.java b/game/core/src/main/java/net/minecraft/core/world/World.java
index 5546e3925..d69e43401 100644
--- a/game/core/src/main/java/net/minecraft/core/world/World.java
+++ b/game/core/src/main/java/net/minecraft/core/world/World.java
@@ -159,7 +159,7 @@ public abstract class World implements MutableWorldSource {
     private final @NotNull ConduitHandler conduitHandler = new ConduitHandler(this);
     private final @NotNull LightingEngine lightingEngine = new LightingEngineLegacy(this);
 
-    private World(
+    protected World(
         final @NotNull Dimension dimension,
         final @NotNull LevelStorage levelStorage,
         final @Nullable WorldConfiguration worldConfiguration,
diff --git a/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/overworld/ChunkDecoratorOverworld.java b/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/overworld/ChunkDecoratorOverworld.java
index 06ac14c92..83d211e27 100644
--- a/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/overworld/ChunkDecoratorOverworld.java
+++ b/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/overworld/ChunkDecoratorOverworld.java
@@ -591,7 +591,7 @@ public class ChunkDecoratorOverworld
             int j16 = x + rand.nextInt(16) + 8;
             int j21 = z + rand.nextInt(16) + 8;
             int i22 = this.world.getHeightValue(j16, j21);
-            (new WorldFeatureSponge()).place(this.world, rand, j16, i22, j21);
+            (new WorldFeatureSponge()).place(this.world, rand, new TilePos(j16, i22, j21));
         }
 
         int k16 = 0;
diff --git a/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureSponge.java b/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureSponge.java
index 5ab9f583f..46b87fe4a 100644
--- a/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureSponge.java
+++ b/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureSponge.java
@@ -2,29 +2,34 @@ 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 WorldFeatureSponge extends WorldFeature
+public class WorldFeatureSponge implements WorldFeatureInterface
 {
-
     @Override
-	public boolean place(World world, Random random, int x, int y, int z)
-    {
-        if (world.getBlockId(x, y - 1, z) != Blocks.FLUID_WATER_STILL.id())
+    public boolean place(@NotNull World world, @NotNull Random random, @NotNull TilePosc tilePos) {
+        int x = tilePos.x();
+        int y = tilePos.y();
+        int z = tilePos.z();
+        TilePos queryPos = new TilePos(tilePos);
+        if (world.getBlockType(queryPos.set(x, y - 1, z)) != Blocks.FLUID_WATER_STILL)
             return false;
         for(int l = 0; l < 16; l++) {
-            int i1 = (x + random.nextInt(8)) - random.nextInt(8);
-            int k1 = (z + random.nextInt(8)) - random.nextInt(8);
+            int rx = (x + random.nextInt(8)) - random.nextInt(8);
+            int rz = (z + random.nextInt(8)) - random.nextInt(8);
 
-            for (; world.getBlockId(i1, y - 1, k1) == Blocks.FLUID_WATER_STILL.id(); y--);
+            for (; world.getBlockType(queryPos.set(rx, y - 1, rz)) == Blocks.FLUID_WATER_STILL; y--);
 
-            if(world.getBlockId(i1, y-1, k1) != Blocks.SAND.id() && world.getBlockId(i1, y-1, k1) != Blocks.DIRT.id())
+            if(world.getBlockType(queryPos.set(rx, y-1, rz)) != Blocks.SAND && world.getBlockType(queryPos.set(rx, y-1, rz)) != Blocks.DIRT)
                 continue;
-            if(world.getBlockId(i1, y+1, k1) == 0)
+            if(world.isAirBlock(queryPos.set(rx, y+1, rz)))
                 continue;
 
-            world.setBlockRaw(i1, y, k1, Blocks.SPONGE_WET.id());
+            world.setBlockTypeNotify(queryPos.set(rx, y, rz), Blocks.SPONGE_WET);
         }
         return true;
     }
diff --git a/game/core/src/main/resources/assets/minecraft/sounds/sounds.json b/game/core/src/main/resources/assets/minecraft/sounds/sounds.json
index 10b3a8b1a..b15e45ff1 100644
--- a/game/core/src/main/resources/assets/minecraft/sounds/sounds.json
+++ b/game/core/src/main/resources/assets/minecraft/sounds/sounds.json
@@ -1270,7 +1270,7 @@
       },
       {
         "name": "btamusic/underground/untitled.ogg",
-        "title": "Untitled Cave Song",
+        "title": "Karst",
         "author": "ShignBright",
         "stream": true
       }
diff --git a/util/seed-viewer/build.gradle.kts b/util/seed-viewer/build.gradle.kts
index 0d1f2e98f..17d9b0c1e 100644
--- a/util/seed-viewer/build.gradle.kts
+++ b/util/seed-viewer/build.gradle.kts
@@ -7,7 +7,7 @@ plugins {
 }
 
 group = "org.useless"
-version = "1.1.2"
+version = "1.2.0"
 
 val include: Configuration by configurations.creating
 
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 610a57353..6cbe26ba8 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
@@ -11,8 +11,16 @@ import net.minecraft.core.world.biome.Biomes;
 import net.minecraft.core.world.biome.provider.BiomeProvider;
 import net.minecraft.core.world.biome.provider.BiomeProviderOverworld;
 import net.minecraft.core.world.pos.ChunkPos;
+import net.minecraft.core.world.save.ISaveFormat;
 import net.minecraft.core.world.save.LevelData;
+import net.minecraft.core.world.save.LevelStorage;
+import net.minecraft.core.world.save.LevelStorageDummy;
+import net.minecraft.core.world.save.SaveFormatBase;
+import net.minecraft.core.world.save.SaveFormats;
+import net.minecraft.core.world.settings.WorldConfiguration;
+import net.minecraft.core.world.type.WorldType;
 import net.minecraft.core.world.type.WorldTypes;
+import org.jetbrains.annotations.NotNull;
 import org.useless.seedviewer.bta.worldloader.ChunkLoader;
 import org.useless.seedviewer.data.ViewerChunk;
 import org.useless.seedviewer.gui.ChunkProvider;
@@ -21,6 +29,8 @@ import org.slf4j.Logger;
 import java.io.File;
 import java.io.IOException;
 
+import static net.minecraft.core.Global.CURRENT_SAVE_VERSION;
+
 public class BTAChunkProvider implements ChunkProvider {
     private static final Logger LOGGER = LogUtils.getLogger();
     static {
@@ -41,14 +51,20 @@ public class BTAChunkProvider implements ChunkProvider {
     private final BiomeProvider biomeProvider;
 
 
-    public BTAChunkProvider(long seed) {
-          this.biomeProvider = WorldTypes.NETHER_DEFAULT.createBiomeProvider(new DummyWorld(Dimension.NETHER));
+    public BTAChunkProvider(@NotNull WorldType worldType, @NotNull Dimension dimension, long seed) {
+        LevelStorage storage = new LevelStorageDummy();
+        WorldConfiguration config = new WorldConfiguration(); config.setNumericSeed(seed);
+        this.biomeProvider = worldType.createBiomeProvider(new DummyWorld(dimension, storage, config, null, null));
     }
 
-    public BTAChunkProvider(File worldFolder, LevelData data) {
-//        this.biomeProvider = new BiomeProviderOverworld(data.getRandomSeed(), WorldTypes.OVERWORLD_EXTENDED);
-        this.chunkLoader = new ChunkLoader(worldFolder, Dimension.OVERWORLD);
-        throw new IllegalArgumentException("This is currently broken!");
+    public BTAChunkProvider(@NotNull File worldFolder, @NotNull Dimension dimension, @NotNull LevelData data) {
+        ISaveFormat activeFormat = SaveFormats.createSaveFormat(CURRENT_SAVE_VERSION, new File(worldFolder.getParent()));
+        if (!(activeFormat instanceof SaveFormatBase saveFormat)) {
+            throw new IllegalStateException("No save format registered for current save version " + CURRENT_SAVE_VERSION);
+        }
+        LevelStorage saveHandler = saveFormat.getLevelStorage(worldFolder.getName(), false);
+        this.biomeProvider = new DummyWorld(dimension, saveHandler, null, data, saveHandler.getDimensionDataOrDefault(dimension)).getBiomeProvider();
+        this.chunkLoader = new ChunkLoader(worldFolder, dimension, data.getRandomSeed());
     }
     @Override
     public ViewerChunk getChunk(ChunkPos location) {
diff --git a/util/seed-viewer/src/main/java/org/useless/seedviewer/bta/BTAWorld.java b/util/seed-viewer/src/main/java/org/useless/seedviewer/bta/BTAWorld.java
index 97e631027..57193de34 100644
--- a/util/seed-viewer/src/main/java/org/useless/seedviewer/bta/BTAWorld.java
+++ b/util/seed-viewer/src/main/java/org/useless/seedviewer/bta/BTAWorld.java
@@ -1,5 +1,6 @@
 package org.useless.seedviewer.bta;
 
+import net.minecraft.core.world.Dimension;
 import net.minecraft.core.world.World;
 import net.minecraft.core.world.save.LevelData;
 import org.jetbrains.annotations.NotNull;
@@ -11,10 +12,12 @@ import java.io.File;
 import java.util.Objects;
 
 public class BTAWorld implements ViewerWorld {
-    private final @NotNull File worldFolder;
+    public final @NotNull File worldFolder;
+    private final @NotNull Dimension dimension;
     private final LevelData data;
-    public BTAWorld(@NotNull File worldFolder) {
+    public BTAWorld(@NotNull File worldFolder, @NotNull Dimension dimension) {
         this.worldFolder = worldFolder;
+        this.dimension = dimension;
         this.data = Objects.requireNonNull(LevelDataLoader.getLevelData(worldFolder), "Failed to get level data from world '" + worldFolder + "'");
     }
     @Override
@@ -39,6 +42,6 @@ public class BTAWorld implements ViewerWorld {
 
     @Override
     public ChunkProvider getChunkProvider() {
-        return new BTAChunkProvider(this.worldFolder, this.data);
+        return new BTAChunkProvider(this.worldFolder, this.dimension, this.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 53e7c5216..c79f74f61 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,10 @@ 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.DimensionData;
+import net.minecraft.core.world.save.LevelData;
 import net.minecraft.core.world.save.LevelStorage;
 import net.minecraft.core.world.save.LevelStorageDummy;
 import net.minecraft.core.world.settings.WorldConfiguration;
@@ -16,8 +19,14 @@ import java.util.Objects;
 public class DummyWorld extends World {
     public static @Nullable ChunkLoader CHUNK_LOADER = null;
 
-    public DummyWorld(final @NotNull Dimension dimension) {
-        super(new LevelStorageDummy(), new WorldConfiguration(), dimension);
+    public DummyWorld(
+        final @NotNull Dimension dimension,
+        final @NotNull LevelStorage levelStorage,
+        final @Nullable WorldConfiguration worldConfiguration,
+        final @Nullable LevelData levelData,
+        final @Nullable DimensionData dimensionData
+    ) {
+        super(dimension, levelStorage, worldConfiguration, levelData, dimensionData);
     }
 
     @Override
@@ -25,6 +34,11 @@ public class DummyWorld extends World {
         // Do nothing
     }
 
+    @Override
+    protected void getInitialSpawnLocation(@Nullable Biome startingBiome) {
+
+    }
+
     @Override
     protected @NotNull ChunkProvider createChunkProvider(final @NotNull LevelStorage levelStorage) {
         return Objects.requireNonNullElseGet(CHUNK_LOADER, () -> new ChunkLoaderEmpty(this));
diff --git a/util/seed-viewer/src/main/java/org/useless/seedviewer/bta/worldloader/ChunkLoader.java b/util/seed-viewer/src/main/java/org/useless/seedviewer/bta/worldloader/ChunkLoader.java
index e20cfe7ad..c10967a29 100644
--- a/util/seed-viewer/src/main/java/org/useless/seedviewer/bta/worldloader/ChunkLoader.java
+++ b/util/seed-viewer/src/main/java/org/useless/seedviewer/bta/worldloader/ChunkLoader.java
@@ -15,7 +15,9 @@ import net.minecraft.core.world.chunk.provider.ChunkProvider;
 import net.minecraft.core.world.chunk.reader.*;
 import net.minecraft.core.world.pos.ChunkPos;
 import net.minecraft.core.world.pos.ChunkPosc;
+import net.minecraft.core.world.save.LevelStorageDummy;
 import net.minecraft.core.world.save.mcregion.RegionFileCache;
+import net.minecraft.core.world.settings.WorldConfiguration;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 import org.slf4j.Logger;
@@ -33,10 +35,11 @@ public class ChunkLoader implements ChunkProvider {
 
 
     private final File dimensionDir;
-    public ChunkLoader(File worldFolder, Dimension dimension) {
+    public ChunkLoader(File worldFolder, Dimension dimension, long seed) {
         this.dimensionDir = new File(worldFolder, "dimensions/" + dimension.id);
         DummyWorld.CHUNK_LOADER = this;
-        this.dummyWorld = new DummyWorld(dimension);
+        WorldConfiguration configuration = new WorldConfiguration(); configuration.setNumericSeed(seed);
+        this.dummyWorld = new DummyWorld(dimension, new LevelStorageDummy(), configuration, null, null);
         this.empty = new EmptyChunk(this.dummyWorld, 0, 0);
     }
 
diff --git a/util/seed-viewer/src/main/java/org/useless/seedviewer/gui/ChunkView.java b/util/seed-viewer/src/main/java/org/useless/seedviewer/gui/ChunkView.java
index 9d7ab22b3..40e57a7dc 100644
--- a/util/seed-viewer/src/main/java/org/useless/seedviewer/gui/ChunkView.java
+++ b/util/seed-viewer/src/main/java/org/useless/seedviewer/gui/ChunkView.java
@@ -16,8 +16,8 @@ import java.util.concurrent.Future;
 
 public class ChunkView {
     private static final Logger LOGGER = LogUtils.getLogger();
-    private static final ExecutorService InitializerService = Executors.newFixedThreadPool(10);
-    private static final ExecutorService PostProcessorService = Executors.newFixedThreadPool(10);
+    public static final ExecutorService InitializerService = Executors.newFixedThreadPool(10);
+    public static final ExecutorService PostProcessorService = Executors.newFixedThreadPool(10);
 
     private final BufferedImage biomeMapImage = new BufferedImage(ViewerChunk.CHUNK_SIZE_X, ViewerChunk.CHUNK_SIZE_Z, BufferedImage.TYPE_INT_ARGB);
     private final BufferedImage terrainMapImage = new BufferedImage(ViewerChunk.CHUNK_SIZE_X, ViewerChunk.CHUNK_SIZE_Z, BufferedImage.TYPE_INT_ARGB);
diff --git a/util/seed-viewer/src/main/java/org/useless/seedviewer/gui/SeedViewer.java b/util/seed-viewer/src/main/java/org/useless/seedviewer/gui/SeedViewer.java
index 075973989..d050b4260 100644
--- a/util/seed-viewer/src/main/java/org/useless/seedviewer/gui/SeedViewer.java
+++ b/util/seed-viewer/src/main/java/org/useless/seedviewer/gui/SeedViewer.java
@@ -14,6 +14,9 @@ import javax.swing.*;
 import java.awt.*;
 import java.awt.event.ComponentAdapter;
 import java.awt.event.ComponentEvent;
+import java.awt.event.WindowEvent;
+import java.awt.event.WindowListener;
+import java.awt.event.WindowStateListener;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Properties;
@@ -25,6 +28,8 @@ public class SeedViewer extends JFrame {
     public static final int TICKS_PER_SECOND = 10;
     public static final int BEZEL = 15;
 
+    private volatile boolean running = true;
+
     // Storage
     public final Properties launchProperties;
     private volatile boolean needsResize = true;
@@ -52,7 +57,7 @@ public class SeedViewer extends JFrame {
         Thread mainThread = new Thread(
             () -> {
                 long tickCount = 0;
-                while (true) {
+                while (this.running) {
                     try {
                         tick();
                         tickCount++;
@@ -72,7 +77,7 @@ public class SeedViewer extends JFrame {
                 }
             }
         );
-        mainThread.setName("Main");
+        mainThread.setName("SeedViewer Main");
         mainThread.setPriority(6);
         mainThread.start();
     }
@@ -80,7 +85,46 @@ public class SeedViewer extends JFrame {
     public void initFrame() {
         // Creating instance of JFrame
         LOGGER.info("Initializing Frame");
-        this.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
+        boolean main = Boolean.parseBoolean(launchProperties.getProperty("main", "true"));
+        this.setDefaultCloseOperation(main ? EXIT_ON_CLOSE : DISPOSE_ON_CLOSE);
+        this.addWindowListener(new WindowListener() {
+            @Override
+            public void windowOpened(WindowEvent e) {
+
+            }
+
+            @Override
+            public void windowClosing(WindowEvent e) {
+
+            }
+
+            @Override
+            public void windowClosed(WindowEvent e) {
+                SeedViewer.this.running = false;
+                ChunkView.InitializerService.shutdown();
+                ChunkView.PostProcessorService.shutdown();
+            }
+
+            @Override
+            public void windowIconified(WindowEvent e) {
+
+            }
+
+            @Override
+            public void windowDeiconified(WindowEvent e) {
+
+            }
+
+            @Override
+            public void windowActivated(WindowEvent e) {
+
+            }
+
+            @Override
+            public void windowDeactivated(WindowEvent e) {
+
+            }
+        });
         this.setTitle("BTA Seed Viewer! Version: " + SeedViewerMain.VERSION);
         this.setSize(960, 720);
         this.setMinimumSize(new Dimension(480, 360));
diff --git a/util/seed-viewer/src/main/java/org/useless/seedviewer/gui/components/InputPanel.java b/util/seed-viewer/src/main/java/org/useless/seedviewer/gui/components/InputPanel.java
index ad0befb86..c31f91562 100644
--- a/util/seed-viewer/src/main/java/org/useless/seedviewer/gui/components/InputPanel.java
+++ b/util/seed-viewer/src/main/java/org/useless/seedviewer/gui/components/InputPanel.java
@@ -2,6 +2,12 @@ package org.useless.seedviewer.gui.components;
 
 import com.mojang.logging.LogUtils;
 import net.minecraft.core.Global;
+import net.minecraft.core.data.registry.Registries;
+import net.minecraft.core.lang.I18n;
+import net.minecraft.core.world.Dimension;
+import net.minecraft.core.world.type.WorldType;
+import net.minecraft.core.world.type.WorldTypes;
+import org.useless.seedviewer.collections.ObjectWrapper;
 import org.useless.seedviewer.gui.SeedViewer;
 import org.slf4j.Logger;
 
@@ -32,7 +38,12 @@ public class InputPanel extends JPanel {
     public JCheckBox showBiomesBox;
     public JCheckBox showTerrainBox;
     public JCheckBox showCrosshairBox;
+    public JLabel seedInputLabel;
     public JTextField seedInputBox;
+    public JLabel worldTypeLabel;
+    public JComboBox<WorldType> worldTypeBox;
+    public JLabel dimensionLabel;
+    public JComboBox<Dimension> dimensionBox;
     public JButton screenshot;
     public JButton openWorld;
     public JButton closeWorld;
@@ -91,6 +102,10 @@ public class InputPanel extends JPanel {
         addManaged(this.showTerrainBox);
         addManaged(this.showCrosshairBox);
 
+        this.seedInputLabel = new JLabel("Seed:");
+        this.seedInputLabel.setSize(0, textFieldHeight);
+        addManaged(this.seedInputLabel);
+
         this.seedInputBox = new JTextField(this.seedViewer.viewport.seed.toString());
         this.seedInputBox.setSize(0, textFieldHeight);
         this.seedInputBox.addActionListener(new AbstractAction() {
@@ -109,6 +124,59 @@ public class InputPanel extends JPanel {
         });
         addManaged(this.seedInputBox);
 
+        this.worldTypeLabel = new JLabel("World Type:");
+        this.worldTypeLabel.setSize(0, textFieldHeight);
+        addManaged(this.worldTypeLabel);
+
+        this.worldTypeBox = new JComboBox<>(Registries.WORLD_TYPES.values().toArray(new WorldType[0]));
+        this.worldTypeBox.setSelectedItem(this.seedViewer.viewport.worldType.get());
+        this.worldTypeBox.setSize(0, textFieldHeight);
+        this.worldTypeBox.setRenderer(new DefaultListCellRenderer() {
+            @Override
+            public Component getListCellRendererComponent(JList<?> list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
+                var comp = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
+                if (value instanceof WorldType worldType) {
+                    setText(I18n.getInstance().translateKey(worldType.getLanguageKey() + ".name"));
+                }
+                return comp;
+            }
+        });
+        this.worldTypeBox.addActionListener(new AbstractAction() {
+            @Override
+            public void actionPerformed(ActionEvent e) {
+                seedViewer.viewport.setWorldType((WorldType) worldTypeBox.getSelectedItem());
+            }
+        });
+        seedViewer.viewport.worldType.addChangeListener(newValue -> worldTypeBox.setSelectedItem(newValue));
+        addManaged(this.worldTypeBox);
+
+        this.dimensionLabel = new JLabel("Dimension:");
+        this.dimensionLabel.setSize(0, textFieldHeight);
+        addManaged(this.dimensionLabel);
+
+        this.dimensionBox = new JComboBox<>(Dimension.getDimensionList().values().toArray(new Dimension[0]));
+        this.dimensionBox.setSelectedItem(this.seedViewer.viewport.dimension.get());
+        this.dimensionBox.setSize(0, textFieldHeight);
+        this.dimensionBox.setRenderer(new DefaultListCellRenderer() {
+            @Override
+            public Component getListCellRendererComponent(JList<?> list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
+                var comp = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
+                if (value instanceof Dimension dimension) {
+                    setText(I18n.getInstance().translateKey("dimension." + dimension.languageKey + ".name"));
+                }
+                return comp;
+            }
+        });
+        this.dimensionBox.addActionListener(new AbstractAction() {
+            @Override
+            public void actionPerformed(ActionEvent e) {
+                seedViewer.viewport.setDimension((Dimension) dimensionBox.getSelectedItem());
+            }
+        });
+        seedViewer.viewport.dimension.addChangeListener(newValue -> dimensionBox.setSelectedItem(newValue));
+        addManaged(this.dimensionBox);
+
+
         this.screenshot = new JButton("Save Screenshot");
         this.screenshot.setSize(0, button);
         this.screenshot.addActionListener(new AbstractAction() {
@@ -173,10 +241,10 @@ public class InputPanel extends JPanel {
                 if (result == JFileChooser.APPROVE_OPTION) {
                     File selectedFile = worldSelector.getSelectedFile();
                     LOGGER.info("Selected file: {}", selectedFile.getAbsolutePath());
-                    InputPanel.this.seedViewer.viewport.setWorld(selectedFile);
+                    InputPanel.this.seedViewer.viewport.setWorld(selectedFile, Dimension.OVERWORLD);
 
                 } else {
-                    InputPanel.this.seedViewer.viewport.setWorld(null);
+                    InputPanel.this.seedViewer.viewport.setWorld(null, Dimension.OVERWORLD);
                 }
             }
         });
@@ -196,7 +264,7 @@ public class InputPanel extends JPanel {
         this.closeWorld.addActionListener(new AbstractAction() {
             @Override
             public void actionPerformed(ActionEvent e) {
-                InputPanel.this.seedViewer.viewport.setWorld(null);
+                InputPanel.this.seedViewer.viewport.setWorld(null, Dimension.OVERWORLD);
             }
         });
         addManaged(this.closeWorld);
@@ -209,23 +277,43 @@ public class InputPanel extends JPanel {
     }
 
     public void onWorldOpen() {
+        this.seedInputLabel.setEnabled(false);
+        this.seedInputLabel.setVisible(false);
         this.seedInputBox.setEnabled(false);
         this.seedInputBox.setVisible(false);
+        this.worldTypeLabel.setEnabled(false);
+        this.worldTypeLabel.setVisible(false);
+        this.worldTypeBox.setEnabled(false);
+        this.worldTypeBox.setVisible(false);
 
         this.closeWorld.setEnabled(true);
         this.closeWorld.setVisible(true);
         this.showTerrainBox.setEnabled(true);
         this.showTerrainBox.setVisible(true);
+        this.dimensionLabel.setEnabled(true);
+        this.dimensionLabel.setVisible(true);
+        this.dimensionBox.setEnabled(true);
+        this.dimensionBox.setVisible(true);
     }
 
     public void onWorldClose() {
+        this.seedInputLabel.setEnabled(true);
+        this.seedInputLabel.setVisible(true);
         this.seedInputBox.setEnabled(true);
         this.seedInputBox.setVisible(true);
+        this.worldTypeLabel.setEnabled(true);
+        this.worldTypeLabel.setVisible(true);
+        this.worldTypeBox.setEnabled(true);
+        this.worldTypeBox.setVisible(true);
 
         this.closeWorld.setEnabled(false);
         this.closeWorld.setVisible(false);
         this.showTerrainBox.setEnabled(false);
         this.showTerrainBox.setVisible(false);
+        this.dimensionLabel.setEnabled(false);
+        this.dimensionLabel.setVisible(false);
+        this.dimensionBox.setEnabled(false);
+        this.dimensionBox.setVisible(false);
     }
 
     public void addManaged(Component c) {
diff --git a/util/seed-viewer/src/main/java/org/useless/seedviewer/gui/components/Viewport.java b/util/seed-viewer/src/main/java/org/useless/seedviewer/gui/components/Viewport.java
index 9805540c4..3d3d56a5d 100644
--- a/util/seed-viewer/src/main/java/org/useless/seedviewer/gui/components/Viewport.java
+++ b/util/seed-viewer/src/main/java/org/useless/seedviewer/gui/components/Viewport.java
@@ -1,8 +1,12 @@
 package org.useless.seedviewer.gui.components;
 
 import com.mojang.logging.LogUtils;
+import net.minecraft.core.world.Dimension;
+import net.minecraft.core.world.World;
 import net.minecraft.core.world.pos.ChunkPos;
 import net.minecraft.core.world.pos.ChunkTilePos;
+import net.minecraft.core.world.type.WorldType;
+import net.minecraft.core.world.type.WorldTypes;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 import org.useless.seedviewer.bta.BTAWorld;
@@ -54,6 +58,8 @@ public class Viewport extends JLabel {
     public ChunkProvider chunkProvider = new TestChunkProvider();
     public final ObjectWrapper<@NotNull Long> seed = new ObjectWrapper<>(100L);
     public final ObjectWrapper<@Nullable ViewerWorld> world = new ObjectWrapper<>(null);
+    public final ObjectWrapper<@NotNull Dimension> dimension = new ObjectWrapper<>(Dimension.OVERWORLD);
+    public final ObjectWrapper<@NotNull WorldType> worldType = new ObjectWrapper<>(WorldTypes.OVERWORLD_EXTENDED);
 
     public final ObjectWrapper<@NotNull Float> zoom = new ObjectWrapper<>(1F);
     public final ObjectWrapper<@NotNull Float> viewX = new ObjectWrapper<>(0F);
@@ -205,23 +211,47 @@ public class Viewport extends JLabel {
         clearChunkViews();
         this.viewX.set(0F);
         this.viewZ.set(0F);
-        this.chunkProvider = new BTAChunkProvider(this.seed.get());
+        this.chunkProvider = new BTAChunkProvider(this.worldType.get(), this.dimension.get(), this.seed.get());
         this.seedViewer.queueResize();
     }
 
-    public synchronized void setWorld(@Nullable File file) {
+    public synchronized void setWorldType(@NotNull WorldType worldType) {
+        this.worldType.set(worldType);
+        clearChunkViews();
+        this.viewX.set(0F);
+        this.viewZ.set(0F);
+        this.chunkProvider = new BTAChunkProvider(this.worldType.get(), this.dimension.get(), this.seed.get());
+        this.seedViewer.queueResize();
+    }
+
+    public synchronized void setDimension(@NotNull Dimension dimension) {
+        this.dimension.set(dimension);
+        clearChunkViews();
+        this.viewX.set(0F);
+        this.viewZ.set(0F);
+        if (this.world.get() instanceof BTAWorld btaWorld) {
+            setWorld(btaWorld.worldFolder, dimension);
+        } else {
+            this.chunkProvider = new BTAChunkProvider(this.worldType.get(), this.dimension.get(), this.seed.get());
+        }
+        this.seedViewer.queueResize();
+    }
+
+    public synchronized void setWorld(@Nullable File file, @NotNull Dimension dimension) {
+        this.dimension.set(dimension);
         if (file != null) {
             try {
-                this.world.set(new BTAWorld(file));
+                this.world.set(new BTAWorld(file, dimension));
                 this.chunkProvider = Objects.requireNonNull(this.world.get()).getChunkProvider();
             } catch (Exception e) {
                 LOGGER.error("", e);
                 this.world.set(null);
-                this.chunkProvider = new BTAChunkProvider(this.seed.get());
+                this.chunkProvider = new BTAChunkProvider(this.worldType.get(), dimension, this.seed.get());
             }
         } else {
             this.world.set(null);
-            this.chunkProvider = new BTAChunkProvider(this.seed.get());
+            this.worldType.set(WorldTypes.OVERWORLD_EXTENDED);
+            this.chunkProvider = new BTAChunkProvider(this.worldType.get(), dimension, this.seed.get());
         }
 
         clearChunkViews();
