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 943af4200..1cdba1545 100644
--- a/game/client/src/main/java/net/minecraft/client/Minecraft.java
+++ b/game/client/src/main/java/net/minecraft/client/Minecraft.java
@@ -102,6 +102,7 @@ import net.minecraft.core.data.DataLoader;
 import net.minecraft.core.data.registry.Registries;
 import net.minecraft.core.entity.EntityDispatcher;
 import net.minecraft.core.entity.EntityItem;
+import net.minecraft.core.entity.animal.MobPig;
 import net.minecraft.core.entity.SkinVariantList;
 import net.minecraft.core.entity.player.Player;
 import net.minecraft.core.enums.EnumOS;
@@ -2057,8 +2058,12 @@ public class Minecraft
         Dimension lastDim = Dimension.getDimensionList().get(this.thePlayer.dimension);
         Dimension newDim = Dimension.getDimensionList().get(dim);
         LOGGER.info("Switching to dimension \"{}\"!!", newDim.getTranslatedName());
+        MobPig mount = this.thePlayer.vehicle instanceof MobPig p ? p : null;
         this.thePlayer.dimension = dim;
         this.currentWorld.setEntityDead(this.thePlayer);
+        if (mount != null) {
+            this.currentWorld.setEntityDead(mount);
+        }
         this.thePlayer.removed = false;
         double x = this.thePlayer.x;
         double z = this.thePlayer.z;
@@ -2095,6 +2100,14 @@ public class Minecraft
             this.thePlayer.moveTo(x, y, z, this.thePlayer.yRot, this.thePlayer.xRot);
             this.currentWorld.updateEntityWithOptionalForce(this.thePlayer, false);
             (new PortalHandler()).teleportEntity(this.currentWorld, this.thePlayer, portalColor, lastDim, newDim);
+            if (mount != null) {
+                this.thePlayer.clearPendingVehicleTag();
+                mount.removed = false;
+                mount.world = this.currentWorld;
+                mount.moveTo(this.thePlayer.x, this.thePlayer.y, this.thePlayer.z, mount.yRot, mount.xRot);
+                this.currentWorld.entityJoinedWorld(mount);
+                this.thePlayer.startRiding(mount);
+            }
         }
     }
 
@@ -2369,10 +2382,14 @@ public class Minecraft
 
         if (previousPlayer != null) {
             this.thePlayer.messageHistory = previousPlayer.messageHistory;
+            this.thePlayer.rotationLock = previousPlayer.rotationLock;
+            this.thePlayer.rotationLockHorizontal = previousPlayer.rotationLockHorizontal;
+            this.thePlayer.rotationLockVertical = previousPlayer.rotationLockVertical;
             if (!this.currentWorld.isClientSide) {
                 this.thePlayer.inventory.transferAllContents(previousPlayer.inventory);
             }
         }
+        this.thePlayer.syncPlacementMode();
 
         if (canRespawnAtBed) {
             this.thePlayer.setPlayerSpawnPoint(spawnCoordinates);
diff --git a/game/client/src/main/java/net/minecraft/client/entity/player/PlayerLocal.java b/game/client/src/main/java/net/minecraft/client/entity/player/PlayerLocal.java
index 46ca4805d..9bad9c1ac 100644
--- a/game/client/src/main/java/net/minecraft/client/entity/player/PlayerLocal.java
+++ b/game/client/src/main/java/net/minecraft/client/entity/player/PlayerLocal.java
@@ -144,10 +144,8 @@ public class PlayerLocal extends Player {
         }
 
         this.prevTimeInPortal = this.timeInPortal;
-        if (this.inPortal && !this.hasNoPhysics()) {
-            if (!this.world.isClientSide && this.vehicle != null) {
-                startRiding(null);
-            }
+      
+        if (this.inPortal && !hasNoPhysics()) {
             if (this.mc.currentScreen instanceof ScreenContainerAbstract) // Close out of containers like the inventory
             {
                 this.mc.displayScreen(null);
@@ -402,12 +400,10 @@ public class PlayerLocal extends Player {
         }
         if (i > 0 && stat.isAchievement()) {
             Achievement achievement = (Achievement) stat;
-            if (achievement.parent == null || this.mc.statsCounter.isUnlocked(achievement.parent)) {
-                if (!this.mc.statsCounter.isUnlocked(achievement)) {
-                    this.mc.guiToasts.addToast(new AchievementToast(achievement, false));
-                }
-                this.mc.statsCounter.add(stat, i);
+            if (!this.mc.statsCounter.isUnlocked(achievement)) {
+                this.mc.guiToasts.addToast(new AchievementToast(achievement, false));
             }
+            this.mc.statsCounter.add(stat, i);
         } else {
             this.mc.statsCounter.add(stat, i);
         }
diff --git a/game/client/src/main/java/net/minecraft/client/entity/player/PlayerLocalMultiplayer.java b/game/client/src/main/java/net/minecraft/client/entity/player/PlayerLocalMultiplayer.java
index 556f24d0d..1d5c84f87 100644
--- a/game/client/src/main/java/net/minecraft/client/entity/player/PlayerLocalMultiplayer.java
+++ b/game/client/src/main/java/net/minecraft/client/entity/player/PlayerLocalMultiplayer.java
@@ -7,8 +7,9 @@ import net.minecraft.core.data.registry.Registries;
 import net.minecraft.core.entity.Entity;
 import net.minecraft.core.entity.EntityItem;
 import net.minecraft.core.entity.player.Player;
+import net.minecraft.core.entity.animal.MobPig;
 import net.minecraft.core.entity.vehicle.EntityBoat;
-import net.minecraft.core.net.packet.PacketBoatControl;
+import net.minecraft.core.net.packet.PacketVehicleControl;
 import net.minecraft.core.net.packet.PacketMessage;
 import net.minecraft.core.net.packet.PacketPlayerConfig;
 import net.minecraft.core.net.packet.PacketUpdatePlayerState;
@@ -108,13 +109,7 @@ public class PlayerLocalMultiplayer extends PlayerLocal implements Player.Player
         boolean hasRotated = yRotDiff != 0.0D || xRotDiff != 0.0D;
         if(vehicle != null)
         {
-            if(hasMoved)
-            {
-                sendQueue.addToSendQueue(new PacketMovePlayer.Pos(xd, -999D, zd, onGround));
-            } else
-            {
-                sendQueue.addToSendQueue(new PacketMovePlayer.PosRot(xd, -999D, zd, yRot, xRot, onGround));
-            }
+            sendQueue.addToSendQueue(new PacketMovePlayer.PosRot(xd, -999D, zd, yRot, xRot, onGround));
             hasMoved = false;
         } else if(hasMoved && hasRotated) {
             sendQueue.addToSendQueue(new PacketMovePlayer.PosRot(x, bb.minY, z, yRot, xRot, onGround));
@@ -259,7 +254,10 @@ public class PlayerLocalMultiplayer extends PlayerLocal implements Player.Player
     @Override
     public void sendSpecialVehiclePacket() {
         if (vehicle instanceof EntityBoat) {
-            sendQueue.addToSendQueue(new PacketBoatControl((EntityBoat) vehicle));
+            sendQueue.addToSendQueue(new PacketVehicleControl((Entity) vehicle));
+        } else if (vehicle instanceof MobPig pig) {
+            double trueY = pig.bb.minY + (double) pig.heightOffset;
+            sendQueue.addToSendQueue(new PacketVehicleControl(pig.id, pig.x, trueY, pig.z, pig.yRot, pig.consumePendingFallDistance()));
         }
     }
 
diff --git a/game/client/src/main/java/net/minecraft/client/gui/ScreenConsole.java b/game/client/src/main/java/net/minecraft/client/gui/ScreenConsole.java
index dad670792..c6bcae7f4 100644
--- a/game/client/src/main/java/net/minecraft/client/gui/ScreenConsole.java
+++ b/game/client/src/main/java/net/minecraft/client/gui/ScreenConsole.java
@@ -9,6 +9,8 @@ import net.minecraft.client.gui.text.ITextField;
 import net.minecraft.client.gui.text.TextFieldEditor;
 import net.minecraft.client.console.shell.RuntimeExecutor;
 import net.minecraft.client.option.GameSettings;
+import net.minecraft.client.render.font.FontRenderer;
+import net.minecraft.client.render.font.FontRendererDefault;
 import net.minecraft.client.render.font.Fonts;
 import net.minecraft.client.render.window.CursorShape;
 import net.minecraft.core.net.command.TextFormatting;
@@ -37,7 +39,10 @@ public class ScreenConsole extends Screen {
         private int width = 0;
         private int scroll = 0;
 
-        public LogRenderer() {
+        private final FontRenderer font;
+
+        public LogRenderer(FontRenderer font) {
+            this.font = font;
             this.preludes.put(LogMessage.Level.ERROR, TextFormatting.RED);
             this.preludes.put(LogMessage.Level.WARN, TextFormatting.YELLOW);
             this.preludes.put(LogMessage.Level.INFO, TextFormatting.WHITE);
@@ -65,14 +70,13 @@ public class ScreenConsole extends Screen {
                 this.scroll = 0;
             }
 
-            var font = Minecraft.getMinecraft().font;
-            font.startDrawingDeferred();
+            this.font.startDrawingDeferred();
             final int lines = Math.min(heightLines, WORKED_LINES.size() - this.scroll);
             for (int i = lines - 1; i >= 0; i--) {
                 final @NotNull String line = WORKED_LINES.get((WORKED_LINES.size() - 1) - (i + this.scroll));
-                font.render(line, x, y + getHeight(numLines) - LINE_HEIGHT - LINE_HEIGHT * i).setFont(Fonts.MONOSPACE).setColor(0xFF_CF_CF_CF).call();
+                this.font.render(line, x, y + getHeight(numLines) - LINE_HEIGHT - LINE_HEIGHT * i).setColor(0xFF_CF_CF_CF).call();
             }
-            font.draw(this.zLevel);
+            this.font.draw(this.zLevel);
             renderScrollIndicator(x, y, numLines);
         }
 
@@ -182,9 +186,11 @@ public class ScreenConsole extends Screen {
         private int historyIndex = 0;
 
         private final Highlighter<String> highlighter = new Highlighter<>(new RendererFactory());
+        private final FontRenderer font;
 
-        public InputRenderer() {
+        public InputRenderer(FontRenderer font) {
             this.editor = new TextFieldEditor(this, true);
+            this.font = font;
         }
 
         public void tick() {
@@ -195,31 +201,31 @@ public class ScreenConsole extends Screen {
             boolean addedStart = false;
             final @NotNull String @NotNull [] splitHighlighted = this.highlightedText.split("\n", -1);
             for (int i = 0; i < splitHighlighted.length; i++) {
-                Minecraft.getMinecraft().font.render((addedStart ? "  " : "> ") + splitHighlighted[i].replace("\n", ""), x, y).setFont(Fonts.MONOSPACE).setZ(this.zLevel).setColor(0xFF_E0_E0_E0).call();
+                this.font.render((addedStart ? "  " : "> ") + splitHighlighted[i].replace("\n", ""), x, y + i * LINE_HEIGHT).setZ(this.zLevel).setColor(0xFF_E0_E0_E0).call();
                 addedStart = true;
             }
 
             final @NotNull String @NotNull [] split = this.text.split("\n", -1);
-            final int offsetWidth = MathHelper.ceil(Minecraft.getMinecraft().font.stringWidthDouble("  "));
+            final int offsetWidth = MathHelper.ceil(this.font.stringWidthDouble("  "));
             final int[] cursorPos = getCursor();
             final int cursorX;
             if (cursorPos[0] == split[cursorPos[1]].length()) {
-                cursorX = MathHelper.ceil(Minecraft.getMinecraft().font.stringWidthDouble(split[cursorPos[1]]));
+                cursorX = MathHelper.ceil(this.font.stringWidthDouble(split[cursorPos[1]]));
             } else {
-                cursorX = MathHelper.ceil(Minecraft.getMinecraft().font.stringWidthDouble(split[cursorPos[1]].substring(0, cursorPos[0])));
+                cursorX = MathHelper.ceil(this.font.stringWidthDouble(split[cursorPos[1]].substring(0, cursorPos[0])));
             }
             if ((this.cursorCounter / 6) % 2 == 0) {
-                Minecraft.getMinecraft().font.render("█", x + offsetWidth + cursorX, y + cursorPos[1] * LINE_HEIGHT).setFont(Fonts.MONOSPACE).setZ(this.zLevel).setColor(0xFF_FF_FF_FF).call();
+                this.font.render("█", x + offsetWidth + cursorX, y + cursorPos[1] * LINE_HEIGHT).setZ(this.zLevel).setColor(0xFF_FF_FF_FF).call();
                 if (this.editor.getCursor() < this.text.length() && this.text.charAt(this.editor.getCursor()) != '\n') {
-                    Minecraft.getMinecraft().font.render("" + this.text.charAt(this.editor.getCursor()), x + offsetWidth + cursorX, y + cursorPos[1] * LINE_HEIGHT).setFont(Fonts.MONOSPACE).setZ(this.zLevel).setColor(0xFF_00_00_00).call();
+                    this.font.render("" + this.text.charAt(this.editor.getCursor()), x + offsetWidth + cursorX, y + cursorPos[1] * LINE_HEIGHT).setZ(this.zLevel).setColor(0xFF_00_00_00).call();
                 }
             } else {
-                Minecraft.getMinecraft().font.render("_", x + offsetWidth + cursorX, y + cursorPos[1] * LINE_HEIGHT).setFont(Fonts.MONOSPACE).setZ(this.zLevel).setColor(0xFF_FF_FF_FF).call();
+                this.font.render("_", x + offsetWidth + cursorX, y + cursorPos[1] * LINE_HEIGHT).setZ(this.zLevel).setColor(0xFF_FF_FF_FF).call();
             }
         }
 
         public void click(final int mx, final int my) {
-            final int oneChar = MathHelper.ceil(Minecraft.getMinecraft().font.stringWidthDouble(" "));
+            final int oneChar = MathHelper.ceil(this.font.stringWidthDouble(" "));
 
             final int cx = (mx - (oneChar * 2)) / oneChar;
             final int cy = my / LINE_HEIGHT;
@@ -382,12 +388,14 @@ public class ScreenConsole extends Screen {
 
     private final @NotNull LogRenderer logRenderer;
     private final @NotNull InputRenderer inputRenderer;
+    private final FontRenderer monospaceFont;
 
     public ScreenConsole(final @Nullable Screen parent) {
         super(parent);
-
-        this.logRenderer = new LogRenderer();
-        this.inputRenderer = new InputRenderer();
+        this.monospaceFont = new FontRendererDefault(Fonts.MONOSPACE);
+        this.monospaceFont.init();
+        this.logRenderer = new LogRenderer(this.monospaceFont);
+        this.inputRenderer = new InputRenderer(this.monospaceFont);
     }
 
     private int getMaxY() {
diff --git a/game/client/src/main/java/net/minecraft/client/gui/ScreenHudEditor.java b/game/client/src/main/java/net/minecraft/client/gui/ScreenHudEditor.java
index 4a63e1bb0..eaf8818d5 100644
--- a/game/client/src/main/java/net/minecraft/client/gui/ScreenHudEditor.java
+++ b/game/client/src/main/java/net/minecraft/client/gui/ScreenHudEditor.java
@@ -183,12 +183,7 @@ public class ScreenHudEditor extends Screen {
         }
 
         if (eventKey == GameSettings.KEY_HUD_EDITOR_RESET.getKeyCode()) {
-            HudComponents.INSTANCE.fromSettingsString(HudComponents.DEFAULT_LAYOUT);
-            for (HudComponent component : HudComponents.INSTANCE.getComponents()) {
-                for (Option<?> option : component.getRawOptions()) {
-                    option.resetValueToDefault();
-                }
-            }
+            HudComponents.INSTANCE.resetToDefault();
             this.mc.sndManager.playSound("random.click", SoundCategory.GUI_SOUNDS, 1.0F, 1.0F);
             return;
         }
diff --git a/game/client/src/main/java/net/minecraft/client/gui/container/ScreenInventoryCreative.java b/game/client/src/main/java/net/minecraft/client/gui/container/ScreenInventoryCreative.java
index 90b1e8cdb..4767708ca 100644
--- a/game/client/src/main/java/net/minecraft/client/gui/container/ScreenInventoryCreative.java
+++ b/game/client/src/main/java/net/minecraft/client/gui/container/ScreenInventoryCreative.java
@@ -116,7 +116,11 @@ public class ScreenInventoryCreative extends ScreenInventory implements ITextCha
             GameSettings.LAST_CREATIVE_INVENTORY_PAGE.set(clamped);
             GameSettings.saveOptions();
         }
-        this.container.setInventoryStatus(clamped, this.container.searchText);
+        if (clamped == this.container.page) {
+            this.container.broadcastInventoryStatus();
+        } else {
+            this.container.setInventoryStatus(clamped, this.container.searchText);
+        }
     }
 
     private void persistCreativeInventoryPage() {
diff --git a/game/client/src/main/java/net/minecraft/client/gui/hud/HudIngame.java b/game/client/src/main/java/net/minecraft/client/gui/hud/HudIngame.java
index ef2f5e185..5820f286c 100644
--- a/game/client/src/main/java/net/minecraft/client/gui/hud/HudIngame.java
+++ b/game/client/src/main/java/net/minecraft/client/gui/hud/HudIngame.java
@@ -19,7 +19,9 @@ import net.minecraft.client.render.renderer.Shaders;
 import net.minecraft.client.render.renderer.State;
 import net.minecraft.client.render.tessellator.TessellatorShader;
 import net.minecraft.client.render.tessellator.TessellatorTerrainImpl;
+import net.minecraft.core.block.BlockLogicConduit;
 import net.minecraft.core.block.BlockLogicPortal;
+import net.minecraft.core.block.BlockLogicWireRedstone;
 import net.minecraft.core.enums.HumanArmorShape;
 import net.minecraft.core.player.gamemode.Gamemodes;
 import net.minecraft.core.util.helper.Direction;
@@ -385,8 +387,14 @@ public class HudIngame extends Gui {
                         drawDebugScreenLineRight(hudTessellater, hudBuilder.append("Light: Sky: ").append(this.mc.currentWorld.getSavedLightValue(LightLayer.Sky, lightX, lightY, lightZ)).append(" Block: ").append(this.mc.currentWorld.getSavedLightValue(LightLayer.Block, lightX, lightY, lightZ))); hudBuilder.setLength(0);
                     }
 
-                    drawDebugScreenLineRight(hudTessellater, hudBuilder.append("Signal: ").append(this.mc.currentWorld.hasSignal(hitTile.tilePos, hitTile.side))); hudBuilder.setLength(0);
-                    drawDebugScreenLineRight(hudTessellater, hudBuilder.append("Direct signal: ").append(this.mc.currentWorld.hasDirectSignal(hitTile.tilePos, hitTile.side))); hudBuilder.setLength(0);
+                    Side signalSide = hitTile.side.opposite();
+                    drawDebugScreenLineRight(hudTessellater, hudBuilder.append("Signal: ").append(this.mc.currentWorld.hasSignal(hitTile.tilePos, signalSide))); hudBuilder.setLength(0);
+                    drawDebugScreenLineRight(hudTessellater, hudBuilder.append("Direct signal: ").append(this.mc.currentWorld.hasDirectSignal(hitTile.tilePos, signalSide))); hudBuilder.setLength(0);
+
+                    int signalStrength = getSignalStrength(block, meta, hitTile.tilePos);
+                    if (signalStrength >= 0) {
+                        drawDebugScreenLineRight(hudTessellater, hudBuilder.append("Signal strength: ").append(signalStrength)); hudBuilder.setLength(0);
+                    }
 
                     for (Tag<Block<?>> tag : BlockTags.TAG_LIST) {
                         if (tag.appliesTo(block)) {
@@ -734,4 +742,26 @@ public class HudIngame extends Gui {
     public static Direction getFacingDirection(Player player) {
         return directions[(MathHelper.floor((double) ((player.yRot * 4F) / 360F) + 0.5D) + 2) & 3];
     }
+
+    /**
+     * Resolves the 0-15 redstone signal strength emitted by a redstone component.
+     * Returns -1 for blocks that are not redstone components, signalling that no line should be drawn.
+     */
+    private int getSignalStrength(final @NotNull Block<?> block, final int meta, final @NotNull net.minecraft.core.world.pos.TilePosc tilePos) {
+        if (block == Blocks.WIRE_REDSTONE) {
+            return meta & BlockLogicWireRedstone.MASK_POWER;
+        }
+        if (block == Blocks.CONDUIT) {
+            return BlockLogicConduit.getPower(this.mc.currentWorld, tilePos);
+        }
+        if (block.isSignalSource()) {
+            for (final Side side : Side.sides) {
+                if (this.mc.currentWorld.hasSignal(tilePos, side)) {
+                    return BlockLogicWireRedstone.SIGNAL_MAX;
+                }
+            }
+            return BlockLogicWireRedstone.SIGNAL_MIN;
+        }
+        return -1;
+    }
 }
\ No newline at end of file
diff --git a/game/client/src/main/java/net/minecraft/client/gui/hud/component/HudComponent.java b/game/client/src/main/java/net/minecraft/client/gui/hud/component/HudComponent.java
index 6b089a1da..75025ead8 100644
--- a/game/client/src/main/java/net/minecraft/client/gui/hud/component/HudComponent.java
+++ b/game/client/src/main/java/net/minecraft/client/gui/hud/component/HudComponent.java
@@ -86,7 +86,7 @@ public abstract class HudComponent {
     }
 
     public void setScaleOverride(int value) {
-        this.getGuiScaleOverrideOption().value = Math.max(0, value);
+        this.getGuiScaleOverrideOption().value = OptionGuiScaleOverride.clampValue(value);
     }
 
     public static int getEffectiveGlobalScale() {
diff --git a/game/client/src/main/java/net/minecraft/client/gui/hud/component/HudComponents.java b/game/client/src/main/java/net/minecraft/client/gui/hud/component/HudComponents.java
index 6ee04f34d..895b47967 100644
--- a/game/client/src/main/java/net/minecraft/client/gui/hud/component/HudComponents.java
+++ b/game/client/src/main/java/net/minecraft/client/gui/hud/component/HudComponents.java
@@ -7,6 +7,7 @@ import net.minecraft.client.gui.options.components.BooleanOptionComponent;
 import net.minecraft.client.gui.options.components.KeyBindingComponent;
 import net.minecraft.client.gui.options.components.ToggleableOptionComponent;
 import net.minecraft.client.option.GameSettings;
+import net.minecraft.client.option.Option;
 import net.minecraft.core.enums.HumanArmorShape;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
@@ -148,6 +149,18 @@ public class HudComponents {
         return this.components;
     }
 
+    public void resetToDefault() {
+        if (DEFAULT_LAYOUT == null) {
+            return;
+        }
+        fromSettingsString(DEFAULT_LAYOUT);
+        for (HudComponent component : getComponents()) {
+            for (Option<?> option : component.getRawOptions()) {
+                option.resetValueToDefault();
+            }
+        }
+    }
+
     public @Nullable HudComponent getComponent(String key) {
         for (HudComponent component : this.components) {
             if (component.getKey().equals(key)) {
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 d2dbd742c..638f06345 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
@@ -522,6 +522,10 @@ public abstract class OptionsPages {
                 .withComponent(new ShortcutComponent("gui.options.page.debug.button.reload_commands", () ->{
                     if (Minecraft.getMinecraft().currentWorld != null) Minecraft.getMinecraft().currentWorld.getCommandManager().reload();
                 }))
+                .withComponent(new ShortcutComponent("gui.options.page.debug.button.reset_hud_onboarding", () -> {
+                    GameSettings.SEEN_HUD_EDITOR_ONBOARDING.set(false);
+                    GameSettings.saveOptions();
+                }))
             ));
     }
 }
\ No newline at end of file
diff --git a/game/client/src/main/java/net/minecraft/client/net/handler/PacketHandlerClient.java b/game/client/src/main/java/net/minecraft/client/net/handler/PacketHandlerClient.java
index 47d2bc3f8..0bd4e7842 100644
--- a/game/client/src/main/java/net/minecraft/client/net/handler/PacketHandlerClient.java
+++ b/game/client/src/main/java/net/minecraft/client/net/handler/PacketHandlerClient.java
@@ -41,7 +41,6 @@ import net.minecraft.core.entity.Mob;
 import net.minecraft.core.entity.EntityPainting;
 import net.minecraft.core.entity.player.Player;
 import net.minecraft.core.entity.projectile.Projectile;
-import net.minecraft.core.entity.vehicle.EntityBoat;
 import net.minecraft.core.enums.ArtType;
 import net.minecraft.core.item.ItemMap;
 import net.minecraft.core.item.ItemStack;
@@ -292,14 +291,19 @@ public class PacketHandlerClient extends PacketHandler {
     @Override
     public void handleEntityFling(final @NotNull PacketEntityFling packetEntityFling) {
         final Entity entity = getEntityByID(packetEntityFling.entityId);
-        if (entity != null) {
-            entity.pushesThisTick = packetEntityFling.pushesTick;
-            entity.fallDistance = 0;
-            entity.xd = packetEntityFling.xd;
-            entity.yd = packetEntityFling.yd;
-            entity.zd = packetEntityFling.zd;
-            entity.pushTime = packetEntityFling.pushTime;
+        if (entity == null) {
+            return;
+        }
+        if (entity == this.mc.thePlayer.vehicle) {
+            entity.fling(packetEntityFling.xd, packetEntityFling.yd, packetEntityFling.zd, packetEntityFling.pushTime);
+            return;
         }
+        entity.pushesThisTick = packetEntityFling.pushesTick;
+        entity.fallDistance = 0;
+        entity.xd = packetEntityFling.xd;
+        entity.yd = packetEntityFling.yd;
+        entity.zd = packetEntityFling.zd;
+        entity.pushTime = packetEntityFling.pushTime;
     }
 
     @Override
@@ -606,10 +610,10 @@ public class PacketHandlerClient extends PacketHandler {
     }
 
     @Override
-    public void handleBoatControl(@NotNull PacketBoatControl packetBoatControl) {
-        Entity e = this.worldClientMP.getEntityFromId(packetBoatControl.entityId);
-        if (e instanceof EntityBoat) {
-            ((EntityBoat) e).handleControlDirect(packetBoatControl.targetXD, packetBoatControl.targetZD, packetBoatControl.targetYRot);
+    public void handleVehicleControl(@NotNull PacketVehicleControl packetVehicleControl) {
+        Entity e = this.worldClientMP.getEntityFromId(packetVehicleControl.entityId);
+        if (e != null) {
+            e.handleControlDirect(packetVehicleControl.targetXD, packetVehicleControl.targetYD, packetVehicleControl.targetZD, packetVehicleControl.targetYRot);
         }
     }
 
@@ -732,8 +736,12 @@ public class PacketHandlerClient extends PacketHandler {
         } else {
             vehicle = getEntityByID(packetSetRiding.vehicleId);
         }
-        if (passenger != null && vehicle != null) {
-            passenger.startRiding(vehicle);
+        if (passenger != null) {
+            if (vehicle != null) {
+                passenger.startRiding(vehicle);
+            } else if (!packetSetRiding.isTileEntity && packetSetRiding.vehicleId == -1) {
+                passenger.startRiding(null);
+            }
         }
     }
 
@@ -793,6 +801,7 @@ public class PacketHandlerClient extends PacketHandler {
             this.worldClientMP.isClientSide = true;
             this.mc.changeWorld(this.worldClientMP);
             this.mc.thePlayer.dimension = packetRespawn.respawnDimensionId;
+            this.mc.thePlayer.vehicle = null;
             this.mc.displayScreen(new ScreenDownloadTerrain(this));
         }
         this.mc.respawn(true, packetRespawn.respawnDimensionId);
@@ -884,42 +893,35 @@ public class PacketHandlerClient extends PacketHandler {
     public void handleContainerSetSlot(@NotNull PacketContainerSetSlot packetContainerSetSlot) {
         if (packetContainerSetSlot.windowId == -1) {
             this.mc.thePlayer.inventory.setHeldItemStack(packetContainerSetSlot.myItemStack);
+            this.mc.thePlayer.containerMenu.stateId = packetContainerSetSlot.stateId;
         } else if (packetContainerSetSlot.windowId == 0 && packetContainerSetSlot.itemSlot >= ContainerInventory.MAIN_INVENTORY_SIZE && packetContainerSetSlot.itemSlot < ContainerInventory.MAIN_INVENTORY_SIZE + ContainerInventory.HOTBAR_SIZE) {
             ItemStack itemstack = this.mc.thePlayer.inventoryMenu.getSlot(packetContainerSetSlot.itemSlot).getItemStack();
             if (packetContainerSetSlot.myItemStack != null && (itemstack == null || itemstack.stackSize < packetContainerSetSlot.myItemStack.stackSize)) {
                 packetContainerSetSlot.myItemStack.animationsToGo = 5;
             }
             this.mc.thePlayer.inventoryMenu.setItem(packetContainerSetSlot.itemSlot, packetContainerSetSlot.myItemStack);
+            this.mc.thePlayer.inventoryMenu.stateId = packetContainerSetSlot.stateId;
         } else if (packetContainerSetSlot.windowId == this.mc.thePlayer.containerMenu.containerId) {
             this.mc.thePlayer.containerMenu.setItem(packetContainerSetSlot.itemSlot, packetContainerSetSlot.myItemStack);
+            this.mc.thePlayer.containerMenu.stateId = packetContainerSetSlot.stateId;
         }
     }
 
     @Override
     public void handleContainerAck(@NotNull PacketContainerAck packetContainerAck) {
-        MenuAbstract container = null;
-        if (packetContainerAck.windowId == 0) {
-            container = this.mc.thePlayer.inventoryMenu;
-        } else if (packetContainerAck.windowId == this.mc.thePlayer.containerMenu.containerId) {
-            container = this.mc.thePlayer.containerMenu;
-        }
-        if (container != null) {
-            if (packetContainerAck.accepted) {
-                container.deleteBackup(packetContainerAck.shortWindowId);
-            } else {
-                container.rollbackToBackup(packetContainerAck.shortWindowId);
-                addToSendQueue(new PacketContainerAck(packetContainerAck.windowId, packetContainerAck.shortWindowId, true));
-            }
-        }
+        //reconciliation of state ids and delta corrections render container ack deprecated
     }
 
     @Override
     public void handleContainerSetContent(@NotNull PacketContainerSetContent packetContainerSetContent) {
         if (packetContainerSetContent.windowId == 0) {
             this.mc.thePlayer.inventoryMenu.setAll(packetContainerSetContent.stackList);
+            this.mc.thePlayer.inventoryMenu.stateId = packetContainerSetContent.stateId;
         } else if (packetContainerSetContent.windowId == this.mc.thePlayer.containerMenu.containerId) {
             this.mc.thePlayer.containerMenu.setAll(packetContainerSetContent.stackList);
+            this.mc.thePlayer.containerMenu.stateId = packetContainerSetContent.stateId;
         }
+        this.mc.thePlayer.inventory.setHeldItemStack(packetContainerSetContent.carriedItem);
     }
 
     @Override
diff --git a/game/client/src/main/java/net/minecraft/client/option/GameSettings.java b/game/client/src/main/java/net/minecraft/client/option/GameSettings.java
index e8445791d..b21898223 100644
--- a/game/client/src/main/java/net/minecraft/client/option/GameSettings.java
+++ b/game/client/src/main/java/net/minecraft/client/option/GameSettings.java
@@ -9,7 +9,6 @@ import net.minecraft.client.discord.RichPresenceHandlerThread;
 import net.minecraft.client.enums.MusicFrequency;
 import net.minecraft.client.enums.SelectedSoundtrack;
 import net.minecraft.client.gui.TooltipElement;
-import net.minecraft.client.gui.hud.component.HudComponent;
 import net.minecraft.client.gui.hud.component.HudComponents;
 import net.minecraft.client.gui.options.ScreenOptions;
 import net.minecraft.client.input.InputDevice;
@@ -134,7 +133,7 @@ public final class GameSettings {
     public static final @NotNull OptionBoolean VIEW_BOBBING = register(new OptionBoolean("bobView", true));
     public static final @NotNull OptionBoolean TRANSPARENCY_DEPTH_CHECK = register(new OptionBoolean("transparencyDepthCheck", true));
     public static final @NotNull OptionEnum<ParticlesQuality> PARTICLES_QUALITY = register(new OptionEnum<>("particleQuality", ParticlesQuality.class, ParticlesQuality.ALL));
-    public static final @NotNull OptionRange PARTICLES_CAP = register(new OptionRange("particleCap", 50000, 1000, 100000));
+    public static final @NotNull OptionRange PARTICLES_CAP = register(new OptionRange("particleCap", 4000, 500, 15000));
     public static final @NotNull OptionRange PARTICLE_RENDER_DISTANCE = register(new OptionRange("particleRenderDistance", 8, 1, 24)
         .addOnChangeCallback((mc, option) -> {
             if (mc.currentScreen instanceof ScreenOptions) {
@@ -272,11 +271,6 @@ public final class GameSettings {
             }
         })
         .withDisplayStringProvider((mc, i18n, option) -> {
-            for (HudComponent component : HudComponents.INSTANCE.getComponents()) {
-                if (component.getScaleOverride() > 0) {
-                    return i18n.translateKey("options.guiScale.disabled");
-                }
-            }
             if (option.value == 0) {
                 return i18n.translateKey("options.guiScale.auto");
             } else {
@@ -695,6 +689,11 @@ public final class GameSettings {
         HudComponents.DEFAULT_LAYOUT = HudComponents.INSTANCE.toSettingsString();
 
         loadOptions();
+
+        if (!SEEN_HUD_EDITOR_ONBOARDING.value) {
+            HudComponents.INSTANCE.resetToDefault();
+        }
+
         saveOptions();
     }
 
diff --git a/game/client/src/main/java/net/minecraft/client/option/OptionGuiScaleOverride.java b/game/client/src/main/java/net/minecraft/client/option/OptionGuiScaleOverride.java
index d75a8cb59..096b93e0a 100644
--- a/game/client/src/main/java/net/minecraft/client/option/OptionGuiScaleOverride.java
+++ b/game/client/src/main/java/net/minecraft/client/option/OptionGuiScaleOverride.java
@@ -1,24 +1,32 @@
 package net.minecraft.client.option;
 
 import net.minecraft.client.Minecraft;
-import net.minecraft.client.ScaledResolution;
 import net.minecraft.core.lang.I18n;
 import org.jetbrains.annotations.NotNull;
 
 /**
- * Per-instance scale override for HUD components. Behaves much like
- * {@link OptionGuiScale}, but with a "Disabled" entry instead of "Auto"; when
- * {@code value == 0} the component should fall back to the global GUI scale.
+ * Per-instance scale override for HUD components. Has a fixed 1x
+ * to {@value #MAX_SCALE}x range plus a "Disabled" entry; when 0 the
+ * component should fall back to the global GUI scale.
  *
  * <p>Instances of this class are intentionally NOT registered through
  * {@link GameSettings#register}: each HUD Component
  * owns its own instance and serializes it as part of its own settings string.
  */
 public class OptionGuiScaleOverride extends OptionToggleable<Integer> {
+    public static final int MAX_SCALE = 12;
+
     public OptionGuiScaleOverride() {
         super("guiScaleOverride", 0, new Integer[1]);
     }
 
+    public static int clampValue(int value) {
+        if (value <= 0) {
+            return 0;
+        }
+        return Math.min(value, MAX_SCALE);
+    }
+
     @Override
     public void toggle() {
         toggle(1);
@@ -32,28 +40,24 @@ public class OptionGuiScaleOverride extends OptionToggleable<Integer> {
 
     @Override
     public int getValueIndex() {
-        int max = size();
-        if (this.value <= 0 || this.value >= max) {
+        if (this.value <= 0 || this.value > MAX_SCALE) {
             return 0;
         }
-        return max - this.value;
+        return size() - this.value;
     }
 
     @Override
     public void setValueWithIndex(int i) {
-        if (i != 0) {
-            this.value = size() - i;
-        } else {
+        if (i <= 0 || i > MAX_SCALE) {
             this.value = 0;
+        } else {
+            this.value = size() - i;
         }
     }
 
     @Override
     public int size() {
-        Minecraft mc = Minecraft.getMinecraft();
-        int width = mc.gameWindow.getWidthScreenCoords();
-        int height = mc.gameWindow.getHeightScreenCoords();
-        return ScaledResolution.getMaxScale(width, height);
+        return MAX_SCALE + 1;
     }
 
     @Override
@@ -64,7 +68,7 @@ public class OptionGuiScaleOverride extends OptionToggleable<Integer> {
     @Override
     public void fromOptionsString(@NotNull String string) {
         try {
-            this.value = Integer.parseInt(string);
+            this.value = clampValue(Integer.parseInt(string));
         } catch (NumberFormatException e) {
             this.value = 0;
         }
diff --git a/game/client/src/main/java/net/minecraft/client/player/controller/PlayerControllerMP.java b/game/client/src/main/java/net/minecraft/client/player/controller/PlayerControllerMP.java
index 3947bb6d1..4dc8f5ed9 100644
--- a/game/client/src/main/java/net/minecraft/client/player/controller/PlayerControllerMP.java
+++ b/game/client/src/main/java/net/minecraft/client/player/controller/PlayerControllerMP.java
@@ -1,5 +1,7 @@
 package net.minecraft.client.player.controller;
 
+import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap;
+import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
 import net.minecraft.client.Minecraft;
 import net.minecraft.client.entity.player.PlayerLocalMultiplayer;
 import net.minecraft.client.net.handler.PacketHandlerClient;
@@ -9,6 +11,7 @@ import net.minecraft.core.entity.Entity;
 import net.minecraft.core.entity.player.Player;
 import net.minecraft.core.enums.ArtType;
 import net.minecraft.core.item.ItemStack;
+import net.minecraft.core.player.inventory.menu.MenuAbstract;
 import net.minecraft.core.net.packet.*;
 import net.minecraft.core.util.helper.Direction;
 import net.minecraft.core.util.helper.Side;
@@ -116,9 +119,29 @@ public class PlayerControllerMP
 
     @Override
     public @Nullable ItemStack handleInventoryMouseClick(final int windowId, final @NotNull InventoryAction action, final int @Nullable [] args, final @NotNull Player player) {
-        final short actionId = player.containerMenu.backup(player.inventory);
+        final MenuAbstract menu = player.containerMenu;
+
+        final int slotCount = menu.slots.size();
+        final ItemStack[] before = new ItemStack[slotCount];
+        for (int i = 0; i < slotCount; i++) {
+            final ItemStack stack = menu.getSlot(i).getItemStack();
+            before[i] = stack == null ? null : stack.copy();
+        }
+
+        final int stateId = menu.getStateId();
         final @Nullable ItemStack itemStack = super.handleInventoryMouseClick(windowId, action, args, player);
-        this.packetHandler.addToSendQueue(new PacketContainerClick(windowId, action, args, itemStack, actionId));
+
+        final Int2ObjectMap<ItemStack> changedSlots = new Int2ObjectArrayMap<>();
+        for (int i = 0; i < slotCount; i++) {
+            final ItemStack now = menu.getSlot(i).getItemStack();
+            if (!ItemStack.areItemStacksEqual(before[i], now)) {
+                changedSlots.put(i, now == null ? null : now.copy());
+            }
+        }
+        final ItemStack held = player.inventory.getHeldItemStack();
+        final @Nullable ItemStack carriedItem = held == null ? null : held.copy();
+
+        this.packetHandler.addToSendQueue(new PacketContainerClick(windowId, action, args, stateId, changedSlots, carriedItem));
         return itemStack;
     }
 
diff --git a/game/client/src/main/java/net/minecraft/client/render/OpenGLHelper.java b/game/client/src/main/java/net/minecraft/client/render/OpenGLHelper.java
index d27048aa6..a2a33c714 100644
--- a/game/client/src/main/java/net/minecraft/client/render/OpenGLHelper.java
+++ b/game/client/src/main/java/net/minecraft/client/render/OpenGLHelper.java
@@ -103,23 +103,24 @@ public class OpenGLHelper {
     }
 
     public static void checkError(String info) {
-        if (Global.DEBUG_MODE) {
-            int errorCode = GL41.glGetError();
-
-            if (errorCode != GL41.GL_NO_ERROR) {
-                if (Global.BUILD_CHANNEL.isUnstableBuild()) {
-                    String error = errorCode + " (" + getErrorDescription(errorCode) + "): " + info;
-                    throw new RuntimeException("OpenGL Error " + error);
-                }
-
-                if (!errors.contains(info)) {
-                    errors.add(info);
-                    String error = errorCode + " (" + getErrorDescription(errorCode) + "): " + info;
-                    LOGGER.error("########## GL ERROR ##########");
-                    LOGGER.error("OpenGL Error {}", error);
-                }
-            }
-        }
+        // This error checking is not very helpful, the newer logger callback work vastly better
+//        if (Global.DEBUG_MODE) {
+//            int errorCode = GL41.glGetError();
+//
+//            if (errorCode != GL41.GL_NO_ERROR) {
+//                if (Global.BUILD_CHANNEL.isUnstableBuild()) {
+//                    String error = errorCode + " (" + getErrorDescription(errorCode) + "): " + info;
+//                    throw new RuntimeException("OpenGL Error " + error);
+//                }
+//
+//                if (!errors.contains(info)) {
+//                    errors.add(info);
+//                    String error = errorCode + " (" + getErrorDescription(errorCode) + "): " + info;
+//                    LOGGER.error("########## GL ERROR ##########");
+//                    LOGGER.error("OpenGL Error {}", error);
+//                }
+//            }
+//        }
     }
 
     public static String getErrorDescription(int error) {
diff --git a/game/client/src/main/java/net/minecraft/client/render/ParticleEngine.java b/game/client/src/main/java/net/minecraft/client/render/ParticleEngine.java
index 0daeb80e6..28df94f8c 100644
--- a/game/client/src/main/java/net/minecraft/client/render/ParticleEngine.java
+++ b/game/client/src/main/java/net/minecraft/client/render/ParticleEngine.java
@@ -13,7 +13,6 @@ import net.minecraft.client.render.renderer.CompareFunc;
 import net.minecraft.client.render.renderer.GLRenderer;
 import net.minecraft.client.render.renderer.Shaders;
 import net.minecraft.client.render.renderer.State;
-import net.minecraft.client.render.tessellator.TessellatorGeneral;
 import net.minecraft.client.render.tessellator.TessellatorParticle;
 import net.minecraft.client.render.tessellator.TessellatorParticleShader;
 import net.minecraft.client.render.texture.stitcher.TextureRegistry;
@@ -22,6 +21,7 @@ import net.minecraft.core.block.Blocks;
 import net.minecraft.core.block.material.MaterialGas;
 import net.minecraft.core.block.material.MaterialLiquid;
 import net.minecraft.core.entity.player.Player;
+import net.minecraft.core.util.helper.MathHelper;
 import net.minecraft.core.util.helper.Side;
 import net.minecraft.core.world.World;
 import net.minecraft.core.world.pos.TilePosc;
@@ -30,9 +30,7 @@ import org.joml.primitives.AABBd;
 import org.joml.primitives.AABBdc;
 import org.lwjgl.opengl.GL41;
 
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
 import java.util.Random;
 
 /**
@@ -45,15 +43,11 @@ public final class ParticleEngine {
 
     private final @NotNull TessellatorParticleShader shaderParticleTessellator = new TessellatorParticleShader(4000 * 40);
     private World world;
-    private final List<Particle>[] particles;
+    private final @NotNull List<@NotNull Particle> @NotNull [] particles;
     private final Random rand;
 
-    private final Map<Class<?>, ObjectArrayList<Particle>> particlePools = new HashMap<>();
-    private final AABBd cullingAABB = new AABBd();
-    private int globalTick = 0;
-    private double lastCamX, lastCamY, lastCamZ;
-
     public ParticleEngine(World world) {
+        //noinspection unchecked
         this.particles = new ObjectArrayList[TEXTURE_COUNT];
         this.rand = new Random();
         if (world != null) {
@@ -72,54 +66,20 @@ public final class ParticleEngine {
     public void add(@NotNull Particle particle) {
         int t = particle.getParticleTexture();
 
-        if (this.particles[t].size() >= GameSettings.PARTICLES_CAP.value) {
-            // Find the furthest particle to delete instead of just the oldest
-            int indexToRemove = getIndexToRemove(t);
-
-            // Return the furthest particle to the Object Pool
-            Particle removed = this.particles[t].remove(indexToRemove);
-            ObjectArrayList<Particle> pool = this.particlePools.computeIfAbsent(removed.getClass(), k -> new ObjectArrayList<>());
-            if (pool.size() < GameSettings.PARTICLES_CAP.value) {
-                pool.add(removed);
-            }
+        var s = this.particles[t].size();
+        if (s >= GameSettings.PARTICLES_CAP.value) {
+            this.particles[t].remove(this.rand.nextInt(s));
         }
 
         this.particles[t].add(particle);
         particle.init();
     }
 
-    private int getIndexToRemove(int t) {
-        int indexToRemove = 0;
-        double maxDistSq = -1;
-
-        // Only scan a handful of particles to avoid CPU lag when overflowing
-        int scanLimit = Math.min(50, this.particles[t].size());
-
-        for (int i = 0; i < scanLimit; i++) {
-            Particle check = this.particles[t].get(i);
-            double distSq = (check.x - lastCamX)*(check.x - lastCamX) +
-                    (check.y - lastCamY)*(check.y - lastCamY) +
-                    (check.z - lastCamZ)*(check.z - lastCamZ);
-
-            if (distSq > maxDistSq) {
-                maxDistSq = distSq;
-                indexToRemove = i;
-            }
-        }
-        return indexToRemove;
-    }
 
     /**
      * Ticks all the particles in the engine.
      */
     public void tick() {
-        this.globalTick++;
-
-        Player localPlayer = this.world.players.isEmpty() ? null : this.world.players.get(0);
-        if (localPlayer != null) {
-            this.world.particleEmitters.tick(this.world, localPlayer, GameSettings.PARTICLE_RENDER_DISTANCE.value);
-        }
-
         for (int tt = 0; tt < this.particles.length; tt++) {
             for (int i = 0; i < this.particles[tt].size(); i++) {
                 Particle p = this.particles[tt].get(i);
@@ -127,16 +87,6 @@ public final class ParticleEngine {
                 p.tick();
                 if (p.removed) {
                     this.particles[tt].remove(i--);
-
-                    Class<?> c = p.getClass();
-                    ObjectArrayList<Particle> pl = this.particlePools.get(c);
-                    if (pl == null) {
-                        pl = new ObjectArrayList<>();
-                        this.particlePools.put(c, pl);
-                    }
-                    if (pl.size() < GameSettings.PARTICLES_CAP.value) {
-                        pl.add(p);
-                    }
                 }
             }
         }
@@ -149,6 +99,9 @@ public final class ParticleEngine {
      * @param partialTick The time since the last tick.
      */
     public void renderParticles(@NotNull ICamera camera, float partialTick) {
+//        class Cache {
+//            private static final AABBd cullingAABB = new AABBd();
+//        }
         GLRenderer.pushFrame();
         GLRenderer.setColor4f(1f, 1f, 1f, 1f);
         GLRenderer.setShader(Shaders.PARTICLE);
@@ -169,13 +122,9 @@ public final class ParticleEngine {
         double yOff = camera.getY(partialTick);
         double zOff = camera.getZ(partialTick);
 
-        this.lastCamX = xOff;
-        this.lastCamY = yOff;
-        this.lastCamZ = zOff;
-
         double maxDist = GameSettings.PARTICLE_RENDER_DISTANCE.value * 16.0;
         double maxRenderDistSq = maxDist * maxDist;
-        boolean frustumCull = GameSettings.FRUSTUM_CULLING.value;
+//        boolean frustumCull = GameSettings.FRUSTUM_CULLING.value;
 
         for (int tt = 0; tt == WORLD_TEXTURE; tt++) {
             if (this.particles[tt].isEmpty()) continue;
@@ -186,22 +135,25 @@ public final class ParticleEngine {
             for (int i = 0; i < this.particles[tt].size(); i++) {
                 Particle p = this.particles[tt].get(i);
 
-                double distSq = (p.x - xOff)*(p.x - xOff) + (p.y - yOff)*(p.y - yOff) + (p.z - zOff)*(p.z - zOff);
+                double dx = p.x - xOff;
+                double dy = p.y - yOff;
+                double dz = p.z - zOff;
+                double distSq = dx * dx + dy * dy + dz * dz;
                 if (distSq > maxRenderDistSq) continue;
 
-                if (frustumCull) {
-                    double lerpX = net.minecraft.core.util.helper.MathHelper.lerp(p.xo, p.x, partialTick);
-                    double lerpY = net.minecraft.core.util.helper.MathHelper.lerp(p.yo, p.y, partialTick);
-                    double lerpZ = net.minecraft.core.util.helper.MathHelper.lerp(p.zo, p.z, partialTick);
-                    double r = p.size * 0.1;
-
-                    this.cullingAABB.setMin(lerpX - r, lerpY - r, lerpZ - r);
-                    this.cullingAABB.setMax(lerpX + r, lerpY + r, lerpZ + r);
-
-                    if (!camera.getFrustum().isVisible(this.cullingAABB, partialTick)) {
-                        continue;
-                    }
-                }
+//                if (frustumCull) {
+//                    double lerpX = MathHelper.lerp(p.xo, p.x, partialTick);
+//                    double lerpY = MathHelper.lerp(p.yo, p.y, partialTick);
+//                    double lerpZ = MathHelper.lerp(p.zo, p.z, partialTick);
+//                    double r = p.size * 0.1;
+//
+//                    Cache.cullingAABB.setMin(lerpX - r, lerpY - r, lerpZ - r);
+//                    Cache.cullingAABB.setMax(lerpX + r, lerpY + r, lerpZ + r);
+//
+//                    if (!camera.getFrustum().isVisible(Cache.cullingAABB, partialTick)) {
+//                        continue;
+//                    }
+//                }
 
                 p.render(tessellator, partialTick);
             }
@@ -211,15 +163,6 @@ public final class ParticleEngine {
         GLRenderer.popFrame();
     }
 
-    @SuppressWarnings("unchecked")
-    private <T extends Particle> T getPooledParticle(Class<T> type) {
-        ObjectArrayList<Particle> pool = this.particlePools.computeIfAbsent(type, k -> new ObjectArrayList<>());
-        if (!pool.isEmpty()) {
-            return (T) pool.remove(pool.size() - 1);
-        }
-        return null;
-    }
-
     /**
      * Renders all the particles in the engine whose texture type is ENTITY_PARTICLE_TEXTURE.
      * These particles are drawn using OpenGL normal-based lighting, unlike the other particles,
@@ -228,7 +171,7 @@ public final class ParticleEngine {
      * @param camera      The camera relative to whose perspective the particles should be drawn.
      * @param partialTick The time since the last tick.
      */
-    public void renderLitParticles(ICamera camera, float partialTick) {
+    public void renderLitParticles(@NotNull ICamera camera, float partialTick) {
         int tt = ENTITY_PARTICLE_TEXTURE;
         if (this.particles[tt].isEmpty()) {
             return;
@@ -291,13 +234,7 @@ public final class ParticleEngine {
                     double velY = yp - blockPos.y() - centerY;
                     double velZ = zp - blockPos.z() - centerZ;
 
-                    ParticleBlockBreaking particle = getPooledParticle(ParticleBlockBreaking.class);
-                    if (particle == null) {
-                        particle = new ParticleBlockBreaking(this.world, xp, yp, zp, velX, velY, velZ, block, side, data);
-                    } else {
-                        particle.reset(this.world, xp, yp, zp, velX, velY, velZ, block, side, data);
-                    }
-
+                    ParticleBlockBreaking particle = new ParticleBlockBreaking(this.world, xp, yp, zp, velX, velY, velZ, block, side, data);
                     add(particle.init(blockPos));
                 }
             }
@@ -337,25 +274,11 @@ public final class ParticleEngine {
             xp = blockPos.x() + bounds.maxX() + r;
         }
 
-        ParticleBlockBreaking p = getPooledParticle(ParticleBlockBreaking.class);
-        if (p == null) {
-            p = new ParticleBlockBreaking(this.world, xp, yp, zp, 0.0D, 0.0D, 0.0D, block, side, this.world.getBlockData(blockPos));
-        } else {
-            p.reset(this.world, xp, yp, zp, 0.0D, 0.0D, 0.0D, block, side, this.world.getBlockData(blockPos));
-        }
-
+        ParticleBlockBreaking p = new ParticleBlockBreaking(this.world, xp, yp, zp, 0.0D, 0.0D, 0.0D, block, side, this.world.getBlockData(blockPos));
         add(p.init(blockPos).setPower(0.2F).setScale(0.6F));
     }
 
     public int countParticles() {
         return this.particles[WORLD_TEXTURE].size() + this.particles[ENTITY_PARTICLE_TEXTURE].size();
     }
-
-    public int getGlobalTick() {
-        return globalTick;
-    }
-
-    public void setGlobalTick(int globalTick) {
-        this.globalTick = globalTick;
-    }
 }
diff --git a/game/client/src/main/java/net/minecraft/client/render/WorldRenderer.java b/game/client/src/main/java/net/minecraft/client/render/WorldRenderer.java
index 7d5b8e246..c299780a8 100644
--- a/game/client/src/main/java/net/minecraft/client/render/WorldRenderer.java
+++ b/game/client/src/main/java/net/minecraft/client/render/WorldRenderer.java
@@ -170,6 +170,10 @@ public class WorldRenderer {
         for (int i = 0; i < entities.size(); i++) {
             final Entity entity = entities.get(i);
 
+            if (entity == this.mc.thePlayer.vehicle && entity.passesThroughRiderTargeting(playerItem)) {
+                continue;
+            }
+
             if (playerItem != null) {
                 if (!playerItem.interactsWithEntity(entity)) continue;
             } else {
diff --git a/game/client/src/main/java/net/minecraft/client/render/block/color/BlockColorConduit.java b/game/client/src/main/java/net/minecraft/client/render/block/color/BlockColorConduit.java
index c6a3a258f..ab3b8efe0 100644
--- a/game/client/src/main/java/net/minecraft/client/render/block/color/BlockColorConduit.java
+++ b/game/client/src/main/java/net/minecraft/client/render/block/color/BlockColorConduit.java
@@ -43,9 +43,8 @@ public class BlockColorConduit extends BlockColor {
     }
 
     @Override
-    public int getWorldColor(WorldSource world, @NotNull TilePosc tilePos, int tintIndex) {
-        int data = world.getBlockData(tilePos);
-        int strength = data / BlockLogicConduit.STATE_COUNT;
+    public int getWorldColor(@NotNull WorldSource world, @NotNull TilePosc tilePos, int tintIndex) {
+        int strength = BlockLogicConduit.getPower(world, tilePos);
         strength = Math.max(0, Math.min(15, strength));
         return powerColors[strength];
     }
diff --git a/game/client/src/main/java/net/minecraft/client/render/block/model/BlockModelConduit.java b/game/client/src/main/java/net/minecraft/client/render/block/model/BlockModelConduit.java
index 7c09ff5fc..de26cd26c 100644
--- a/game/client/src/main/java/net/minecraft/client/render/block/model/BlockModelConduit.java
+++ b/game/client/src/main/java/net/minecraft/client/render/block/model/BlockModelConduit.java
@@ -17,6 +17,8 @@ public class BlockModelConduit<T extends BlockLogicConduit> extends BlockModelGe
 
     private final ModelState[] stateMapOn = new ModelState[BlockLogicConduit.STATE_COUNT];
     private final ModelState[] stateMapOff = new ModelState[BlockLogicConduit.STATE_COUNT];
+    private final ModelState[] capStateMapOn = new ModelState[6];
+    private final ModelState[] capStateMapOff = new ModelState[6];
     private final ModelState inertState;
 
     public BlockModelConduit(Block<T> block) {
@@ -25,17 +27,31 @@ public class BlockModelConduit<T extends BlockLogicConduit> extends BlockModelGe
         StaticBlockModel onCore = BlockModelDispatcher.loadDataModel("minecraft:block/rubyglass/conduit/conduit_core").asModel();
         StaticBlockModel onStraight = BlockModelDispatcher.loadDataModel("minecraft:block/rubyglass/conduit/conduit_straight").asModel();
         StaticBlockModel onCorner = BlockModelDispatcher.loadDataModel("minecraft:block/rubyglass/conduit/conduit_corner").asModel();
+        StaticBlockModel onCap = BlockModelDispatcher.loadDataModel("minecraft:block/rubyglass/conduit/conduit_cap").asModel();
 
         StaticBlockModel offCore = BlockModelDispatcher.loadDataModel("minecraft:block/rubyglass/conduit/conduit_core_off").asModel();
         StaticBlockModel offStraight = BlockModelDispatcher.loadDataModel("minecraft:block/rubyglass/conduit/conduit_straight_off").asModel();
         StaticBlockModel offCorner = BlockModelDispatcher.loadDataModel("minecraft:block/rubyglass/conduit/conduit_corner_off").asModel();
+        StaticBlockModel offCap = BlockModelDispatcher.loadDataModel("minecraft:block/rubyglass/conduit/conduit_cap_off").asModel();
         this.inertState = new ModelState(onCore, 0, 0, 0);
 
         populateStateMap(stateMapOn, onCore, onStraight, onCorner);
         populateStateMap(stateMapOff, offCore, offStraight, offCorner);
+        populateCapStateMap(capStateMapOn, onCap);
+        populateCapStateMap(capStateMapOff, offCap);
     }
 
-    private void populateStateMap(ModelState[] map, StaticBlockModel core, StaticBlockModel straight, StaticBlockModel corner) {
+    private void populateCapStateMap(ModelState @NotNull [] map, StaticBlockModel cap) {
+        //B
+        map[0] = new ModelState(cap, 0, 0, 0); //DOWN
+        map[1] = new ModelState(cap, 2, 0, 0); //UP
+        map[2] = new ModelState(cap, 1, 0, 0); //N
+        map[3] = new ModelState(cap, 3, 0, 0); //S
+        map[4] = new ModelState(cap, 0, 0, 3); //W
+        map[5] = new ModelState(cap, 0, 0, 1); //E
+    }
+
+    private void populateStateMap(ModelState @NotNull [] map, StaticBlockModel core, StaticBlockModel straight, StaticBlockModel corner) {
         // Core
         map[0] = new ModelState(core, 0, 0, 0); //Closed
 
@@ -63,36 +79,35 @@ public class BlockModelConduit<T extends BlockLogicConduit> extends BlockModelGe
         map[15] = new ModelState(corner, 0, 2, 1); //SE
     }
 
-    @Override
-    public @NotNull StaticBlockModel getModelFromData(int data) {
+    private @NotNull ModelState resolveState(int data) {
         int state = data & BlockLogicConduit.STATE_MASK;
-        int strength = (data >> BlockLogicConduit.POWER_SHIFT) & BlockLogicConduit.POWER_MASK;
 
-        if (state == 0 && strength == 15) {
-            return inertState.model;
+        if (state != 0) {
+            int strength = (data >> BlockLogicConduit.POWER_SHIFT) & BlockLogicConduit.POWER_MASK;
+            return strength == 0 ? stateMapOff[state] : stateMapOn[state];
+        }
+
+        if (BlockLogicConduit.isCap(data)) {
+            int sideId = BlockLogicConduit.getCapSide(data).id;
+            return BlockLogicConduit.isCapPowered(data) ? capStateMapOn[sideId] : capStateMapOff[sideId];
         }
 
-        if (strength == 0) {
-            return stateMapOff[state].model;
-        } else {
-            return stateMapOn[state].model;
+        int strength = (data >> BlockLogicConduit.POWER_SHIFT) & BlockLogicConduit.POWER_MASK;
+        if (strength == BlockLogicConduit.INERT_MARKER) {
+            return inertState;
         }
+        return stateMapOff[0];
+    }
+
+    @Override
+    public @NotNull StaticBlockModel getModelFromData(int data) {
+        return resolveState(data).model;
     }
 
     @Override
     public boolean renderAttached(final @NotNull TessellatorGeneral tessellator, @NotNull final WorldSource worldSource, @NotNull TilePosc tilePos, boolean cullFaces, final @Nullable IconCoordinate overrideTexture) {
         int data = worldSource.getBlockData(tilePos);
-        int state = data & BlockLogicConduit.STATE_MASK;
-        int strength = (data >> BlockLogicConduit.POWER_SHIFT) & BlockLogicConduit.POWER_MASK;
-
-        ModelState activeState;
-        if (state == 0 && strength == 15) {
-            activeState = inertState;
-        } else if (strength == 0) {
-            activeState = stateMapOff[state];
-        } else {
-            activeState = stateMapOn[state];
-        }
+        ModelState activeState = resolveState(data);
 
         return activeState.model.renderAttached(this, tessellator, worldSource, tilePos, activeState.rotX, activeState.rotY, activeState.rotZ, 0, 0, 0, false, cullFaces, overrideTexture);
     }
diff --git a/game/client/src/main/java/net/minecraft/client/render/entity/EntityRendererItem.java b/game/client/src/main/java/net/minecraft/client/render/entity/EntityRendererItem.java
index 62cdf9781..19e2afc00 100644
--- a/game/client/src/main/java/net/minecraft/client/render/entity/EntityRendererItem.java
+++ b/game/client/src/main/java/net/minecraft/client/render/entity/EntityRendererItem.java
@@ -1,7 +1,9 @@
 package net.minecraft.client.render.entity;
 
 import net.minecraft.client.option.GameSettings;
+import net.minecraft.client.render.renderer.BlendFactor;
 import net.minecraft.client.render.renderer.GLRenderer;
+import net.minecraft.client.render.renderer.State;
 import net.minecraft.core.entity.EntityItem;
 import org.jetbrains.annotations.NotNull;
 import net.minecraft.client.render.item.model.ItemModelDispatcher;
@@ -17,19 +19,26 @@ public class EntityRendererItem extends EntityRenderer<EntityItem> {
         this.shadowOpacity = 0.75F;
     }
 
+    static float getDespawnFadeAlpha(final @NotNull EntityItem entity, final float partialTick) {
+        return EntityItem.getDespawnFadeAlpha(entity.age, entity.lifetime, partialTick);
+    }
+
     public void render(@NotNull TessellatorGeneral tessellator, final @NotNull EntityItem entity, final double x, final double y, final double z, final float yaw, final float partialTick) {
-        int prime = ((entity.lifetime * 3)/4);
-        if (entity.lifetime >= 0 && (entity.age + partialTick) > prime) {
-            int pastPrime = entity.age - prime;
-            double toDeath = 1 - ((pastPrime + partialTick) * 4d)/entity.lifetime; // progress of age from 75% of lifetime to 100%
-            if (Math.cos(((pastPrime + partialTick) * Math.PI)/ (2.5 + 5.5 * toDeath)) > 0) return;
-        }
         final ItemStack itemstack = entity.item;
         if (itemstack == null) {
             return;
         }
+        final float fadeAlpha = getDespawnFadeAlpha(entity, partialTick);
+        if (entity.age >= entity.lifetime && entity.lifetime >= 0 && fadeAlpha <= EntityItem.DESPAWN_ALPHA_THRESHOLD) {
+            return;
+        }
         Item item = itemstack.getItem();
         GLRenderer.pushFrame();
+        if (fadeAlpha < 1.0f) {
+            GLRenderer.enableState(State.BLEND);
+            GLRenderer.setBlendFunc(BlendFactor.SRC_ALPHA, BlendFactor.ONE_MINUS_SRC_ALPHA);
+            GLRenderer.setColor4f(1.0f, 1.0f, 1.0f, fadeAlpha);
+        }
         final float bobbingOffset = MathHelper.sin((entity.age + partialTick) / 10F + entity.bobOffset) * 0.1F + 0.1F;
         final float yw = (float) Math.toDegrees(((entity.age + partialTick) / 20F + entity.bobOffset));
         byte renderCount = 1;
@@ -44,6 +53,18 @@ public class EntityRendererItem extends EntityRenderer<EntityItem> {
         }
         GLRenderer.modelM4f().translate((float) x, (float) (y + bobbingOffset), (float) z);
         ItemModelDispatcher.getInstance().getDispatch(item).renderItemEntity(tessellator, itemstack, GameSettings.ITEMS_3D.value, renderCount, entity.age, yw, entity.getLightIndex(partialTick), partialTick);
+        if (fadeAlpha < 1.0f) {
+            GLRenderer.setColor4f(1.0f, 1.0f, 1.0f, 1.0f);
+        }
         GLRenderer.popFrame();
     }
+
+    @Override
+    public void postRender(@NotNull TessellatorGeneral tessellator, @NotNull EntityItem entity, double x, double y, double z, float yaw, float partialTick) {
+        final float fadeAlpha = getDespawnFadeAlpha(entity, partialTick);
+        final float savedShadowOpacity = this.shadowOpacity;
+        this.shadowOpacity *= fadeAlpha;
+        super.postRender(tessellator, entity, x, y, z, yaw, partialTick);
+        this.shadowOpacity = savedShadowOpacity;
+    }
 }
diff --git a/game/client/src/main/java/net/minecraft/client/render/entity/MobRenderer.java b/game/client/src/main/java/net/minecraft/client/render/entity/MobRenderer.java
index 957ee5aad..3393df3ae 100644
--- a/game/client/src/main/java/net/minecraft/client/render/entity/MobRenderer.java
+++ b/game/client/src/main/java/net/minecraft/client/render/entity/MobRenderer.java
@@ -46,12 +46,21 @@ public abstract class MobRenderer<T extends Mob> extends EntityRenderer<T> {
 
         this.setupModels.clear();
         final int maxRenderLayer = maxRenderLayer(entity);
+        final float renderAlpha = getRenderAlpha(entity, partialTick);
+        final boolean translucent = renderAlpha < 1.0f;
+        //should fix water/particles rendering through pigu :3
+        if (translucent) {
+            GLRenderer.setDepthMask(false);
+        }
         for (int layer = 0; layer <= maxRenderLayer; layer++) {
             try {
                 GLRenderer.pushFrame();
                 final StaticEntityModel model = getAndSetupModelForLayer(entity, 1f, partialTick, layer);
                 this.setupModels.add(model);
                 if (model != null) {
+                    if (translucent) {
+                        GLRenderer.setColor4f(1.0f, 1.0f, 1.0f, renderAlpha);
+                    }
                     model.render();
                 }
                 GLRenderer.popFrame();
@@ -59,6 +68,10 @@ public abstract class MobRenderer<T extends Mob> extends EntityRenderer<T> {
                 LOGGER.error("Error setting up model on layer '{}' in renderer '{}'", layer, getClass().getSimpleName(), e);
             }
         }
+        if (translucent) {
+            GLRenderer.setColor4f(1.0f, 1.0f, 1.0f, 1.0f);
+            GLRenderer.setDepthMask(true);
+        }
         renderAdditional(tessellator, entity, partialTick);
 
         final int argb = getOverlayColor(entity, partialTick);
@@ -133,6 +146,10 @@ public abstract class MobRenderer<T extends Mob> extends EntityRenderer<T> {
         return baseSize;
     }
 
+    protected float getRenderAlpha(final @NotNull T entity, final float partialTick) {
+        return 1.0f;
+    }
+
     protected float getLimbSwing(final @NotNull T entity, final float partialTick) {
         return entity.walkAnimPos - entity.walkAnimSpeed * (1.0F - partialTick);
     }
diff --git a/game/client/src/main/java/net/minecraft/client/render/entity/MobRendererPig.java b/game/client/src/main/java/net/minecraft/client/render/entity/MobRendererPig.java
index 5c85103b8..512c0b7c6 100644
--- a/game/client/src/main/java/net/minecraft/client/render/entity/MobRendererPig.java
+++ b/game/client/src/main/java/net/minecraft/client/render/entity/MobRendererPig.java
@@ -1,20 +1,102 @@
 package net.minecraft.client.render.entity;
 
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.option.GameSettings;
+import net.minecraft.core.block.Blocks;
 import net.minecraft.core.entity.animal.MobPig;
+import net.minecraft.core.entity.player.Player;
+import net.minecraft.core.item.ItemStack;
+import net.minecraft.client.render.tessellator.TessellatorGeneral;
 import net.minecraft.core.util.helper.MathHelper;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
+import org.joml.Vector3d;
+import org.joml.Vector3dc;
+import org.joml.primitives.AABBd;
 import org.useless.dragonfly.models.entity.BoneTransform;
 import org.useless.dragonfly.models.entity.StaticEntityModel;
 
 public class MobRendererPig extends MobRendererQuadruped<MobPig> {
 
+    @Override
+    protected void renderSpecials(@NotNull final TessellatorGeneral tessellator, @NotNull final MobPig entity, final double x, final double y, final double z) {
+        if (entity.passenger != null) {
+            return;
+        }
+        super.renderSpecials(tessellator, entity, x, y, z);
+    }
+
     public MobRendererPig(final float shadowSize) {
         super(shadowSize);
     }
 
+    private static final float LOOK_FADE_TARGET = 0.2f;
+    private static final float LOOK_FADE_RATE = 16.0f;
+
+    @Override
+    protected float getRenderAlpha(@NotNull final MobPig entity, final float partialTick) {
+        final long now = System.nanoTime();
+        float dt = entity.lookFadeLastRenderNanos == 0L ? 0f : (now - entity.lookFadeLastRenderNanos) / 1_000_000_000f;
+        entity.lookFadeLastRenderNanos = now;
+        dt = MathHelper.clamp(dt, 0f, 0.1f);
+
+        if (!isRiderLookingAtOwnPig(entity, partialTick)) {
+            entity.lookFadeAlpha = 1.0f;
+            return 1.0f;
+        }
+
+        final float t = 1.0f - (float) Math.exp(-LOOK_FADE_RATE * dt);
+        entity.lookFadeAlpha += (LOOK_FADE_TARGET - entity.lookFadeAlpha) * t;
+        if (Math.abs(entity.lookFadeAlpha - LOOK_FADE_TARGET) < 0.01f) {
+            entity.lookFadeAlpha = LOOK_FADE_TARGET;
+        }
+        return entity.lookFadeAlpha;
+    }
+
+    @Override
+    public float getShadowSize(@NotNull final MobPig entity) {
+        if (entity.lookFadeAlpha < 0.9f) {
+            return 0.0f;
+        }
+        return super.getShadowSize(entity);
+    }
+
+    private boolean isRiderLookingAtOwnPig(@NotNull final MobPig entity, final float partialTick) {
+        if (GameSettings.THIRD_PERSON_VIEW.value != 0) {
+            return false;
+        }
+
+        final Minecraft mc = Minecraft.getMinecraft();
+        final Player player = mc.thePlayer;
+        if (player == null || entity != player.vehicle) {
+            return false;
+        }
+
+        final ItemStack held = player.inventory.getCurrentItem();
+        if (held != null && (held.itemID == Blocks.MUSHROOM_BROWN.id() || held.itemID == Blocks.MUSHROOM_RED.id())) {
+            return false;
+        }
+
+        final Vector3dc eye = player.getPosition(partialTick, true);
+        final Vector3dc look = player.getViewVector(partialTick);
+        if (look == null) {
+            return false;
+        }
+
+        final double reach = player.getGamemode().getEntityReachDistance() + 1.0;
+        final Vector3d end = new Vector3d(look).mul(reach).add(eye);
+
+        final double offX = (entity.xo - entity.x) * (1.0 - partialTick);
+        final double offY = (entity.yo - entity.y) * (1.0 - partialTick);
+        final double offZ = (entity.zo - entity.z) * (1.0 - partialTick);
+
+        final AABBd renderedBox = entity.bb.translate(offX, offY, offZ, new AABBd());
+        return MathHelper.aabbClip(renderedBox, eye, end) != null;
+    }
+
     @Override
     protected @Nullable StaticEntityModel getAndSetupModelForLayer(@NotNull final MobPig entity, final float brightness, final float partialTick, final int layer) {
+        final boolean sitting = entity.getSitting();
         final StaticEntityModel model;
         if (layer == 1) {
             bindTexture("/assets/minecraft/textures/entity/saddle.png");
@@ -25,23 +107,51 @@ public class MobRendererPig extends MobRendererQuadruped<MobPig> {
         model.resetBones();
 
         final BoneTransform head = model.getTransform("head");
-        final BoneTransform leg0 = model.getTransform("leg0");
-        final BoneTransform leg1 = model.getTransform("leg1");
-        final BoneTransform leg2 = model.getTransform("leg2");
-        final BoneTransform leg3 = model.getTransform("leg3");
-
         final float bodyYaw = getBodyYaw(entity, partialTick);
         final float headYaw = getHeadYaw(entity, partialTick) - bodyYaw;
         final float headPitch = getHeadPitch(entity, partialTick);
-        final float limbSwing = getLimbSwing(entity, partialTick);
-        final float limbYaw = getLimbYaw(entity, partialTick);
-
-        head.rotX = headPitch;
-        head.rotY = headYaw;
-        leg0.rotX = MathHelper.cos(limbSwing * 0.6662F) * 1.4F * limbYaw;
-        leg1.rotX = MathHelper.cos(limbSwing * 0.6662F + MathHelper.PI) * 1.4F * limbYaw;
-        leg2.rotX = MathHelper.cos(limbSwing * 0.6662F + MathHelper.PI) * 1.4F * limbYaw;
-        leg3.rotX = MathHelper.cos(limbSwing * 0.6662F) * 1.4F * limbYaw;
+
+        if (sitting) {
+            final BoneTransform body = model.getTransform("body");
+            final BoneTransform leg0 = model.getTransform("leg0");
+            final BoneTransform leg1 = model.getTransform("leg1");
+
+            body.rotX = -27.5 * MathHelper.DEG_TO_RAD;
+            body.posY = -4;
+            body.posZ = 1;
+
+            leg0.rotX = -90 * MathHelper.DEG_TO_RAD;
+            leg0.rotY = 15 * MathHelper.DEG_TO_RAD;
+            leg0.posX = -1;
+            leg0.posY = -4;
+
+            leg1.rotX = -90 * MathHelper.DEG_TO_RAD;
+            leg1.rotY = -15 * MathHelper.DEG_TO_RAD;
+            leg1.posX = 1;
+            leg1.posY = -4;
+
+            head.posZ = 1;
+            head.rotX += headPitch;
+            head.rotY += headYaw;
+
+        } else {
+
+            head.rotX = headPitch;
+            head.rotY = headYaw;
+
+            final BoneTransform leg0 = model.getTransform("leg0");
+            final BoneTransform leg1 = model.getTransform("leg1");
+            final BoneTransform leg2 = model.getTransform("leg2");
+            final BoneTransform leg3 = model.getTransform("leg3");
+            final float limbSwing = getLimbSwing(entity, partialTick);
+            final float limbYaw = getLimbYaw(entity, partialTick);
+
+            leg0.rotX = MathHelper.cos(limbSwing * 0.6662F) * 1.4F * limbYaw;
+            leg1.rotX = MathHelper.cos(limbSwing * 0.6662F + MathHelper.PI) * 1.4F * limbYaw;
+            leg2.rotX = MathHelper.cos(limbSwing * 0.6662F + MathHelper.PI) * 1.4F * limbYaw;
+            leg3.rotX = MathHelper.cos(limbSwing * 0.6662F) * 1.4F * limbYaw;
+
+        }
 
         return model;
     }
diff --git a/game/client/src/main/java/net/minecraft/client/render/entity/MobRendererPlayer.java b/game/client/src/main/java/net/minecraft/client/render/entity/MobRendererPlayer.java
index be6db2908..6ed6552e2 100644
--- a/game/client/src/main/java/net/minecraft/client/render/entity/MobRendererPlayer.java
+++ b/game/client/src/main/java/net/minecraft/client/render/entity/MobRendererPlayer.java
@@ -40,6 +40,14 @@ public class MobRendererPlayer extends MobRendererBipedArmored<Player> {
         super(0.5F);
     }
 
+    @Override
+    public float getShadowSize(final @NotNull Player entity) {
+        if (entity.vehicle != null) {
+            return 0.0f;
+        }
+        return super.getShadowSize(entity);
+    }
+
     protected @NotNull StaticEntityModel getActiveModel(final @NotNull Player player, final boolean onlyArms) {
         var overrideModel = EntityGeometryMojangData.Cache.getPlayerModelOverride(player.uuid,
             onlyArms ? EntityGeometryMojangData.PlayerModelOverrideComponent.ARMS : EntityGeometryMojangData.PlayerModelOverrideComponent.MAIN);
@@ -226,9 +234,20 @@ public class MobRendererPlayer extends MobRendererBipedArmored<Player> {
                 GLRenderer.modelM4f().translate(0.0F, 0.0F, 0.125F);
                 final float yawOff = player.yBodyRotO + (player.yBodyRot - player.yBodyRotO) * partialTick;
                 final float bodyAngle = 5;
-                final double xd = MathHelper.lerp(player.xdO, player.xd, partialTick);
-                final double yd = Math.min(MathHelper.lerp(player.ydO, player.yd, partialTick), 0);
-                final double zd = MathHelper.lerp(player.zdO, player.zd, partialTick);
+
+                final double xd;
+                final double yd;
+                final double zd;
+                if (player.vehicle != null) {
+                    xd = player.x - player.xo;
+                    yd = Math.min(player.y - player.yo, 0);
+                    zd = player.z - player.zo;
+                } else {
+                    xd = MathHelper.lerp(player.xdO, player.xd, partialTick);
+                    yd = Math.min(MathHelper.lerp(player.ydO, player.yd, partialTick), 0);
+                    zd = MathHelper.lerp(player.zdO, player.zd, partialTick);
+                }
+
                 final double vel = -1 / (3 * Math.hypot(xd, zd) + 1) + 1;
                 final double moveAng = Math.atan2(xd, zd);
                 final double yawRad = Math.toRadians(yawOff);
diff --git a/game/client/src/main/java/net/minecraft/client/render/particle/Particle.java b/game/client/src/main/java/net/minecraft/client/render/particle/Particle.java
index 4410a5f79..870318f16 100644
--- a/game/client/src/main/java/net/minecraft/client/render/particle/Particle.java
+++ b/game/client/src/main/java/net/minecraft/client/render/particle/Particle.java
@@ -3,6 +3,7 @@ package net.minecraft.client.render.particle;
 import net.minecraft.client.render.ParticleEngine;
 import net.minecraft.client.render.tessellator.TessellatorParticle;
 import net.minecraft.client.render.texture.stitcher.IconCoordinate;
+import net.minecraft.client.render.texture.stitcher.TextureRegistry;
 import net.minecraft.core.Global;
 import net.minecraft.core.block.Block;
 import net.minecraft.core.util.helper.LightIndexHelper;
@@ -21,7 +22,7 @@ import java.util.concurrent.ThreadLocalRandom;
  */
 public class Particle {
     protected static final Random random = new Random();
-    public IconCoordinate tex;
+    public @Nullable IconCoordinate tex;
     protected float uo;
     protected float vo;
     protected int age;
@@ -51,8 +52,6 @@ public class Particle {
     public boolean onGround = false;
     public boolean removed = false;
 
-    private final TilePos qPos = new TilePos();
-
     public @NotNull World world;
 
     /**
@@ -88,33 +87,6 @@ public class Particle {
         this.age = 0;
     }
 
-    public Particle resetBase(@NotNull World world, double x, double y, double z, double xa, double ya, double za) {
-        this.world = world;
-        this.x = x;
-        this.y = y;
-        this.z = z;
-        this.rCol = this.gCol = this.bCol = 1.0F;
-        ThreadLocalRandom tlr = ThreadLocalRandom.current();
-        this.xd = xa + (float) (tlr.nextDouble() * 2D - 1.0D) * 0.4F;
-        this.yd = ya + (float) (tlr.nextDouble() * 2D - 1.0D) * 0.4F;
-        this.zd = za + (float) (tlr.nextDouble() * 2D - 1.0D) * 0.4F;
-        float speed = (float) (tlr.nextDouble() + tlr.nextDouble() + 1.0D) * 0.15F;
-        float dd = MathHelper.sqrt(this.xd * this.xd + this.yd * this.yd + this.zd * this.zd);
-        this.xd = (this.xd / dd) * speed * 0.4D;
-        this.yd = (this.yd / dd) * speed * 0.4D + 0.1D;
-        this.zd = (this.zd / dd) * speed * 0.4D;
-        this.uo = random.nextFloat() * 3F;
-        this.vo = random.nextFloat() * 3F;
-        this.size = (random.nextFloat() * 0.5F + 0.5F) * 2.0F;
-        this.lifetime = (int) (4F / (random.nextFloat() * 0.9F + 0.1F));
-        this.age = 0;
-
-        this.removed = false;
-        this.noPhysics = false;
-        this.onGround = false;
-        return this;
-    }
-
     public void init() {
         this.xo = this.x;
         this.yo = this.y;
@@ -174,6 +146,7 @@ public class Particle {
     }
 
     public void move(double dx, double dy, double dz) {
+        class Cache { private static final @NotNull TilePos queryPos = new TilePos(); }
         if (this.noPhysics) {
             this.x += dx;
             this.y += dy;
@@ -185,8 +158,8 @@ public class Particle {
 
         // Y-Axis
         if (dy != 0) {
-            this.qPos.set(MathHelper.floor(this.x), MathHelper.floor(this.y + dy), MathHelper.floor(this.z));
-            AABBdc bb = getCollisionBox(this.qPos);
+            Cache.queryPos.set(MathHelper.floor(this.x), MathHelper.floor(this.y + dy), MathHelper.floor(this.z));
+            AABBdc bb = getCollisionBox(Cache.queryPos);
 
             boolean collided = false;
             if (bb != null && this.x >= bb.minX() && this.x <= bb.maxX() && this.z >= bb.minZ() && this.z <= bb.maxZ()) {
@@ -213,8 +186,8 @@ public class Particle {
 
         // X-Axis
         if (dx != 0) {
-            this.qPos.set(MathHelper.floor(this.x + dx), MathHelper.floor(this.y), MathHelper.floor(this.z));
-            AABBdc bb = getCollisionBox(this.qPos);
+            Cache.queryPos.set(MathHelper.floor(this.x + dx), MathHelper.floor(this.y), MathHelper.floor(this.z));
+            AABBdc bb = getCollisionBox(Cache.queryPos);
 
             boolean collided = false;
             if (bb != null && this.y >= bb.minY() && this.y <= bb.maxY() && this.z >= bb.minZ() && this.z <= bb.maxZ()) {
@@ -236,8 +209,8 @@ public class Particle {
 
         // Z-Axis
         if (dz != 0) {
-            this.qPos.set(MathHelper.floor(this.x), MathHelper.floor(this.y), MathHelper.floor(this.z + dz));
-            AABBdc bb = getCollisionBox(this.qPos);
+            Cache.queryPos.set(MathHelper.floor(this.x), MathHelper.floor(this.y), MathHelper.floor(this.z + dz));
+            AABBdc bb = getCollisionBox(Cache.queryPos);
 
             boolean collided = false;
             if (bb != null && this.x >= bb.minX() && this.x <= bb.maxX() && this.y >= bb.minY() && this.y <= bb.maxY()) {
@@ -302,18 +275,19 @@ public class Particle {
     }
 
     public float calcBrightness(float pt) {
+        class Cache { private static final @NotNull TilePos queryPos = new TilePos(); }
         if (Global.accessor.isFullbrightEnabled()) {
             return 1.0f;
         }
 
-        this.qPos.set(
+        Cache.queryPos.set(
             MathHelper.floor(MathHelper.lerp(this.xo, this.x, pt)),
             MathHelper.floor(MathHelper.lerp(this.yo, this.y, pt)),
             MathHelper.floor(MathHelper.lerp(this.zo, this.z, pt))
         );
 
-        if (this.world.areBlocksLoaded(this.qPos, this.qPos)) {
-            return this.world.getLightBrightness(this.qPos);
+        if (this.world.areBlocksLoaded(Cache.queryPos, Cache.queryPos)) {
+            return this.world.getLightBrightness(Cache.queryPos);
         }
         return 0;
     }
@@ -323,23 +297,24 @@ public class Particle {
     }
 
     public byte calcLightIndex(float pt) {
+        class Cache { private static final @NotNull TilePos queryPos = new TilePos(); }
         if (Global.accessor.isFullbrightEnabled()) return LightIndexHelper.lightIndex2i(15, 15);
 
         int bX = MathHelper.floor(MathHelper.lerp(this.xo, this.x, pt));
         int bY = MathHelper.floor(MathHelper.lerp(this.yo, this.y, pt));
         int bZ = MathHelper.floor(MathHelper.lerp(this.zo, this.z, pt));
 
-        if (this.world.getBlockLitInteriorSurface(this.qPos.set(bX, bY, bZ))) {
+        if (this.world.getBlockLitInteriorSurface(Cache.queryPos.set(bX, bY, bZ))) {
             byte lIdx = 0;
-            lIdx = LightIndexHelper.max(lIdx, this.world.getSavedLightIndex(this.qPos.set(bX, bY + 1, bZ)));
-            lIdx = LightIndexHelper.max(lIdx, this.world.getSavedLightIndex(this.qPos.set(bX - 1, bY, bZ)));
-            lIdx = LightIndexHelper.max(lIdx, this.world.getSavedLightIndex(this.qPos.set(bX + 1, bY, bZ)));
-            lIdx = LightIndexHelper.max(lIdx, this.world.getSavedLightIndex(this.qPos.set(bX, bY, bZ + 1)));
-            lIdx = LightIndexHelper.max(lIdx, this.world.getSavedLightIndex(this.qPos.set(bX, bY, bZ - 1)));
+            lIdx = LightIndexHelper.max(lIdx, this.world.getSavedLightIndex(Cache.queryPos.set(bX, bY + 1, bZ)));
+            lIdx = LightIndexHelper.max(lIdx, this.world.getSavedLightIndex(Cache.queryPos.set(bX - 1, bY, bZ)));
+            lIdx = LightIndexHelper.max(lIdx, this.world.getSavedLightIndex(Cache.queryPos.set(bX + 1, bY, bZ)));
+            lIdx = LightIndexHelper.max(lIdx, this.world.getSavedLightIndex(Cache.queryPos.set(bX, bY, bZ + 1)));
+            lIdx = LightIndexHelper.max(lIdx, this.world.getSavedLightIndex(Cache.queryPos.set(bX, bY, bZ - 1)));
             return lIdx;
         }
 
-        return this.world.getSavedLightIndex(this.qPos.set(bX, bY, bZ));
+        return this.world.getSavedLightIndex(Cache.queryPos.set(bX, bY, bZ));
     }
 
     public byte getLightIndex(float partialTick) {
diff --git a/game/client/src/main/java/net/minecraft/client/render/particle/ParticleAcidBoiling.java b/game/client/src/main/java/net/minecraft/client/render/particle/ParticleAcidBoiling.java
index 131c3a8a2..bcce0bc5b 100644
--- a/game/client/src/main/java/net/minecraft/client/render/particle/ParticleAcidBoiling.java
+++ b/game/client/src/main/java/net/minecraft/client/render/particle/ParticleAcidBoiling.java
@@ -1,20 +1,14 @@
 package net.minecraft.client.render.particle;
 
-import net.minecraft.client.render.texture.stitcher.IconCoordinate;
-import net.minecraft.client.render.texture.stitcher.TextureRegistry;
 import net.minecraft.core.block.material.Materials;
 import net.minecraft.core.util.helper.MathHelper;
 import net.minecraft.core.world.World;
 import net.minecraft.core.world.pos.TilePos;
-import org.jetbrains.annotations.NotNull;
 
 public class ParticleAcidBoiling extends Particle {
-
 	private float sinTimer = 0.0F;
 	private final boolean isReversed;
 	private final boolean needsWater;
-	private final TilePos qpos = new TilePos();
-	private final @NotNull IconCoordinate popping = TextureRegistry.getTexture("minecraft:particle/acid_boiling_pop");
 
 	public ParticleAcidBoiling(World world, double x, double y, double z, double xa, double ya, double za, boolean needsWater) {
 		super(world, x, y, z, xa, ya, za);
@@ -22,7 +16,7 @@ public class ParticleAcidBoiling extends Particle {
 		this.rCol = 1.0F;
 		this.gCol = 1.0F;
 		this.bCol = 1.0F;
-		this.tex = TextureRegistry.getTexture("minecraft:particle/acid_boiling");
+		this.tex = ParticleTextureCache.ICON_ACID_BOILING;
 		this.size *= random.nextFloat() * 0.7F + 0.3F;
 		this.xd = xa * 0.2 + (Math.random() * (double) 2.0F - (double) 1.0F) * (double) 0.02F;
 		this.yd = ya * 0.2 + (Math.random() * (double) 2.0F - (double) 1.0F) * (double) 0.02F;
@@ -33,6 +27,9 @@ public class ParticleAcidBoiling extends Particle {
 
 	@Override
 	public void tick() {
+		class Cache {
+			private static final TilePos queryPos = new TilePos();
+		}
 		++this.sinTimer;
 		if (this.sinTimer > 100.0F) {
 			this.sinTimer = 0.0F;
@@ -43,7 +40,7 @@ public class ParticleAcidBoiling extends Particle {
 		this.yo = this.y;
 		this.zo = this.z;
 		double expand = 0.05;
-		boolean inWater = this.world.getBlockMaterial(this.qpos.set((int) this.x, (int) this.y, (int) this.z)) == Materials.ACID;
+		boolean inWater = this.world.getBlockMaterial(Cache.queryPos.set((int) this.x, (int) this.y, (int) this.z)) == Materials.ACID;
 		if (inWater) {
 			this.yd = 0.2;
 			this.xd = MathHelper.sin(this.sinTimer * 0.95F) / 30.0F * (float) (this.isReversed ? -1 : 1);
@@ -61,7 +58,7 @@ public class ParticleAcidBoiling extends Particle {
 		}
 
 		if (this.lifetime == 6) {
-			this.tex = this.popping;
+			this.tex = ParticleTextureCache.ICON_ACID_POPPING;
 			this.world.spawnParticle("arrowtrail", this.x, this.y, this.z, 0.0F, 0.0F, 0.0F, 1, false);
 		} else if (this.lifetime <= 0) {
 			this.remove();
diff --git a/game/client/src/main/java/net/minecraft/client/render/particle/ParticleArrowGolden.java b/game/client/src/main/java/net/minecraft/client/render/particle/ParticleArrowGolden.java
index dc1c3086e..0896a5451 100644
--- a/game/client/src/main/java/net/minecraft/client/render/particle/ParticleArrowGolden.java
+++ b/game/client/src/main/java/net/minecraft/client/render/particle/ParticleArrowGolden.java
@@ -1,7 +1,6 @@
 package net.minecraft.client.render.particle;
 
 import net.minecraft.client.render.tessellator.TessellatorParticle;
-import net.minecraft.client.render.texture.stitcher.TextureRegistry;
 import net.minecraft.core.world.World;
 import org.jetbrains.annotations.NotNull;
 
@@ -20,7 +19,7 @@ public class ParticleArrowGolden extends Particle {
         this.xd += xa;
         this.yd += ya;
         this.zd += za;
-        this.rCol = gCol = 0.8f;
+        this.rCol = this.gCol = 0.8f;
         this.bCol = (float) (Math.random() * 0.3D);
         this.size *= 0.75F;
         this.size *= scale;
@@ -52,12 +51,7 @@ public class ParticleArrowGolden extends Particle {
         if (this.age++ >= this.lifetime) {
             this.remove();
         }
-        int val = (7 - ((this.age * 8) / this.lifetime));
-        if (val >= 0) {
-            this.tex = TextureRegistry.getTexture("minecraft:particle/puff_" + val);
-        } else {
-            this.tex = null;
-        }
+        this.tex = ParticleTextureCache.getPuffStage(this.age, this.lifetime);
         this.yd += 0.004D;
         this.move(this.xd, this.yd, this.zd);
         if (this.y == this.yo) {
@@ -72,5 +66,4 @@ public class ParticleArrowGolden extends Particle {
             this.zd *= 0.7D;
         }
     }
-
 }
diff --git a/game/client/src/main/java/net/minecraft/client/render/particle/ParticleAsh.java b/game/client/src/main/java/net/minecraft/client/render/particle/ParticleAsh.java
index 2ae163ce5..0f080c642 100644
--- a/game/client/src/main/java/net/minecraft/client/render/particle/ParticleAsh.java
+++ b/game/client/src/main/java/net/minecraft/client/render/particle/ParticleAsh.java
@@ -1,6 +1,5 @@
 package net.minecraft.client.render.particle;
 
-import net.minecraft.client.render.texture.stitcher.TextureRegistry;
 import net.minecraft.core.world.World;
 
 public class ParticleAsh extends Particle {
@@ -12,6 +11,7 @@ public class ParticleAsh extends Particle {
         this.rCol = this.gCol = this.bCol = 0.2F + random.nextFloat() * 0.3F;
         this.size = random.nextFloat() * random.nextFloat() * 6F + 1.0F;
         this.lifetime = (int) (64 / (random.nextFloat() * 0.8D + 0.2D)) + 2;
+        this.tex = ParticleTextureCache.ICON_PUFF_STAGES[0];
     }
 
     @Override
@@ -23,7 +23,6 @@ public class ParticleAsh extends Particle {
         if (this.age++ >= this.lifetime) {
             this.remove();
         }
-        this.tex = TextureRegistry.getTexture("minecraft:particle/puff_0");
         move(this.xd, this.yd, this.zd);
         this.xd *= 0.9D;
         this.zd *= 0.9D;
diff --git a/game/client/src/main/java/net/minecraft/client/render/particle/ParticleBlockBreaking.java b/game/client/src/main/java/net/minecraft/client/render/particle/ParticleBlockBreaking.java
index 7c2fc09f4..e17638089 100644
--- a/game/client/src/main/java/net/minecraft/client/render/particle/ParticleBlockBreaking.java
+++ b/game/client/src/main/java/net/minecraft/client/render/particle/ParticleBlockBreaking.java
@@ -15,24 +15,18 @@ import net.minecraft.core.world.pos.TilePosc;
 import org.jetbrains.annotations.NotNull;
 
 public class ParticleBlockBreaking extends Particle {
-    private Block<?> block;
-    private Side side;
+    private final Block<?> block;
+    private final Side side;
     private final @NotNull TilePos initPos = new TilePos(Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE);
 
     public ParticleBlockBreaking(World world, double x, double y, double z, double xa, double ya, double za, Block<?> block, Side side, int meta) {
         super(world, x, y, z, xa, ya, za);
-        this.reset(world, x, y, z, xa, ya, za, block, side, meta);
-    }
-
-    public ParticleBlockBreaking reset(World world, double x, double y, double z, double xa, double ya, double za, Block<?> block, Side side, int meta) {
-        super.resetBase(world, x, y, z, xa, ya, za);
         this.side = side;
         this.block = block;
         this.tex = BlockModelDispatcher.getInstance().getDispatch(block).getParticleTexture(this.side, meta);
         this.gravity = block.blockParticleGravity;
         this.rCol = this.gCol = this.bCol = 0.6F;
         this.size /= 2.0F;
-        return this;
     }
 
     public ParticleBlockBreaking init(@NotNull TilePosc tilePos) {
@@ -63,23 +57,26 @@ public class ParticleBlockBreaking extends Particle {
 
     @Override
     public byte calcLightIndex(float partialTick) {
+        class Cache {
+            private static final @NotNull TilePos queryPos = new TilePos();
+            private static final @NotNull TilePos blockPos = new TilePos();
+        }
+
         if (Global.accessor.isFullbrightEnabled()) return LightIndexHelper.lightIndex2i(15, 15);
-        TilePos blockPos = new TilePos(
+        Cache.blockPos.set(
             MathHelper.lerp(this.xo, this.x, partialTick),
             MathHelper.lerp(this.yo, this.y, partialTick),
             MathHelper.lerp(this.zo, this.z, partialTick));
 
         byte lightIndex = 0;
-        if (this.world.getBlockLitInteriorSurface(blockPos) || blockPos.equals(initPos)) {
-            TilePos queryPos = new TilePos();
-
-            lightIndex = LightIndexHelper.max(lightIndex, this.world.getSavedLightIndex(blockPos.up(queryPos)));
-            lightIndex = LightIndexHelper.max(lightIndex, this.world.getSavedLightIndex(blockPos.west(queryPos)));
-            lightIndex = LightIndexHelper.max(lightIndex, this.world.getSavedLightIndex(blockPos.east(queryPos)));
-            lightIndex = LightIndexHelper.max(lightIndex, this.world.getSavedLightIndex(blockPos.south(queryPos)));
-            lightIndex = LightIndexHelper.max(lightIndex, this.world.getSavedLightIndex(blockPos.north(queryPos)));
+        if (this.world.getBlockLitInteriorSurface(Cache.blockPos) || Cache.blockPos.equals(this.initPos)) {
+            lightIndex = LightIndexHelper.max(lightIndex, this.world.getSavedLightIndex(Cache.blockPos.up(Cache.queryPos)));
+            lightIndex = LightIndexHelper.max(lightIndex, this.world.getSavedLightIndex(Cache.blockPos.west(Cache.queryPos)));
+            lightIndex = LightIndexHelper.max(lightIndex, this.world.getSavedLightIndex(Cache.blockPos.east(Cache.queryPos)));
+            lightIndex = LightIndexHelper.max(lightIndex, this.world.getSavedLightIndex(Cache.blockPos.south(Cache.queryPos)));
+            lightIndex = LightIndexHelper.max(lightIndex, this.world.getSavedLightIndex(Cache.blockPos.north(Cache.queryPos)));
         } else {
-            lightIndex = this.world.getSavedLightIndex(blockPos);
+            lightIndex = this.world.getSavedLightIndex(Cache.blockPos);
         }
 
         return lightIndex;
diff --git a/game/client/src/main/java/net/minecraft/client/render/particle/ParticleBubble.java b/game/client/src/main/java/net/minecraft/client/render/particle/ParticleBubble.java
index 659823227..68516117b 100644
--- a/game/client/src/main/java/net/minecraft/client/render/particle/ParticleBubble.java
+++ b/game/client/src/main/java/net/minecraft/client/render/particle/ParticleBubble.java
@@ -1,6 +1,5 @@
 package net.minecraft.client.render.particle;
 
-import net.minecraft.client.render.texture.stitcher.TextureRegistry;
 import net.minecraft.core.block.material.Materials;
 import net.minecraft.core.world.Dimension;
 import net.minecraft.core.world.World;
@@ -8,6 +7,7 @@ import net.minecraft.core.world.pos.TilePos;
 import org.jetbrains.annotations.NotNull;
 
 public class ParticleBubble extends Particle {
+
     private final boolean needsWater;
 
     public ParticleBubble(World world, double x, double y, double z, double xa, double ya, double za, @NotNull Dimension dimension, boolean needsWater) {
@@ -16,7 +16,7 @@ public class ParticleBubble extends Particle {
         this.rCol = 1.0F;
         this.gCol = 1.0F;
         this.bCol = 1.0F;
-        this.tex = dimension == Dimension.NETHER ? TextureRegistry.getTexture("minecraft:particle/bubble_boiling") : TextureRegistry.getTexture("minecraft:particle/bubble");
+        this.tex = dimension == Dimension.NETHER ? ParticleTextureCache.ICON_BUBBLE_BOILING : ParticleTextureCache.ICON_BUBBLE;
         this.size = this.size * (random.nextFloat() * 0.6F + 0.2F);
         this.xd = xa * 0.2D + (Math.random() * 2D - 1.0D) * 0.02F;
         this.yd = ya * 0.2D + (Math.random() * 2D - 1.0D) * 0.02F;
@@ -26,6 +26,7 @@ public class ParticleBubble extends Particle {
 
     @Override
     public void tick() {
+        class Cache { private static final @NotNull TilePos queryPos = new TilePos(); }
         this.cachedLightmapCoord = calcLightIndex(1f);
         this.xo = this.x;
         this.yo = this.y;
@@ -35,10 +36,10 @@ public class ParticleBubble extends Particle {
         this.xd *= 0.85D;
         this.yd *= 0.85D;
         this.zd *= 0.85D;
-        if (this.needsWater && this.world.getBlockMaterial(new TilePos(this.x, this.y, this.z)) != Materials.WATER) {
+        if (this.lifetime-- <= 0) {
             remove();
         }
-        if (this.lifetime-- <= 0) {
+        if (this.needsWater && this.world.getBlockMaterial(Cache.queryPos.set(this.x, this.y, this.z)) != Materials.WATER) {
             remove();
         }
     }
diff --git a/game/client/src/main/java/net/minecraft/client/render/particle/ParticleBubbleBoiling.java b/game/client/src/main/java/net/minecraft/client/render/particle/ParticleBubbleBoiling.java
index 125f24332..5197e85c3 100644
--- a/game/client/src/main/java/net/minecraft/client/render/particle/ParticleBubbleBoiling.java
+++ b/game/client/src/main/java/net/minecraft/client/render/particle/ParticleBubbleBoiling.java
@@ -1,20 +1,14 @@
 package net.minecraft.client.render.particle;
 
-import net.minecraft.client.render.texture.stitcher.IconCoordinate;
-import net.minecraft.client.render.texture.stitcher.TextureRegistry;
 import net.minecraft.core.block.material.Materials;
 import net.minecraft.core.util.helper.MathHelper;
 import net.minecraft.core.world.World;
 import net.minecraft.core.world.pos.TilePos;
-import org.jetbrains.annotations.NotNull;
 
 public class ParticleBubbleBoiling extends Particle {
     private float sinTimer = 0;
-    private boolean isReversed = false;
+    private final boolean isReversed;
     private final boolean needsWater;
-//    private final AABBd aabbd = new AABBd();
-    private final TilePos qpos = new TilePos();
-    private final @NotNull IconCoordinate popping = TextureRegistry.getTexture("minecraft:particle/bubble_boiling_pop");
 
     public ParticleBubbleBoiling(World world, double x, double y, double z, double xa, double ya, double za, boolean needsWater) {
         super(world, x, y, z, xa, ya, za);
@@ -22,7 +16,7 @@ public class ParticleBubbleBoiling extends Particle {
         this.rCol = 1.0F;
         this.gCol = 1.0F;
         this.bCol = 1.0F;
-        this.tex = TextureRegistry.getTexture("minecraft:particle/bubble_boiling");
+        this.tex = ParticleTextureCache.ICON_BUBBLE_BOILING;
         this.size = this.size * (random.nextFloat() * 0.7F + 0.3F);
         this.xd = xa * 0.2D + (Math.random() * 2D - 1.0D) * 0.02F;
         this.yd = ya * 0.2D + (Math.random() * 2D - 1.0D) * 0.02F;
@@ -34,6 +28,10 @@ public class ParticleBubbleBoiling extends Particle {
 
     @Override
     public void tick() {
+        class Cache {
+            private static final TilePos queryPos = new TilePos();
+//            private static final AABBd aabbd = new AABBd();
+        }
         this.sinTimer++;
         if (this.sinTimer > 100) {
             this.sinTimer = 0;
@@ -43,10 +41,10 @@ public class ParticleBubbleBoiling extends Particle {
         this.xo = this.x;
         this.yo = this.y;
         this.zo = this.z;
-        final double expand = 0.05;
-//        this.aabbd.setMin(this.x - expand, this.y - expand, this.z - expand).setMax(this.x + expand, this.y, this.z + expand);
-//        boolean inWater = this.world.isAABBInMaterial(this.qpos, Materials.WATER);
-        boolean inWater = this.world.getBlockMaterial(this.qpos.set((int) this.x, (int) (this.y), (int) this.z)) == Materials.WATER;
+//        final double expand = 0.05;
+//        Cache.aabbd.setMin(this.x - expand, this.y - expand, this.z - expand).setMax(this.x + expand, this.y, this.z + expand);
+//        boolean inWater = this.world.isAABBInMaterial(Cache.qpos, Materials.WATER);
+        boolean inWater = this.world.getBlockMaterial(Cache.queryPos.set(this.x, this.y, this.z)) == Materials.WATER;
         if (inWater) {
             this.yd = 0.20D;
 
@@ -63,8 +61,8 @@ public class ParticleBubbleBoiling extends Particle {
             this.lifetime = MathHelper.clamp(this.lifetime, 0, 8);
         }
         if (this.lifetime == 6) {
-            this.tex = this.popping;
-            world.spawnParticle("smoke", x, y, z, 0.0D, 0.0D, 0.0D, 1, false);
+            this.tex = ParticleTextureCache.ICON_BUBBLE_BOILING_POP;
+            this.world.spawnParticle("smoke", this.x, this.y, this.z, 0.0D, 0.0D, 0.0D, 1, false);
         } else if (this.lifetime <= 0) {
             remove();
         }
diff --git a/game/client/src/main/java/net/minecraft/client/render/particle/ParticleExplode.java b/game/client/src/main/java/net/minecraft/client/render/particle/ParticleExplode.java
index e449080c7..f29a4d7d0 100644
--- a/game/client/src/main/java/net/minecraft/client/render/particle/ParticleExplode.java
+++ b/game/client/src/main/java/net/minecraft/client/render/particle/ParticleExplode.java
@@ -1,6 +1,5 @@
 package net.minecraft.client.render.particle;
 
-import net.minecraft.client.render.texture.stitcher.TextureRegistry;
 import net.minecraft.core.world.World;
 
 public class ParticleExplode extends Particle {
@@ -23,12 +22,7 @@ public class ParticleExplode extends Particle {
         if (this.age++ >= this.lifetime) {
             this.remove();
         }
-        int val = (7 - ((this.age * 8) / this.lifetime));
-        if (val >= 0) {
-            this.tex = TextureRegistry.getTexture("minecraft:particle/puff_" + val);
-        } else {
-            this.tex = null;
-        }
+        this.tex = ParticleTextureCache.getPuffStage(this.age, this.lifetime);
         this.yd += 0.004D;
         move(this.xd, this.yd, this.zd);
         this.xd *= 0.9D;
diff --git a/game/client/src/main/java/net/minecraft/client/render/particle/ParticleFirefly.java b/game/client/src/main/java/net/minecraft/client/render/particle/ParticleFirefly.java
index 53bcb7bef..ded9c16fa 100644
--- a/game/client/src/main/java/net/minecraft/client/render/particle/ParticleFirefly.java
+++ b/game/client/src/main/java/net/minecraft/client/render/particle/ParticleFirefly.java
@@ -1,8 +1,6 @@
 package net.minecraft.client.render.particle;
 
-import net.minecraft.client.render.LightmapHelper;
 import net.minecraft.client.render.tessellator.TessellatorParticle;
-import net.minecraft.client.render.texture.stitcher.TextureRegistry;
 import net.minecraft.client.render.tessellator.TessellatorGeneral;
 import net.minecraft.core.entity.animal.MobFireflyCluster;
 import net.minecraft.core.util.helper.LightIndexHelper;
@@ -43,7 +41,7 @@ public class ParticleFirefly extends Particle {
         this.lifetime = (int) (24D + random.nextInt(6));
         this.lifetime *= (int) scale;
         this.noPhysics = false;
-        this.tex = TextureRegistry.getTexture("minecraft:particle/firefly");
+        this.tex = ParticleTextureCache.ICON_FIREFLY;
         if (random.nextInt(2) == 0) {
             this.isReversed = true;
         }
diff --git a/game/client/src/main/java/net/minecraft/client/render/particle/ParticleFluidDrip.java b/game/client/src/main/java/net/minecraft/client/render/particle/ParticleFluidDrip.java
index b5b6b997b..7e96784df 100644
--- a/game/client/src/main/java/net/minecraft/client/render/particle/ParticleFluidDrip.java
+++ b/game/client/src/main/java/net/minecraft/client/render/particle/ParticleFluidDrip.java
@@ -83,8 +83,9 @@ public class ParticleFluidDrip extends Particle {
 
     @Override
     public byte calcLightIndex(float partialTick) {
+        class Cache { private static final @NotNull TilePos queryPos = new TilePos(); }
         if (this.fullBright) {
-            return LightIndexHelper.lightIndex2i(this.world.getSavedLightValue(LightLayer.Sky, new TilePos(this.x, this.y, this.z)), 15);
+            return LightIndexHelper.lightIndex2i(this.world.getSavedLightValue(LightLayer.Sky, Cache.queryPos.set(this.x, this.y, this.z)), 15);
         }
         return super.calcLightIndex(partialTick);
     }
diff --git a/game/client/src/main/java/net/minecraft/client/render/particle/ParticleHeart.java b/game/client/src/main/java/net/minecraft/client/render/particle/ParticleHeart.java
index b7af7e7af..cb953a275 100644
--- a/game/client/src/main/java/net/minecraft/client/render/particle/ParticleHeart.java
+++ b/game/client/src/main/java/net/minecraft/client/render/particle/ParticleHeart.java
@@ -1,7 +1,6 @@
 package net.minecraft.client.render.particle;
 
 import net.minecraft.client.render.tessellator.TessellatorParticle;
-import net.minecraft.client.render.texture.stitcher.TextureRegistry;
 import net.minecraft.core.world.World;
 import org.jetbrains.annotations.NotNull;
 
@@ -23,7 +22,7 @@ public class ParticleHeart extends Particle {
         this.oSize = this.size;
         this.lifetime = 16;
         this.noPhysics = false;
-        this.tex = TextureRegistry.getTexture("minecraft:particle/heart");
+        this.tex = ParticleTextureCache.ICON_HEART;
     }
 
     @Override
diff --git a/game/client/src/main/java/net/minecraft/client/render/particle/ParticleLava.java b/game/client/src/main/java/net/minecraft/client/render/particle/ParticleLava.java
index cda09824a..5431ed214 100644
--- a/game/client/src/main/java/net/minecraft/client/render/particle/ParticleLava.java
+++ b/game/client/src/main/java/net/minecraft/client/render/particle/ParticleLava.java
@@ -1,7 +1,6 @@
 package net.minecraft.client.render.particle;
 
 import net.minecraft.client.render.tessellator.TessellatorParticle;
-import net.minecraft.client.render.texture.stitcher.TextureRegistry;
 import net.minecraft.core.world.World;
 import org.jetbrains.annotations.NotNull;
 
@@ -19,7 +18,7 @@ public class ParticleLava extends Particle {
         this.oSize = this.size;
         this.lifetime = (int) (16D / (Math.random() * 0.8D + 0.2D));
         this.noPhysics = false;
-        this.tex = TextureRegistry.getTexture("minecraft:particle/bubble_lava");
+        this.tex = ParticleTextureCache.ICON_BUBBLE_LAVA;
     }
 
     @Override
diff --git a/game/client/src/main/java/net/minecraft/client/render/particle/ParticleLeaf.java b/game/client/src/main/java/net/minecraft/client/render/particle/ParticleLeaf.java
index c06799116..de44ba8fc 100644
--- a/game/client/src/main/java/net/minecraft/client/render/particle/ParticleLeaf.java
+++ b/game/client/src/main/java/net/minecraft/client/render/particle/ParticleLeaf.java
@@ -2,6 +2,7 @@ package net.minecraft.client.render.particle;
 
 import net.minecraft.client.render.block.color.BlockColorDispatcher;
 import net.minecraft.client.render.tessellator.TessellatorParticle;
+import net.minecraft.client.render.texture.stitcher.IconCoordinate;
 import net.minecraft.client.render.texture.stitcher.TextureRegistry;
 import net.minecraft.core.block.Block;
 import net.minecraft.core.block.BlockLogicLeavesBase;
@@ -13,9 +14,10 @@ import org.jetbrains.annotations.Nullable;
 import org.joml.primitives.AABBdc;
 
 public class ParticleLeaf extends Particle {
+    private static final @NotNull IconCoordinate ICON_OAK_LEAVES = TextureRegistry.getTexture("minecraft:block/leaves/oak_fancy");
     public ParticleLeaf(World world, double x, double y, double z, double xa, double ya, double za) {
         super(world, x, y, z, xa, ya, za);
-        this.tex = TextureRegistry.getTexture("minecraft:block/leaves/oak_fancy");
+        this.tex = ICON_OAK_LEAVES;
         this.gravity = 0.125F;
         this.size /= 2.0F;
         this.lifetime *= 16;
diff --git a/game/client/src/main/java/net/minecraft/client/render/particle/ParticleNote.java b/game/client/src/main/java/net/minecraft/client/render/particle/ParticleNote.java
index 3f44568c7..ee1aa6c51 100644
--- a/game/client/src/main/java/net/minecraft/client/render/particle/ParticleNote.java
+++ b/game/client/src/main/java/net/minecraft/client/render/particle/ParticleNote.java
@@ -1,8 +1,6 @@
 package net.minecraft.client.render.particle;
 
-
 import net.minecraft.client.render.tessellator.TessellatorParticle;
-import net.minecraft.client.render.texture.stitcher.TextureRegistry;
 import net.minecraft.core.util.helper.MathHelper;
 import net.minecraft.core.world.World;
 import org.jetbrains.annotations.NotNull;
@@ -28,7 +26,7 @@ public class ParticleNote extends Particle {
         this.oSize = this.size;
         this.lifetime = 6;
         this.noPhysics = false;
-        this.tex = TextureRegistry.getTexture("minecraft:particle/note");
+        this.tex = ParticleTextureCache.ICON_NOTE;
     }
 
     @Override
diff --git a/game/client/src/main/java/net/minecraft/client/render/particle/ParticlePickupAnimation.java b/game/client/src/main/java/net/minecraft/client/render/particle/ParticlePickupAnimation.java
index c091499d3..51da06bcf 100644
--- a/game/client/src/main/java/net/minecraft/client/render/particle/ParticlePickupAnimation.java
+++ b/game/client/src/main/java/net/minecraft/client/render/particle/ParticlePickupAnimation.java
@@ -6,6 +6,7 @@ import net.minecraft.core.entity.Entity;
 import net.minecraft.client.render.EntityRendererDispatcher;
 import net.minecraft.core.world.World;
 import net.minecraft.core.world.pos.TilePos;
+import org.jetbrains.annotations.NotNull;
 
 public class ParticlePickupAnimation extends Particle {
     private final Entity item;
@@ -25,6 +26,7 @@ public class ParticlePickupAnimation extends Particle {
 
     @Override
     public void renderLit(float partialTick, double xOff, double yOff, double zOff) {
+        class Cache { private static final @NotNull TilePos queryPos = new TilePos(); }
         float time = (this.life + partialTick) / this.lifeTime;
         time *= time;
         double xo = this.item.x;
@@ -36,11 +38,11 @@ public class ParticlePickupAnimation extends Particle {
         double xx = xo + (xt - xo) * time;
         double yy = yo + (yt - yo) * time;
         double zz = zo + (zt - zo) * time;
-        TilePos tilePos = new TilePos(xx, yy, zz);
+        Cache.queryPos.set(xx, yy, zz);
         xx -= xOff;
         yy -= yOff;
         zz -= zOff;
-        GLRenderer.setLightmapCoord1i(this.item.world.getLightIndex(tilePos, 0));
+        GLRenderer.setLightmapCoord1i(this.item.world.getLightIndex(Cache.queryPos, 0));
         GLRenderer.setColor4f(1f, 1f, 1f, 1f);
         EntityRendererDispatcher.instance.renderEntityWithPosYaw(GLRenderer.getTessellator(), this.item, (float) xx, (float) yy, (float) zz, this.item.yRot, partialTick);
     }
diff --git a/game/client/src/main/java/net/minecraft/client/render/particle/ParticlePortal.java b/game/client/src/main/java/net/minecraft/client/render/particle/ParticlePortal.java
index f063bd052..bf7f22437 100644
--- a/game/client/src/main/java/net/minecraft/client/render/particle/ParticlePortal.java
+++ b/game/client/src/main/java/net/minecraft/client/render/particle/ParticlePortal.java
@@ -1,7 +1,6 @@
 package net.minecraft.client.render.particle;
 
 import net.minecraft.client.render.tessellator.TessellatorParticle;
-import net.minecraft.client.render.texture.stitcher.TextureRegistry;
 import net.minecraft.client.util.helper.Colors;
 import net.minecraft.core.util.helper.Color;
 import net.minecraft.core.world.World;
@@ -30,7 +29,7 @@ public class ParticlePortal extends Particle {
         this.bCol *= color.getBlue() / 255f;
         this.lifetime = (int) (Math.random() * 10D) + 40;
         this.noPhysics = true;
-        this.tex = TextureRegistry.getTexture("minecraft:particle/puff_" + (int) (Math.random() * 8D));
+        this.tex = ParticleTextureCache.ICON_PUFF_STAGES[(int) (Math.random() * ParticleTextureCache.ICON_PUFF_STAGES.length)];
     }
 
     @Override
diff --git a/game/client/src/main/java/net/minecraft/client/render/particle/ParticlePuffRGB.java b/game/client/src/main/java/net/minecraft/client/render/particle/ParticlePuffRGB.java
index aed803848..da21927f2 100644
--- a/game/client/src/main/java/net/minecraft/client/render/particle/ParticlePuffRGB.java
+++ b/game/client/src/main/java/net/minecraft/client/render/particle/ParticlePuffRGB.java
@@ -1,7 +1,6 @@
 package net.minecraft.client.render.particle;
 
 import net.minecraft.client.render.tessellator.TessellatorParticle;
-import net.minecraft.client.render.texture.stitcher.TextureRegistry;
 import net.minecraft.core.enums.LightLayer;
 import net.minecraft.core.util.helper.LightIndexHelper;
 import net.minecraft.core.util.helper.MathHelper;
@@ -10,7 +9,7 @@ import org.jetbrains.annotations.NotNull;
 
 public class ParticlePuffRGB extends Particle {
     private final float oSize;
-    private int brightness;
+    private final int brightness;
 
     public ParticlePuffRGB(World world, double x, double y, double z, float red, float green, float blue, int brightness) {
         this(world, x, y, z, 1.0F, red, green, blue, brightness);
@@ -18,10 +17,7 @@ public class ParticlePuffRGB extends Particle {
 
     public ParticlePuffRGB(World world, double x, double y, double z, float scale, float red, float green, float blue, int brightness) {
         super(world, x, y, z, 0.0D, 0.0D, 0.0D);
-        this.brightness = -1;
-        if (brightness >= 0) {
-            this.brightness = brightness;
-        }
+        this.brightness = brightness >= 0 ? brightness : -1;
         this.xd *= 0.1D;
         this.yd *= 0.1D;
         this.zd *= 0.1D;
@@ -39,14 +35,8 @@ public class ParticlePuffRGB extends Particle {
 
     @Override
     public void render(@NotNull TessellatorParticle tessellatorParticle, float partialTick) {
-        float l = ((this.age + partialTick) / this.lifetime) * 32F;
-        if (l < 0.0F) {
-            l = 0.0F;
-        }
-        if (l > 1.0F) {
-            l = 1.0F;
-        }
-        this.size = this.oSize * l;
+        float s = MathHelper.clamp(((this.age + partialTick) / this.lifetime) * 32F, 0, 1);
+        this.size = this.oSize * s;
         if (this.tex == null) return;
         int xTile = MathHelper.floor(this.x);
         int yTile = MathHelper.floor(this.y);
@@ -63,7 +53,7 @@ public class ParticlePuffRGB extends Particle {
             MathHelper.lerp(this.xo, this.x, partialTick),
             MathHelper.lerp(this.yo, this.y, partialTick),
             MathHelper.lerp(this.zo, this.z, partialTick), this.size * 0.1f,
-            this.tex.getIconUMin() + this.tex.getIconUSize(), this.tex.getIconVMin(), this.tex.getIconUSize() , this.tex.getIconVSize());
+            this.tex.getIconUMin(), this.tex.getIconVMin(), this.tex.getIconUSize(), this.tex.getIconVSize());
     }
 
     @Override
@@ -75,12 +65,7 @@ public class ParticlePuffRGB extends Particle {
         if (this.age++ >= this.lifetime) {
             this.remove();
         }
-        int val = (7 - ((this.age * 8) / this.lifetime));
-        if (val >= 0) {
-            this.tex = TextureRegistry.getTexture("minecraft:particle/puff_" + val);
-        } else {
-            this.tex = null;
-        }
+        this.tex = ParticleTextureCache.getPuffStage(this.age, this.lifetime);
         this.move(this.xd, this.yd, this.zd);
         if (this.y == this.yo) {
             this.xd *= 1.1D;
diff --git a/game/client/src/main/java/net/minecraft/client/render/particle/ParticleRedstoneDust.java b/game/client/src/main/java/net/minecraft/client/render/particle/ParticleRedstoneDust.java
index 2ba5837e9..312e74cfa 100644
--- a/game/client/src/main/java/net/minecraft/client/render/particle/ParticleRedstoneDust.java
+++ b/game/client/src/main/java/net/minecraft/client/render/particle/ParticleRedstoneDust.java
@@ -1,8 +1,6 @@
 package net.minecraft.client.render.particle;
 
-import net.minecraft.client.render.LightmapHelper;
 import net.minecraft.client.render.tessellator.TessellatorParticle;
-import net.minecraft.client.render.texture.stitcher.TextureRegistry;
 import net.minecraft.client.util.helper.Colors;
 import net.minecraft.core.enums.LightLayer;
 import net.minecraft.core.util.helper.Color;
@@ -86,12 +84,7 @@ public class ParticleRedstoneDust extends Particle {
         if (this.age++ >= this.lifetime) {
             this.remove();
         }
-        int val = (7 - ((this.age * 8) / this.lifetime));
-        if (val >= 0) {
-            this.tex = TextureRegistry.getTexture("minecraft:particle/puff_" + val);
-        } else {
-            this.tex = null;
-        }
+        this.tex = ParticleTextureCache.getPuffStage(this.age, this.lifetime);
         this.move(this.xd, this.yd, this.zd);
         if (this.y == this.yo) {
             this.xd *= 1.1D;
diff --git a/game/client/src/main/java/net/minecraft/client/render/particle/ParticleRubyglassLightning.java b/game/client/src/main/java/net/minecraft/client/render/particle/ParticleRubyglassLightning.java
index 24c67e917..d27fe6c9a 100644
--- a/game/client/src/main/java/net/minecraft/client/render/particle/ParticleRubyglassLightning.java
+++ b/game/client/src/main/java/net/minecraft/client/render/particle/ParticleRubyglassLightning.java
@@ -9,6 +9,7 @@ import net.minecraft.client.render.renderer.State;
 import net.minecraft.client.render.tessellator.TessellatorGeneral;
 import net.minecraft.core.util.helper.LightIndexHelper;
 import net.minecraft.core.world.World;
+import net.minecraft.core.world.pos.TilePos;
 import org.jetbrains.annotations.NotNull;
 
 import java.util.Random;
@@ -49,6 +50,10 @@ public class ParticleRubyglassLightning extends Particle{
 
     @Override
     public void renderLit(float partialTick, double camOffX, double camOffY, double camOffZ) {
+        class Cache {
+            private static final double @NotNull [] xOffs = new double[8];
+            private static final double @NotNull [] zOffs = new double[8];
+        }
         TessellatorGeneral t = GLRenderer.getTessellator();
         GLRenderer.pushFrame();
         GLRenderer.setLightmapCoord1i(this.cachedLightmapCoord);
@@ -61,15 +66,14 @@ public class ParticleRubyglassLightning extends Particle{
         GLRenderer.enableState(State.BLEND);
         GLRenderer.setBlendFunc(BlendFactor.SRC_ALPHA, BlendFactor.ONE);
 
-        final double @NotNull [] xOffs = new double[8];
-        final double @NotNull [] zOffs = new double[8];
+
         double xOff = 0.0D;
         double zOff = 0.0D;
 
         rand.setSeed(this.seed);
         for (int h = 7; h >= 0; h--) {
-            xOffs[h] = xOff;
-            zOffs[h] = zOff;
+            Cache.xOffs[h] = xOff;
+            Cache.zOffs[h] = zOff;
             xOff += rand.nextInt(11) - 5;
             zOff += rand.nextInt(11) - 5;
         }
@@ -87,8 +91,8 @@ public class ParticleRubyglassLightning extends Particle{
                     ht = hs - 2;
                 }
 
-                double xo0 = xOffs[hs] - xOff;
-                double zo0 = zOffs[hs] - zOff;
+                double xo0 = Cache.xOffs[hs] - xOff;
+                double zo0 = Cache.zOffs[hs] - zOff;
 
                 // h = segment
                 for (int h = hs; h >= ht; h--) {
diff --git a/game/client/src/main/java/net/minecraft/client/render/particle/ParticleSmoke.java b/game/client/src/main/java/net/minecraft/client/render/particle/ParticleSmoke.java
index 0e0ab3ff5..eaea83f52 100644
--- a/game/client/src/main/java/net/minecraft/client/render/particle/ParticleSmoke.java
+++ b/game/client/src/main/java/net/minecraft/client/render/particle/ParticleSmoke.java
@@ -1,7 +1,7 @@
 package net.minecraft.client.render.particle;
 
 import net.minecraft.client.render.tessellator.TessellatorParticle;
-import net.minecraft.client.render.texture.stitcher.TextureRegistry;
+import net.minecraft.core.util.helper.MathHelper;
 import net.minecraft.core.world.World;
 import org.jetbrains.annotations.NotNull;
 
@@ -31,14 +31,8 @@ public class ParticleSmoke extends Particle {
 
     @Override
     public void render(@NotNull TessellatorParticle tessellatorParticle, float partialTick) {
-        float l = ((this.age + partialTick) / this.lifetime) * 32F;
-        if (l < 0.0F) {
-            l = 0.0F;
-        }
-        if (l > 1.0F) {
-            l = 1.0F;
-        }
-        this.size = this.oSize * l;
+        float s = MathHelper.clamp(((this.age + partialTick) / this.lifetime) * 32F, 0, 1);
+        this.size = this.oSize * s;
         super.render(tessellatorParticle, partialTick);
     }
 
@@ -50,12 +44,7 @@ public class ParticleSmoke extends Particle {
         if (this.age++ >= this.lifetime) {
             this.remove();
         }
-        int val = (7 - ((this.age * 8) / this.lifetime));
-        if (val >= 0) {
-            this.tex = TextureRegistry.getTexture("minecraft:particle/puff_" + val);
-        } else {
-            this.tex = null;
-        }
+        this.tex = ParticleTextureCache.getPuffStage(this.age, this.lifetime);
         this.yd += 0.004D;
         this.move(this.xd, this.yd, this.zd);
         if (this.y == this.yo) {
diff --git a/game/client/src/main/java/net/minecraft/client/render/particle/ParticleSnowShovel.java b/game/client/src/main/java/net/minecraft/client/render/particle/ParticleSnowShovel.java
index 20a4154d8..5777f456f 100644
--- a/game/client/src/main/java/net/minecraft/client/render/particle/ParticleSnowShovel.java
+++ b/game/client/src/main/java/net/minecraft/client/render/particle/ParticleSnowShovel.java
@@ -2,7 +2,6 @@ package net.minecraft.client.render.particle;
 
 
 import net.minecraft.client.render.tessellator.TessellatorParticle;
-import net.minecraft.client.render.texture.stitcher.TextureRegistry;
 import net.minecraft.core.world.World;
 import org.jetbrains.annotations.NotNull;
 
@@ -51,12 +50,7 @@ public class ParticleSnowShovel extends Particle {
         if (this.age++ >= this.lifetime) {
             this.remove();
         }
-        int val = (7 - ((this.age * 8) / this.lifetime));
-        if (val >= 0) {
-            this.tex = TextureRegistry.getTexture("minecraft:particle/puff_" + val);
-        } else {
-            this.tex = null;
-        }
+        this.tex = ParticleTextureCache.getPuffStage(this.age, this.lifetime);
         this.yd -= 0.03D;
         this.move(this.xd, this.yd, this.zd);
         this.xd *= 0.99D;
@@ -67,5 +61,4 @@ public class ParticleSnowShovel extends Particle {
             this.zd *= 0.7D;
         }
     }
-
 }
diff --git a/game/client/src/main/java/net/minecraft/client/render/particle/ParticleTextureCache.java b/game/client/src/main/java/net/minecraft/client/render/particle/ParticleTextureCache.java
new file mode 100644
index 000000000..412ba1073
--- /dev/null
+++ b/game/client/src/main/java/net/minecraft/client/render/particle/ParticleTextureCache.java
@@ -0,0 +1,52 @@
+package net.minecraft.client.render.particle;
+
+import net.minecraft.client.render.texture.stitcher.IconCoordinate;
+import net.minecraft.client.render.texture.stitcher.TextureRegistry;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+public class ParticleTextureCache {
+    // Statically prefetch icons to avoid the cost of dynamically fetching them at runtime
+    public static final @NotNull IconCoordinate @NotNull [] ICON_PUFF_STAGES = new IconCoordinate[] {
+            TextureRegistry.getTexture("minecraft:particle/puff_0"),
+            TextureRegistry.getTexture("minecraft:particle/puff_1"),
+            TextureRegistry.getTexture("minecraft:particle/puff_2"),
+            TextureRegistry.getTexture("minecraft:particle/puff_3"),
+            TextureRegistry.getTexture("minecraft:particle/puff_4"),
+            TextureRegistry.getTexture("minecraft:particle/puff_5"),
+            TextureRegistry.getTexture("minecraft:particle/puff_6"),
+            TextureRegistry.getTexture("minecraft:particle/puff_7"),
+    };
+
+    public static @Nullable IconCoordinate getPuffStage(int age, int lifetime) {
+        if (age < 0) return ICON_PUFF_STAGES[ICON_PUFF_STAGES.length - 1];
+        if (age > lifetime) return null;
+        final int maxIndex = ICON_PUFF_STAGES.length - 1;
+        int val = (maxIndex - ((age * maxIndex) / lifetime));
+        return ICON_PUFF_STAGES[val];
+    }
+
+    public static final @NotNull IconCoordinate @NotNull [] ICON_SPLASH_RAIN = new IconCoordinate[] {
+            TextureRegistry.getTexture("minecraft:particle/splash_rain_0"),
+            TextureRegistry.getTexture("minecraft:particle/splash_rain_1"),
+            TextureRegistry.getTexture("minecraft:particle/splash_rain_2"),
+            TextureRegistry.getTexture("minecraft:particle/splash_rain_3"),
+    };
+    public static final @NotNull IconCoordinate @NotNull [] ICON_SPLASH_RAIN_BLOOD = new IconCoordinate[] {
+            TextureRegistry.getTexture("minecraft:particle/splash_rain_blood_0"),
+            TextureRegistry.getTexture("minecraft:particle/splash_rain_blood_1"),
+            TextureRegistry.getTexture("minecraft:particle/splash_rain_blood_2"),
+            TextureRegistry.getTexture("minecraft:particle/splash_rain_blood_3"),
+    };
+
+    public static final @NotNull IconCoordinate ICON_ACID_POPPING = TextureRegistry.getTexture("minecraft:particle/acid_boiling_pop");
+    public static final @NotNull IconCoordinate ICON_ACID_BOILING = TextureRegistry.getTexture("minecraft:particle/acid_boiling");
+    public static final @NotNull IconCoordinate ICON_BUBBLE = TextureRegistry.getTexture("minecraft:particle/bubble");
+    public static final @NotNull IconCoordinate ICON_BUBBLE_LAVA = TextureRegistry.getTexture("minecraft:particle/bubble_lava");
+    public static final @NotNull IconCoordinate ICON_BUBBLE_BOILING = TextureRegistry.getTexture("minecraft:particle/bubble_boiling");
+    public static final @NotNull IconCoordinate ICON_BUBBLE_BOILING_POP = TextureRegistry.getTexture("minecraft:particle/bubble_boiling_pop");
+
+    public static final @NotNull IconCoordinate ICON_FIREFLY = TextureRegistry.getTexture("minecraft:particle/firefly");
+    public static final @NotNull IconCoordinate ICON_HEART = TextureRegistry.getTexture("minecraft:particle/heart");
+    public static final @NotNull IconCoordinate ICON_NOTE = TextureRegistry.getTexture("minecraft:particle/note");
+}
diff --git a/game/client/src/main/java/net/minecraft/client/render/particle/ParticleVentSmoke.java b/game/client/src/main/java/net/minecraft/client/render/particle/ParticleVentSmoke.java
index 5d0b75f4d..7f4b9229e 100644
--- a/game/client/src/main/java/net/minecraft/client/render/particle/ParticleVentSmoke.java
+++ b/game/client/src/main/java/net/minecraft/client/render/particle/ParticleVentSmoke.java
@@ -1,7 +1,7 @@
 package net.minecraft.client.render.particle;
 
 import net.minecraft.client.render.tessellator.TessellatorParticle;
-import net.minecraft.client.render.texture.stitcher.TextureRegistry;
+import net.minecraft.core.util.helper.MathHelper;
 import net.minecraft.core.world.World;
 import org.jetbrains.annotations.NotNull;
 
@@ -44,20 +44,24 @@ public class ParticleVentSmoke extends Particle {
 
     @Override
     public void tick() {
+        int cX = MathHelper.floor(this.x);
+        int cY = MathHelper.floor(this.y);
+        int cZ = MathHelper.floor(this.z);
+
+        if (cX != this.bX || cY != this.bY || cZ != this.bZ) {
+            this.bX = cX;
+            this.bY = cY;
+            this.bZ = cZ;
+            this.cachedLightmapCoord = calcLightIndex(1f);
+        }
+        
         this.xo = this.x;
         this.yo = this.y;
         this.zo = this.z;
         if (this.age++ >= this.lifetime) {
             this.remove();
         }
-        int val = (14 - ((this.age * 14) / this.lifetime));
-        if(val > 7)
-            val = 7;
-        if (val >= 0) {
-            this.tex = TextureRegistry.getTexture("minecraft:particle/puff_" + val);
-        } else {
-            this.tex = null;
-        }
+        this.tex = ParticleTextureCache.getPuffStage(this.age, this.lifetime);
         this.yd += 0.004D;
         this.move(this.xd, this.yd, this.zd);
         if (this.y == this.yo) {
diff --git a/game/client/src/main/java/net/minecraft/client/render/particle/ParticleWaterDrop.java b/game/client/src/main/java/net/minecraft/client/render/particle/ParticleWaterDrop.java
index 7303d06d1..7120f132e 100644
--- a/game/client/src/main/java/net/minecraft/client/render/particle/ParticleWaterDrop.java
+++ b/game/client/src/main/java/net/minecraft/client/render/particle/ParticleWaterDrop.java
@@ -1,6 +1,5 @@
 package net.minecraft.client.render.particle;
 
-import net.minecraft.client.render.texture.stitcher.TextureRegistry;
 import net.minecraft.core.block.BlockLogicFluid;
 import net.minecraft.core.block.material.Material;
 import net.minecraft.core.util.helper.MathHelper;
@@ -18,13 +17,14 @@ public class ParticleWaterDrop extends Particle {
         this.rCol = 1.0F;
         this.gCol = 1.0F;
         this.bCol = 1.0F;
-        this.tex = TextureRegistry.getTexture((dimension == Dimension.NETHER ? "minecraft:particle/splash_rain_blood_" : "minecraft:particle/splash_rain_") + random.nextInt(4));
+        this.tex = (dimension == Dimension.NETHER ? ParticleTextureCache.ICON_SPLASH_RAIN_BLOOD : ParticleTextureCache.ICON_SPLASH_RAIN)[random.nextInt(4)];
         this.gravity = 0.06F;
         this.lifetime = (int) (8D / (Math.random() * 0.8D + 0.2D));
     }
 
     @Override
     public void tick() {
+        class Cache {private static final @NotNull TilePos queryPos = new TilePos(); }
         this.xo = this.x;
         this.yo = this.y;
         this.zo = this.z;
@@ -43,10 +43,10 @@ public class ParticleWaterDrop extends Particle {
             this.xd *= 0.7D;
             this.zd *= 0.7D;
         }
-        TilePos tilePos = new TilePos(this.x, this.y, this.z);
-        Material m = this.world.getBlockMaterial(tilePos);
+        Cache.queryPos.set(this.x, this.y, this.z);
+        Material m = this.world.getBlockMaterial(Cache.queryPos);
         if (m.isLiquid() || m.isSolid()) {
-            double y0 = (MathHelper.floor(this.y) + 1) - BlockLogicFluid.getHeight(this.world.getBlockData(tilePos));
+            double y0 = (MathHelper.floor(this.y) + 1) - BlockLogicFluid.getHeight(this.world.getBlockData(Cache.queryPos));
             if (this.y < y0) {
                 remove();
             }
diff --git a/game/client/src/main/java/net/minecraft/client/render/terrain/ChunkRendererMultiDraw.java b/game/client/src/main/java/net/minecraft/client/render/terrain/ChunkRendererMultiDraw.java
index b9f29d5ec..c3eb22959 100644
--- a/game/client/src/main/java/net/minecraft/client/render/terrain/ChunkRendererMultiDraw.java
+++ b/game/client/src/main/java/net/minecraft/client/render/terrain/ChunkRendererMultiDraw.java
@@ -16,7 +16,6 @@ import net.minecraft.core.world.World;
 import net.minecraft.core.world.WorldSource;
 import net.minecraft.core.world.chunk.Chunk;
 import net.minecraft.core.world.chunk.ChunkCache;
-import net.minecraft.core.world.particle.ParticleEmitterRange;
 import net.minecraft.core.world.pos.TilePos;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
@@ -83,14 +82,6 @@ public class ChunkRendererMultiDraw extends ChunkRenderer {
                     for (pos.x = this.min.x; pos.x < this.max.x; pos.x++) {
                         final @NotNull Block<?> block = chunkCache.getBlockType(pos);
 
-                        if (renderPass == 0) {
-                            ParticleEmitterRange type = block.getLogic().getParticleEmitterRange();
-
-                            if (type != null) {
-                                this.getWorld().particleEmitters.add(new TilePos(pos), type);
-                            }
-                        }
-
                         if (!hasStartedDrawing) {
                             hasStartedDrawing = true;
                             TESSELLATOR.startDrawingQuads();
diff --git a/game/client/src/main/java/net/minecraft/client/util/debug/OpenGLLoggerCallback.java b/game/client/src/main/java/net/minecraft/client/util/debug/OpenGLLoggerCallback.java
index 305026db1..dff1ec2ba 100644
--- a/game/client/src/main/java/net/minecraft/client/util/debug/OpenGLLoggerCallback.java
+++ b/game/client/src/main/java/net/minecraft/client/util/debug/OpenGLLoggerCallback.java
@@ -27,7 +27,7 @@ public class OpenGLLoggerCallback implements GLDebugMessageCallbackI {
                     getSourceName(source),
                     getTypeName(type),
                     getIdName(id),
-                    msg, new RuntimeException()
+                    msg, new RuntimeException("")
                 );
                 break;
             }
@@ -37,7 +37,7 @@ public class OpenGLLoggerCallback implements GLDebugMessageCallbackI {
                     getSourceName(source),
                     getTypeName(type),
                     getIdName(id),
-                    msg, new RuntimeException()
+                    msg, new RuntimeException("")
                 );
                 break;
             }
@@ -47,7 +47,7 @@ public class OpenGLLoggerCallback implements GLDebugMessageCallbackI {
                     getSourceName(source),
                     getTypeName(type),
                     getIdName(id),
-                    msg, new RuntimeException()
+                    msg, new RuntimeException("")
                 );
                 break;
             }
diff --git a/game/client/src/main/java/net/minecraft/client/world/WorldClient.java b/game/client/src/main/java/net/minecraft/client/world/WorldClient.java
index 59e45954c..d11407054 100644
--- a/game/client/src/main/java/net/minecraft/client/world/WorldClient.java
+++ b/game/client/src/main/java/net/minecraft/client/world/WorldClient.java
@@ -8,10 +8,12 @@ import net.minecraft.client.render.worldtype.WorldTypeFXDispatcher;
 import net.minecraft.client.world.chunk.provider.ChunkProviderDynamic;
 import net.minecraft.client.world.chunk.provider.ChunkProviderStatic;
 import net.minecraft.client.world.chunk.provider.ChunkProviderThreadedClient;
+import net.minecraft.core.block.entity.TileEntity;
 import net.minecraft.core.entity.player.Player;
 import net.minecraft.core.util.helper.MathHelper;
 import net.minecraft.core.world.Dimension;
 import net.minecraft.core.world.World;
+import net.minecraft.core.world.chunk.Chunk;
 import net.minecraft.core.world.chunk.ChunkLoader;
 import net.minecraft.core.world.chunk.provider.ChunkProvider;
 import net.minecraft.core.world.pos.TilePos;
@@ -48,6 +50,27 @@ public class WorldClient extends World {
         this.mc = parent.mc;
     }
 
+    @Override
+    protected void updateTileEntities() {
+        final var particleRenderDistance = GameSettings.PARTICLE_RENDER_DISTANCE.value * 16;
+        for (int i = this.tileEntityList.size() - 1; i >= 0; i--) {
+            final TileEntity tileEntity = this.tileEntityList.get(i);
+            if (!tileEntity.isInvalid() && this.isBlockLoaded(tileEntity.tilePos)) {
+                tileEntity.tick();
+                if (tileEntity instanceof TileEntity.AnimationTicking animated) {
+                    final double dx = tileEntity.tilePos.x - this.mc.thePlayer.x;
+                    final double dy = tileEntity.tilePos.y - this.mc.thePlayer.y;
+                    final double dz = tileEntity.tilePos.z - this.mc.thePlayer.z;
+                    animated.animationTick(particleRenderDistance, dx * dx + dy * dy + dz * dz);
+                }
+            }
+            if (tileEntity.isInvalid()) {
+                this.tileEntityList.remove(i);
+                this.getChunk(tileEntity.tilePos).removeTileEntity(tileEntity);
+            }
+        }
+    }
+
     @Override
     public void setLightningFlicker(final int lightningFlicker) {
         this.lightningFlicker = lightningFlicker;
diff --git a/game/client/src/main/java/net/minecraft/client/world/chunk/provider/ChunkProviderClient.java b/game/client/src/main/java/net/minecraft/client/world/chunk/provider/ChunkProviderClient.java
index f18cc4cca..d2f6ae367 100644
--- a/game/client/src/main/java/net/minecraft/client/world/chunk/provider/ChunkProviderClient.java
+++ b/game/client/src/main/java/net/minecraft/client/world/chunk/provider/ChunkProviderClient.java
@@ -14,15 +14,13 @@ import org.jetbrains.annotations.Nullable;
 import java.util.*;
 
 public class ChunkProviderClient implements ChunkProvider {
-    protected static final @NotNull ChunkPos MIN_POS = new ChunkPos(Integer.MIN_VALUE, Integer.MIN_VALUE);
-
     private final @NotNull Chunk blankChunk;
     private final @NotNull Map<ChunkPos, Chunk> chunkMapping = new Object2ObjectOpenHashMap<>();
     private final @NotNull World world;
 
 
     protected @NotNull Chunk lastQueriedChunk;
-    protected @NotNull ChunkPos lastQueriedChunkPos = MIN_POS;
+    protected @NotNull ChunkPos lastQueriedChunkPos = new ChunkPos(Integer.MIN_VALUE, Integer.MIN_VALUE);
 
     public ChunkProviderClient(@NotNull World world) {
         this.blankChunk = new EmptyChunk(world, new ChunkPos(0, 0));
@@ -51,6 +49,10 @@ public class ChunkProviderClient implements ChunkProvider {
             chunk.onUnload();
         }
         this.chunkMapping.remove(chunkPos);
+        if (chunkPos.equals(this.lastQueriedChunkPos)) {
+            this.lastQueriedChunkPos.set(Integer.MIN_VALUE, Integer.MIN_VALUE);
+            this.lastQueriedChunk = this.blankChunk;
+        }
     }
 
     @Override
diff --git a/game/client/src/main/java/org/useless/dragonfly/data/entity/mojang/EntityGeometryMojangData.java b/game/client/src/main/java/org/useless/dragonfly/data/entity/mojang/EntityGeometryMojangData.java
index fbb8b25d3..cae9dc94a 100644
--- a/game/client/src/main/java/org/useless/dragonfly/data/entity/mojang/EntityGeometryMojangData.java
+++ b/game/client/src/main/java/org/useless/dragonfly/data/entity/mojang/EntityGeometryMojangData.java
@@ -410,6 +410,7 @@ public class EntityGeometryMojangData implements EntityModelData {
                     }
                 }
             }
+
         }
     }
 
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/rubyglass/conduit/conduit_cap.json b/game/client/src/main/resources/assets/minecraft/models/block/rubyglass/conduit/conduit_cap.json
new file mode 100644
index 000000000..04f04c2a6
--- /dev/null
+++ b/game/client/src/main/resources/assets/minecraft/models/block/rubyglass/conduit/conduit_cap.json
@@ -0,0 +1,40 @@
+{
+  "format_version": "1.21.6",
+  "credit": "Made with Blockbench",
+  "textures": {
+    "closed": "minecraft:block/rubyglass/conduit/closed/closed",
+    "closed_overlay": "minecraft:block/rubyglass/conduit/closed/closed_overlay",
+    "open": "minecraft:block/rubyglass/conduit/open/open",
+    "open_overlay": "minecraft:block/rubyglass/conduit/open/open_overlay",
+    "plug": "minecraft:block/rubyglass/conduit/plug/plug",
+    "plug_overlay": "minecraft:block/rubyglass/conduit/plug/plug_overlay",
+    "particle": "minecraft:block/rubyglass/conduit/closed/closed"
+  },
+  "elements": [
+    {
+      "name": "Conduit Casing",
+      "from": [0, 0, 0],
+      "to": [16, 16, 16],
+      "faces": {
+        "north": {"uv": [0, 0, 16, 16], "rotation": 270, "texture": "#plug", "cullface": "north"},
+        "east": {"uv": [0, 0, 16, 16], "rotation": 270, "texture": "#plug", "cullface": "east"},
+        "south": {"uv": [0, 0, 16, 16], "rotation": 270, "texture": "#plug", "cullface": "south"},
+        "west": {"uv": [0, 0, 16, 16], "rotation": 270, "texture": "#plug", "cullface": "west"},
+        "up": {"uv": [0, 0, 16, 16], "texture": "#closed", "cullface": "up"},
+        "down": {"uv": [0, 0, 16, 16], "texture": "#open", "cullface": "down"}
+      }
+    },
+    {
+      "name": "Redstone Overlay",
+      "from": [0, 0, 0],
+      "to": [16, 16, 16],
+      "faces": {
+        "north": {"uv": [0, 0, 16, 16], "rotation": 270, "texture": "#plug_overlay", "cullface": "north", "tintindex": 0, "shade": false},
+        "east": {"uv": [0, 0, 16, 16], "rotation": 270, "texture": "#plug_overlay", "cullface": "east", "tintindex": 0, "shade": false},
+        "south": {"uv": [0, 0, 16, 16], "rotation": 270, "texture": "#plug_overlay", "cullface": "south", "tintindex": 0, "shade": false},
+        "west": {"uv": [0, 0, 16, 16], "rotation": 270, "texture": "#plug_overlay", "cullface": "west", "tintindex": 0, "shade": false},
+        "down": {"uv": [0, 0, 16, 16], "texture": "#open_overlay", "cullface": "down", "tintindex": 0, "shade": false}
+      }
+    }
+  ]
+}
\ No newline at end of file
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/rubyglass/conduit/conduit_cap_off.json b/game/client/src/main/resources/assets/minecraft/models/block/rubyglass/conduit/conduit_cap_off.json
new file mode 100644
index 000000000..c823c86d8
--- /dev/null
+++ b/game/client/src/main/resources/assets/minecraft/models/block/rubyglass/conduit/conduit_cap_off.json
@@ -0,0 +1,27 @@
+{
+  "format_version": "1.21.6",
+  "credit": "Made with Blockbench",
+  "textures": {
+    "closed": "minecraft:block/rubyglass/conduit/closed/closed",
+    "closed_overlay": "minecraft:block/rubyglass/conduit/closed/closed_overlay",
+    "open": "minecraft:block/rubyglass/conduit/open/open",
+    "open_overlay": "minecraft:block/rubyglass/conduit/open/open_overlay",
+    "plug": "minecraft:block/rubyglass/conduit/plug/plug",
+    "particle": "minecraft:block/rubyglass/conduit/closed/closed"
+  },
+  "elements": [
+    {
+      "name": "Conduit Casing",
+      "from": [0, 0, 0],
+      "to": [16, 16, 16],
+      "faces": {
+        "north": {"uv": [0, 0, 16, 16], "rotation": 270, "texture": "#plug", "cullface": "north"},
+        "east": {"uv": [0, 0, 16, 16], "rotation": 270, "texture": "#plug", "cullface": "east"},
+        "south": {"uv": [0, 0, 16, 16], "rotation": 270, "texture": "#plug", "cullface": "south"},
+        "west": {"uv": [0, 0, 16, 16], "rotation": 270, "texture": "#plug", "cullface": "west"},
+        "up": {"uv": [0, 0, 16, 16], "texture": "#closed", "cullface": "up"},
+        "down": {"uv": [0, 0, 16, 16], "texture": "#open", "cullface": "down"}
+      }
+    }
+  ]
+}
\ No newline at end of file
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/workbench.json b/game/client/src/main/resources/assets/minecraft/models/block/workbench.json
index 4bf928508..8ee85a0af 100644
--- a/game/client/src/main/resources/assets/minecraft/models/block/workbench.json
+++ b/game/client/src/main/resources/assets/minecraft/models/block/workbench.json
@@ -3,9 +3,9 @@
   "textures": {
     "top": "minecraft:block/workbench/top",
     "bottom": "minecraft:block/workbench/bottom",
-    "north": "minecraft:block/workbench/front",
-    "south": "minecraft:block/workbench/front",
-    "west": "minecraft:block/workbench/side",
-    "east": "minecraft:block/workbench/side"
+    "north": "minecraft:block/workbench/side",
+    "south": "minecraft:block/workbench/side",
+    "west": "minecraft:block/workbench/front",
+    "east": "minecraft:block/workbench/front"
   }
 }
\ No newline at end of file
diff --git a/game/client/src/main/resources/assets/minecraft/models/entity/pig/pig.animation.json b/game/client/src/main/resources/assets/minecraft/models/entity/pig/pig.animation.json
new file mode 100644
index 000000000..40f84bd3c
--- /dev/null
+++ b/game/client/src/main/resources/assets/minecraft/models/entity/pig/pig.animation.json
@@ -0,0 +1,25 @@
+{
+	"format_version": "1.8.0",
+	"animations": {
+		"animation.pigsitting": {
+			"loop": true,
+			"bones": {
+				"body": {
+					"rotation": [-27.5, 0, 0],
+					"position": [0, -4, 1]
+				},
+				"head": {
+					"position": [0, 0, 1]
+				},
+				"leg0": {
+					"rotation": [-90, 15, 0],
+					"position": [-1, -4, 0]
+				},
+				"leg1": {
+					"rotation": [-90, -15, 0],
+					"position": [1, -4, 0]
+				}
+			}
+		}
+	}
+}
diff --git a/game/client/src/main/resources/credits.txt b/game/client/src/main/resources/credits.txt
index f8d77498e..78a297d84 100644
--- a/game/client/src/main/resources/credits.txt
+++ b/game/client/src/main/resources/credits.txt
@@ -8,7 +8,7 @@ Bigjango
 Flamarine
 Kirisamé Soup (@kirisoup)
 Martin (@sunsetsatellite)
-MelonMojito (@watermelonmojito)
+MelonMojito (@melon_mojito)
 NebulaBC
 Useless (@uselessbullets)
 
@@ -72,6 +72,8 @@ LukeisStuff
 --Retired BTA helper
 --Textures
 --Nether update work
+Lux64
+--Conduit bit packing ideas
 mzov4j
 --Log top texture adjustments
 --Bone shale texture
diff --git a/game/core/src/main/java/net/minecraft/core/achievement/stat/StatsCounter.java b/game/core/src/main/java/net/minecraft/core/achievement/stat/StatsCounter.java
index 2f903e673..d7ec1a765 100644
--- a/game/core/src/main/java/net/minecraft/core/achievement/stat/StatsCounter.java
+++ b/game/core/src/main/java/net/minecraft/core/achievement/stat/StatsCounter.java
@@ -115,7 +115,7 @@ public class StatsCounter {
 	}
 
 	public boolean canUnlock(Achievement achievement) {
-		return achievement.parent == null || isUnlocked(achievement.parent);
+		return achievement.parent == null || isUnlocked(achievement.parent) || isUnlocked(achievement);
 	}
 
 	public int readStat(@Nullable Stat stat) {
diff --git a/game/core/src/main/java/net/minecraft/core/block/Block.java b/game/core/src/main/java/net/minecraft/core/block/Block.java
index cd6a1ac78..3880df35b 100644
--- a/game/core/src/main/java/net/minecraft/core/block/Block.java
+++ b/game/core/src/main/java/net/minecraft/core/block/Block.java
@@ -589,8 +589,8 @@ public final class Block<T extends BlockLogic>
     }
 
     @Override
-    public boolean isEquivalent(final @NotNull World world, final @NotNull TilePosc thisPos, final @NotNull TilePosc thatPos) {
-        return this.logic.isEquivalent(world, thisPos, thatPos);
+    public boolean isEquivalent(final @NotNull WorldSource world, final @NotNull TilePosc thisPos, final @NotNull TilePosc thatPos, final @NotNull Block<?> thatBlock) {
+        return this.logic.isEquivalent(world, thisPos, thatPos, thatBlock);
     }
 
     @Override
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockInterface.java b/game/core/src/main/java/net/minecraft/core/block/BlockInterface.java
index 241264472..a259c11c0 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockInterface.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockInterface.java
@@ -390,7 +390,11 @@ interface BlockInterface {
         return this.isClimbable(world, new TilePos(x, y, z));
     }
 
-    boolean isEquivalent(final @NotNull World world, final @NotNull TilePosc thisPos, final @NotNull TilePosc thatPos);
+    boolean isEquivalent(final @NotNull WorldSource world, final @NotNull TilePosc thisPos, final @NotNull TilePosc thatPos, final @NotNull Block<?> thatBlock);
+    
+    default boolean isEquivalent(final @NotNull WorldSource world, final @NotNull TilePosc thisPos, final @NotNull TilePosc thatPos) {
+        return this.isEquivalent(world, thisPos, thatPos, world.getBlockType(thatPos));
+    }
 
     @Deprecated
     default boolean isEquivalent(final @NotNull World world, final int thisX, final int thisY, final int thisZ, final int thatX, final int thatY, final int thatZ) {
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 668fdc792..e46945dd1 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
@@ -28,7 +28,6 @@ import net.minecraft.core.util.phys.BoundingVolume;
 import net.minecraft.core.util.phys.HitResult;
 import net.minecraft.core.world.World;
 import net.minecraft.core.world.WorldSource;
-import net.minecraft.core.world.particle.ParticleEmitterRange;
 import net.minecraft.core.world.pos.TilePosc;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
@@ -211,18 +210,12 @@ public class BlockLogic implements BlockInterface, IItemConvertible {
 
     @Override
     public void onPlacedByWorld(final @NotNull World world, final @NotNull TilePosc tilePos) {
-        ParticleEmitterRange t = this.getParticleEmitterRange();
-        if (t != null) {
-            world.particleEmitters.add(tilePos, t);
-        }
+
     }
 
     @Override
     public void onRemoved(final @NotNull World world, final @NotNull TilePosc tilePos, final int data) {
-        ParticleEmitterRange t = this.getParticleEmitterRange();
-        if (t != null) {
-            world.particleEmitters.remove(tilePos);
-        }
+
     }
 
     @Override
@@ -244,11 +237,6 @@ public class BlockLogic implements BlockInterface, IItemConvertible {
     @Override
     public void onPlacedByMob(final @NotNull World world, final @NotNull TilePosc tilePos, final @NotNull Side side, final @NotNull Mob mob, final double xHit, final double yHit) {
         this.onPlacedOnSide(world, tilePos, side, xHit, yHit);
-
-        ParticleEmitterRange t = this.getParticleEmitterRange();
-        if (t != null) {
-            world.particleEmitters.add(tilePos, t);
-        }
     }
 
     @Override
@@ -514,54 +502,84 @@ public class BlockLogic implements BlockInterface, IItemConvertible {
         return false;
     }
 
-    /**
-     * Metadata compared by the matcher. Override to ignore orientation while keeping visual state (e.g. powered).
-     */
-    protected int getMatcherEquivalenceData(final int data) {
-        return data;
+    @Override
+    public boolean isEquivalent(final @NotNull WorldSource world, final @NotNull TilePosc thisPos, final @NotNull TilePosc thatPos, final @NotNull Block<?> thatBlock) {
+        return
+            this.isEquivalentBlockForMatcher(thatBlock) &&
+            this.isEquivalentDataForMatcher(world, thisPos, thatPos) &&
+            this.isEquivalentEntityForMatcher(world, thisPos, thatPos);
     }
 
-    /**
-     * 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;
+    // prevents accidental mis-impl
+    @Override
+    public final boolean isEquivalent(final @NotNull WorldSource world, final @NotNull TilePosc thisPos, final @NotNull TilePosc thatPos) {
+        return BlockInterface.super.isEquivalent(world, thisPos, thatPos);
     }
 
-    public record MatcherSnapshot(@NotNull Block<?> block, int meta, @NotNull BlockLogic logic) {}
-
-    /**
-     * 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());
+    protected boolean isEquivalentBlockForMatcher(final @NotNull Block<?> other) {
+        return this.block == other;
     }
 
-    public static @NotNull MatcherSnapshot resolveMatcherBlock(final @NotNull World world, final @NotNull TilePosc tilePos) {
-        return world.getBlockType(tilePos).getLogic().resolveForMatcher(world, tilePos);
+    protected final boolean isEquivalentDataForMatcher(
+        @NotNull WorldSource world, @NotNull TilePosc thisPos, @NotNull TilePosc thatPos
+    ) {
+        return this.matcherDataEquivImpl == null || this.matcherDataEquivImpl.isEquivalentDataForMatcherImpl(
+            world.getBlockData(thisPos),
+            world.getBlockData(thatPos)
+        );
     }
 
-    public static boolean areBlocksEquivalentForMatcher(
-        final @NotNull World world,
-        final @NotNull TilePosc posA,
-        final @NotNull TilePosc posB
+    protected final boolean isEquivalentEntityForMatcher(
+        @NotNull WorldSource world, @NotNull TilePosc thisPos, @NotNull TilePosc thatPos
     ) {
-        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;
+        if (this.matcherEntityEquivImpl == null) return true;
+
+        final TileEntity a, b;
+        if (this.matcherEntityEquivImpl.isEntityRequiredForMatching()) {
+            a = world.getTileEntity(thisPos);
+            if (a == null) return false;
+
+            final Class<?> entityClass = this.matcherEntityEquivImpl.getEntityEquivalencyClass();
+            if (!entityClass.isInstance(a)) return false;
+
+            b = world.getTileEntity(thatPos);
+            if (b == null || !entityClass.isInstance(b)) return false;
+        }
+        else {
+            a = world.getTileEntity(thisPos);
+            b = world.getTileEntity(thatPos);
         }
-        return a.logic().getMatcherEquivalenceData(a.meta()) == b.logic().getMatcherEquivalenceData(b.meta());
+        return this.matcherEntityEquivImpl.isEquivalentEntityForMatcherImpl(a, b);
     }
 
-    @Override
-    public boolean isEquivalent(final @NotNull World world, final @NotNull TilePosc thisPos, final @NotNull TilePosc thatPos) {
-        return areBlocksEquivalentForMatcher(world, thisPos, thatPos);
+    private final @Nullable MatcherDataEquivalency matcherDataEquivImpl =
+        (this instanceof MatcherDataEquivalency impl) ? impl : null;
+
+    public interface MatcherDataEquivalency {
+        default boolean isEquivalentDataForMatcherImpl(final int thisData, final int thatData) {
+            return thisData == thatData;
+        }
+
+        public interface Masked extends MatcherDataEquivalency {
+            int getMatcherDataEquivalencyMask();
+
+            @Override
+            default boolean isEquivalentDataForMatcherImpl(int thisData, int thatData) {
+                final int mask = getMatcherDataEquivalencyMask();
+                return (thisData & mask) == (thatData & mask);
+            }
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    private final @Nullable MatcherEntityEquivalency<TileEntity> matcherEntityEquivImpl =
+        (this instanceof MatcherEntityEquivalency impl) ? (MatcherEntityEquivalency<TileEntity>)impl : null;
+
+    public interface MatcherEntityEquivalency<TE extends TileEntity> {
+        default boolean isEntityRequiredForMatching() { return true; }
+        @NotNull Class<TE> getEntityEquivalencyClass();
+
+        boolean isEquivalentEntityForMatcherImpl(final TE thisEntity, final TE thatEntity);
     }
 
     @Override
@@ -578,8 +596,4 @@ public class BlockLogic implements BlockInterface, IItemConvertible {
     public @NotNull ItemStack getDefaultStack() {
         return this.block.getDefaultStack();
     }
-
-    public @Nullable ParticleEmitterRange getParticleEmitterRange() {
-        return null; // null by default, 16 block radius
-    }
 }
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicActivator.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicActivator.java
index c82bba4ed..9779a578f 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
@@ -24,12 +24,14 @@ import java.util.Random;
 import static net.minecraft.core.block.BlockLogicConduit.packEventConduitData;
 
 public class BlockLogicActivator
-    extends BlockLogicVeryRotatable {
+    extends BlockLogicVeryRotatable
+    implements BlockLogic.MatcherDataEquivalency.Masked
+{
     public static final int MASK_POWERED = 0b0000_1000;
 
     @Override
-    protected int getMatcherEquivalenceData(final int data) {
-        return data & MASK_POWERED;
+    public int getMatcherDataEquivalencyMask() {
+        return MASK_POWERED;
     }
 
     public BlockLogicActivator(final @NotNull Block<?> block) {
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicAir.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicAir.java
index f381b4741..f8fe18d53 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicAir.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicAir.java
@@ -121,9 +121,4 @@ public class BlockLogicAir
     public int getPistonPushReaction(@NotNull World world, @NotNull TilePosc tilePos) {
         return Material.PISTON_DESTROY_ON_PUSH;
     }
-
-    @Override
-    public boolean isEquivalent(@NotNull World world, @NotNull TilePosc thisPos, @NotNull TilePosc thatPos) {
-        return false;
-    }
 }
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 6c4214482..ec2265d18 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,11 +17,6 @@ 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();
@@ -47,5 +42,4 @@ public class BlockLogicAxisAligned
         if (meta == 1) return Axis.Z;
         return Axis.Y;
     }
-
 }
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicBasket.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicBasket.java
index 980dada5f..121397c0a 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicBasket.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicBasket.java
@@ -26,7 +26,10 @@ import org.jetbrains.annotations.Nullable;
 
 import java.util.Objects;
 
-public class BlockLogicBasket extends BlockLogic {
+public class BlockLogicBasket
+extends BlockLogic
+implements BlockLogic.MatcherEntityEquivalency<TileEntityBasket>
+{
     protected BlockLogicBasket(final @NotNull Block<?> block, final @NotNull Material material) {
         super(block, material);
         block.withEntity(TileEntityBasket::new);
@@ -86,12 +89,14 @@ public class BlockLogicBasket extends BlockLogic {
     }
 
     @Override
-    public boolean isEquivalent(@NotNull World world, @NotNull TilePosc thisPos, @NotNull TilePosc thatPos) {
-        assert world.getBlockType(thisPos) == this.block;
-        if (world.getBlockType(thatPos) != this.block) return false;
-        if (!(world.getTileEntity(thisPos) instanceof TileEntityBasket thisBasket) ||
-            !(world.getTileEntity(thatPos) instanceof TileEntityBasket thatBasket))
-            return false;
+    public @NotNull Class<TileEntityBasket> getEntityEquivalencyClass() {
+        return TileEntityBasket.class;
+    }
+
+    @Override
+    public boolean isEquivalentEntityForMatcherImpl(
+        @NotNull TileEntityBasket thisBasket, @NotNull TileEntityBasket thatBasket
+    ) {
         return getFillLevel(thisBasket) == getFillLevel(thatBasket);
     }
 
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 4289fa091..45c1bdaea 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
@@ -25,7 +25,8 @@ import java.util.Objects;
 
 public class BlockLogicBed
     extends BlockLogic
-    implements ISupportable {
+    implements ISupportable, BlockLogic.MatcherDataEquivalency.Masked
+{
 
     public BlockLogicBed(final @NotNull Block<?> block) {
         super(block, Materials.WOOD);
@@ -33,8 +34,8 @@ public class BlockLogicBed
     }
 
     @Override
-    protected int getMatcherEquivalenceData(final int data) {
-        return data & ~(DIRECTION.mask | IS_OCCUPIED.mask);
+    public int getMatcherDataEquivalencyMask() {
+        return ~(DIRECTION.mask | IS_OCCUPIED.mask);
     }
 
     @Override
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 63d149c3f..ae0603624 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
@@ -26,7 +26,7 @@ import java.util.Random;
 
 public class BlockLogicButton
     extends BlockLogic
-    implements IPaintable, ISupportable {
+    implements IPaintable, ISupportable, BlockLogic.MatcherDataEquivalency.Masked  {
     public static final int MASK_POWERED = 0b0000_1000;
     public static final int MASK_DIRECTION = 0b0000_0111;
     public static final int SIDE_WEST = 1;
@@ -42,8 +42,8 @@ public class BlockLogicButton
     }
 
     @Override
-    protected int getMatcherEquivalenceData(final int data) {
-        return data & MASK_POWERED;
+    public int getMatcherDataEquivalencyMask() {
+        return ~MASK_DIRECTION;
     }
 
     @Override
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicCactus.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicCactus.java
index 0c8daee2f..cbd627b8b 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicCactus.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicCactus.java
@@ -144,9 +144,4 @@ public class BlockLogicCactus
         }
         return true;
     }
-
-    @Override
-    protected int getMatcherEquivalenceData(int data) {
-        return 0;
-    }
 }
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 8c9255995..a2205ce9b 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
@@ -19,18 +19,20 @@ import java.util.Objects;
 
 public class BlockLogicChest
     extends BlockLogic
-    implements IPaintable {
+    implements IPaintable, BlockLogic.MatcherDataEquivalency {
     public BlockLogicChest(final @NotNull Block<?> block, final @NotNull Material material) {
         super(block, material);
         block.withEntity(TileEntityChest::new);
     }
 
     @Override
-    protected int getMatcherEquivalenceData(int data) {
-        data &= ~directionBits;
+    public boolean isEquivalentDataForMatcherImpl(int thisData, int thatData) {
+        final int mask1 = ~(directionBits | typeBits);
+        if ((thisData & mask1) != (thatData & mask1)) return false;
+
         assert Type.SINGLE.ordinal() == 0;
-        if ((data & typeBits) != 0) data |= typeBits;
-        return data;
+        // would be easier if we use 0b01 and 0b11 instead of 0b01 and 0b10
+        return ((thisData & typeBits) == 0) == ((thatData & typeBits) == 0);
     }
 
     @Override
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 cb86b616d..ba134cfca 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
@@ -1,11 +1,13 @@
 package net.minecraft.core.block;
 
 import net.minecraft.core.block.material.Material;
+import net.minecraft.core.current.util.BlockState;
+import net.minecraft.core.block.piston.BlockLogicPistonBase;
+import net.minecraft.core.block.piston.BlockLogicPistonHead;
+import net.minecraft.core.block.piston.PistonCommon;
 import net.minecraft.core.entity.Entity;
 import net.minecraft.core.entity.Mob;
 import net.minecraft.core.entity.player.Player;
-import net.minecraft.core.item.ItemStack;
-import net.minecraft.core.item.Items;
 import net.minecraft.core.sound.SoundCategory;
 import net.minecraft.core.util.helper.Direction;
 import net.minecraft.core.util.helper.Side;
@@ -17,12 +19,13 @@ import net.minecraft.core.world.pos.TilePosc;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
-import java.lang.reflect.Array;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Random;
 
-public class BlockLogicConduit extends BlockLogic {
+public class BlockLogicConduit extends BlockLogic
+    implements BlockLogic.MatcherDataEquivalency
+{
 
     public static final Side[][] STATE_TABLE = {
             {},                                                   // 0
@@ -37,14 +40,51 @@ public class BlockLogicConduit extends BlockLogic {
             {Side.NORTH, Side.WEST},   {Side.NORTH, Side.EAST},   // 12-13
             {Side.SOUTH, Side.WEST},   {Side.SOUTH, Side.EAST}    // 14-15
     };
-
     private static final int PROPAGATION_LENGTH = 15;
     public static final int STATE_COUNT = 16;
-
     public static final int STATE_MASK = 0b1111;
     public static final int POWER_MASK = 0b1111;
     public static final int POWER_SHIFT = 4;
 
+    private static final Side[][] CAP_SIDES = {
+            {Side.BOTTOM}, // 0
+            {Side.TOP},    // 1
+            {Side.NORTH},  // 2
+            {Side.SOUTH},  // 3
+            {Side.WEST},   // 4
+            {Side.EAST}    // 5
+    };
+    public static final int CAP_POWERED = 0b1000;
+    public static final int CAP_SIDE_MASK = 0b0111;
+    public static final int INERT_MARKER = 0b1111;
+
+    // Connection priorities (higher takes priority).
+    private static int pc = -1;
+    private static final int CONNECT_NONE = pc++;
+    private static final int CONNECT_RAIL_POWERED = pc++;
+    private static final int CONNECT_NOTEBLOCK = pc++;
+    private static final int CONNECT_SPIKES = pc++;
+    private static final int CONNECT_DISPENSER = pc++;
+    private static final int CONNECT_PISTON = pc++;
+    private static final int CONNECT_ACTIVATOR = pc++;
+    private static final int CONNECT_BUTTON = pc++;
+    private static final int CONNECT_LEVER = pc++;
+    private static final int CONNECT_TORCH = pc++;
+    private static final int CONNECT_REDSTONE_LANTERN = pc++;
+    private static final int CONNECT_REDSTONE_BLOCK = pc++;
+    private static final int CONNECT_MOTION_SENSOR = pc++;
+    private static final int CONNECT_MATCHER = pc++;
+    private static final int CONNECT_TIMER = pc++;
+    private static final int CONNECT_REPEATER = pc++;
+    private static final int CONNECT_REDSTONE_DUST = pc++;
+    private static final int CONNECT_CONDUIT = pc++;
+
+    // Shape categories for matcher equivalence
+    private static final int SHAPE_CLOSED = 0;
+    private static final int SHAPE_CAPPED = 1;
+    private static final int SHAPE_STRAIGHT = 2;
+    private static final int SHAPE_CORNER = 3;
+
     public BlockLogicConduit(@NotNull Block<?> block, @NotNull Material material) {
         super(block, material);
     }
@@ -62,16 +102,10 @@ public class BlockLogicConduit extends BlockLogic {
 
     @Override
     public void animationTick(final @NotNull World world, final @NotNull TilePosc tilePos, final @NotNull Random rand) {
-        int data = world.getBlockData(tilePos);
+        int power = getPower(world, tilePos);
 
-        if (data == 15 << POWER_SHIFT) {
-            return;
-        }
-
-        int strength = (data >> POWER_SHIFT) & POWER_MASK;
-
-        if (strength > 0) {
-            world.playBlockEvent(tilePos, LevelListener.EVENT_CONDUIT_SURFACE_PARTICLES, packEventConduitData(Side.NONE, strength));
+        if (power > 0) {
+            world.playBlockEvent(tilePos, LevelListener.EVENT_CONDUIT_SURFACE_PARTICLES, packEventConduitData(null, power));
         }
     }
 
@@ -87,7 +121,7 @@ public class BlockLogicConduit extends BlockLogic {
 
     @Override
     public boolean suffocatesEntities(@NotNull WorldSource source, @NotNull TilePosc tilePos, @NotNull Class<? extends Entity> entityClass) {
-                return true;
+        return true;
     }
 
     @Override
@@ -96,14 +130,14 @@ public class BlockLogicConduit extends BlockLogic {
     @Override
     public void onNeighborChanged(final @NotNull World world, final @NotNull TilePosc cTilePos, final @NotNull Block<?> block) {
         if (world.isClientSide) return;
-        world.getConduitHandler().onConduitUpdated(cTilePos);
+        world.getWireHandler().onWireUpdated(cTilePos);
     }
 
     @Override
     public void onPlacedByWorld(@NotNull World world, @NotNull TilePosc tilePos) {
         super.onPlacedByWorld(world, tilePos);
         if (!world.isClientSide) {
-            world.getConduitHandler().onConduitAdded(tilePos);
+            world.getWireHandler().onWireAdded(tilePos);
         }
     }
 
@@ -111,37 +145,13 @@ public class BlockLogicConduit extends BlockLogic {
     public void onRemoved(@NotNull World world, @NotNull TilePosc tilePos, int data) {
         super.onRemoved(world, tilePos, data);
         if (!world.isClientSide) {
-            world.getConduitHandler().onConduitRemoved(tilePos, data);
+            world.getWireHandler().onWireRemoved(tilePos, new BlockState(this.block, data));
         }
     }
 
     // TODO: onMoved and onFlung
 
-    /**
-     * Defines the placement and routing behavior of the conduit when placed into the world by an entity.
-     * <p>
-     * This method handles three distinct placement modes:
-     * <ul>
-     * <li><b>Rotation Lock:</b> Absolute override. Forces the conduit to place as a straight pipe aligned
-     * with the player's active rotation lock direction.</li>
-     * <li><b>Sneaking (Manual Placement):</b> Bypasses auto-connection. If placed against a closed conduit port,
-     * it will force that port open and connect to it. Otherwise, defaults to routing a connection from the
-     * clicked block face toward the placing entity.</li>
-     * <li><b>Standard (Auto-connection):</b> Scans adjacent blocks for valid, open conduit ports to link up with.</li>
-     * </ul>
-     * <li><i>1 Valid Neighbor:</i> Routes from the neighbor toward the entity.</li>
-     * <li><i>2 Valid Neighbors:</i> Creates a straight or corner connection linking both neighbors.</li>
-     * <li><i>3+ Valid Neighbors:</i> Defaults to routing from the block placed against toward the entity.</li>
-     * </ul>
-     * </li>
-     * </ul>
-     * @param world          World the block is being placed in.
-     * @param cTilePos       The position where this new conduit is being placed.
-     * @param sidePlacedOnto The face of the existing block that the entity clicked against to place this block.
-     * @param mob            The mob placing the block.
-     * @param xHit           The X coordinate of the cursor hit on the block face.
-     * @param yHit           The Y coordinate of the cursor hit on the block face.
-     */
+    // Javadocs were out of date for placement and interaction and I can't be bothered to rewrite them
     @Override
     public void onPlacedByMob(final @NotNull World world, final @NotNull TilePosc cTilePos, final @NotNull Side sidePlacedOnto, final @NotNull Mob mob, final double xHit, final double yHit) {
         Side sideFacingPlayer = Direction.getLockable(mob).opposite().side();
@@ -149,84 +159,50 @@ public class BlockLogicConduit extends BlockLogic {
 
         int stateToPlaceAs = 0;
 
-        if (mob instanceof Player player) {
-            if (player.rotationLock != null) {
-                Side lockSide = player.rotationLock.side();
-                stateToPlaceAs = getStateFromTwoSides(lockSide, lockSide.opposite());
-            }
-        }
-
-        if (stateToPlaceAs == 0 && mob.isSneaking()) {
-            boolean openedClosedFace = false;
+        if (mob.isSneaking()) {
             TilePos clickedPos = new TilePos(cTilePos.x() + sideClickedAgainst.offsetX(), cTilePos.y() + sideClickedAgainst.offsetY(), cTilePos.z() + sideClickedAgainst.offsetZ());
 
             if (world.getBlockType(clickedPos) == this.block) {
                 int clickedData = world.getBlockData(clickedPos);
 
-                if (!isSideOpen(clickedData, sidePlacedOnto)) {
-                    int clickedState = clickedData & STATE_MASK;
-                    int clickedStrength = (clickedData >> POWER_SHIFT) & POWER_MASK;
-                    int newClickedState = getNextState(clickedState, sidePlacedOnto);
+                if (!isInert(clickedData) && !isSideOpen(clickedData, sidePlacedOnto)) {
+                    int newClickedData = getNextDataForFace(world, clickedPos, clickedData, sidePlacedOnto);
 
-                    if (newClickedState != clickedState) {
-                        world.setBlockDataNotify(clickedPos, (clickedStrength << POWER_SHIFT) | newClickedState);
+                    if (newClickedData != clickedData) {
+                        world.setBlockDataNotify(clickedPos, newClickedData);
                         this.onNeighborChanged(world, clickedPos, this.block);
-                        openedClosedFace = true;
                     }
 
                 }
             }
 
-            if (openedClosedFace) {
-                stateToPlaceAs = getStateFromTwoSides(sideClickedAgainst, sideClickedAgainst.opposite());
-            } else {
-                if (sideClickedAgainst != sideFacingPlayer && sideFacingPlayer != Side.NONE) {
-                    stateToPlaceAs = getStateFromTwoSides(sideClickedAgainst, sideFacingPlayer);
-                } else {
-                    stateToPlaceAs = getStateFromTwoSides(sideClickedAgainst, sideClickedAgainst.opposite());
-                }
-            }
+            stateToPlaceAs = connectTowardPlayer(sideClickedAgainst, sideFacingPlayer);
         }
-        else if (stateToPlaceAs == 0) {
-            List<Side> validSides = new ArrayList<>();
-
-            for (Side side : Side.values()) {
-                TilePos nTilePos = new TilePos(cTilePos.x() + side.offsetX(), cTilePos.y() + side.offsetY(), cTilePos.z() + side.offsetZ());
-
-                if (world.getBlockType(nTilePos) == this.block) {
-                    int nData = world.getBlockData(nTilePos);
-
-                    if (isSideOpen(nData, side.opposite())) {
-                        validSides.add(side);
-                    }
+        else {
+            final int[] priorities = new int[Side.sides.length];
+            List<Side> validNeighbors = new ArrayList<>();
+
+            for (Side side : Side.sides) {
+                int priority = connectionPriority(world, cTilePos, side);
+                priorities[side.id] = priority;
+                if (priority > CONNECT_NONE) {
+                    validNeighbors.add(side);
                 }
             }
 
-            int numOfValidSides = validSides.size();
-
-            if (numOfValidSides == 1) {
-                Side autoConnectedSide = validSides.get(0);
-
-                if (sideFacingPlayer != autoConnectedSide && sideFacingPlayer != Side.NONE) {
-                    stateToPlaceAs = getStateFromTwoSides(autoConnectedSide, sideFacingPlayer);
-                } else {
-                    stateToPlaceAs = getStateFromTwoSides(autoConnectedSide, autoConnectedSide.opposite());
-                }
-            } else if (numOfValidSides == 2) {
-                stateToPlaceAs = getStateFromTwoSides(validSides.get(0), validSides.get(1));
-            } else if (numOfValidSides > 2) {
-                TilePos nTilePos = new TilePos(cTilePos).add(sideClickedAgainst);
-
-                boolean isMatchingNeighbor = world.getBlockType(nTilePos) == this.block;
-                boolean isNeighborSideOpen = isSideOpen(world.getBlockData(nTilePos), sidePlacedOnto);
-
-                if (isMatchingNeighbor && isNeighborSideOpen) {
-                    if (sideFacingPlayer != sideClickedAgainst && sideFacingPlayer != Side.NONE) {
-                        stateToPlaceAs = getStateFromTwoSides(sideClickedAgainst, sideFacingPlayer);
-                    } else {
-                        stateToPlaceAs = getStateFromTwoSides(sideClickedAgainst, sideClickedAgainst.opposite());
+            if (validNeighbors.size() == 1) {
+                stateToPlaceAs = connectTowardPlayer(validNeighbors.get(0), sideFacingPlayer);
+            } else if (validNeighbors.size() >= 2) {
+                validNeighbors.sort((side1, side2) -> {
+                    if (priorities[side1.id] != priorities[side2.id]) {
+                        return Integer.compare(priorities[side2.id], priorities[side1.id]);
                     }
-                }
+                    int rank1 = tiebreakRank(side1, sideClickedAgainst, sideFacingPlayer);
+                    int rank2 = tiebreakRank(side2, sideClickedAgainst, sideFacingPlayer);
+                    if (rank1 != rank2) return Integer.compare(rank1, rank2);
+                    return Integer.compare(side1.id, side2.id);
+                });
+                stateToPlaceAs = getStateFromTwoSides(validNeighbors.get(0), validNeighbors.get(1));
             }
         }
 
@@ -237,30 +213,6 @@ public class BlockLogicConduit extends BlockLogic {
     }
 
 
-    /**
-     * Handles right-click interactions with an existing conduit in the world.
-     * <p>
-     * This method controls the manual toggling of conduit connection ports. The interaction succeeds
-     * and toggles the port state under the following conditions:
-     * <ul>
-     * <li>The player is holding nothing (empty hand).</li>
-     * <li>The player is holding another conduit, but clicks on a <b>closed</b> port.</li>
-     * </ul>
-     * <p>
-     * If the player is holding a different block/item, or holding a conduit and clicks an <b>open</b> port,
-     * this method yields the interaction to allow standard block placement or item usage.
-     * <p>
-     * Successful interactions will update the block state, trigger neighbor updates, play activation
-     * sound effects, and spawn visual port particles.
-     * @param world    The world containing the block.
-     * @param cTilePos The position of the conduit being interacted with.
-     * @param player   The player interacting with the block.
-     * @param side     The specific face of the conduit that was clicked (can be null if interacted via indirect means).
-     * @param xHit     The precise X coordinate of the cursor hit on the block face.
-     * @param yHit     The precise Y coordinate of the cursor hit on the block face.
-     * @return {@code true} if the interaction was consumed (port toggled); {@code false} if the interaction
-     * should pass through to default item/block placement logic.
-     */
     @Override
     public boolean onInteracted(final @NotNull World world, final @NotNull TilePosc cTilePos, final @NotNull Player player, final @Nullable Side side, final double xHit, final double yHit) {
         if(side == null) return false;
@@ -274,52 +226,63 @@ public class BlockLogicConduit extends BlockLogic {
 
         int cData = world.getBlockData(cTilePos);
         int cState = cData & STATE_MASK;
-        int cStrength = (cData >> POWER_SHIFT) & POWER_MASK;
 
-        if (holdingNothing && player.isSneaking() && cState == 0) {
-            if (cStrength == 0) {
-                world.setBlockDataNotify(cTilePos, (15 << POWER_SHIFT));
+        if (holdingNothing && player.isSneaking() && cState == 0 && !isCap(cData)) {
+            int inertValue = getUpperNibble(cData);
+            if (inertValue == 0) {
+                world.setBlockDataNotify(cTilePos, (INERT_MARKER << POWER_SHIFT));
                 world.playSoundEffect(player, SoundCategory.WORLD_SOUNDS, cTilePos.x(), cTilePos.y(), cTilePos.z(), "random.fizz", 0.5f, 2.6f + (world.rand.nextFloat() - world.rand.nextFloat()) * 0.8f);
                 world.playSoundEffect(player, SoundCategory.WORLD_SOUNDS, cTilePos.x(), cTilePos.y(), cTilePos.z(), "tile.activator.use", 1, 1.2f);
                 world.playSoundEffect(player, SoundCategory.WORLD_SOUNDS, cTilePos.x(), cTilePos.y(), cTilePos.z(), "note.hat", 0.7f, 0.4f);
-            } else if (cStrength == 15) {
+                return true;
+            } else if (inertValue == INERT_MARKER) {
                 world.setBlockDataNotify(cTilePos, 0);
                 world.playSoundEffect(player, SoundCategory.WORLD_SOUNDS, cTilePos.x(), cTilePos.y(), cTilePos.z(), "tile.activator.use", 1, 1.2f);
                 world.playSoundEffect(player, SoundCategory.WORLD_SOUNDS, cTilePos.x(), cTilePos.y(), cTilePos.z(), "note.hat", 0.7f, 0.4f);
+                return true;
             }
-            return true;
         }
 
-        if (holdingConduit && isSideOpen(cData, side)) {
+        if (isInert(cData)) {
             return false;
         }
 
-        int nextState = getNextState(cState, side);
+        Side[] openSides = getOpenSides(cData);
+        boolean clickedSideOpen = isSideOpen(cData, side);
 
-        if (nextState != cState) {
-            int newStrength = cStrength;
+        if (holdingConduit && clickedSideOpen && world.isAirBlock(new TilePos(cTilePos).add(side))) {
+            return false;
+        }
 
-            if (nextState == 0 || (cState == 0 && cStrength == 15)) {
-                newStrength = 0;
+        int nextData;
+        if (clickedSideOpen) {
+            if (openSides.length <= 1) {
+                nextData = 0;
+            } else {
+                Side newSide = (openSides[0] == side) ? openSides[1] : openSides[0];
+                nextData = makeCap(newSide, false);
             }
+        } else {
+            nextData = getNextDataForFace(world, cTilePos, cData, side);
+        }
 
-            world.setBlockDataNotify(cTilePos, (newStrength << POWER_SHIFT) | nextState);
-            onNeighborChanged(world, cTilePos, this.block);
+        if (nextData == cData) {
+            return false;
+        }
 
-            world.playSoundEffect(player, SoundCategory.WORLD_SOUNDS, cTilePos.x(), cTilePos.y(), cTilePos.z(), "tile.activator.use", 1, 1.2f);
-            world.playSoundEffect(player, SoundCategory.WORLD_SOUNDS, cTilePos.x(), cTilePos.y(), cTilePos.z(), "note.hat", 0.7f, 0.4f);
-            world.playBlockEvent(cTilePos, LevelListener.EVENT_CONDUIT_PORT_PARTICLES, packEventConduitData(side, newStrength));
+        world.setBlockDataNotify(cTilePos, nextData);
+        onNeighborChanged(world, cTilePos, this.block);
 
-            return true;
-        }
+        world.playSoundEffect(player, SoundCategory.WORLD_SOUNDS, cTilePos.x(), cTilePos.y(), cTilePos.z(), "tile.activator.use", 1, 1.2f);
+        world.playSoundEffect(player, SoundCategory.WORLD_SOUNDS, cTilePos.x(), cTilePos.y(), cTilePos.z(), "note.hat", 0.7f, 0.4f);
+        world.playBlockEvent(cTilePos, LevelListener.EVENT_CONDUIT_PORT_PARTICLES, packEventConduitData(side, getPower(nextData)));
 
-        return false;
+        return true;
     }
 
-    public static byte packEventConduitData(Side side, int strength) {
-        if (side == null) side = Side.NONE;
+    public static byte packEventConduitData(@Nullable Side side, int strength) {
         int s = Math.max(0, Math.min(PROPAGATION_LENGTH, strength));
-        int sideId = side.id & 0x07;
+        int sideId = (side == null) ? 0 : (side.id & 0x07);
         return (byte) ((sideId << POWER_SHIFT) | (s & POWER_MASK));
     }
 
@@ -335,9 +298,9 @@ public class BlockLogicConduit extends BlockLogic {
 
     private static boolean isEmitting(@NotNull WorldSource world, @NotNull TilePosc tilePos, @NotNull Side side) {
         final int data = world.getBlockData(tilePos);
-        if ((data & STATE_MASK) == 0) return false;
+        if (getOpenSides(data).length == 0) return false;
 
-        final int currentPower = (data >>> POWER_SHIFT) & POWER_MASK;
+        final int currentPower = getPower(data);
         if (currentPower == 0) return false;
 
         final Side sideAgainst = side.opposite();
@@ -349,22 +312,45 @@ public class BlockLogicConduit extends BlockLogic {
         return isSideOpen(data, sideAgainst);
     }
 
-    private int getNextState(int currentState, @NotNull Side sideQueried) {
-        if (currentState == 0) {
-            return getStateFromTwoSides(sideQueried, sideQueried.opposite());
-        }
 
-        Side[] currentSides = STATE_TABLE[currentState];
-        for (Side side : currentSides) {
-            if (side == sideQueried) {
-                return 0;
+    public static int getNextDataForFace(@NotNull World world, @NotNull TilePosc cTilePos, int cData, @NotNull Side side) {
+        int nextData = getNextData(cData, side);
+
+        Side[] openSides = getOpenSides(cData);
+        if (nextData == cData && (cData & STATE_MASK) != 0 && openSides.length == 2) {
+            int priority1 = connectionPriority(world, cTilePos, openSides[0]);
+            int priority2 = connectionPriority(world, cTilePos, openSides[1]);
+
+            if (priority1 != priority2) {
+                Side keptSide = (priority2 > priority1) ? openSides[1] : openSides[0];
+                nextData = getStateFromTwoSides(keptSide, side);
+            } else {
+                boolean air1 = world.isAirBlock(new TilePos(cTilePos).add(openSides[0]));
+                boolean air2 = world.isAirBlock(new TilePos(cTilePos).add(openSides[1]));
+                if (air1 != air2) {
+                    Side keptSide = air1 ? openSides[1] : openSides[0];
+                    nextData = getStateFromTwoSides(keptSide, side);
+                }
             }
         }
+        return nextData;
+    }
 
-        return currentState;
+    public static int getNextData(int data, @NotNull Side side) {
+        Side[] openSides = getOpenSides(data);
+        for (Side s : openSides) {
+            if (s == side) return data;
+        }
+        if (openSides.length == 0) {
+            return makeCap(side, false);
+        }
+        if (openSides.length == 1) {
+            return getStateFromTwoSides(openSides[0], side);
+        }
+        return data;
     }
 
-    private int getStateFromTwoSides(@NotNull Side s1, @NotNull Side s2) {
+    private static int getStateFromTwoSides(@NotNull Side s1, @NotNull Side s2) {
         if (s1 == s2) return 0;
 
         for (int i = 1; i < STATE_TABLE.length; i++) {
@@ -380,8 +366,6 @@ public class BlockLogicConduit extends BlockLogic {
         return 0;
     }
 
-
-
     /**
      * Determines the incoming signal strength from a neighboring block on any given side of the conduit and converts it. <div></div>
      * n = neighbor (ex. nTilePos = neighboring blocks TilePos) <div></div>
@@ -396,18 +380,18 @@ public class BlockLogicConduit extends BlockLogic {
         TilePos nTilePos = new TilePos(cTilePos.x() + side.offsetX(), cTilePos.y() + side.offsetY(), cTilePos.z() + side.offsetZ());
         Block<?> nBlockType = world.getBlockType(nTilePos);
 
-        int cStrength = (world.getBlockData(cTilePos) >> POWER_SHIFT) & POWER_MASK;
+        int cStrength = getPower(world.getBlockData(cTilePos));
 
         if(nBlockType == Blocks.WIRE_REDSTONE) {
             int nData = world.getBlockData(nTilePos);
             int nStrength = nData & BlockLogicWireRedstone.MASK_POWER;
-            return Math.max(0, signalConverter(nStrength, false) - 1);
+            return Math.max(0, signalConverter(nStrength) - 1);
         }
 
         if (nBlockType == Blocks.CONDUIT) {
             int nData = world.getBlockData(nTilePos);
             if (isSideOpen(nData, side.opposite())) {
-                int nStrength = (nData >> POWER_SHIFT) & POWER_MASK;
+                int nStrength = getPower(nData);
 
                 if (nStrength <= cStrength) return 0;
 
@@ -431,6 +415,18 @@ public class BlockLogicConduit extends BlockLogic {
                     }
                 }
             } else {
+                if (nBlockType == Blocks.PUMPKIN_REDSTONE) {
+                    Side pumpkinFace = Side.fromId(world1.getBlockData(nTilePos));
+                    for (Side s : Side.sides) {
+                        if (s == pumpkinFace) continue;
+                        TilePos queryPos = new TilePos(nTilePos.x() + s.offsetX(), nTilePos.y() + s.offsetY(), nTilePos.z() + s.offsetZ());
+                        Block<?> queryBlock = world1.getBlockType(queryPos);
+                        if (queryBlock != Blocks.WIRE_REDSTONE && queryBlock != Blocks.CONDUIT
+                                && queryBlock.isEmittingDirectSignal(world1, queryPos, s)) {
+                            return PROPAGATION_LENGTH;
+                        }
+                    }
+                }
                 if (nBlockType.isEmittingSignal(world1, nTilePos, side)) {
                     return PROPAGATION_LENGTH;
                 }
@@ -443,17 +439,6 @@ public class BlockLogicConduit extends BlockLogic {
         return 0;
     }
 
-    /**
-     * Determines a conduit power level output toward a given side, or 0 if that side is closed. <div></div>
-     *      c = conduit (ex. cStrength = this conduit Signal Strength) <div></div>
-     * @param cData The metadata of the conduit.
-     * @param sideTowardReceiver The side of the conduit facing the block asking for power.
-     * @return Conduit power (0-{@link #PROPAGATION_LENGTH})
-     */
-    public static int getOutputPower(int cData, @NotNull Side sideTowardReceiver) {
-        return isSideOpen(cData, sideTowardReceiver) ? (cData >> POWER_SHIFT) & POWER_MASK : 0;
-    }
-
     /**
      * Determines if a specific side on a conduit is open. <div></div>
      *      c = conduit (ex. cStrength = this conduit Signal Strength) <div></div>
@@ -462,15 +447,7 @@ public class BlockLogicConduit extends BlockLogic {
      * @return true if the side is open, false otherwise.
      */
     public static boolean isSideOpen(int cData, @NotNull Side side) {
-        int cState = cData & STATE_MASK;
-
-        if (cState == 0) {
-            return false;
-        }
-
-        Side[] openSides = STATE_TABLE[cState];
-
-        for (Side s : openSides) {
+        for (Side s : getOpenSides(cData)) {
             if (s == side) {
                 return true;
             }
@@ -479,30 +456,207 @@ public class BlockLogicConduit extends BlockLogic {
         return false;
     }
 
+    private static int getUpperNibble(int data) {
+        return (data >> POWER_SHIFT) & POWER_MASK;
+    }
+
+    private static int capSideIndex(int data) {
+        if ((data & STATE_MASK) != 0) return -1;
+        int sideBits = getUpperNibble(data) & CAP_SIDE_MASK;
+        return (sideBits >= 1 && sideBits <= 6) ? sideBits - 1 : -1;
+    }
+
+    /**
+     * Raw stored power for the given data, without resolving caps or inert markers.
+     */
+    public static int getPower(int data) {
+        if ((data & STATE_MASK) != 0) return getUpperNibble(data);
+        if (isCap(data)) return isCapPowered(data) ? 1 : 0;
+        return 0;
+    }
+
+    /**
+     * Power for the conduit at the given position.
+     */
+    public static int getPower(@NotNull WorldSource world, @NotNull TilePosc tilePos) {
+        int data = world.getBlockData(tilePos);
+        if (isCap(data)) {
+            if (!isCapPowered(data)) return 0;
+            int signal = getAndConvertNeighborSignal(world, tilePos, getCapSide(data));
+            return signal > 0 ? signal : PROPAGATION_LENGTH;
+        }
+        if (isInert(data)) return PROPAGATION_LENGTH;
+        return getPower(data);
+    }
+
+    public static boolean isInert(int data) {
+        return (data & STATE_MASK) == 0 && getUpperNibble(data) == INERT_MARKER;
+    }
+
+    public static int makeCap(@NotNull Side side, boolean powered) {
+        int nibble = (powered ? CAP_POWERED : 0) | (side.id + 1);
+        return (nibble << POWER_SHIFT);
+    }
+
+    public static @NotNull Side getCapSide(int data) {
+        return Side.fromId(capSideIndex(data));
+    }
+
+    public static boolean isCapPowered(int data) {
+        return (getUpperNibble(data) & CAP_POWERED) != 0;
+    }
+
+    public static boolean isCap(int data) {
+        return capSideIndex(data) >= 0;
+    }
+
+    public static int withPower(int data, int newPower) {
+        int state = data & STATE_MASK;
+        if (state != 0) {
+            int p = Math.max(0, Math.min(POWER_MASK, newPower));
+            return (p << POWER_SHIFT) | state;
+        }
+        if (isCap(data)) {
+            return makeCap(getCapSide(data), newPower > 0);
+        }
+        return data;
+    }
+
+    private static int connectTowardPlayer(@NotNull Side anchor, @NotNull Side sideFacingPlayer) {
+        if (anchor == sideFacingPlayer) {
+            return makeCap(anchor, false);
+        }
+        return getStateFromTwoSides(anchor, sideFacingPlayer);
+    }
+
+    private static int connectionPriority(@NotNull World world, @NotNull TilePosc cTilePos, @NotNull Side side) {
+        TilePos nTilePos = new TilePos(cTilePos.x() + side.offsetX(), cTilePos.y() + side.offsetY(), cTilePos.z() + side.offsetZ());
+        Block<?> nBlock = world.getBlockType(nTilePos);
+        int nData = world.getBlockData(nTilePos);
+        var logic = nBlock.getLogic();
+        Side against = side.opposite();
+
+        // TODO should probably make connections handled per block rather then by the conduit itself.
+        //  Held off for now as the exact same issue occurs in redstone dust, and should be handled together.
+
+        if (nBlock == Blocks.CONDUIT) {
+            if (isInert(nData)) return CONNECT_NONE;
+            return isSideOpen(nData, against) ? CONNECT_CONDUIT : CONNECT_NONE;
+        }
+        if (logic instanceof BlockLogicPistonHead) {
+            return CONNECT_NONE;
+        }
+        if (logic instanceof BlockLogicPistonBase) {
+            Side head = Direction.fromId(PistonCommon.DIRECTION.get(nData)).side();
+            return head == against ? CONNECT_NONE : CONNECT_PISTON;
+        }
+        if (logic instanceof BlockLogicActivator) {
+            Side face = BlockLogicVeryRotatable.metaToDirection(nData).side();
+            return face == against ? CONNECT_NONE : CONNECT_ACTIVATOR;
+        }
+        if (logic instanceof BlockLogicDispenser) {
+            Side face = BlockLogicVeryRotatable.metaToDirection(nData).side();
+            return face == against ? CONNECT_NONE : CONNECT_DISPENSER;
+        }
+        if (logic instanceof BlockLogicMotionSensor) {
+            Side face = BlockLogicVeryRotatable.metaToDirection(nData).side();
+            return face == against ? CONNECT_NONE : CONNECT_MOTION_SENSOR;
+        }
+        if (nBlock == Blocks.WIRE_REDSTONE) {
+            return CONNECT_REDSTONE_DUST;
+        }
+        if (logic instanceof BlockLogicPumpkinRedstone) {
+            Side face = BlockLogicVeryRotatable.metaToDirection(nData).side();
+            return face == against ? CONNECT_REDSTONE_LANTERN : CONNECT_NONE;
+        }
+        if (nBlock == Blocks.BLOCK_REDSTONE) {
+            return CONNECT_REDSTONE_BLOCK;
+        }
+        if (logic instanceof BlockLogicRepeater) {
+            Side axis = BlockLogicRepeater.getSideFromMeta(nData);
+            return (side == axis || side == axis.opposite()) ? CONNECT_REPEATER : CONNECT_NONE;
+        }
+        if (logic instanceof BlockLogicTimer) {
+            Side axis = BlockLogicTimer.getSideFromMeta(nData);
+            return (side == axis || side == axis.opposite()) ? CONNECT_TIMER : CONNECT_NONE;
+        }
+        if (nBlock == Blocks.MATCHER || nBlock == Blocks.MATCHER_ACTIVE) {
+            return (side.axis() != BlockLogicAxisAligned.metaToAxis(nData)) ? CONNECT_MATCHER : CONNECT_NONE;
+        }
+        if (logic instanceof BlockLogicTorchRedstone) {
+            return CONNECT_TORCH;
+        }
+        if (logic instanceof BlockLogicLever) {
+            return CONNECT_LEVER;
+        }
+        if (logic instanceof BlockLogicButton) {
+            return CONNECT_BUTTON;
+        }
+        if (nBlock == Blocks.SPIKES) {
+            return CONNECT_SPIKES;
+        }
+        if (nBlock == Blocks.NOTEBLOCK) {
+            return CONNECT_NOTEBLOCK;
+        }
+        if (nBlock == Blocks.RAIL_POWERED) {
+            return CONNECT_RAIL_POWERED;
+        }
+        return CONNECT_NONE;
+    }
+
+    private static int tiebreakRank(@NotNull Side side, @NotNull Side sideClickedAgainst, @NotNull Side sideFacingPlayer) {
+        if (side == sideClickedAgainst) return 0;
+        if (side == sideFacingPlayer) return 1;
+        return 2;
+    }
+
     /**
      * Determines which sides on a given conduit are open. <div></div>
      *      c = conduit (ex. cStrength = this conduit Signal Strength) <div></div>
      * @param cData The metadata of the conduit to check.
-     * @return a {@link Side} {@link Array} of sides that are open.
+     * @return a {@link Side} array of the sides that are open.
      */
     public static Side[] getOpenSides(int cData){
-        return STATE_TABLE[cData & STATE_MASK];
+        int state = cData & STATE_MASK;
+        if (state != 0) return STATE_TABLE[state];
+
+        int capSide = capSideIndex(cData);
+        return capSide >= 0 ? CAP_SIDES[capSide] : STATE_TABLE[0];
     }
 
     /**
      * Converts signal strengths between redstone and conduit.
      * @param strength The signal strength to convert
-     * @param conduitToRedstone Whether to convert conduit -> redstone or vice versa
      * @return The converted signal strength
      */
-    public static int signalConverter(int strength, boolean conduitToRedstone) {
+    public static int signalConverter(int strength) {
         return Math.max(0, Math.min(PROPAGATION_LENGTH, strength));
     }
 
     @Override
-    protected int getMatcherEquivalenceData(int data) {
-        if ((data & STATE_MASK) == 0) assert getOpenSides(data).length == 0;
-        else data |= STATE_MASK;
-        return data;
+    public boolean isEquivalent(@NotNull WorldSource world, @NotNull TilePosc thisPos, @NotNull TilePosc thatPos, @NotNull Block<?> thatBlock) {
+        if (!super.isEquivalent(world, thisPos, thatPos, thatBlock)) return false;
+        if (isCap(world.getBlockData(thisPos))) {
+            return getPower(world, thisPos) == getPower(world, thatPos);
+        }
+        return true;
+    }
+
+    @Override
+    public boolean isEquivalentDataForMatcherImpl(int thisData, int thatData) {
+        return getMatcherShape(thisData) == getMatcherShape(thatData)
+                && getMatcherPower(thisData) == getMatcherPower(thatData);
+    }
+    private static int getMatcherShape(int data) {
+        int state = data & STATE_MASK;
+        if (state != 0) {
+            return (state <= 3) ? SHAPE_STRAIGHT : SHAPE_CORNER;
+        }
+        return isCap(data) ? SHAPE_CAPPED : SHAPE_CLOSED;
+    }
+
+    private static int getMatcherPower(int data) {
+        if (isInert(data)) return PROPAGATION_LENGTH;
+        return getPower(data);
     }
 }
\ No newline at end of file
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicCropsPumpkin.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicCropsPumpkin.java
index e042cb912..df84bf46c 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicCropsPumpkin.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicCropsPumpkin.java
@@ -3,7 +3,7 @@ package net.minecraft.core.block;
 import net.minecraft.core.block.entity.TileEntity;
 import net.minecraft.core.entity.player.Player;
 import net.minecraft.core.enums.EnumDropCause;
-import net.minecraft.core.item.IBonemealable;
+// import net.minecraft.core.item.IBonemealable;
 import net.minecraft.core.item.ItemStack;
 import net.minecraft.core.item.Items;
 import net.minecraft.core.util.helper.Side;
@@ -20,7 +20,7 @@ import java.util.Random;
 
 public class BlockLogicCropsPumpkin
     extends BlockLogicFlower
-    implements IBonemealable {
+    /*implements IBonemealable*/ {
     public BlockLogicCropsPumpkin(final @NotNull Block<?> block) {
         super(block);
     }
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicCropsWheat.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicCropsWheat.java
index 9b2fdfbd6..1b6e4ac01 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicCropsWheat.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicCropsWheat.java
@@ -5,7 +5,7 @@ import java.util.Random;
 import net.minecraft.core.block.entity.TileEntity;
 import net.minecraft.core.entity.player.Player;
 import net.minecraft.core.enums.EnumDropCause;
-import net.minecraft.core.item.IBonemealable;
+// import net.minecraft.core.item.IBonemealable;
 import net.minecraft.core.item.Item;
 import net.minecraft.core.item.Items;
 import net.minecraft.core.item.ItemStack;
@@ -16,7 +16,7 @@ import net.minecraft.core.world.pos.TilePosc;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
-public class BlockLogicCropsWheat extends BlockLogicFlower implements IBonemealable {
+public class BlockLogicCropsWheat extends BlockLogicFlower /*implements IBonemealable*/ {
     public static final int MASK_GROWTH = 0b0000_0111;
     public static final int MAX_GROWTH_STATE = 7;
 
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 55e368fea..faacd85aa 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,11 +27,6 @@ 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 b9a16b98a..46dd64bbc 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
@@ -30,7 +30,7 @@ import java.util.function.Supplier;
 // TODO this needs massive refactor
 public class BlockLogicDoor
     extends BlockLogic
-    implements IPaintable {
+    implements IPaintable/*, BlockLogic.MatcherDataEquivalency*/ {
     public static final int MASK_ROTATION = 0b0000_0011;
     public static final int MASK_OPENED = 0b0000_0100;
     public static final int MASK_HINGE = 0b0000_1000; // 0 == left hinge; 1 == right hinge
@@ -52,24 +52,16 @@ 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(int data) {
-        final int MASK_NON_COLOR = MASK_OPENED | MASK_HINGE | MASK_ROTATION;
-        assert (MASK_HINGE >>> 1) == MASK_OPENED;
-        int data2 = data | ((data & MASK_HINGE) >>> 1); // flips the opened bit if hinge bit is set
-        data &= ~MASK_NON_COLOR;
-        data2 &= MASK_NON_COLOR;
-        data |= data2;
-        return data;
-    }
+    // this doesn't actually work that well
+    // @Override
+    // public boolean isEquivalentDataForMatcherImpl(int thisData, int thatData) {
+    //     // assert (MASK_HINGE >>> 1) == MASK_OPENED;
+    //     thisData &= MASK_OPENED | MASK_HINGE;
+    //     thatData &= MASK_OPENED | MASK_HINGE;
+    //     thisData ^= thisData >>> 1;
+    //     thatData ^= thatData >>> 1;
+    //     return (thisData & MASK_OPENED) == (thatData & MASK_OPENED);
+    // }
 
     @Override
     public void onPlacedOnSide(final @NotNull World world, final @NotNull TilePosc tilePos, final @NotNull Side side, final double xHit, final double yHit) {
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicDoorPainted.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicDoorPainted.java
index 7e8b34df3..930c49f81 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicDoorPainted.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicDoorPainted.java
@@ -19,6 +19,15 @@ public class BlockLogicDoorPainted
         super(block, material, isTop, false, null);
     }
 
+    public static final int MASK_COLOR = 0xF0;
+
+    // @Override
+    // public boolean isEquivalentDataForMatcherImpl(int thisData, int thatData) {
+    //     return 
+    //         (thisData & MASK_COLOR) == (thatData & MASK_COLOR) && 
+    //         super.isEquivalentDataForMatcherImpl(thisData, thatData);
+    // }
+
     @Override
     public @NotNull ItemStack @Nullable [] getBreakResult(final @NotNull World world, final @NotNull EnumDropCause dropCause, final int data, final @Nullable TileEntity tileEntity) {
         return new ItemStack[]{new ItemStack(Items.DOOR_OAK_PAINTED, 1, 15 - (data >> 4 & 0xF))};
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicEdible.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicEdible.java
index 3da2eec24..76a72897d 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicEdible.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicEdible.java
@@ -19,7 +19,8 @@ import org.jetbrains.annotations.Nullable;
 import java.util.function.Supplier;
 
 public abstract class BlockLogicEdible
-    extends BlockLogic {
+    extends BlockLogic 
+    implements BlockLogic.MatcherDataEquivalency {
     private final @NotNull Supplier<@NotNull Item> itemSupplier;
     public int maxBites;
 
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicFarmland.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicFarmland.java
index 237696e82..2d3f67923 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicFarmland.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicFarmland.java
@@ -28,7 +28,7 @@ import java.util.Random;
 
 public class BlockLogicFarmland
     extends BlockLogic
-    implements IBonemealable {
+    implements IBonemealable, BlockLogic.MatcherDataEquivalency {
     public static final int MASK_FERTILIZED = 0b0001_0000;
     public static final int MASK_WET = 0b0000_1111;
 
@@ -246,8 +246,8 @@ public class BlockLogicFarmland
     }
 
     @Override
-    protected int getMatcherEquivalenceData(int data) {
-        if ((data & MASK_WET) != 0) data |= MASK_WET;
-        return data;
+    public boolean isEquivalentDataForMatcherImpl(int thisData, int thatData) {
+        if ((thisData & ~MASK_WET) != (thatData & ~MASK_WET)) return false;
+        return ((thisData & MASK_WET) == 0) == ((thatData & MASK_WET) == 0);
     }
 }
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 11dad2ba7..d25f78220 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
@@ -23,7 +23,7 @@ import org.joml.primitives.AABBdc;
 
 public class BlockLogicFenceGate
     extends BlockLogic
-    implements IPaintable {
+    implements IPaintable, BlockLogic.MatcherDataEquivalency.Masked {
     public static final int MASK_DIRECTION = 0b0000_0011;
     public static final int MASK_OPEN = 0b0000_0100;
     public static final int MASK_POWERED = 0b0000_1000;
@@ -33,9 +33,15 @@ public class BlockLogicFenceGate
     }
 
     @Override
-    protected int getMatcherEquivalenceData(final int data) {
-        assert (data & (MASK_POWERED | MASK_OPEN)) != MASK_POWERED : "fence gate should be opened when powered";
-        return data & ~(MASK_DIRECTION | MASK_POWERED);
+    public int getMatcherDataEquivalencyMask() {
+        return ~(MASK_DIRECTION | MASK_POWERED);
+    }
+
+    @Override
+    public boolean isEquivalentDataForMatcherImpl(int thisData, int thatData) {
+        assert (thisData & (MASK_POWERED | MASK_OPEN)) != MASK_POWERED : "fence gate should be opened when powered";
+        assert (thatData & (MASK_POWERED | MASK_OPEN)) != MASK_POWERED : "fence gate should be opened when powered";
+        return BlockLogic.MatcherDataEquivalency.Masked.super.isEquivalentDataForMatcherImpl(thisData, thatData);
     }
 
     @Override
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicFire.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicFire.java
index 790404ddc..53b5267a0 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicFire.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicFire.java
@@ -397,9 +397,4 @@ public class BlockLogicFire
     public void onEntityCollision(final @NotNull World world, final @NotNull TilePosc tilePos, final @NotNull Entity entity) {
         entity.activeFireBlock = this.block;
     }
-
-    @Override
-    protected int getMatcherEquivalenceData(int data) {
-        return 0;
-    }
 }
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicFlag.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicFlag.java
index a6f8badc5..67cd126d4 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicFlag.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicFlag.java
@@ -22,7 +22,9 @@ import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 import org.joml.primitives.AABBdc;
 
-public class BlockLogicFlag extends BlockLogic {
+public class BlockLogicFlag extends BlockLogic
+    implements BlockLogic.MatcherEntityEquivalency<TileEntityFlag>
+{
 
     public BlockLogicFlag(@NotNull Block<?> block, @NotNull Material material) {
         super(block, material);
@@ -88,14 +90,14 @@ public class BlockLogicFlag extends BlockLogic {
         return Material.PISTON_DESTROY_ON_PUSH;
     }
 
+    @Override
+    public @NotNull Class<TileEntityFlag> getEntityEquivalencyClass() {
+        return TileEntityFlag.class;
+    }
+
     // TODO: pure color comparison instead of dye index comparison
     @Override
-    public boolean isEquivalent(@NotNull World world, @NotNull TilePosc thisPos, @NotNull TilePosc thatPos) {
-        assert world.getBlockType(thisPos) == this.block;
-        if (world.getBlockType(thatPos) != this.block) return false;
-        if (!(world.getTileEntity(thisPos) instanceof TileEntityFlag thisFlag) ||
-            !(world.getTileEntity(thatPos) instanceof TileEntityFlag thatFlag))
-            return false;
+    public boolean isEquivalentEntityForMatcherImpl(@NotNull TileEntityFlag thisFlag, @NotNull TileEntityFlag thatFlag) {
         if (thisFlag.colorHash != thatFlag.colorHash) return false;
         if (thisFlag.colorHash != ~0) {
             if (thisFlag.flipped != thatFlag.flipped) return false;
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicFlower.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicFlower.java
index 0faccf85a..f3ab0af12 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicFlower.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicFlower.java
@@ -22,7 +22,7 @@ import java.util.Random;
 
 public class BlockLogicFlower
     extends BlockLogic
-    implements IBonemealable {
+    implements IBonemealable, BlockLogic.MatcherDataEquivalency.Masked {
     public static final int MASK_PERMANENT = 0b1000_0000;
 
     public boolean killedByWeather = false;
@@ -161,7 +161,7 @@ public class BlockLogicFlower
     }
 
     @Override
-    protected int getMatcherEquivalenceData(int data) {
-        return data & ~ MASK_PERMANENT;
+    public int getMatcherDataEquivalencyMask() {
+        return ~MASK_PERMANENT;
     }
 }
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicFluid.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicFluid.java
index e7428b752..5ffc6f297 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicFluid.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicFluid.java
@@ -238,10 +238,12 @@ public abstract class BlockLogicFluid extends BlockLogic {
     }
 
     @Override
-    protected boolean isSameBlockForMatcher(@NotNull Block<?> other) {
+    protected boolean isEquivalentBlockForMatcher(@NotNull Block<?> other) {
+        if (other == this.block) return true;
         return other.getLogic() instanceof BlockLogicFluid fluid && fluid.material == this.material;
     }
 
+
     public static boolean isStillEquivalentToFlowing(Block<?> block1, Block<?> block2) {
         if (block1 == null || block2 == null) return false;
         if (block1 == block2) return true;
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicFluidFlowing.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicFluidFlowing.java
index 29190bc4f..49e10c5e3 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicFluidFlowing.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicFluidFlowing.java
@@ -14,7 +14,7 @@ import it.unimi.dsi.fastutil.ints.IntArrayList;
 import java.util.Arrays;
 import java.util.Random;
 
-public class BlockLogicFluidFlowing extends BlockLogicFluid {
+public class BlockLogicFluidFlowing extends BlockLogicFluid implements BlockLogic.MatcherDataEquivalency {
     int maxCount;
     public final @NotNull Block<?> blockStill;
 
@@ -83,11 +83,12 @@ public class BlockLogicFluidFlowing extends BlockLogicFluid {
             if (b != null) {
                 b.dropWithCause(world, EnumDropCause.WORLD, spreadPos, world.getBlockData(spreadPos), null, null);
             }
-            if (localFlowDecay >= 8) {
-                world.setBlockTypeDataNotify(spreadPos, this.block, localFlowDecay);
-            } else {
-                world.setBlockTypeDataNotify(spreadPos, this.block, localFlowDecay + 8);
-            }
+            world.setBlockTypeDataNotify(spreadPos, this.block, 8);
+            // if (localFlowDecay >= 8) {
+            //     world.setBlockTypeDataNotify(spreadPos, this.block, localFlowDecay);
+            // } else {
+            //     world.setBlockTypeDataNotify(spreadPos, this.block, localFlowDecay + 8);
+            // }
         } else if (localFlowDecay >= 0 && (localFlowDecay == 0 || isFluidBlocking(world, tilePos.down(queryPos)))) {
             final int meta = (localFlowDecay < MAX_SPREAD)
                 ? localFlowDecay + flowDecayMod
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicFluidStill.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicFluidStill.java
index e4ebf706e..896b7daa7 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicFluidStill.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicFluidStill.java
@@ -3,7 +3,6 @@ package net.minecraft.core.block;
 import java.util.Random;
 
 import net.minecraft.core.block.material.Material;
-import net.minecraft.core.util.helper.Side;
 import net.minecraft.core.world.World;
 import net.minecraft.core.world.pos.TilePosc;
 import org.jetbrains.annotations.NotNull;
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 b80fefd85..2313016d4 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,11 +14,6 @@ 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/BlockLogicGrowthRubyglass.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicGrowthRubyglass.java
index 4e6b54adb..ea739bee2 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicGrowthRubyglass.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicGrowthRubyglass.java
@@ -16,7 +16,7 @@ import org.jetbrains.annotations.Nullable;
 import org.joml.primitives.AABBd;
 import org.joml.primitives.AABBdc;
 
-public class BlockLogicGrowthRubyglass extends BlockLogic {
+public class BlockLogicGrowthRubyglass extends BlockLogic implements BlockLogic.MatcherDataEquivalency.Masked {
 
 	public static final int DIRECTION_MASK = 0b0000_0111;
 	public static final int SHAPE_MASK = 0b0001_1000;
@@ -226,7 +226,7 @@ public class BlockLogicGrowthRubyglass extends BlockLogic {
     }
 
 	@Override
-	protected int getMatcherEquivalenceData(int data) {
-		return data & ~DIRECTION_MASK;
+	public int getMatcherDataEquivalencyMask() {
+		return ~DIRECTION_MASK;
 	}
 }
\ No newline at end of file
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicJar.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicJar.java
index ae7d3de80..d59f551d5 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicJar.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicJar.java
@@ -18,7 +18,6 @@ import net.minecraft.core.util.helper.Side;
 import net.minecraft.core.world.World;
 import net.minecraft.core.world.WorldSource;
 import net.minecraft.core.world.pos.TilePosc;
-import net.minecraft.core.world.wind.WindProvider;
 
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
@@ -27,7 +26,12 @@ import org.joml.primitives.AABBdc;
 
 import java.util.function.Supplier;
 
-public class BlockLogicJar extends BlockLogic implements ISupportable {
+public class BlockLogicJar extends BlockLogic
+implements 
+    ISupportable,
+    BlockLogic.MatcherDataEquivalency,
+    BlockLogic.MatcherEntityEquivalency<TileEntityFlowerJar>
+{
 
     private final @NotNull Supplier<@NotNull Item> itemSupplier;
 
@@ -137,19 +141,22 @@ public class BlockLogicJar extends BlockLogic implements ISupportable {
     }
 
     @Override
-    public boolean isEquivalent(@NotNull World world, @NotNull TilePosc thisPos, @NotNull TilePosc thatPos) {
-        assert world.getBlockType(thisPos) == this.block;
-        if (world.getBlockType(thatPos) != this.block) return false;
-        final int thisData, thatData; {
-            thisData = world.getBlockData(thisPos);
-            thatData = world.getBlockData(thatPos);
-        }
-        if ((thisData | thatData) == 0) return true;
-        if (!(world.getTileEntity(thisPos) instanceof TileEntityFlowerJar thisFlowerJar) ||
-            !(world.getTileEntity(thatPos) instanceof TileEntityFlowerJar thatFlowerJar))
-            return false;
-        if (thisFlowerJar.flowerInPot != thatFlowerJar.flowerInPot) return false;
-        if (thisFlowerJar.flowerData != thatFlowerJar.flowerData) return false;
-        return true;
+    public boolean isEntityRequiredForMatching() {
+        return false;
+    }
+
+    @Override
+    public @NotNull Class<TileEntityFlowerJar> getEntityEquivalencyClass() {
+        return TileEntityFlowerJar.class;
+    }
+
+    @Override
+    public boolean isEquivalentEntityForMatcherImpl(
+        @Nullable TileEntityFlowerJar thisJar, @Nullable TileEntityFlowerJar thatJar
+    ) {
+        if (thisJar == null || thatJar == null) return thisJar == null && thatJar == null;
+        return 
+            thisJar.flowerInPot == thatJar.flowerInPot &&
+            thisJar.flowerData == thatJar.flowerData;
     }
 }
\ No newline at end of file
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicJarButterfly.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicJarButterfly.java
index 5cba540b4..a6924d945 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicJarButterfly.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicJarButterfly.java
@@ -25,7 +25,7 @@ import org.joml.primitives.AABBdc;
 
 import java.util.function.Supplier;
 
-public class BlockLogicJarButterfly extends BlockLogic implements ISupportable {
+public class BlockLogicJarButterfly extends BlockLogic implements ISupportable, BlockLogic.MatcherEntityEquivalency<TileEntityJarButterfly> {
     public static final int MASK_HANGING = 0b0000_0001;
     public static final int MASK_ANGLED = 0b0000_0010;
     private final @NotNull Supplier<@NotNull Item> itemSupplier;
@@ -152,12 +152,14 @@ public class BlockLogicJarButterfly extends BlockLogic implements ISupportable {
     }
 
     @Override
-    public boolean isEquivalent(@NotNull World world, @NotNull TilePosc thisPos, @NotNull TilePosc thatPos) {
-        assert world.getBlockType(thisPos) == this.block;
-        if (world.getBlockType(thatPos) != this.block) return false;
-        if (!(world.getTileEntity(thisPos) instanceof TileEntityJarButterfly thisJar) ||
-            !(world.getTileEntity(thatPos) instanceof TileEntityJarButterfly thatJar))
-            return false;
-        return thisJar.color.id() == thatJar.color.id();
+    public @NotNull Class<TileEntityJarButterfly> getEntityEquivalencyClass() {
+        return TileEntityJarButterfly.class;
+    }
+
+    @Override
+    public boolean isEquivalentEntityForMatcherImpl(
+        @NotNull TileEntityJarButterfly thisButterfly, @NotNull TileEntityJarButterfly thatButterfly
+    ) {
+        return thisButterfly.color.id() == thatButterfly.color.id();
     }
 }
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicJukebox.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicJukebox.java
index b33ba3f0a..0f49116de 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicJukebox.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicJukebox.java
@@ -7,12 +7,17 @@ import net.minecraft.core.entity.player.Player;
 import net.minecraft.core.util.helper.Direction;
 import net.minecraft.core.util.helper.Side;
 import net.minecraft.core.world.World;
+import net.minecraft.core.world.WorldSource;
 import net.minecraft.core.world.pos.TilePos;
 import net.minecraft.core.world.pos.TilePosc;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
-public class BlockLogicJukebox extends BlockLogic {
+public class BlockLogicJukebox extends BlockLogic 
+implements
+    BlockLogic.MatcherDataEquivalency,
+    BlockLogic.MatcherEntityEquivalency<TileEntityJukebox>
+{
 	public BlockLogicJukebox(@NotNull Block<?> block) {
         super(block, Materials.WOOD);
         block.withEntity(TileEntityJukebox::new);
@@ -51,19 +56,36 @@ public class BlockLogicJukebox extends BlockLogic {
     }
 
     @Override
-    public boolean isEquivalent(@NotNull World world, @NotNull TilePosc thisPos, @NotNull TilePosc thatPos) {
-        assert world.getBlockType(thisPos) == this.block;
-        if (world.getBlockType(thatPos) != this.block) return false;
+    public boolean isEquivalent(
+        @NotNull WorldSource world,
+        @NotNull TilePosc thisPos, @NotNull TilePosc thatPos,
+        @NotNull Block<?> thatBlock
+    ) {
+        if (!this.isEquivalentBlockForMatcher(thatBlock)) return false;
         final int thisData, thatData; {
             thisData = world.getBlockData(thisPos);
             thatData = world.getBlockData(thatPos);
         }
         if (thisData != thatData) return false;
-        if ((thisData | thatData) == 0) return true;
+        if (thatData == 0 /*&& thatData == 0*/) return true;
+        return this.isEquivalentEntityForMatcher(world, thisPos, thatPos);
+    }
 
-        if (!(world.getTileEntity(thisPos) instanceof TileEntityJukebox thisJukebox) ||
-            !(world.getTileEntity(thatPos) instanceof TileEntityJukebox thatJukebox))
-            return false;
+    @Override
+    public boolean isEntityRequiredForMatching() {
+        return false;
+    }
+
+    @Override
+    public @NotNull Class<TileEntityJukebox> getEntityEquivalencyClass() {
+        return TileEntityJukebox.class;
+    }
+
+    @Override
+    public boolean isEquivalentEntityForMatcherImpl(
+        @Nullable TileEntityJukebox thisJukebox, @Nullable TileEntityJukebox thatJukebox
+    ) {
+        if (thisJukebox == null || thatJukebox == null) return thisJukebox == null && thatJukebox == null;
         return thisJukebox.record == thatJukebox.record;
     }
 }
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 1c6969c32..2f8cbeef1 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,11 +22,6 @@ 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 2fec961c1..1d17e7402 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,7 +173,7 @@ public class BlockLogicLamp extends BlockLogic implements IPainted {
     }
 
     @Override
-    protected boolean isSameBlockForMatcher(final @NotNull Block<?> other) {
+    protected boolean isEquivalentBlockForMatcher(@NotNull Block<?> other) {
         return other.getLogic() instanceof BlockLogicLamp lamp && this.isActive == lamp.isActive;
     }
 }
\ No newline at end of file
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicLayerBase.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicLayerBase.java
index 30776c77a..f2d134527 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicLayerBase.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicLayerBase.java
@@ -14,7 +14,7 @@ import org.jetbrains.annotations.Nullable;
 import org.joml.primitives.AABBd;
 import org.joml.primitives.AABBdc;
 
-public abstract class BlockLogicLayerBase extends BlockLogic
+public abstract class BlockLogicLayerBase extends BlockLogic implements BlockLogic.MatcherDataEquivalency
 {
     public final @Nullable Block<?> fullBlock;
     public static final int MASK_HEIGHT = 0b111;
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicLayerLeaves.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicLayerLeaves.java
index 13b1d03e7..4c083affd 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicLayerLeaves.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicLayerLeaves.java
@@ -18,7 +18,7 @@ import org.joml.primitives.AABBdc;
 
 import java.util.Random;
 
-public class BlockLogicLayerLeaves extends BlockLogicLayerSupportable {
+public class BlockLogicLayerLeaves extends BlockLogicLayerSupportable implements BlockLogic.MatcherDataEquivalency.Masked {
     public static final int MASK_PERMANENT = 0b1000_0000;
 
     public BlockLogicLayerLeaves(@NotNull Block<? extends BlockLogicLayerLeaves> block, @NotNull Block<?> fullBlock, @NotNull Material material) {
@@ -93,7 +93,7 @@ public class BlockLogicLayerLeaves extends BlockLogicLayerSupportable {
     }
 
     @Override
-    protected int getMatcherEquivalenceData(int data) {
-        return super.getMatcherEquivalenceData(data) & ~MASK_PERMANENT;
+    public int getMatcherDataEquivalencyMask() {
+        return ~MASK_PERMANENT;
     }
 }
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicLeavesBase.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicLeavesBase.java
index df0b4cd1a..fea1184e5 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicLeavesBase.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicLeavesBase.java
@@ -201,9 +201,4 @@ public class BlockLogicLeavesBase extends BlockLogic {
     public float getAmbientOcclusionStrength(@NotNull WorldSource source, @NotNull TilePosc tilePos) {
         return enableTreeShadowing ? 0.8f : 0.0f;
     }
-
-    @Override
-    protected int getMatcherEquivalenceData(int data) {
-        return data & ~MASK_DECAY_DATA;
-    }
 }
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicLeavesCacao.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicLeavesCacao.java
index 39f7684d8..e672a9731 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicLeavesCacao.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicLeavesCacao.java
@@ -21,7 +21,7 @@ import org.jetbrains.annotations.Nullable;
 
 import java.util.Random;
 
-public class BlockLogicLeavesCacao extends BlockLogicLeavesBase implements IBonemealable {
+public class BlockLogicLeavesCacao extends BlockLogicLeavesBase implements IBonemealable, BlockLogic.MatcherDataEquivalency.Masked {
 
     public static final int MASK_GROWTH_DATA = 0b1111_0000;
     public static final int MAX_GROWTH_STATE = 4;
@@ -133,4 +133,9 @@ public class BlockLogicLeavesCacao extends BlockLogicLeavesBase implements IBone
     public static int setGrowthRate(int meta, int growthRate) {
         return (meta & ~MASK_GROWTH_DATA) | (growthRate << 4) & MASK_GROWTH_DATA;
     }
+
+    @Override
+    public int getMatcherDataEquivalencyMask() {
+        return ~MASK_DECAY_DATA;
+    }
 }
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicLeavesCherryFlowering.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicLeavesCherryFlowering.java
index fcd2d4395..430595cbf 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicLeavesCherryFlowering.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicLeavesCherryFlowering.java
@@ -17,7 +17,7 @@ import org.jetbrains.annotations.Nullable;
 
 import java.util.Random;
 
-public class BlockLogicLeavesCherryFlowering extends BlockLogicLeavesCherry implements IBonemealable
+public class BlockLogicLeavesCherryFlowering extends BlockLogicLeavesCherry implements IBonemealable, BlockLogic.MatcherDataEquivalency.Masked
 {
     public static final int MASK_GROWTH_DATA = 0b1111_0000;
     public static final int MAX_GROWTH_STATE = 1;
@@ -112,4 +112,9 @@ public class BlockLogicLeavesCherryFlowering extends BlockLogicLeavesCherry impl
     public static int setGrowthRate(int meta, int growthRate) {
         return (meta & ~MASK_GROWTH_DATA) | (growthRate << 4) & MASK_GROWTH_DATA;
     }
+
+    @Override
+    public int getMatcherDataEquivalencyMask() {
+        return ~MASK_DECAY_DATA;
+    }
 }
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 e029e54be..a1c166143 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
@@ -16,14 +16,13 @@ import net.minecraft.core.util.helper.Direction;
 import net.minecraft.core.util.helper.Side;
 import net.minecraft.core.world.World;
 import net.minecraft.core.world.WorldSource;
-import net.minecraft.core.world.pos.TilePos;
 import net.minecraft.core.world.pos.TilePosc;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 import org.joml.primitives.AABBd;
 import org.joml.primitives.AABBdc;
 
-public class BlockLogicLever extends BlockLogic implements ISupportable {
+public class BlockLogicLever extends BlockLogic implements ISupportable, BlockLogic.MatcherDataEquivalency.Masked {
     public static final int MASK_POWERED = 0b0001_0000;
     public static final int MASK_ROTATION = 0b0000_1111;
     public static final int ROTATION_EAST = 1;
@@ -40,8 +39,8 @@ public class BlockLogicLever extends BlockLogic implements ISupportable {
     }
 
     @Override
-    protected int getMatcherEquivalenceData(final int data) {
-        return data & MASK_POWERED;
+    public int getMatcherDataEquivalencyMask() {
+        return MASK_POWERED;
     }
 
     @Override
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 e1066c02f..13508dc26 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
@@ -11,6 +11,7 @@ import net.minecraft.core.enums.EnumDropCause;
 import net.minecraft.core.enums.PlacementMode;
 import net.minecraft.core.item.ItemStack;
 import net.minecraft.core.util.helper.Axis;
+import net.minecraft.core.util.helper.Direction;
 import net.minecraft.core.util.helper.Side;
 import net.minecraft.core.world.LevelListener;
 import net.minecraft.core.world.World;
@@ -32,11 +33,6 @@ public class BlockLogicMatcher extends BlockLogicAxisAligned {
         this.isActive = isActive;
     }
 
-    @Override
-    protected boolean isSameBlockForMatcher(final @NotNull Block<?> other) {
-        return other.getLogic() instanceof BlockLogicMatcher matcher && (this.isActive == matcher.isActive);
-    }
-
     @Override
     public int tickDelay() {
         return 4;
@@ -113,40 +109,45 @@ public class BlockLogicMatcher extends BlockLogicAxisAligned {
     }
 
     public void updateState(@NotNull World world, @NotNull TilePosc tilePos) {
-        Axis a = BlockLogicAxisAligned.metaToAxis(world.getBlockData(tilePos) & BlockLogicAxisAligned.MASK_DIRECTION);
+        final int meta = world.getBlockData(tilePos);
+        final Axis axis = BlockLogicAxisAligned.metaToAxis(meta & BlockLogicAxisAligned.MASK_DIRECTION);
 
-        TilePos front = new TilePos(tilePos);
-        TilePos back = new TilePos(tilePos);
-        switch (a) {
-            case X:
-                front.x += 1;
-                back.x -= 1;
-                break;
-            case Y:
-                front.y += 1;
-                back.y -= 1;
-                break;
-            case Z:
-            default:
-                front.z += 1;
-                back.z -= 1;
-                break;
+        final TilePos frontPos = new TilePos(tilePos);
+        final TilePos backPos = new TilePos(tilePos);
+        switch (axis) {
+            default -> { return; }
+            case X -> {
+                frontPos.x += 1;
+                backPos.x -= 1;
+            }
+            case Y -> {
+                frontPos.y += 1;
+                backPos.y -= 1;
+            }
+            case Z -> {
+                frontPos.z += 1;
+                backPos.z -= 1;
+            }
         }
 
+        final Block<?> front = world.getBlockType(frontPos);
+        final Block<?> back = world.getBlockType(backPos);
 
-        final @Nullable Block<?> frontBlock = world.getBlockType(front);
-        final boolean backMatchesFront = frontBlock.isEquivalent(world, front, back);
+        final boolean isUnionImpl =
+            (front == back) ||
+            front.getLogic().getClass() == back.getLogic().getClass();
+        
+        final boolean matches = (isUnionImpl)
+            ? front.isEquivalent(world, frontPos, backPos, back)
+            : front.isEquivalent(world, frontPos, backPos, back) && back.isEquivalent(world, backPos, frontPos, front);
 
-        if (!this.isActive && backMatchesFront) {
-            world.setBlockTypeDataNotify(tilePos, Blocks.MATCHER_ACTIVE, world.getBlockData(tilePos));
-        } else if (this.isActive && !backMatchesFront) {
-            world.setBlockTypeDataNotify(tilePos, Blocks.MATCHER, world.getBlockData(tilePos));
-        }
+        if (this.isActive == matches) return;
+        world.setBlockTypeDataNotify(tilePos, (matches) ? Blocks.MATCHER_ACTIVE : Blocks.MATCHER, meta);
     }
 
     @Override
     public void animationTick(@NotNull World world, @NotNull TilePosc tilePos, @NotNull Random rand) {
-        if (!this.isActive) {return;}
+        if (!this.isActive) return;
         world.playBlockEvent(tilePos, LevelListener.EVENT_CONDUIT_SURFACE_PARTICLES, packEventConduitData(null, 10));
     }
 
@@ -168,4 +169,14 @@ public class BlockLogicMatcher extends BlockLogicAxisAligned {
         return new ItemStack[]{new ItemStack(Blocks.MATCHER)};
     }
 
+    public static void updateNeighboringMatchers(
+        final @NotNull World world,
+        final @NotNull TilePosc origin
+    ) {
+        final var query = new TilePos();
+        for (final var dir : Direction.all) {
+            final var b = world.getBlockType(origin.add(dir, query));
+            if (b.getLogic() instanceof BlockLogicMatcher matcher) matcher.scheduleUpdate(world, query);
+        }
+    }
 }
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicMeshGold.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicMeshGold.java
index db9199595..9cbe60533 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicMeshGold.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicMeshGold.java
@@ -1,6 +1,5 @@
 package net.minecraft.core.block;
 
-import net.minecraft.core.block.entity.TileEntity;
 import net.minecraft.core.block.entity.TileEntityMeshGold;
 import net.minecraft.core.entity.Entity;
 import net.minecraft.core.entity.EntityItem;
@@ -12,7 +11,7 @@ import net.minecraft.core.world.pos.TilePosc;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
-public class BlockLogicMeshGold extends BlockLogicMesh {
+public class BlockLogicMeshGold extends BlockLogicMesh implements BlockLogic.MatcherEntityEquivalency<TileEntityMeshGold> {
     public BlockLogicMeshGold(@NotNull Block<?> block) {
         super(block);
         block.withEntity(TileEntityMeshGold::new);
@@ -44,15 +43,13 @@ public class BlockLogicMeshGold extends BlockLogicMesh {
     }
 
     @Override
-    public boolean isEquivalent(@NotNull World world, @NotNull TilePosc thisPos, @NotNull TilePosc thatPos) {
-        if (super.isEquivalent(world, thisPos, thatPos)) {
-            TileEntity thisEntity = world.getTileEntity(thisPos);
-            TileEntity otherEntity = world.getTileEntity(thatPos);
-            if (thisEntity instanceof TileEntityMeshGold thisMesh && otherEntity instanceof TileEntityMeshGold otherMesh) {
-                return stacksAreSame(thisMesh.filterItem, otherMesh.filterItem);
-            }
-        }
-        return false;
+    public @NotNull Class<TileEntityMeshGold> getEntityEquivalencyClass() {
+        return TileEntityMeshGold.class;
+    }
+
+    @Override
+    public boolean isEquivalentEntityForMatcherImpl(@NotNull TileEntityMeshGold thisMesh, @NotNull TileEntityMeshGold thatMesh) {
+        return stacksAreSame(thisMesh.filterItem, thatMesh.filterItem);
     }
 
     public boolean stacksAreSame(@Nullable ItemStack a, @Nullable ItemStack b) {
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicMobSpawner.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicMobSpawner.java
index 6f1777a07..464e5c4e3 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicMobSpawner.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicMobSpawner.java
@@ -15,7 +15,7 @@ import net.minecraft.core.world.pos.TilePosc;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
-public class BlockLogicMobSpawner extends BlockLogicMobSpawnerDeactivated {
+public class BlockLogicMobSpawner extends BlockLogicMobSpawnerDeactivated implements BlockLogic.MatcherEntityEquivalency<TileEntityMobSpawner> {
 
     public BlockLogicMobSpawner(@NotNull Block<?> block) {
         super(block);
@@ -102,12 +102,14 @@ public class BlockLogicMobSpawner extends BlockLogicMobSpawnerDeactivated {
     }
 
     @Override
-    public boolean isEquivalent(@NotNull World world, @NotNull TilePosc thisPos, @NotNull TilePosc thatPos) {
-        assert world.getBlockType(thisPos) == this.block;
-        if (world.getBlockType(thatPos) != this.block) return false;
-        if (!(world.getTileEntity(thisPos) instanceof TileEntityMobSpawner thisSpawner) ||
-            !(world.getTileEntity(thatPos) instanceof TileEntityMobSpawner thatSpawner))
-            return false;
+    public @NotNull Class<TileEntityMobSpawner> getEntityEquivalencyClass() {
+        return TileEntityMobSpawner.class;
+    }
+
+    @Override
+    public boolean isEquivalentEntityForMatcherImpl(
+        @NotNull TileEntityMobSpawner thisSpawner, @NotNull TileEntityMobSpawner thatSpawner
+    ) {
         final @Nullable String thisId, thatId; {
             thisId = thisSpawner.getMobId();
             thatId = thatSpawner.getMobId();
@@ -115,5 +117,5 @@ public class BlockLogicMobSpawner extends BlockLogicMobSpawnerDeactivated {
         if (thisId == thatId) return true;
         if (thisId == null || thatId == null) return false;
         return thisId.equals(thatId);
-    }
+   }
 }
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 83a4f2f06..b9e7533fa 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
@@ -13,6 +13,7 @@ import net.minecraft.core.sound.SoundCategory;
 import net.minecraft.core.util.helper.Direction;
 import net.minecraft.core.util.helper.Side;
 import net.minecraft.core.world.World;
+import net.minecraft.core.world.WorldSource;
 import net.minecraft.core.block.material.Material;
 import net.minecraft.core.world.pos.TilePos;
 import net.minecraft.core.world.pos.TilePosc;
@@ -20,27 +21,25 @@ import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 import org.slf4j.Logger;
 
-public class BlockLogicNote extends BlockLogic {
+public class BlockLogicNote extends BlockLogic implements BlockLogic.MatcherDataEquivalency {
     private static final Logger LOGGER = LogUtils.getLogger();
     public static final int MASK_NOTE = 0b0011_1111;
     public static final int MASK_POWERED = 0b1000_0000;
 
     @Override
-    public boolean isEquivalent(@NotNull World world, @NotNull TilePosc thisPos, @NotNull TilePosc thatPos) {
-        assert world.getBlockType(thisPos) == this.block;
-        if (world.getBlockType(thatPos) != this.block) return false;
-        final int thisData, thatData; {
-            thisData = world.getBlockData(thisPos);
-            thatData = world.getBlockData(thatPos);
+    public boolean isEquivalent(
+        @NotNull WorldSource world,
+        @NotNull TilePosc thisPos, @NotNull TilePosc thatPos,
+        @NotNull Block<?> thatBlock
+    ) {
+        if (!this.isEquivalentBlockForMatcher(thatBlock)) return false;
+        if (!this.isEquivalentDataForMatcher(world, thisPos, thatPos)) return false;
+        final @NotNull Instrument thisInstrument, thatInstrument; {
+            final var query = new TilePos();
+            thisInstrument = Instrument.getInstrumentFromBlock(world.getBlockType(thisPos.down(query)));
+            thatInstrument = Instrument.getInstrumentFromBlock(world.getBlockType(thatPos.down(query)));
         }
-        if (thisData != thatData) return false; // We check note as well because it sucks otherwise
-        // // TODO instrument update
-        // final @NotNull Instrument thisInstru, thatInstru; {
-        //     final var query = new TilePos();
-        //     thisInstru = Instrument.getInstrumentFromBlock(world.getBlockType(thisPos.down(query)));
-        //     thatInstru = Instrument.getInstrumentFromBlock(world.getBlockType(thatPos.down(query)));
-        // }
-        // if (thisInstru != thatInstru) return false; // reference eq
+        if (thisInstrument != thatInstrument) return false; // reference eq
         return true;
     }
 
@@ -56,10 +55,12 @@ public class BlockLogicNote extends BlockLogic {
         final int data = world.getBlockData(tilePos);
         final boolean hasSignal = world.hasNeighborSignal(tilePos);
         final boolean isPowered = isPowered(data);
-        if (isPowered == hasSignal) return;
-        world.setBlockData(tilePos, data ^ MASK_POWERED);
-        if (hasSignal) triggerNote(world, tilePos);
-        world.notifyBlocksOfNeighborChange(tilePos, this.block);
+        if (isPowered != hasSignal) {
+            world.setBlockData(tilePos, data ^ MASK_POWERED);
+            if (hasSignal) triggerNote(world, tilePos);
+            // world.notifyBlocksOfNeighborChange(tilePos, this.block);
+        }
+        BlockLogicMatcher.updateNeighboringMatchers(world, tilePos);
     }
 
     @Override
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicOverlayPebbles.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicOverlayPebbles.java
index abacf19c0..8380cd13d 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicOverlayPebbles.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicOverlayPebbles.java
@@ -12,14 +12,13 @@ import net.minecraft.core.item.ItemStack;
 import net.minecraft.core.util.helper.Side;
 import net.minecraft.core.world.World;
 import net.minecraft.core.world.WorldSource;
-import net.minecraft.core.world.pos.TilePos;
 import net.minecraft.core.world.pos.TilePosc;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 import org.joml.primitives.AABBdc;
 
 public class BlockLogicOverlayPebbles extends BlockLogic
-    implements ISupportable
+    implements ISupportable, BlockLogic.MatcherDataEquivalency
 {
     public static final int MAX_PEBBLES = 3;
     public static final int MASK_PEBBLE_AMOUNT = 0b0000_0011;
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicPlanksPainted.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicPlanksPainted.java
index 60d30cd09..5df791596 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicPlanksPainted.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicPlanksPainted.java
@@ -12,7 +12,7 @@ import net.minecraft.core.world.pos.TilePosc;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
-public class BlockLogicPlanksPainted extends BlockLogic implements IPainted
+public class BlockLogicPlanksPainted extends BlockLogic implements IPainted, BlockLogic.MatcherDataEquivalency
 {
     public BlockLogicPlanksPainted(@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 f2af23610..6c3a6c0b6 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
@@ -7,6 +7,8 @@ import net.minecraft.core.block.support.ISupport;
 import net.minecraft.core.block.support.PartialSupport;
 import net.minecraft.core.entity.Entity;
 import net.minecraft.core.entity.EntityItem;
+import net.minecraft.core.entity.animal.MobPig;
+import net.minecraft.core.entity.player.Player;
 import net.minecraft.core.enums.EnumDropCause;
 import net.minecraft.core.item.ItemStack;
 import net.minecraft.core.item.Items;
@@ -26,7 +28,7 @@ import org.joml.primitives.AABBdc;
 
 import java.util.Random;
 
-public class BlockLogicPortal extends BlockLogicTransparent implements IPainted {
+public class BlockLogicPortal extends BlockLogicTransparent implements IPainted, BlockLogic.MatcherDataEquivalency.Masked {
     public static final int MAX_PORTAL_SIZE = 32;
     public static final int MASK_COLOR = 0b1111_0000;
     public static final int MASK_ROTATION = 0b0000_0001;
@@ -41,8 +43,8 @@ public class BlockLogicPortal extends BlockLogicTransparent implements IPainted
     public final @NotNull Block<?> portalTrigger;
 
     @Override
-    protected int getMatcherEquivalenceData(final int data) {
-        return data & ~MASK_ROTATION;
+    public int getMatcherDataEquivalencyMask() {
+        return ~(MASK_ROTATION | MASK_SOURCE);
     }
 
     public BlockLogicPortal(@NotNull Block<?> block, @NotNull Dimension targetDimension, @NotNull Block<?> portalMaterial, @NotNull Block<?> portalTrigger) {
@@ -319,7 +321,11 @@ public class BlockLogicPortal extends BlockLogicTransparent implements IPainted
                 }
             }
         }
-        if (entity.vehicle == null && entity.passenger == null) {
+        if (entity instanceof Player ridingPlayer && ridingPlayer.vehicle instanceof MobPig) {
+            ridingPlayer.handlePortal(this.block.id(), getColor(world, tilePos));
+        } else if (entity instanceof MobPig && entity.passenger instanceof Player riderPlayer) {
+            riderPlayer.handlePortal(this.block.id(), getColor(world, tilePos));
+        } else if (entity.vehicle == null && entity.passenger == null) {
             entity.handlePortal(this.block.id(), getColor(world, tilePos));
         }
     }
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 ddf3577e6..c90f2b9ba 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
@@ -12,7 +12,6 @@ import net.minecraft.core.util.helper.Side;
 import net.minecraft.core.sound.SoundCategory;
 import net.minecraft.core.world.World;
 import net.minecraft.core.world.WorldSource;
-import net.minecraft.core.world.pos.TilePos;
 import net.minecraft.core.world.pos.TilePosc;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
@@ -22,13 +21,19 @@ import org.joml.primitives.AABBdc;
 import java.util.List;
 import java.util.Random;
 
-public class BlockLogicPressurePlate<T extends Entity> extends BlockLogic implements IPaintable, ISupportable {
+public class BlockLogicPressurePlate<T extends Entity> extends BlockLogic
+implements
+    IPaintable,
+    ISupportable,
+    BlockLogic.MatcherDataEquivalency.Masked
+{
     public static final int MASK_POWERED = 0b0000_0001;
 
     @Override
-    protected int getMatcherEquivalenceData(final int data) {
-        return data & MASK_POWERED;
+    public int getMatcherDataEquivalencyMask() {
+        return MASK_POWERED;
     }
+
     public static final int MASK_SIDE = 0b0000_1110;
     private final @NotNull Class<T> triggerMobClass;
 
@@ -117,27 +122,39 @@ public class BlockLogicPressurePlate<T extends Entity> extends BlockLogic implem
         Direction dir = sideFromMeta(meta).direction();
         boolean isSteppedOn = false;
         float pixel = 0.125F;
-        List<T> list = switch (sideFromMeta(meta)) {
+        AABBdc box = switch (sideFromMeta(meta)) {
             case BOTTOM ->
-                world.getEntitiesWithinAABB(this.triggerMobClass, new AABBd(pixel, 0.0F, pixel, 1.0F - pixel, (isPressed ? pixel / 2f : pixel), 1.0F - pixel).translate(tilePos.x(), tilePos.y(), tilePos.z()));
+                new AABBd(pixel, 0.0F, pixel, 1.0F - pixel, (isPressed ? pixel / 2f : pixel), 1.0F - pixel).translate(tilePos.x(), tilePos.y(), tilePos.z());
             case TOP ->
-                world.getEntitiesWithinAABB(this.triggerMobClass, new AABBd(pixel, 1f - (isPressed ? pixel / 2f : pixel), pixel, 1.0F - pixel, 1f, 1.0F - pixel).translate(tilePos.x(), tilePos.y(), tilePos.z()));
+                new AABBd(pixel, 1f - (isPressed ? pixel / 2f : pixel), pixel, 1.0F - pixel, 1f, 1.0F - pixel).translate(tilePos.x(), tilePos.y(), tilePos.z());
             case NORTH ->
-                world.getEntitiesWithinAABB(this.triggerMobClass, new AABBd(pixel, pixel, 0, 1 - pixel, 1 - pixel, (isPressed ? pixel / 2f : pixel)).translate(tilePos.x(), tilePos.y(), tilePos.z()));
+                new AABBd(pixel, pixel, 0, 1 - pixel, 1 - pixel, (isPressed ? pixel / 2f : pixel)).translate(tilePos.x(), tilePos.y(), tilePos.z());
             case SOUTH ->
-                world.getEntitiesWithinAABB(this.triggerMobClass, new AABBd(pixel, pixel, 1f - (isPressed ? pixel / 2f : pixel), 1 - pixel, 1 - pixel, 1).translate(tilePos.x(), tilePos.y(), tilePos.z()));
+                new AABBd(pixel, pixel, 1f - (isPressed ? pixel / 2f : pixel), 1 - pixel, 1 - pixel, 1).translate(tilePos.x(), tilePos.y(), tilePos.z());
             case WEST ->
-                world.getEntitiesWithinAABB(this.triggerMobClass, new AABBd(0, pixel, pixel, (isPressed ? pixel / 2f : pixel), 1 - pixel, 1 - pixel).translate(tilePos.x(), tilePos.y(), tilePos.z()));
+                new AABBd(0, pixel, pixel, (isPressed ? pixel / 2f : pixel), 1 - pixel, 1 - pixel).translate(tilePos.x(), tilePos.y(), tilePos.z());
             default ->
-                world.getEntitiesWithinAABB(this.triggerMobClass, new AABBd(1 - (isPressed ? pixel / 2f : pixel), pixel, pixel, 1, 1 - pixel, 1 - pixel).translate(tilePos.x(), tilePos.y(), tilePos.z()));
+                new AABBd(1 - (isPressed ? pixel / 2f : pixel), pixel, pixel, 1, 1 - pixel, 1 - pixel).translate(tilePos.x(), tilePos.y(), tilePos.z());
         };
 
+        List<T> list = world.getEntitiesWithinAABB(this.triggerMobClass, box);
         for (T t : list) {
             if (t.canInteract()) {
                 isSteppedOn = true;
                 break;
             }
         }
+
+        if (!isSteppedOn) {
+            List<Entity> vehicles = world.getEntitiesWithinAABB(Entity.class, box);
+            for (Entity vehicle : vehicles) {
+                Entity rider = vehicle.getPassenger();
+                if (this.triggerMobClass.isInstance(rider) && rider.canInteract()) {
+                    isSteppedOn = true;
+                    break;
+                }
+            }
+        }
         if (isSteppedOn && !isPressed) {
             world.setBlockData(tilePos, meta | MASK_POWERED);
             world.markBlockNeedsUpdate(tilePos);
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 cb789a486..94561ecb9 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
@@ -23,7 +23,7 @@ import org.joml.primitives.AABBdc;
 import java.util.ArrayList;
 import java.util.List;
 
-public class BlockLogicRail extends BlockLogic implements ISupportable {
+public class BlockLogicRail extends BlockLogic implements ISupportable, BlockLogic.MatcherDataEquivalency {
     public static final int MASK_DIRECTION = 0b0000_0111;
     public static final int MASK_POWERED = 0b0000_1000;
     public final boolean isPowered;
@@ -39,20 +39,17 @@ public class BlockLogicRail extends BlockLogic implements ISupportable {
     }
 
     @Override
-    protected int getMatcherEquivalenceData(int data) {
+    public boolean isEquivalentDataForMatcherImpl(int thisData, int thatData) {
         final int dirMask = (this.isPowered) ? MASK_DIRECTION : MASK_DIRECTION | MASK_POWERED;
-        var railDir = RailDirection.getFromMeta(data & dirMask);
-        if (railDir.isStraight()) {
-            railDir = RailDirection.STRAIGHT_NS;
-        } else if (railDir.isSloped()) {
-            railDir = RailDirection.SLOPE_E;
-        } else if (railDir.isCurved()) {
-            assert !this.isPowered;
-            railDir = RailDirection.TURN_ES;
-        }
-        data &= ~MASK_DIRECTION;
-        data |= railDir.meta & dirMask;
-        return data;
+        if (this.isPowered && (thisData & MASK_POWERED) != (thatData & MASK_POWERED)) return false;
+
+        final var thisDir = RailDirection.getFromMeta(thisData & dirMask);;
+        final var thatDir = RailDirection.getFromMeta(thatData & dirMask);;
+
+        if (thisDir.isStraight()) return thatDir.isStraight();
+        if (thisDir.isCurved()) return thatDir.isCurved();
+        if (thisDir.isSloped()) return thatDir.isSloped();
+        return false;
     }
 
     public @NotNull RailDirection getRailDirection(@NotNull WorldSource world, @NotNull TilePosc tilePos) {
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 3accdf548..49d86f1cc 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
@@ -23,7 +23,7 @@ import org.jetbrains.annotations.Nullable;
 
 import java.util.Random;
 
-public class BlockLogicRepeater extends BlockLogic implements ISupportable {
+public class BlockLogicRepeater extends BlockLogic implements ISupportable, BlockLogic.MatcherDataEquivalency.Masked {
     public static final double[] torchPosOffset = {
         -0.0625D, 0.0625D, 0.1875D, 0.3125D
     };
@@ -46,13 +46,8 @@ public class BlockLogicRepeater extends BlockLogic implements ISupportable {
     }
 
     @Override
-    protected boolean isSameBlockForMatcher(final @NotNull Block<?> other) {
-        return other.getLogic() instanceof BlockLogicRepeater repeater && (this.isRepeaterPowered == repeater.isRepeaterPowered);
-    }
-
-    @Override
-    protected int getMatcherEquivalenceData(final int data) {
-        return data & MASK_TICK_DELAY;
+    public int getMatcherDataEquivalencyMask() {
+        return MASK_TICK_DELAY;
     }
 
     @Override
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicRope.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicRope.java
index ffd78e7c0..6e1a1bca6 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicRope.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicRope.java
@@ -161,10 +161,4 @@ public class BlockLogicRope
     public ItemStack[] getBreakResult(@NotNull World world, @NotNull EnumDropCause dropCause, int data, @Nullable TileEntity tileEntity) {
         return new ItemStack[]{new ItemStack(Items.ROPE)};
     }
-
-    @Override
-    protected int getMatcherEquivalenceData(int data) {
-        // return data & ~MASK_ROPE_CONNECT;
-        return 0;
-    }
 }
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 f8ce3cecf..772126c65 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,11 +16,6 @@ public 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 41ff6a93b..c3cd42236 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
@@ -20,7 +20,7 @@ import org.joml.primitives.AABBdc;
 
 import java.util.Random;
 
-public class BlockLogicRubyglassCrystal extends BlockLogicTransparent {
+public class BlockLogicRubyglassCrystal extends BlockLogicTransparent implements BlockLogic.MatcherDataEquivalency {
 	public static final int BREAK_STAGES = 3;
 	private final @NotNull AABBd checkAABB = new AABBd();
 	public BlockLogicRubyglassCrystal(@NotNull Block<?> block) {
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicSaplingBase.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicSaplingBase.java
index 204c5b483..8ec08268a 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicSaplingBase.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicSaplingBase.java
@@ -3,7 +3,7 @@ package net.minecraft.core.block;
 import java.util.Random;
 
 import net.minecraft.core.entity.player.Player;
-import net.minecraft.core.item.IBonemealable;
+// import net.minecraft.core.item.IBonemealable;
 import net.minecraft.core.item.ItemStack;
 import net.minecraft.core.util.helper.MathHelper;
 import net.minecraft.core.util.helper.Side;
@@ -13,7 +13,7 @@ import net.minecraft.core.world.pos.TilePosc;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
-public abstract class BlockLogicSaplingBase extends BlockLogicFlower implements IBonemealable {
+public abstract class BlockLogicSaplingBase extends BlockLogicFlower /*implements IBonemealable*/ {
     public boolean canGrowOnSand = false;
 
     public BlockLogicSaplingBase(@NotNull Block<?> block) {
@@ -68,9 +68,4 @@ public abstract class BlockLogicSaplingBase extends BlockLogicFlower implements
     }
 
     public abstract void growTree(@NotNull World world, @NotNull TilePosc tilePos, @NotNull Random random);
-
-    @Override
-    protected int getMatcherEquivalenceData(int data) {
-        return 0;
-    }
 }
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 55b145a16..8bcc92e0d 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
@@ -23,7 +23,12 @@ import org.jetbrains.annotations.Nullable;
 import org.joml.primitives.AABBd;
 import org.joml.primitives.AABBdc;
 
-public class BlockLogicSign extends BlockLogic implements IPaintable {
+public class BlockLogicSign extends BlockLogic 
+implements 
+    IPaintable, 
+    BlockLogic.MatcherDataEquivalency.Masked,
+    BlockLogic.MatcherEntityEquivalency<TileEntitySign>
+{
     public static final int MASK_SIDE = 0b0000_1111;
     public static final int SIDE_NORTH = 2;
     public static final int SIDE_SOUTH = 3;
@@ -41,18 +46,24 @@ public class BlockLogicSign extends BlockLogic implements IPaintable {
     }
 
     @Override
-    public boolean isEquivalent(@NotNull World world, @NotNull TilePosc thisPos, @NotNull TilePosc thatPos) {
-        assert world.getBlockType(thisPos) == this.block;
-        if (world.getBlockType(thatPos) != this.block) return false;
-        if (((world.getBlockData(thisPos) ^ world.getBlockData(thatPos)) & ~MASK_SIDE) != 0) return false;
-        if (!(world.getTileEntity(thisPos) instanceof TileEntitySign thisSign) ||
-            !(world.getTileEntity(thatPos) instanceof TileEntitySign thatSign))
-            return false;
+    public int getMatcherDataEquivalencyMask() {
+        return ~MASK_SIDE;
+    }
+
+    @Override
+    public @NotNull Class<TileEntitySign> getEntityEquivalencyClass() {
+        return TileEntitySign.class;
+    }
+
+    @Override
+    public boolean isEquivalentEntityForMatcherImpl(
+        @NotNull TileEntitySign thisSign, @NotNull TileEntitySign thatSign
+    ) {
         if (thisSign.getPicture() != thatSign.getPicture()) return false;
 
         boolean isAllBlank = true;
-        assert thisSign.signText.length == 4 && thatSign.signText.length == 4;
-        for (int i = 0; i < 4; i++) {
+        assert thisSign.signText.length == thatSign.signText.length;
+        for (int i = 0; i < thisSign.signText.length; i++) {
             final boolean thisIsBlank, thatIsBlank; {
                 thisIsBlank = thisSign.signText[i].isEmpty() || thisSign.signText[i].isBlank();
                 thatIsBlank = thatSign.signText[i].isEmpty() || thatSign.signText[i].isBlank();
@@ -64,7 +75,7 @@ public class BlockLogicSign extends BlockLogic implements IPaintable {
 
         if (thisSign.getColor() != thatSign.getColor()) return false;
         if (thisSign.isGlowing() != thatSign.isGlowing()) return false;
-        for (int i = 0; i < 4; i++) {
+        for (int i = 0; i < thisSign.signText.length; i++) {
             if (!thisSign.signText[i].equals(thatSign.signText[i])) return false;
         }
         return true;
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicSlab.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicSlab.java
index cd85f0a6f..7d9e75bef 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicSlab.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicSlab.java
@@ -27,7 +27,7 @@ import org.joml.primitives.AABBdc;
 
 import java.util.Random;
 
-public class BlockLogicSlab extends BlockLogic
+public class BlockLogicSlab extends BlockLogic implements BlockLogic.MatcherDataEquivalency
 {
     public @NotNull Block<?> modelBlock;
     public int modelBlockMetadata;
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 f9b83120b..9c4df7e19 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
@@ -26,7 +26,7 @@ import org.jetbrains.annotations.Nullable;
 import org.joml.primitives.AABBd;
 import org.joml.primitives.AABBdc;
 
-public class BlockLogicSpikes extends BlockLogic
+public class BlockLogicSpikes extends BlockLogic implements BlockLogic.MatcherDataEquivalency.Masked
 {
     public static final int MASK_POWERED = 0b0000_0001;
     public static final int MASK_DIRECTION = 0b0000_1110;
@@ -39,8 +39,8 @@ public class BlockLogicSpikes extends BlockLogic
     }
 
     @Override
-    protected int getMatcherEquivalenceData(final int data) {
-        return data & MASK_POWERED;
+    public int getMatcherDataEquivalencyMask() {
+        return MASK_POWERED;
     }
 
     @Override
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 9d86f24b0..599e968d3 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
@@ -1,5 +1,6 @@
 package net.minecraft.core.block;
 
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Random;
 
@@ -16,82 +17,21 @@ import net.minecraft.core.entity.Entity;
 import net.minecraft.core.enums.EnumDropCause;
 import net.minecraft.core.item.ItemStack;
 import net.minecraft.core.util.helper.Direction;
+import net.minecraft.core.util.helper.MathHelper;
 import net.minecraft.core.util.helper.Side;
+import net.minecraft.core.util.phys.BoundingVolume;
+import net.minecraft.core.util.phys.HitResult;
 import net.minecraft.core.world.WorldSource;
 import net.minecraft.core.world.World;
 import net.minecraft.core.world.pos.TilePosc;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 import org.joml.Vector3d;
+import org.joml.Vector3dc;
 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
-//    {
-//        // EAST UP
-//        System.out.println("EAST UP");
-//        BoundingVolume vol = BoundingVolume.newFilledVolume();
-//        vol.carveVolume(0, 8, 0, 7, 15, 15);
-//        vol.bake();
-//        selectionVolumes.put(0b0000, vol);
-//
-//        // WEST UP
-//        System.out.println("WEST UP");
-//        vol = BoundingVolume.newFilledVolume();
-//        vol.carveVolume(8, 8, 0, 15, 15, 15);
-//        vol.bake();
-//        selectionVolumes.put(0b0001, vol);
-//
-//        // SOUTH UP
-//        System.out.println("SOUTH UP");
-//        vol = BoundingVolume.newFilledVolume();
-//        vol.carveVolume(0, 8, 0, 15, 15, 7);
-//        vol.bake();
-//        selectionVolumes.put(0b0010, vol);
-//
-//        // NORTH UP
-//        System.out.println("NORTH UP");
-//        vol = BoundingVolume.newFilledVolume();
-//        vol.carveVolume(0, 8, 8, 15, 15, 15);
-//        vol.bake();
-//        selectionVolumes.put(0b0011, vol);
-//
-//        // EAST DOWN
-//        System.out.println("EAST DOWN");
-//        vol = BoundingVolume.newFilledVolume();
-//        vol.carveVolume(0, 0, 0, 7, 7, 15);
-//        vol.bake();
-//        selectionVolumes.put(0b1000, vol);
-//
-//        // WEST DOWN
-//        System.out.println("WEST DOWN");
-//        vol = BoundingVolume.newFilledVolume();
-//        vol.carveVolume(8, 0, 0, 15, 7, 15);
-//        vol.bake();
-//        selectionVolumes.put(0b1001, vol);
-//
-//        // SOUTH DOWN
-//        System.out.println("SOUTH DOWN");
-//        vol = BoundingVolume.newFilledVolume();
-//        vol.carveVolume(0, 0, 0, 15, 7, 7);
-//        vol.bake();
-//        selectionVolumes.put(0b1010, vol);
-//
-//        // NORTH DOWN
-//        System.out.println("NORTH DOWN");
-//        vol = BoundingVolume.newFilledVolume();
-//        vol.carveVolume(0, 0, 8, 15, 7, 15);
-//        vol.bake();
-//        selectionVolumes.put(0b1011, vol);
-//    }
+public class BlockLogicStairs extends BlockLogic implements BlockLogic.MatcherDataEquivalency.Masked {
     public static final int MASK_ROTATION_HORIZONTAL = 0b0000_0011;
     public static final int MASK_ROTATION_VERTICAL = 0b0000_1000;
 
@@ -176,6 +116,44 @@ public class BlockLogicStairs extends BlockLogic {
         PartialSupport.INSTANCE.up().right(),    // South
     };
 
+    private static AABBd @NotNull [] stairLocalParts(int hRotation, float stepYOffset) {
+        return switch (hRotation) {
+            case H_ROTATION_WEST -> new AABBd[]{
+                    new AABBd(0.0, stepYOffset, 0.0, 0.5, 0.5 + stepYOffset, 1.0),
+                    new AABBd(0.5, 0.0, 0.0, 1.0, 1.0, 1.0)
+            };
+            case H_ROTATION_EAST -> new AABBd[]{
+                    new AABBd(0.0, 0.0, 0.0, 0.5, 1.0, 1.0),
+                    new AABBd(0.5, stepYOffset, 0.0, 1.0, 0.5 + stepYOffset, 1.0)
+            };
+            case H_ROTATION_NORTH -> new AABBd[]{
+                    new AABBd(0.0, stepYOffset, 0.0, 1.0, 0.5 + stepYOffset, 0.5),
+                    new AABBd(0.0, 0.0, 0.5, 1.0, 1.0, 1.0)
+            };
+            default -> new AABBd[]{
+                    new AABBd(0.0, 0.0, 0.0, 1.0, 1.0, 0.5),
+                    new AABBd(0.0, stepYOffset, 0.5, 1.0, 0.5 + stepYOffset, 1.0)
+            };
+        };
+    }
+
+    private static int stairOrientIndex(int meta) {
+        return (meta & MASK_ROTATION_HORIZONTAL) | ((meta & MASK_ROTATION_VERTICAL) >> 1);
+    }
+
+    private static BoundingVolume @NotNull [] bakeStairSelectionVolumes() {
+        BoundingVolume @NotNull [] out = new BoundingVolume[8];
+        for (int orient = 0; orient < 8; orient++) {
+            int h = orient & MASK_ROTATION_HORIZONTAL;
+            float stepY = (orient & 4) != 0 ? 0.5f : 0.0f;
+            AABBd[] parts = stairLocalParts(h, stepY);
+            out[orient] = BoundingVolume.bakeFromBlockSpaceAabbs(parts[0], parts[1]);
+        }
+        return out;
+    }
+
+    private static final BoundingVolume @NotNull [] STAIR_SELECTION_VOLUME = bakeStairSelectionVolumes();
+
     public @NotNull Block<?> modelBlock;
 
     public BlockLogicStairs(@NotNull Block<?> block, @NotNull Block<?> modelBlock) {
@@ -188,6 +166,11 @@ public class BlockLogicStairs extends BlockLogic {
         this.block.withHardness(this.modelBlock.blockHardness);
         this.block.withBlastResistance(this.modelBlock.blastResistance / 3F);
     }
+  
+    @Override
+    public int getMatcherDataEquivalencyMask() {
+        return ~MASK_ROTATION_HORIZONTAL;
+    }
 
     @Override
     public @NotNull Material getMaterial() {
@@ -217,31 +200,48 @@ public class BlockLogicStairs extends BlockLogic {
 
     @Override
     public void getCollisionAABBs(@NotNull World world, @NotNull TilePosc tilePos, @NotNull AABBdc aabb, @NotNull List<@NotNull AABBdc> aabbList) {
+        ArrayList<AABBdc> parts = new ArrayList<>(2);
+        collectStairPartAABBs(world, tilePos, parts);
+        for (AABBdc box : parts) {
+            addIntersectingBoundingBox(aabb, box, aabbList);
+        }
+    }
+
+    public void collectStairPartAABBs(@NotNull World world, @NotNull TilePosc tilePos, @NotNull List<@NotNull AABBdc> out) {
+        int meta = world.getBlockData(tilePos);
+        int hRotation = meta & MASK_ROTATION_HORIZONTAL;
+        float stepYOffset = (meta & MASK_ROTATION_VERTICAL) != 0 ? 0.5f : 0.0f;
+        double px = tilePos.x();
+        double py = tilePos.y();
+        double pz = tilePos.z();
+        for (AABBd part : stairLocalParts(hRotation, stepYOffset)) {
+            out.add(new AABBd(part).translate(px, py, pz));
+        }
+    }
+
+    @Override
+    public @Nullable BoundingVolume getBoundingVolume(@NotNull World world, @NotNull TilePosc tilePos) {
+        return STAIR_SELECTION_VOLUME[stairOrientIndex(world.getBlockData(tilePos))];
+    }
+
+    @Override
+    public @Nullable HitResult collisionRayTrace(@NotNull World world, @NotNull TilePosc tilePos, @NotNull Vector3dc start, @NotNull Vector3dc end, boolean useSelectionAABB) {
         int meta = world.getBlockData(tilePos);
         int hRotation = meta & MASK_ROTATION_HORIZONTAL;
-        int vRotation = meta & MASK_ROTATION_VERTICAL;
-
-        float stepYOffset = vRotation != 0 ? 0.5f : 0.0f;
-
-        switch (hRotation) {
-            case H_ROTATION_WEST:
-                addIntersectingBoundingBox(aabb, new AABBd(0.0F, 0.0F + stepYOffset, 0.0F, 0.5F, 0.5F + stepYOffset, 1.0F).translate(tilePos.x(), tilePos.y(), tilePos.z()), aabbList);
-                addIntersectingBoundingBox(aabb, new AABBd(0.5F, 0.0F, 0.0F, 1.0F, 1.0F, 1.0F).translate(tilePos.x(), tilePos.y(), tilePos.z()), aabbList);
-                break;
-            case H_ROTATION_EAST:
-                addIntersectingBoundingBox(aabb, new AABBd(0.0F, 0.0F, 0.0F, 0.5F, 1.0F, 1.0F).translate(tilePos.x(), tilePos.y(), tilePos.z()), aabbList);
-                addIntersectingBoundingBox(aabb, new AABBd(0.5F, 0.0F + stepYOffset, 0.0F, 1.0F, 0.5F + stepYOffset, 1.0F).translate(tilePos.x(), tilePos.y(), tilePos.z()), aabbList);
-                break;
-            case H_ROTATION_NORTH:
-                addIntersectingBoundingBox(aabb, new AABBd(0.0F, 0.0F + stepYOffset, 0.0F, 1.0F, 0.5F + stepYOffset, 0.5F).translate(tilePos.x(), tilePos.y(), tilePos.z()), aabbList);
-                addIntersectingBoundingBox(aabb, new AABBd(0.0F, 0.0F, 0.5F, 1.0F, 1.0F, 1.0F).translate(tilePos.x(), tilePos.y(), tilePos.z()), aabbList);
-                break;
-            case H_ROTATION_SOUTH:
-            default:
-                addIntersectingBoundingBox(aabb, new AABBd(0.0F, 0.0F, 0.0F, 1.0F, 1.0F, 0.5F).translate(tilePos.x(), tilePos.y(), tilePos.z()), aabbList);
-                addIntersectingBoundingBox(aabb, new AABBd(0.0F, 0.0F + stepYOffset, 0.5F, 1.0F, 0.5F + stepYOffset, 1.0F).translate(tilePos.x(), tilePos.y(), tilePos.z()), aabbList);
-                break;
+        float stepYOffset = (meta & MASK_ROTATION_VERTICAL) != 0 ? 0.5f : 0.0f;
+        @Nullable HitResult closest = null;
+        double closestDistSq = Double.POSITIVE_INFINITY;
+        for (AABBd part : stairLocalParts(hRotation, stepYOffset)) {
+            @Nullable HitResult hit = traceRayAgainstLocalAabb(tilePos, start, end, part);
+            if (hit != null) {
+                double d2 = start.distanceSquared(hit.location);
+                if (d2 < closestDistSq) {
+                    closestDistSq = d2;
+                    closest = hit;
+                }
+            }
         }
+        return closest;
     }
 
     @Override
@@ -277,12 +277,6 @@ public class BlockLogicStairs extends BlockLogic {
     public int tickDelay() {
         return this.modelBlock.tickDelay();
     }
-//    @Override
-//    public BoundingVolume getBoundingVolume(World world, int x, int y, int z)
-//    {
-//        int meta = world.getBlockMetadata(x, y, z);
-//        return selectionVolumes.getOrDefault(meta & 0x0F, null);
-//    }
 
     @Override
     public void onEntityInside(@NotNull World world, @NotNull TilePosc tilePos, @NotNull Entity entity, @NotNull Vector3d velocityDirection) {
@@ -381,4 +375,106 @@ public class BlockLogicStairs extends BlockLogic {
     public @NotNull MaterialColor getMaterialColor() {
         return this.modelBlock.getMaterialColor();
     }
+
+    private static @Nullable HitResult traceRayAgainstLocalAabb(@NotNull TilePosc tilePos, @NotNull Vector3dc start, @NotNull Vector3dc end, @NotNull AABBdc bounds) {
+        double px = tilePos.x();
+        double py = tilePos.y();
+        double pz = tilePos.z();
+        Vector3d startInBlock = start.add(-px, -py, -pz, new Vector3d());
+        Vector3d endInBlock = end.add(-px, -py, -pz, new Vector3d());
+
+        @Nullable Vector3d minVX = MathHelper.vecClipX(startInBlock, endInBlock, bounds.minX(), new Vector3d());
+        @Nullable Vector3d maxVX = MathHelper.vecClipX(startInBlock, endInBlock, bounds.maxX(), new Vector3d());
+        @Nullable Vector3d minVY = MathHelper.vecClipY(startInBlock, endInBlock, bounds.minY(), new Vector3d());
+        @Nullable Vector3d maxVY = MathHelper.vecClipY(startInBlock, endInBlock, bounds.maxY(), new Vector3d());
+        @Nullable Vector3d minVZ = MathHelper.vecClipZ(startInBlock, endInBlock, bounds.minZ(), new Vector3d());
+        @Nullable Vector3d maxVZ = MathHelper.vecClipZ(startInBlock, endInBlock, bounds.maxZ(), new Vector3d());
+
+        if (!isVecInsideYzBounds(bounds, minVX)) minVX = null;
+        if (!isVecInsideYzBounds(bounds, maxVX)) maxVX = null;
+        if (!isVecInsideXzBounds(bounds, minVY)) minVY = null;
+        if (!isVecInsideXzBounds(bounds, maxVY)) maxVY = null;
+        if (!isVecInsideXyBounds(bounds, minVZ)) minVZ = null;
+        if (!isVecInsideXyBounds(bounds, maxVZ)) maxVZ = null;
+
+        @Nullable Vector3d clip = null;
+        @Nullable Side side = null;
+        double bestD2 = Double.POSITIVE_INFINITY;
+
+        if (minVX != null) {
+            double d2 = startInBlock.distanceSquared(minVX);
+            if (d2 < bestD2) {
+                bestD2 = d2;
+                clip = minVX;
+                side = Side.WEST;
+            }
+        }
+        if (maxVX != null) {
+            double d2 = startInBlock.distanceSquared(maxVX);
+            if (d2 < bestD2) {
+                bestD2 = d2;
+                clip = maxVX;
+                side = Side.EAST;
+            }
+        }
+        if (minVY != null) {
+            double d2 = startInBlock.distanceSquared(minVY);
+            if (d2 < bestD2) {
+                bestD2 = d2;
+                clip = minVY;
+                side = Side.BOTTOM;
+            }
+        }
+        if (maxVY != null) {
+            double d2 = startInBlock.distanceSquared(maxVY);
+            if (d2 < bestD2) {
+                bestD2 = d2;
+                clip = maxVY;
+                side = Side.TOP;
+            }
+        }
+        if (minVZ != null) {
+            double d2 = startInBlock.distanceSquared(minVZ);
+            if (d2 < bestD2) {
+                bestD2 = d2;
+                clip = minVZ;
+                side = Side.NORTH;
+            }
+        }
+        if (maxVZ != null) {
+            double d2 = startInBlock.distanceSquared(maxVZ);
+            if (d2 < bestD2) {
+                bestD2 = d2;
+                clip = maxVZ;
+                side = Side.SOUTH;
+            }
+        }
+
+        if (clip == null) {
+            return null;
+        }
+
+        return new HitResult.Tile(tilePos, side, clip.add(px, py, pz, new Vector3d()));
+    }
+
+    private static boolean isVecInsideYzBounds(AABBdc bounds, @Nullable Vector3dc v) {
+        if (v == null) {
+            return false;
+        }
+        return v.y() >= bounds.minY() && v.y() <= bounds.maxY() && v.z() >= bounds.minZ() && v.z() <= bounds.maxZ();
+    }
+
+    private static boolean isVecInsideXzBounds(AABBdc bounds, @Nullable Vector3dc v) {
+        if (v == null) {
+            return false;
+        }
+        return v.x() >= bounds.minX() && v.x() <= bounds.maxX() && v.z() >= bounds.minZ() && v.z() <= bounds.maxZ();
+    }
+
+    private static boolean isVecInsideXyBounds(AABBdc bounds, @Nullable Vector3dc v) {
+        if (v == null) {
+            return false;
+        }
+        return v.x() >= bounds.minX() && v.x() <= bounds.maxX() && v.y() >= bounds.minY() && v.y() <= bounds.maxY();
+    }
 }
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicStatue.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicStatue.java
index 42addc6c8..e32728ada 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicStatue.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicStatue.java
@@ -25,7 +25,7 @@ import java.util.ArrayList;
 import java.util.List;
 import java.util.function.Supplier;
 
-public class BlockLogicStatue extends BlockLogic {
+public class BlockLogicStatue extends BlockLogic implements BlockLogic.MatcherEntityEquivalency<TileEntityStatue> {
     private final boolean isBottom;
 
     public final @Nullable Supplier<@NotNull Item> droppedItem;
@@ -264,19 +264,27 @@ public class BlockLogicStatue extends BlockLogic {
     }
 
     @Override
-    public boolean isEquivalent(@NotNull World world, @NotNull TilePosc thisPos, @NotNull TilePosc thatPos) {
-        assert world.getBlockType(thisPos) == this.block;
-        final var thatBlock = world.getBlockType(thatPos);
-        if (thatBlock != this.block) return false;
-        final var that = (BlockLogicStatue) thatBlock.getLogic();
-
-        final var query = new TilePos();
-        if (!(world.getTileEntity(this.isBottom ? thisPos : thisPos.down(query)) instanceof TileEntityStatue thisStatue) ||
-            !(world.getTileEntity(that.isBottom ? thatPos : thatPos.down(query)) instanceof TileEntityStatue thatStatue))
-            return false;
-        
+    public boolean isEquivalent(
+        @NotNull WorldSource world,
+        @NotNull TilePosc thisPos, @NotNull TilePosc thatPos,
+        @NotNull Block<?> thatBlock
+    ) {
+        return (this.isBottom)
+            ? super.isEquivalent(world, thisPos, thatPos, thatBlock)
+            : super.isEquivalent(world, thisPos.down(new TilePos()), thatPos.down(new TilePos()), thatBlock);
+    }
+
+    @Override
+    public @NotNull Class<TileEntityStatue> getEntityEquivalencyClass() {
+        return TileEntityStatue.class;
+    }
+
+    @Override
+    public boolean isEquivalentEntityForMatcherImpl(
+        @NotNull TileEntityStatue thisStatue, @NotNull TileEntityStatue thatStatue
+    ) {
         if (thisStatue.getPose() != thatStatue.getPose()) return false;
-        
+
         // we assume (but not assert) all stacks to be of size 1
         // TODO: Item.isEquivalent(ItemStack)
         if (!ItemStack.areItemsEqual(thisStatue.getHeldItem(), thatStatue.getHeldItem())) return false;
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicSugarcane.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicSugarcane.java
index 8af566e81..ac5abde80 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicSugarcane.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicSugarcane.java
@@ -131,9 +131,4 @@ public class BlockLogicSugarcane extends BlockLogic implements IBonemealable {
         }
         return true;
     }
-
-    @Override
-    protected int getMatcherEquivalenceData(int data) {
-        return 0;
-    }
 }
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicSulfur.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicSulfur.java
index b904c1c85..4bbc1e14d 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicSulfur.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicSulfur.java
@@ -32,7 +32,7 @@ public class BlockLogicSulfur extends BlockLogic {
 	public ItemStack[] getBreakResult(@NotNull World world, @NotNull EnumDropCause dropCause, int data, @Nullable TileEntity tileEntity) {
 		return switch (dropCause) {
 			case PICK_BLOCK, SILK_TOUCH -> new ItemStack[]{new ItemStack(this)};
-			default -> new ItemStack[]{new ItemStack(Items.SULFUR, 1 + world.rand.nextInt(2))};
+			default -> new ItemStack[]{new ItemStack(Items.SULFUR, 4)};
 		};
 	}
 
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 04f08add0..a9753bd77 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicThermalVent.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicThermalVent.java
@@ -2,6 +2,7 @@ package net.minecraft.core.block;
 
 import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
 import net.minecraft.core.achievement.Achievements;
+import net.minecraft.core.block.entity.TileEntityThermalVent;
 import net.minecraft.core.block.material.Materials;
 import net.minecraft.core.entity.Entity;
 import net.minecraft.core.entity.Mob;
@@ -9,11 +10,9 @@ import net.minecraft.core.entity.player.Player;
 import net.minecraft.core.util.helper.Side;
 import net.minecraft.core.world.Dimension;
 import net.minecraft.core.world.World;
-import net.minecraft.core.world.particle.ParticleEmitterRange;
 import net.minecraft.core.world.pos.TilePos;
 import net.minecraft.core.world.pos.TilePosc;
 import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
 import org.joml.primitives.AABBd;
 
 import java.util.List;
@@ -36,11 +35,11 @@ public class BlockLogicThermalVent extends BlockLogic {
     public BlockLogicThermalVent(final @NotNull Block<?> block) {
         super(block, Materials.BASALT);
         block.setTicking(true);
+        block.withEntity(TileEntityThermalVent::new);
     }
 
     @Override
     public void animationTick(final @NotNull World world, final @NotNull TilePosc tilePos, final @NotNull Random rand) {
-        if (world.isClientSide) return;
         final int state = getVentState(world.getBlockData(tilePos));
 
         final double xPos = (double) tilePos.x() + 0.5D;
@@ -51,12 +50,12 @@ public class BlockLogicThermalVent extends BlockLogic {
             return;
         }
 
-        world.spawnParticle("ventsmoke", xPos, yPos, zPos, 0.0D, 0.005D, 0.0D, 1, 1000, true);
+        world.spawnParticle("ventsmoke", xPos, yPos, zPos, 0.0D, 0.005D, 0.0D, 1, 1000, false);
 
         if (hasVentBelow(world, tilePos) && state >= VENTING_STATE - 1) {
             for (int i = 0; i < 4; i++) {
-                world.spawnParticle("ventsmoke", xPos, yPos, zPos, 0.0D, 0.005D, 0.0D, 0, 1000.0, true);
-                world.spawnParticle("smoke", xPos + rand.nextFloat() - 0.5D, yPos, zPos + rand.nextFloat() - 0.5D, 0.0D, 0.005D, 0.0D, 1, 1000.0, true);
+                world.spawnParticle("ventsmoke", xPos, yPos, zPos, 0.0D, 0.005D, 0.0D, 0, 1000.0, false);
+                world.spawnParticle("smoke", xPos + rand.nextFloat() - 0.5D, yPos, zPos + rand.nextFloat() - 0.5D, 0.0D, 0.005D, 0.0D, 1, 1000.0, false);
             }
         }
     }
@@ -72,16 +71,6 @@ public class BlockLogicThermalVent extends BlockLogic {
         return Block.hasLogicClass(world.getBlockType(tilePos.down(new TilePos())), BlockLogicThermalVent.class);
     }
 
-    @Override
-    protected boolean isSameBlockForMatcher(@NotNull Block<?> other) {
-        return Block.hasLogicClass(other, BlockLogicThermalVent.class);
-    }
-
-    @Override
-    protected int getMatcherEquivalenceData(int data) {
-        return 0;
-    }
-
     @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) {
@@ -198,9 +187,4 @@ public class BlockLogicThermalVent extends BlockLogic {
             world.setBlockData(tilePos, hasVentBelow(world, tilePos) ? world.rand.nextInt(VENTING_STATE) : DORMANT_STATE);
         }
     }
-
-    @Override
-    public @Nullable ParticleEmitterRange getParticleEmitterRange() {
-        return ParticleEmitterRange.FAR;
-    }
 }
\ No newline at end of file
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicTimer.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicTimer.java
index 068ddbb61..ab3dbeafc 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
@@ -25,7 +25,7 @@ import org.jetbrains.annotations.Nullable;
 
 import java.util.Random;
 
-public class BlockLogicTimer extends BlockLogic implements ISupportable {
+public class BlockLogicTimer extends BlockLogic implements ISupportable, BlockLogic.MatcherDataEquivalency.Masked {
     public static final double[] torchPosOffset = {
         -0.0625D, 0.0625D, 0.1875D, 0.3125D
     };
@@ -50,8 +50,8 @@ 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 int getMatcherDataEquivalencyMask() {
+        return ~MASK_DIRECTION;
     }
 
     public BlockLogicTimer(@NotNull Block<?> block) {
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 2b7a5f1b5..41b10f73c 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,11 +34,6 @@ 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 64912292e..5196b9ec6 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
@@ -21,11 +21,6 @@ public class BlockLogicTorchRedstone extends BlockLogicTorch {
         this.torchActive = isActive;
     }
 
-    @Override
-    protected boolean isSameBlockForMatcher(final @NotNull Block<?> other) {
-        return other.getLogic() instanceof BlockLogicTorchRedstone rtorch && rtorch.torchActive == this.torchActive;
-    }
-
     @Override
     public int tickDelay() {
         return 2;
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 4fc8ef0c6..0de34e5db 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
@@ -26,7 +26,7 @@ import org.jetbrains.annotations.Nullable;
 import org.joml.primitives.AABBd;
 import org.joml.primitives.AABBdc;
 
-public class BlockLogicTrapDoor extends BlockLogic implements IPaintable {
+public class BlockLogicTrapDoor extends BlockLogic implements IPaintable, BlockLogic.MatcherDataEquivalency {
     public static final int DIRECTION_SOUTH = 0;
     public static final int DIRECTION_NORTH = 1;
     public static final int DIRECTION_EAST = 2;
@@ -44,9 +44,10 @@ public class BlockLogicTrapDoor extends BlockLogic implements IPaintable {
     }
 
     @Override
-    protected int getMatcherEquivalenceData(final int data) {
-        assert (MASK_OPEN << 1) == MASK_UPPER_HALF;
-        return data & ~(MASK_DIRECTION | ((data & MASK_OPEN) << 1));
+    public boolean isEquivalentDataForMatcherImpl(int thisData, int thatData) {
+        if ((thisData & MASK_OPEN) != (thatData & MASK_OPEN)) return false;
+        if ((thisData & MASK_OPEN) != 0) return true;
+        return (thisData & MASK_UPPER_HALF) == (thatData & MASK_UPPER_HALF);
     }
 
     @Override
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicTrapDoorPainted.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicTrapDoorPainted.java
index 2ccb6b7a0..cd98d5f9e 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicTrapDoorPainted.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicTrapDoorPainted.java
@@ -53,4 +53,12 @@ public class BlockLogicTrapDoorPainted extends BlockLogicTrapDoor implements IPa
     public void setColor(@NotNull World world, @NotNull TilePosc tilePos, @NotNull DyeColor color) {
         IPainted.super.setColor(world, tilePos, color);
     }
+
+    public static final int MASK_COLOR = 0xF0;
+
+    @Override
+    public boolean isEquivalentDataForMatcherImpl(int thisData, int thatData) {
+        if ((thisData & MASK_COLOR) != (thatData & MASK_COLOR)) return false;
+        return super.isEquivalentDataForMatcherImpl(thisData, thatData);
+    }
 }
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 0bfd57ee8..ef39e7d8e 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,11 +16,6 @@ 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/BlockLogicWireRedstone.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicWireRedstone.java
index 3621bd2a6..65755d6a8 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicWireRedstone.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicWireRedstone.java
@@ -9,7 +9,6 @@ import net.minecraft.core.current.util.BlockState;
 import net.minecraft.core.enums.EnumDropCause;
 import net.minecraft.core.item.ItemStack;
 import net.minecraft.core.item.Items;
-import net.minecraft.core.sound.SoundCategory;
 import net.minecraft.core.util.helper.Axis;
 import net.minecraft.core.util.helper.Side;
 import net.minecraft.core.world.World;
@@ -26,7 +25,7 @@ import java.util.Random;
 import java.util.Set;
 import java.util.function.Predicate;
 
-public class BlockLogicWireRedstone extends BlockLogic implements ISupportable {
+public class BlockLogicWireRedstone extends BlockLogic implements ISupportable, BlockLogic.MatcherDataEquivalency.Masked {
     public static final int SIGNAL_MIN = 0;
     public static final int SIGNAL_MAX = 15;
     public static final int SIGNAL_STEP = 1;
@@ -267,8 +266,8 @@ public class BlockLogicWireRedstone extends BlockLogic implements ISupportable {
     private int checkTarget(@NotNull World world, @NotNull TilePos tilePos, int sourceStrength) {
         if(world.getBlockType(tilePos) == Blocks.CONDUIT){
             int cData = world.getBlockData(tilePos);
-            int cStrength = (cData >> BlockLogicConduit.POWER_SHIFT) & BlockLogicConduit.POWER_MASK;
-            return Math.max(BlockLogicConduit.signalConverter(cStrength, true), sourceStrength);
+            int cStrength = BlockLogicConduit.getPower(cData);
+            return Math.max(BlockLogicConduit.signalConverter(cStrength), sourceStrength);
         }
 
         if (world.getBlockType(tilePos) != this.block) {
@@ -409,6 +408,7 @@ public class BlockLogicWireRedstone extends BlockLogic implements ISupportable {
 
     public static boolean shouldConnectTo(@NotNull WorldSource source, @NotNull TilePos tilePos, @NotNull Side side) {
         // TODO make connections handled per block rather then by the dust
+        //  if handling this, the conduit has the same issue, and should be fixed in tandem with the dust
         final Block<?> block = source.getBlockType(tilePos);
         if (block == null) {
             return false;
@@ -559,4 +559,9 @@ public class BlockLogicWireRedstone extends BlockLogic implements ISupportable {
     public @NotNull ISupport getSupportConstraint(@NotNull World world, @NotNull TilePosc tilePos, @NotNull Side side) {
         return side == Side.BOTTOM ? PartialSupport.INSTANCE.center() : PartialSupport.INSTANCE;
     }
+
+    @Override
+    public int getMatcherDataEquivalencyMask() {
+        return MASK_POWER;
+    }
 }
diff --git a/game/core/src/main/java/net/minecraft/core/block/Blocks.java b/game/core/src/main/java/net/minecraft/core/block/Blocks.java
index adae62878..d8009b276 100644
--- a/game/core/src/main/java/net/minecraft/core/block/Blocks.java
+++ b/game/core/src/main/java/net/minecraft/core/block/Blocks.java
@@ -1488,7 +1488,7 @@ public final class Blocks {
         .withSound(BlockSounds.SAND).withHardness(0.7F)
         .withTags(BlockTags.MINEABLE_BY_SHOVEL, BlockTags.CAVES_CUT_THROUGH, BlockTags.NETHER_MOBS_SPAWN, BlockTags.NETHER_SURFACE_BLOCK, BlockTags.INFINITE_BURN_SULFURIC);
     public static final Block<?> BRIMSTONE = register("brimstone", "minecraft:block/brimstone", 1086, block -> new BlockLogicBrimstone(block, Materials.STONE))
-        .withSound(BlockSounds.STONE).withHardness(1.8F).withOverrideColor(MaterialColor.paintedBlack)
+        .withSound(BlockSounds.STONE).withHardness(1F).withOverrideColor(MaterialColor.paintedBlack)
         .withTags(BlockTags.CAVES_CUT_THROUGH, BlockTags.MINEABLE_BY_PICKAXE, BlockTags.CHAINLINK_FENCES_CONNECT, BlockTags.NETHER_MOBS_SPAWN, BlockTags.NETHER_SURFACE_BLOCK, BlockTags.INFINITE_BURN_SULFURIC);
     public static final Block<?> SLAB_BRIMSTONE = register("slab.brimstone", "minecraft:block/slab_brimstone", 1087, block -> new BlockLogicSlab(block, BRIMSTONE))
         .withSound(BlockSounds.STONE).withLitInteriorSurface(true).withOverrideColor(MaterialColor.paintedBlack)
@@ -1497,7 +1497,7 @@ public final class Blocks {
         .withSound(BlockSounds.STONE).withLitInteriorSurface(true).withOverrideColor(MaterialColor.paintedBlack)
         .withTags(BlockTags.MINEABLE_BY_PICKAXE, BlockTags.INFINITE_BURN_SULFURIC);
     public static final Block<?> BRICK_BRIMSTONE = register("brick.brimstone", "minecraft:block/brick_brimstone", 1089, block -> new BlockLogicBrimstone(block, Materials.STONE))
-        .withSound(BlockSounds.STONE).withHardness(1.8F).withOverrideColor(MaterialColor.paintedBlack)
+        .withSound(BlockSounds.STONE).withHardness(1F).withOverrideColor(MaterialColor.paintedBlack)
         .withTags(BlockTags.CAVES_CUT_THROUGH, BlockTags.MINEABLE_BY_PICKAXE, BlockTags.CHAINLINK_FENCES_CONNECT, BlockTags.NETHER_MOBS_SPAWN, BlockTags.NETHER_SURFACE_BLOCK, BlockTags.INFINITE_BURN_SULFURIC);
     public static final Block<?> SLAB_BRICK_BRIMSTONE = register("slab.brick.brimstone", "minecraft:block/slab_brick_brimstone", 1090, block -> new BlockLogicSlab(block, BRICK_BRIMSTONE))
         .withSound(BlockSounds.STONE).withLitInteriorSurface(true).withOverrideColor(MaterialColor.paintedBlack)
diff --git a/game/core/src/main/java/net/minecraft/core/block/entity/TileEntity.java b/game/core/src/main/java/net/minecraft/core/block/entity/TileEntity.java
index dc471bfe8..bd6f2b385 100644
--- a/game/core/src/main/java/net/minecraft/core/block/entity/TileEntity.java
+++ b/game/core/src/main/java/net/minecraft/core/block/entity/TileEntity.java
@@ -4,6 +4,7 @@ import com.mojang.nbt.tags.CompoundTag;
 import net.minecraft.core.block.Blocks;
 import net.minecraft.core.block.motion.CarriedBlock;
 import net.minecraft.core.block.Block;
+import net.minecraft.core.block.BlockLogicMatcher;
 import net.minecraft.core.block.tag.BlockTags;
 import net.minecraft.core.entity.Entity;
 import net.minecraft.core.entity.Mob;
@@ -188,8 +189,12 @@ public abstract class TileEntity implements ICarriable
         return new CarriedBlock(holder, currentBlock, currentMeta, this);
     }
 
-    public void notifyBlocksOfNeighborChange() {
+    protected final void updateNeighboringMatchers() {
         if (this.worldObj == null) return;
-        this.worldObj.notifyBlocksOfNeighborChange(this.tilePos, this.getBlock()); 
+        BlockLogicMatcher.updateNeighboringMatchers(this.worldObj, this.tilePos);
+    }
+
+    public interface AnimationTicking {
+        void animationTick(int particleRenderDistance, double distSqr);
     }
 }
diff --git a/game/core/src/main/java/net/minecraft/core/block/entity/TileEntityDispatcher.java b/game/core/src/main/java/net/minecraft/core/block/entity/TileEntityDispatcher.java
index c43ced3f4..3f2d5ffdf 100644
--- a/game/core/src/main/java/net/minecraft/core/block/entity/TileEntityDispatcher.java
+++ b/game/core/src/main/java/net/minecraft/core/block/entity/TileEntityDispatcher.java
@@ -91,6 +91,7 @@ public class TileEntityDispatcher {
         addMapping(TileEntityMeshGold.class, NamespaceID.fromPool("minecraft", "mesh_gold"));
         addMapping(TileEntityStatue.class, NamespaceID.fromPool("minecraft", "statue_stone"));
         addMapping(TileEntityTimer.class, NamespaceID.fromPool("minecraft", "timer"));
+        addMapping(TileEntityThermalVent.class, NamespaceID.fromPool("minecraft", "thermal_vent"));
     }
 
     @Deprecated
diff --git a/game/core/src/main/java/net/minecraft/core/block/entity/TileEntityFlag.java b/game/core/src/main/java/net/minecraft/core/block/entity/TileEntityFlag.java
index 8ebffe931..3910bf97c 100644
--- a/game/core/src/main/java/net/minecraft/core/block/entity/TileEntityFlag.java
+++ b/game/core/src/main/java/net/minecraft/core/block/entity/TileEntityFlag.java
@@ -71,7 +71,7 @@ public class TileEntityFlag extends TileEntity
     public void setFlipped(boolean flag) {
         if (this.flipped == flag) return;
         this.flipped = flag;
-        this.notifyBlocksOfNeighborChange();
+        this.updateNeighboringMatchers();
     }
 
     public boolean getFlipped() {
@@ -235,7 +235,7 @@ public class TileEntityFlag extends TileEntity
         items[slot] = stack;
         itemsChanged();
         setChanged();
-        this.notifyBlocksOfNeighborChange();
+        this.updateNeighboringMatchers();
     }
 
     private void itemsChanged() {
@@ -255,7 +255,7 @@ public class TileEntityFlag extends TileEntity
             colorHash ^= flagColors[i] << (8 * (i & 0b11));
         }
         isDirty = true;
-        this.notifyBlocksOfNeighborChange();
+        this.updateNeighboringMatchers();
     }
 
     @Override
diff --git a/game/core/src/main/java/net/minecraft/core/block/entity/TileEntityMeshGold.java b/game/core/src/main/java/net/minecraft/core/block/entity/TileEntityMeshGold.java
index 2d8719ed7..bf755857a 100644
--- a/game/core/src/main/java/net/minecraft/core/block/entity/TileEntityMeshGold.java
+++ b/game/core/src/main/java/net/minecraft/core/block/entity/TileEntityMeshGold.java
@@ -85,7 +85,7 @@ public class TileEntityMeshGold extends TileEntityMesh {
         }
         filterItem = null;
         setChanged();
-        this.notifyBlocksOfNeighborChange();
+        this.updateNeighboringMatchers();
         return success | !wasNull;
     }
 
diff --git a/game/core/src/main/java/net/minecraft/core/block/entity/TileEntityMobSpawner.java b/game/core/src/main/java/net/minecraft/core/block/entity/TileEntityMobSpawner.java
index b6f43313c..da4799864 100644
--- a/game/core/src/main/java/net/minecraft/core/block/entity/TileEntityMobSpawner.java
+++ b/game/core/src/main/java/net/minecraft/core/block/entity/TileEntityMobSpawner.java
@@ -45,7 +45,7 @@ public class TileEntityMobSpawner extends TileEntity {
         } catch (Exception e) {
             LOGGER.error("Failed to convert legacy mobspawner id!", e);
         }
-        this.notifyBlocksOfNeighborChange();
+        this.updateNeighboringMatchers();
     }
 
     public boolean anyPlayerInRange() {
diff --git a/game/core/src/main/java/net/minecraft/core/block/entity/TileEntitySign.java b/game/core/src/main/java/net/minecraft/core/block/entity/TileEntitySign.java
index d3f7f5568..63e7eeb6b 100644
--- a/game/core/src/main/java/net/minecraft/core/block/entity/TileEntitySign.java
+++ b/game/core/src/main/java/net/minecraft/core/block/entity/TileEntitySign.java
@@ -93,7 +93,7 @@ public class TileEntitySign extends TileEntity
     public void setPicture(@Nullable EnumSignPicture picture)
     {
         this.selectedPicture = (picture == null) ? 0 : picture.getId();
-        this.notifyBlocksOfNeighborChange();
+        this.updateNeighboringMatchers();
     }
     
     public TextFormatting getColor()
@@ -106,7 +106,7 @@ public class TileEntitySign extends TileEntity
     public void setColor(TextFormatting color)
     {
         selectedColor = color.id;
-        this.notifyBlocksOfNeighborChange();
+        this.updateNeighboringMatchers();
     }
 
     public boolean isGlowing() {
@@ -115,7 +115,7 @@ public class TileEntitySign extends TileEntity
 
     public void setGlowing(boolean glowing) {
         this.glowing = glowing;
-        this.notifyBlocksOfNeighborChange();
+        this.updateNeighboringMatchers();
     }
 
     public boolean isLocked() {
@@ -124,7 +124,7 @@ public class TileEntitySign extends TileEntity
 
     public void setLocked(boolean locked) {
         this.locked = locked;
-        this.notifyBlocksOfNeighborChange();
+        this.updateNeighboringMatchers();
     }
 
     public void finalizeEditor(@NotNull Player editor) {
@@ -135,7 +135,7 @@ public class TileEntitySign extends TileEntity
         if (!allowColorCode) for(int i = 0; i < 4; i++) {
             signText[i] = TextFormatting.removeAllColorFormatting(signText[i]);
         }
-        this.notifyBlocksOfNeighborChange();
+        this.updateNeighboringMatchers();
     }
 
     @Override
diff --git a/game/core/src/main/java/net/minecraft/core/block/entity/TileEntityStatue.java b/game/core/src/main/java/net/minecraft/core/block/entity/TileEntityStatue.java
index 9489ce6a2..a8371cde4 100644
--- a/game/core/src/main/java/net/minecraft/core/block/entity/TileEntityStatue.java
+++ b/game/core/src/main/java/net/minecraft/core/block/entity/TileEntityStatue.java
@@ -125,7 +125,7 @@ public class TileEntityStatue
     public void setPose(final @NotNull Pose pose) {
         this.pose = pose;
         setChanged();
-        this.notifyBlocksOfNeighborChange();
+        this.updateNeighboringMatchers();
     }
 
     public void nextPose() {
@@ -141,7 +141,7 @@ public class TileEntityStatue
     public void setItemInArmorSlot(final @NotNull HumanArmorShape slot, final @Nullable ItemStack item) {
         this.armorPieces[slot.getSlotIndex()] = item;
         setChanged();
-        this.notifyBlocksOfNeighborChange();
+        this.updateNeighboringMatchers();
     }
 
     @Override
@@ -165,7 +165,7 @@ public class TileEntityStatue
     @Override
     public void setHeldItem(@Nullable ItemStack item) {
         this.heldItem = item;
-        this.notifyBlocksOfNeighborChange();
+        this.updateNeighboringMatchers();
     }
 
     @Override
diff --git a/game/core/src/main/java/net/minecraft/core/block/entity/TileEntityThermalVent.java b/game/core/src/main/java/net/minecraft/core/block/entity/TileEntityThermalVent.java
new file mode 100644
index 000000000..db0dfdbe0
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/block/entity/TileEntityThermalVent.java
@@ -0,0 +1,33 @@
+package net.minecraft.core.block.entity;
+
+import com.mojang.nbt.tags.CompoundTag;
+import net.minecraft.core.net.packet.Packet;
+import net.minecraft.core.net.packet.PacketTileEntityData;
+import org.jetbrains.annotations.NotNull;
+
+public class TileEntityThermalVent extends TileEntity implements TileEntity.AnimationTicking {
+    @Override public void readAdditionalData(@NotNull CompoundTag compoundTag) {}
+    @Override public void writeAdditionalData(@NotNull CompoundTag compoundTag) {}
+
+    @Override
+    public Packet getDescriptionPacket() {
+        return new PacketTileEntityData(this);
+    }
+
+    @Override
+    public void animationTick(int particleRenderDistance, double distSqr) {
+        if (this.worldObj == null) return;
+        if (distSqr > particleRenderDistance * particleRenderDistance * 0.75f * 0.75f) return; // Skip if further than 75% of the render distance in blocks
+
+        int throttle = 4;
+        if (distSqr > 4096) {
+            throttle = 64;
+        } else if (distSqr > 1024) {
+            throttle = 16;
+        }
+
+        if (this.worldObj.rand.nextInt(throttle) == 0) {
+            getBlock().animationTick(this.worldObj, this.tilePos, this.worldObj.rand);
+        }
+    }
+}
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 189a41fec..ee783315d 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
@@ -1,7 +1,6 @@
 package net.minecraft.core.block.piston;
 
 import net.minecraft.core.block.Blocks;
-import net.minecraft.core.block.entity.TileEntity;
 import net.minecraft.core.block.material.Material;
 import net.minecraft.core.Global;
 import net.minecraft.core.block.Block;
@@ -34,9 +33,11 @@ import org.joml.Vector3d;
 import org.joml.primitives.AABBd;
 import org.joml.primitives.AABBdc;
 
+import java.util.Random;
+
 import static net.minecraft.core.block.piston.PistonCommon.*;
 
-public class BlockLogicPistonBase extends BlockLogic {
+public class BlockLogicPistonBase extends BlockLogic implements BlockLogic.MatcherDataEquivalency.Masked {
 	public final byte typeId;
 
     final byte pushLimit, pullLimit, crushLimit;
@@ -64,13 +65,8 @@ public class BlockLogicPistonBase extends BlockLogic {
     }
 
     @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;
+    public int getMatcherDataEquivalencyMask() {
+        return ~DIRECTION.mask;
     }
 
     // Piston updates:
@@ -430,11 +426,22 @@ public class BlockLogicPistonBase extends BlockLogic {
         this.signalUpdate(world, tilePos);
     }
 
+    @Override
+    public void updateTick(@NotNull World world, @NotNull TilePosc tilePos, @NotNull Random rand, boolean isRandomTick) {
+        if (isRandomTick || world.isClientSide) return;
+        this.signalUpdate(world, tilePos);
+    }
+
     @Override
     public void onNeighborChanged(@NotNull World world, @NotNull TilePosc tilePos, final @NotNull Block<?> block) {
         if (world.isClientSide) return;
-        final var data = fixLegacyBaseData(world, tilePos);
-        this.signalUpdate(world, tilePos, data);
+        final int data = fixLegacyBaseData(world, tilePos);
+
+        final boolean powered = hasNeighborSignal(world, tilePos, Direction.fromId(DIRECTION.get(data)));
+        final boolean extended = IS_EXTENDED.bool(data);
+        if (powered != extended) {
+            world.scheduleBlockUpdate(tilePos, this.block, 1);
+        }
     }
 
     @Override
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 e6b2c4d44..279fb98c3 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
@@ -11,9 +11,7 @@ import net.minecraft.core.block.material.Materials;
 import net.minecraft.core.block.support.FullSupport;
 import net.minecraft.core.block.support.ISupport;
 import net.minecraft.core.block.support.PartialSupport;
-import net.minecraft.core.entity.player.Player;
 import net.minecraft.core.enums.EnumDropCause;
-import net.minecraft.core.item.Item;
 import net.minecraft.core.item.ItemStack;
 import net.minecraft.core.util.helper.Direction;
 import net.minecraft.core.util.helper.Side;
@@ -29,7 +27,7 @@ import org.joml.primitives.AABBdc;
 
 import static net.minecraft.core.block.piston.PistonCommon.*;
 
-public class BlockLogicPistonHead extends BlockLogic {
+public class BlockLogicPistonHead extends BlockLogic implements BlockLogic.MatcherDataEquivalency.Masked {
 
     // "Good lord. This really is our most NSFW update" -- Jonk
     // "Daaayyymmnn boy! He thick boy! That's a thick ass boy, DAYMN!" -- Melon
@@ -43,8 +41,8 @@ public class BlockLogicPistonHead extends BlockLogic {
     }
 
     @Override
-    protected int getMatcherEquivalenceData(final int data) {
-        return data & ~DIRECTION.mask;
+    public int getMatcherDataEquivalencyMask() {
+        return ~DIRECTION.mask;
     }
 
     @Override
@@ -138,7 +136,6 @@ public class BlockLogicPistonHead extends BlockLogic {
         if (!(world.getBlockType(bpos).getLogic() instanceof BlockLogicPistonBase)) return;
         int bdat = fixLegacyBaseData(world, bpos);
         if (PAIR_COMPARISON.get(data ^ bdat) != 0) return;
-        final var b = world.getBlockType(bpos);
         world.setBlockTypeNotify(bpos, Blocks.AIR);
     }
 
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 fc2e937fc..a898d12c1 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
@@ -139,7 +139,11 @@ public class BlockLogicPistonMoving extends BlockLogic {
     }
 
     @Override
-    public boolean isEquivalent(@NotNull World world, @NotNull TilePosc thisPos, @NotNull TilePosc thatPos) {
+    public boolean isEquivalent(
+        @NotNull WorldSource world,
+        @NotNull TilePosc thisPos, @NotNull TilePosc thatPos,
+        @NotNull Block<?> thatBlock
+    ) {
         return false;
     }
 }
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 d97eda610..f35b038e1 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
@@ -174,6 +174,7 @@ public final class MovingLine {
         if (world.isBlockNormalCube(tilePos)) direct_sig: {
             if (block == Blocks.BLOCK_REDSTONE) break direct_sig;
             if (block == Blocks.PUMPKIN_REDSTONE) break direct_sig;
+            if (block == Blocks.MATCHER) break direct_sig;
             if (block == Blocks.MATCHER_ACTIVE) break direct_sig;
             if (world.hasDirectSignal(queryPos.set(tilePos).add(d1), d1.side())) return true;
             if (world.hasDirectSignal(queryPos.set(tilePos).add(d2), d2.side())) return true;
diff --git a/game/core/src/main/java/net/minecraft/core/current/util/BlockState.java b/game/core/src/main/java/net/minecraft/core/current/util/BlockState.java
index 044e534f0..5470cc82f 100644
--- a/game/core/src/main/java/net/minecraft/core/current/util/BlockState.java
+++ b/game/core/src/main/java/net/minecraft/core/current/util/BlockState.java
@@ -19,7 +19,7 @@ public class BlockState {
 	private final @NotNull Block<?> block;
 	private final int metadata;
 
-	public BlockState(Block<?> block, int metadata) {
+	public BlockState(@NotNull Block<?> block, int metadata) {
 		this.block = block;
 		this.metadata = metadata;
 	}
diff --git a/game/core/src/main/java/net/minecraft/core/current/wire/ConduitHandler.java b/game/core/src/main/java/net/minecraft/core/current/wire/ConduitHandler.java
deleted file mode 100644
index 2e53c3f8d..000000000
--- a/game/core/src/main/java/net/minecraft/core/current/wire/ConduitHandler.java
+++ /dev/null
@@ -1,188 +0,0 @@
-package net.minecraft.core.current.wire;
-
-import net.minecraft.core.block.Block;
-import net.minecraft.core.block.Blocks;
-import net.minecraft.core.block.BlockLogicConduit;
-import net.minecraft.core.util.helper.Side;
-import net.minecraft.core.world.World;
-import net.minecraft.core.world.pos.TilePos;
-import net.minecraft.core.world.pos.TilePosc;
-
-import java.util.*;
-import java.util.PriorityQueue;
-
-
-/**
- * One-Dimensional Implementation of Alternate Current for conduits. <div></div>
- * Handles the logic and updates for any given conduit network and seamlessly integrates with other redstone components. <div></div>
- *
- * @author Melon Mojito (@melon_mojito)
- */
-public class ConduitHandler {
-
-    private final World world;
-    private boolean updating = false;
-    private final Queue<TilePos> updateQueue = new LinkedList<>();
-
-    private record ConduitNode(TilePos tilePos, int currentPower) {}
-
-    public ConduitHandler(World world) {
-        this.world = world;
-    }
-
-    public void onConduitUpdated(TilePosc pos) {
-        queueUpdate(new TilePos(pos));
-    }
-
-    public void onConduitAdded(TilePosc pos) {
-        queueUpdate(new TilePos(pos));
-    }
-
-    public void onConduitRemoved(TilePosc pos, int oldData) {
-        //update the sides this conduit was connected to
-        for (Side side : BlockLogicConduit.getOpenSides(oldData)) {
-            TilePos nPos = new TilePos(pos).add(side);
-            if (world.getBlockType(nPos) == Blocks.CONDUIT) {
-                queueUpdate(nPos);
-            }
-        }
-    }
-
-    private void queueUpdate(TilePos pos) {
-        updateQueue.add(pos);
-        if (!updating) {
-            processQueue();
-        }
-    }
-
-    private void processQueue() {
-        updating = true;
-        try {
-            while (!updateQueue.isEmpty()) {
-                TilePos startPos = updateQueue.poll();
-                // Ensure the block is still a conduit before updating the network,
-                // as it could have been broken while sitting in the queue.
-                if (world.getBlockType(startPos) == Blocks.CONDUIT) {
-                    updateNetwork(startPos);
-                }
-            }
-        } finally {
-            updating = false;
-        }
-    }
-
-    private void updateNetwork(TilePos startPos) {
-        //build the network
-
-        if ((world.getBlockData(startPos) & BlockLogicConduit.STATE_MASK) == 0) {
-            return;
-        }
-
-        Set<TilePos> network = new HashSet<>();
-        Queue<TilePos> toSearch = new LinkedList<>();
-        toSearch.add(startPos);
-
-        while (!toSearch.isEmpty()) {
-            TilePos pos = toSearch.poll();
-            if (network.add(pos)) {
-                int data = world.getBlockData(pos);
-                for (Side side : BlockLogicConduit.getOpenSides(data)) {
-                    TilePos nPos = new TilePos(pos).add(side);
-                    if (world.getBlockType(nPos) == Blocks.CONDUIT &&
-                            BlockLogicConduit.isSideOpen(world.getBlockData(nPos), side.opposite())) {
-                        toSearch.add(nPos);
-                    }
-                }
-            }
-        }
-
-        //external sources: levers, buttons, etc.
-        Map<TilePos, Integer> externalPower = new HashMap<>();
-        for (TilePos tilePos : network) {
-            int data = world.getBlockData(tilePos);
-            int currentPower = (data >> BlockLogicConduit.POWER_SHIFT) & BlockLogicConduit.POWER_MASK;
-            int maxExternalPower = 0;
-
-            for (Side side : BlockLogicConduit.getOpenSides(data)) {
-                TilePos nTilePos = new TilePos(tilePos).add(side);
-                Block<?> nBlockType = world.getBlockType(nTilePos);
-
-                if (nBlockType != Blocks.CONDUIT) {
-                    int external = BlockLogicConduit.getAndConvertNeighborSignal(world, tilePos, side);
-
-                    //break the recursive loop for redstone dust
-                    if (nBlockType == Blocks.WIRE_REDSTONE) {
-                        if (external < currentPower) {
-                            external = 0;
-                        }
-                    }
-
-                    if (external > maxExternalPower) maxExternalPower = external;
-                }
-            }
-            externalPower.put(tilePos, maxExternalPower);
-        }
-
-        //propagate from roots with priority
-        Map<TilePos, Integer> newPowerLevels = new HashMap<>();
-        for (TilePos tilePos : network) newPowerLevels.put(tilePos, 0);
-
-        java.util.PriorityQueue<ConduitNode> powerQueue = new PriorityQueue<>((a, b) -> Integer.compare(b.currentPower, a.currentPower));
-        for (Map.Entry<TilePos, Integer> entry : externalPower.entrySet()) {
-            if (entry.getValue() > 0) {
-                powerQueue.add(new ConduitNode(entry.getKey(), entry.getValue()));
-            }
-        }
-
-        while (!powerQueue.isEmpty()) {
-            ConduitNode node = powerQueue.poll();
-            //ignore weaker path
-            if (node.currentPower <= newPowerLevels.get(node.tilePos)) continue;
-
-            newPowerLevels.put(node.tilePos, node.currentPower);
-
-            //propagate power down the line
-            if (node.currentPower > 1) {
-                int data = world.getBlockData(node.tilePos);
-                for (Side side : BlockLogicConduit.getOpenSides(data)) {
-                    TilePos nPos = new TilePos(node.tilePos).add(side);
-                    if (network.contains(nPos)) {
-                        powerQueue.add(new ConduitNode(nPos, node.currentPower - 1));
-                    }
-                }
-            }
-        }
-
-        Set<TilePos> blocksToNotify = new HashSet<>();
-        for (TilePos tilePos : network) {
-            int oldData = world.getBlockData(tilePos);
-            int oldPower = (oldData >> BlockLogicConduit.POWER_SHIFT) & BlockLogicConduit.POWER_MASK;
-            int newPower = newPowerLevels.get(tilePos);
-
-            if (oldPower != newPower) {
-                int state = oldData & BlockLogicConduit.STATE_MASK;
-
-                world.setBlockData(tilePos, (newPower << BlockLogicConduit.POWER_SHIFT) | state);
-                world.markBlockNeedsUpdate(tilePos);
-
-                //queue updates ONLY for blocks facing the open sides that ARE NOT conduits in this network
-                for (Side side : BlockLogicConduit.getOpenSides(state)) {
-                    TilePos targetPos = new TilePos(tilePos).add(side);
-                    if (!network.contains(targetPos)) {
-                        blocksToNotify.add(targetPos);
-                    }
-                }
-            }
-        }
-
-        //notify all neighbors after the network is done with its logic/updates
-        for (TilePos nTilePos : blocksToNotify) {
-            Block<?> neighborBlockType = world.getBlockType(nTilePos);
-            neighborBlockType.onNeighborChanged(world, nTilePos, Blocks.CONDUIT);
-
-            if (neighborBlockType != Blocks.WIRE_REDSTONE) {
-                world.notifyBlocksOfNeighborChange(nTilePos, Blocks.CONDUIT);
-            }
-        }
-    }
-}
\ No newline at end of file
diff --git a/game/core/src/main/java/net/minecraft/core/current/wire/Node.java b/game/core/src/main/java/net/minecraft/core/current/wire/Node.java
index efabe2edc..c16b1d4ec 100644
--- a/game/core/src/main/java/net/minecraft/core/current/wire/Node.java
+++ b/game/core/src/main/java/net/minecraft/core/current/wire/Node.java
@@ -6,6 +6,7 @@ import net.minecraft.core.world.World;
 import net.minecraft.core.block.Blocks;
 import net.minecraft.core.world.pos.TilePos;
 import net.minecraft.core.world.pos.TilePosc;
+import org.jetbrains.annotations.NotNull;
 
 import java.util.Arrays;
 
@@ -61,7 +62,7 @@ public class Node {
         return this.pos.hashCode();
     }
 
-    Node set(TilePosc pos, BlockState state, boolean clearNeighbors) {
+    Node set(TilePosc pos, @NotNull BlockState state, boolean clearNeighbors) {
         if (state.is(Blocks.WIRE_REDSTONE)) {
             throw new IllegalStateException("Cannot update a regular Node to a WireNode!");
         }
@@ -97,6 +98,13 @@ public class Node {
         return false;
     }
 
+    public boolean isWireAndIs(WireNode.Medium medium) {
+        if (this.isWire()) {
+            return this.asWire().medium == medium;
+        }
+        return false;
+    }
+
     public boolean isConductor() {
         return (this.flags & CONDUCTOR) != 0;
     }
diff --git a/game/core/src/main/java/net/minecraft/core/current/wire/WireConnection.java b/game/core/src/main/java/net/minecraft/core/current/wire/WireConnection.java
index 86c6fa7bd..bb4fcfa04 100644
--- a/game/core/src/main/java/net/minecraft/core/current/wire/WireConnection.java
+++ b/game/core/src/main/java/net/minecraft/core/current/wire/WireConnection.java
@@ -4,27 +4,35 @@ package net.minecraft.core.current.wire;
  * This class represents a connection between some WireNode (the 'owner') and a
  * neighboring WireNode. Two wires are considered to be connected if power can
  * flow from one wire to the other (and/or vice versa).
- * 
+ *
+ * <p>A node here may be any {@link WireNode.Medium}.
+ * Power crosses every connection with a uniform step of {@link WireHandler}'s
+ * POWER_STEP.
+ *
  * @author Space Walker
+ * @author MelonMojito
  */
 public class WireConnection {
 
 	/** The connected wire. */
 	final WireNode wire;
-	/** Cardinal direction to the connected wire. */
+	/** Cardinal (or vertical) direction to the connected wire. */
 	final int iDir;
 	/** True if the owner of the connection can provide power to the connected wire. */
 	final boolean offer;
 	/** True if the connected wire can provide power to the owner of the connection. */
 	final boolean accept;
+	/** True if this connection should contribute to the owner's horizontal flow direction.*/
+	final boolean flow;
 
 	/** The next connection in the sequence. */
 	WireConnection next;
 
-	WireConnection(WireNode wire, int iDir, boolean offer, boolean accept) {
+	WireConnection(WireNode wire, int iDir, boolean offer, boolean accept, boolean flow) {
 		this.wire = wire;
 		this.iDir = iDir;
 		this.offer = offer;
 		this.accept = accept;
+		this.flow = flow;
 	}
 }
diff --git a/game/core/src/main/java/net/minecraft/core/current/wire/WireConnectionManager.java b/game/core/src/main/java/net/minecraft/core/current/wire/WireConnectionManager.java
index 2de7e2267..12ba77d71 100644
--- a/game/core/src/main/java/net/minecraft/core/current/wire/WireConnectionManager.java
+++ b/game/core/src/main/java/net/minecraft/core/current/wire/WireConnectionManager.java
@@ -5,16 +5,20 @@ import net.minecraft.core.block.Blocks;
 import net.minecraft.core.current.wire.WireHandler.Directions;
 import net.minecraft.core.current.wire.WireHandler.NodeProvider;
 import net.minecraft.core.util.helper.Side;
+import org.jetbrains.annotations.NotNull;
 
 import java.util.Arrays;
 import java.util.function.Consumer;
 
+import static net.minecraft.core.current.wire.WireNode.Medium.CONDUIT;
+import static net.minecraft.core.current.wire.WireNode.Medium.DUST;
+
 public class WireConnectionManager {
 
 	/** The owner of these connections. */
 	final WireNode owner;
 
-	/** The first connection for each cardinal direction. */
+	/** The first connection for each cardinal direction. (Flow only) */
 	private final WireConnection[] heads;
 
 	private WireConnection head;
@@ -23,10 +27,7 @@ public class WireConnectionManager {
 	/** The total number of connections. */
 	int total;
 
-	/**
-	 * A 4 bit number that encodes which in direction(s) the owner has connections
-	 * to other wires.
-	 */
+	/** A 4 bit number that encodes direction(s) the owner has flow connections with other dust wires. */
 	private int flowTotal;
 	/** The direction of flow based connections to other wires. */
 	int iFlowDir;
@@ -47,6 +48,19 @@ public class WireConnectionManager {
 			clear();
 		}
 
+		if (owner.is(CONDUIT)) {
+			setConduit(nodes);
+		} else {
+			setDust(nodes);
+			addConduitLinks(nodes);
+		}
+
+		if (total > 0) {
+			iFlowDir = WireHandler.FLOW_IN_TO_FLOW_OUT[flowTotal];
+		}
+	}
+
+	private void setDust(@NotNull NodeProvider nodes) {
 		//tried my best to keep original AltCurrent naming schemes when changing code here
 
 		Node ownerDown = nodes.getNeighbor(owner, Directions.DOWN);
@@ -60,8 +74,8 @@ public class WireConnectionManager {
 		for (int iDir = 0; iDir < Directions.HORIZONTAL.length; iDir++) {
 			Node neighbor = nodes.getNeighbor(owner, iDir);
 
-			if (neighbor.isWire()) {
-				add(neighbor.asWire(), iDir, true, true);
+			if (neighbor.isWireAndIs(DUST)) {
+				add(neighbor.asWire(), iDir, true, true, true);
 
 				continue;
 			}
@@ -77,10 +91,10 @@ public class WireConnectionManager {
 			if (!blockedDown) {
 				Node node = nodes.getNeighbor(neighbor, Directions.DOWN);
 
-				if (node.isWire()) {
+				if (node.isWireAndIs(DUST)) {
 					boolean offerDown = belowIsConductor ||
 							(ownerDown.state.is(Blocks.CONDUIT) && !isConduitOpenTowards(ownerDown, iDir));
-					add(node.asWire(), iDir, offerDown, true);
+					add(node.asWire(), iDir, offerDown, true, true);
 				}
 			}
 
@@ -92,16 +106,46 @@ public class WireConnectionManager {
 			if (!blockedUp) {
 				Node node = nodes.getNeighbor(neighbor, Directions.UP);
 
-				if (node.isWire()) {
+				if (node.isWireAndIs(DUST)) {
 					boolean acceptUp = sideIsConductor ||
 							(neighbor.state.is(Blocks.CONDUIT) && !isConduitOpenTowards(neighbor, iOppDir));
-					add(node.asWire(), iDir, true, acceptUp);
+					add(node.asWire(), iDir, true, acceptUp, true);
 				}
 			}
 		}
+	}
 
-		if (total > 0) {
-			iFlowDir = WireHandler.FLOW_IN_TO_FLOW_OUT[flowTotal];
+	private void addConduitLinks(NodeProvider nodes) {
+		for (int iDir = 0; iDir < Directions.ALL.length; iDir++) {
+			Node neighbor = nodes.getNeighbor(owner, iDir);
+
+			if (neighbor.isWireAndIs(CONDUIT) && isConduitOpenTowards(neighbor, Directions.iOpposite(iDir))) {
+				add(neighbor.asWire(), iDir, true, true, false);
+			}
+		}
+	}
+
+	private void setConduit(NodeProvider nodes) {
+		for (int iDir = 0; iDir < Directions.ALL.length; iDir++) {
+			if (!isConduitOpenTowards(owner, iDir)) {
+				continue;
+			}
+
+			Node neighbor = nodes.getNeighbor(owner, iDir);
+
+			if (!neighbor.isWire()) {
+				continue;
+			}
+
+			WireNode wireNeighbor = neighbor.asWire();
+
+			if (wireNeighbor.is(CONDUIT)) {
+				if (isConduitOpenTowards(neighbor, Directions.iOpposite(iDir))) {
+					add(wireNeighbor, iDir, true, true, false);
+				}
+			} else {
+				add(wireNeighbor, iDir, true, true, false);
+			}
 		}
 	}
 
@@ -111,7 +155,7 @@ public class WireConnectionManager {
 	 * @param iDir The Alternate Current direction we are querying
 	 * @return true if the node is a conduit AND the port facing iDir is open
 	 */
-	private boolean isConduitOpenTowards(Node node, int iDir) {
+	private boolean isConduitOpenTowards(@NotNull Node node, int iDir) {
 		if (node.state.is(Blocks.CONDUIT)) {
 			Side face = Side.NONE;
             face = switch (iDir) {
@@ -140,8 +184,8 @@ public class WireConnectionManager {
 		iFlowDir = -1;
 	}
 
-	private void add(WireNode wire, int iDir, boolean offer, boolean accept) {
-		add(new WireConnection(wire, iDir, offer, accept));
+	private void add(WireNode wire, int iDir, boolean offer, boolean accept, boolean flow) {
+		add(new WireConnection(wire, iDir, offer, accept, flow));
 	}
 
 	private void add(WireConnection connection) {
@@ -155,7 +199,8 @@ public class WireConnectionManager {
 
 		total++;
 
-		if (heads[connection.iDir] == null) {
+		// Only horizontal dust-flow connections are tracked for flow ordering.
+		if (connection.flow && heads[connection.iDir] == null) {
 			heads[connection.iDir] = connection;
 			flowTotal |= (1 << connection.iDir);
 		}
@@ -173,11 +218,20 @@ public class WireConnectionManager {
 
 	/**
 	 * Iterate over all connections. Use this method if the iteration order is
-	 * important.
+	 * important. Horizontal dust-flow connections are visited first, in the order
+	 * determined by the flow direction, then any conduit/vertical connections.
 	 */
 	void forEach(Consumer<WireConnection> consumer, int iFlowDir) {
 		for (int iDir : WireHandler.CARDINAL_UPDATE_ORDERS[iFlowDir]) {
 			for (WireConnection c = heads[iDir]; c != null && c.iDir == iDir; c = c.next) {
+				if (c.flow) {
+					consumer.accept(c);
+				}
+			}
+		}
+
+		for (WireConnection c = head; c != null; c = c.next) {
+			if (!c.flow) {
 				consumer.accept(c);
 			}
 		}
diff --git a/game/core/src/main/java/net/minecraft/core/current/wire/WireHandler.java b/game/core/src/main/java/net/minecraft/core/current/wire/WireHandler.java
index 9565b40a6..b6e901fe4 100644
--- a/game/core/src/main/java/net/minecraft/core/current/wire/WireHandler.java
+++ b/game/core/src/main/java/net/minecraft/core/current/wire/WireHandler.java
@@ -1,6 +1,8 @@
 package net.minecraft.core.current.wire;
 
 import net.minecraft.core.block.BlockLogicConduit;
+import net.minecraft.core.block.BlockLogicMatcher;
+import net.minecraft.core.block.BlockLogicVeryRotatable;
 import net.minecraft.core.block.BlockLogicWireRedstone;
 import net.minecraft.core.block.Block;
 import net.minecraft.core.block.Blocks;
@@ -17,6 +19,8 @@ import java.util.Map;
 import java.util.Queue;
 import java.util.function.Consumer;
 
+import static net.minecraft.core.current.wire.WireNode.Medium.CONDUIT;
+
 /**
  * This class handles power changes for redstone wire. The algorithm was
  * designed with the following goals in mind:
@@ -339,7 +343,18 @@ public class WireHandler {
      * cache and update it.
      */
     private @NotNull Node getNextNode(@NotNull TilePosc pos, @NotNull BlockState state) {
-        return state.is(Blocks.WIRE_REDSTONE) ? new WireNode(this.world, pos, state) : getNextNode().set(pos, state, true);
+        return isPowerNodeState(state) ? new WireNode(this.world, pos, state) : getNextNode().set(pos, state, true);
+    }
+
+    /** closed and inert conduits carry nothing and are treated as ordinary blocks. */
+    private static boolean isPowerNodeState(@NotNull BlockState state) {
+        if (state.is(Blocks.WIRE_REDSTONE)) {
+            return true;
+        }
+        if (state.is(Blocks.CONDUIT)) {
+            return BlockLogicConduit.getOpenSides(state.get()).length > 0;
+        }
+        return false;
     }
 
     /**
@@ -380,7 +395,7 @@ public class WireHandler {
         BlockState state = WorldHelper.getBlockState(this.world, pos);
 
         boolean wasWire = node.isWire();
-        boolean isWire = state.is(Blocks.WIRE_REDSTONE);
+        boolean isWire = isPowerNodeState(state);
 
         if (wasWire != isWire) {
             return getNextNode(pos, state);
@@ -680,7 +695,7 @@ public class WireHandler {
         wire.discovered = true;
         wire.searched = false;
 
-        if (!wire.removed && !wire.shouldBreak && !wire.state.canSurvive(this.world, wire.pos)) {
+        if (!wire.is(CONDUIT) && !wire.removed && !wire.shouldBreak && !wire.state.canSurvive(this.world, wire.pos)) {
             wire.shouldBreak = true;
         }
 
@@ -732,7 +747,7 @@ public class WireHandler {
                 int power = Math.max(POWER_MIN, neighbor.virtualPower - POWER_STEP);
                 int iOpp = Directions.iOpposite(connection.iDir);
 
-                wire.offerPower(power, iOpp);
+                wire.offerPower(power, iOpp, connection.flow);
             }
         });
     }
@@ -741,11 +756,16 @@ public class WireHandler {
      * Determine the redstone signal the given wire receives from non-wire
      * components and update the virtual power accordingly.
      */
-    private void findExternalPower(WireNode wire) {
+    private void findExternalPower(@NotNull WireNode wire) {
         if (wire.removed || wire.shouldBreak || wire.externalPower >= POWER_MAX) {
             return;
         }
 
+        if (wire.is(CONDUIT)) {
+            findExternalPowerConduit(wire);
+            return;
+        }
+
         int maxExternalPower = POWER_MIN;
 
         for (int iDir = 0; iDir < Directions.ALL.length; iDir++) {
@@ -755,32 +775,6 @@ public class WireHandler {
                 continue;
             }
 
-            //conduit logic
-            if (neighboringNode.state.is(Blocks.CONDUIT)) {
-                int cData = neighboringNode.state.get();
-
-                Side conduitFace = getSide(iDir);
-
-                if (BlockLogicConduit.isSideOpen(cData, conduitFace)) {
-                    int cStrength = cData / BlockLogicConduit.STATE_COUNT;
-                    int convertedStrength = BlockLogicConduit.signalConverter(cStrength, true);
-
-                    //step down the signal strength when passing from conduit to wire
-                    convertedStrength = Math.max(0, convertedStrength - 1);
-
-                    //if signal is less than current power, set to 0
-                    if (convertedStrength < wire.currentPower) {
-                        convertedStrength = 0;
-                    }
-
-                    if (convertedStrength > maxExternalPower) {
-                        maxExternalPower = convertedStrength;
-                    }
-                }
-                //this continue took me hours to find. I hate my life
-                continue;
-            }
-
             if (neighboringNode.isConductor() && hasDirectSignalTo(wire, neighboringNode, Directions.iOpposite(iDir))) {
                 maxExternalPower = POWER_MAX;
             }
@@ -796,19 +790,34 @@ public class WireHandler {
         }
     }
 
-    private static @NotNull Side getSide(int iDir) {
-        Side wireSide = Side.NONE;
-        wireSide = switch (iDir) {
-            case Directions.WEST -> Side.WEST;
-            case Directions.NORTH -> Side.NORTH;
-            case Directions.EAST -> Side.EAST;
-            case Directions.SOUTH -> Side.SOUTH;
-            case Directions.DOWN -> Side.BOTTOM;
-            case Directions.UP -> Side.TOP;
-            default -> wireSide;
-        };
+    /**
+     * Determine the redstone signal a conduit node receives from non-network
+     * components through its open faces. Adjacent dust and conduits are skipped
+     * here because they are network nodes and reach this conduit via connections.
+     */
+    private void findExternalPowerConduit(@NotNull WireNode conduit) {
+        int maxExternalPower = POWER_MIN;
+
+        int data = conduit.state.get();
+        for (Side side : BlockLogicConduit.getOpenSides(data)) {
+            TilePos nPos = new TilePos(conduit.pos).add(side);
+            Block<?> nType = this.world.getBlockType(nPos);
+
+            if (nType == Blocks.WIRE_REDSTONE || nType == Blocks.CONDUIT) {
+                continue;
+            }
+
+            int external = BlockLogicConduit.getAndConvertNeighborSignal(this.world, conduit.pos, side);
+            if (external > maxExternalPower) {
+                maxExternalPower = external;
+            }
+        }
 
-        return wireSide.opposite();
+        conduit.externalPower = conduit.clampPower(maxExternalPower);
+
+        if (conduit.externalPower > conduit.virtualPower) {
+            conduit.virtualPower = conduit.externalPower;
+        }
     }
 
     /**
@@ -841,8 +850,19 @@ public class WireHandler {
      * Determine whether the given wire receives a direct signal from non-wire
      * components through the given conductor node.
      */
-    private boolean hasDirectSignalTo(WireNode wire, Node node, int except) {
+    private boolean hasDirectSignalTo(WireNode wire, @NotNull Node node, int except) {
+        if (node.state.is(Blocks.BLOCK_REDSTONE) || node.state.is(Blocks.MATCHER) || node.state.is(Blocks.MATCHER_ACTIVE)) {
+            return false;
+        }
+        Direction excludedFace = node.state.is(Blocks.PUMPKIN_REDSTONE)
+            ? BlockLogicVeryRotatable.metaToDirection(node.state.get())
+            : null;
+
         for (int iDir : Directions.I_EXCEPT[except]) {
+            if (Directions.ALL[iDir] == excludedFace) {
+                continue;
+            }
+
             Node neighbor = getNeighbor(node, iDir);
 
             if (neighbor.state.is(Blocks.WIRE_REDSTONE) || neighbor.state.is(Blocks.CONDUIT)) {
@@ -860,14 +880,14 @@ public class WireHandler {
     /**
      * Check if the given wire needs to update its state in the world.
      */
-    private boolean needsUpdate(WireNode wire) {
+    private boolean needsUpdate(@NotNull WireNode wire) {
         return wire.removed || wire.shouldBreak || wire.virtualPower != wire.currentPower;
     }
 
     /**
      * Queue the given wire for the breadth-first search as a root.
      */
-    private void searchRoot(WireNode wire) {
+    private void searchRoot(@NotNull WireNode wire) {
         int iBackupFlowDir;
 
         if (wire.connections.iFlowDir < 0) {
@@ -985,15 +1005,18 @@ public class WireHandler {
                 discover(neighbor);
                 findPower(neighbor, false);
 
-                // If power from neighboring wires has decreased, check for power
-                // from non-wire components so as to determine how low power can
-                // fall.
+                /*  If power from neighboring wires has decreased, check for power
+                    from non-wire components to determine how low power can
+                    fall. */
                 if (neighbor.virtualPower < neighbor.currentPower) {
                     findExternalPower(neighbor);
                 }
 
                 if (needsUpdate(neighbor)) {
-                    search(neighbor, false, connection.iDir);
+                    /*  The backup flow direction must be a horizontal direction.
+                        Conduit/vertical connections are non-flow and may carry a vertical
+                        iDir, so they fall back to a default rather than corrupting the flow.  */
+                    search(neighbor, false, connection.flow ? connection.iDir : Directions.WEST);
                 }
             }, wire.iFlowDir);
         }
@@ -1057,6 +1080,10 @@ public class WireHandler {
 
                 if (neighborWire != null) {
                     updateBlock(node, neighborWire.state.getBlockType());
+
+                    if (neighborWire.is(CONDUIT)) {
+                        this.world.notifyBlocksOfNeighborChange(node.pos, Blocks.CONDUIT);
+                    }
                 }
             }
         }
@@ -1096,7 +1123,7 @@ public class WireHandler {
             int power = Math.max(POWER_MIN, wire.virtualPower - POWER_STEP);
             int iDir = connection.iDir;
 
-            if (neighbor.offerPower(power, iDir)) {
+            if (neighbor.offerPower(power, iDir, connection.flow)) {
                 queueWire(neighbor);
             }
         }, wire.iFlowDir);
@@ -1105,16 +1132,56 @@ public class WireHandler {
     /**
      * Queue block updates to nodes around the given wire.
      */
-    private void queueNeighbors(WireNode wire) {
-        forEachNeighbor(wire, neighbor -> {
-            queueNeighbor(neighbor, wire);
-        });
+    private void queueNeighbors(@NotNull WireNode wire) {
+        if (wire.is(CONDUIT)) {
+            queueConduitNeighbors(wire);
+        } else {
+            forEachNeighbor(wire, neighbor -> {
+                queueNeighbor(neighbor, wire);
+            });
+        }
+    }
+
+    /**
+     * Queue block updates for the real components a conduit touches: the blocks on
+     * its open faces, plus matchers on any side.
+     * Network nodes are skipped, since power reaches them via connections.
+     */
+    private void queueConduitNeighbors(@NotNull WireNode conduit) {
+        int data = conduit.state.get();
+
+        for (int iDir = 0; iDir < Directions.ALL.length; iDir++) {
+            Node neighbor = getNeighbor(conduit, iDir);
+
+            if (neighbor.isWire()) {
+                continue;
+            }
+
+            boolean isOpen = BlockLogicConduit.isSideOpen(data, iDirToSide(iDir));
+            boolean isMatcher = neighbor.state.getBlockType().getLogic() instanceof BlockLogicMatcher;
+
+            if (isOpen || isMatcher) {
+                queueNeighbor(neighbor, conduit);
+            }
+        }
+    }
+
+    private static @NotNull Side iDirToSide(int iDir) {
+        return switch (iDir) {
+            case Directions.WEST -> Side.WEST;
+            case Directions.NORTH -> Side.NORTH;
+            case Directions.EAST -> Side.EAST;
+            case Directions.SOUTH -> Side.SOUTH;
+            case Directions.DOWN -> Side.BOTTOM;
+            case Directions.UP -> Side.TOP;
+            default -> Side.NONE;
+        };
     }
 
     /**
      * Queue the given node for an update from the given neighboring wire.
      */
-    private void queueNeighbor(Node node, WireNode neighborWire) {
+    private void queueNeighbor(@NotNull Node node, WireNode neighborWire) {
         // Updates to wires are queued when power is transmitted.
         if (!node.isWire()) {
             node.neighborWire = neighborWire;
@@ -1139,7 +1206,7 @@ public class WireHandler {
     /**
      * Emit a block update to the given node.
      */
-    private void updateBlock(Node node, Block<?> neighborBlock) {
+    private void updateBlock(@NotNull Node node, Block<?> neighborBlock) {
         TilePos pos = node.pos;
         BlockState state = WorldHelper.getBlockState(this.world, pos);
 
diff --git a/game/core/src/main/java/net/minecraft/core/current/wire/WireNode.java b/game/core/src/main/java/net/minecraft/core/current/wire/WireNode.java
index 0f8529fca..02e94bb74 100644
--- a/game/core/src/main/java/net/minecraft/core/current/wire/WireNode.java
+++ b/game/core/src/main/java/net/minecraft/core/current/wire/WireNode.java
@@ -1,6 +1,7 @@
 package net.minecraft.core.current.wire;
 
 import com.mojang.logging.LogUtils;
+import net.minecraft.core.block.BlockLogicConduit;
 import net.minecraft.core.block.BlockLogicWireRedstone;
 import net.minecraft.core.block.Blocks;
 import net.minecraft.core.current.util.BlockState;
@@ -8,8 +9,12 @@ import net.minecraft.core.util.helper.MathHelper;
 import net.minecraft.core.world.World;
 import net.minecraft.core.world.pos.TilePos;
 import net.minecraft.core.world.pos.TilePosc;
+import org.jetbrains.annotations.NotNull;
 import org.slf4j.Logger;
 
+import static net.minecraft.core.current.wire.WireNode.Medium.CONDUIT;
+import static net.minecraft.core.current.wire.WireNode.Medium.DUST;
+
 /**
  * A WireNode is a Node that represents a wire in the world. It stores all the
  * information about the wire that the WireHandler needs to calculate power
@@ -20,6 +25,15 @@ import org.slf4j.Logger;
 public class WireNode extends Node {
     private static final Logger LOGGER = LogUtils.getLogger();
 
+    /** Which kind of power-carrying block this node represents. */
+    public enum Medium {DUST, CONDUIT}
+
+    final Medium medium;
+    /**
+     * The highest power level this node can actually store.
+     */
+    final int powerCeiling;
+
     final WireConnectionManager connections;
 
     /** The power level this wire currently holds in the world. */
@@ -49,21 +63,31 @@ public class WireNode extends Node {
     /** The next wire in the simple queue. */
     WireNode next_wire;
 
-    WireNode(World world, TilePosc pos, BlockState state) {
+    WireNode(World world, TilePosc pos, @NotNull BlockState state) {
         super(world);
 
         this.pos = new TilePos(pos);
         this.state = state;
 
-        this.connections = new WireConnectionManager(this);
+        this.medium = state.is(Blocks.CONDUIT) ? CONDUIT : DUST;
+
+        if (is(CONDUIT)) {
+            int data = state.get();
+            this.powerCeiling = BlockLogicConduit.isCap(data) ? 1 : BlockLogicWireRedstone.SIGNAL_MAX;
+            this.virtualPower = this.currentPower = BlockLogicConduit.getPower(data);
+            this.direction = 0;
+        } else {
+            this.powerCeiling = BlockLogicWireRedstone.SIGNAL_MAX;
+            this.virtualPower = this.currentPower = this.state.get() & BlockLogicWireRedstone.MASK_POWER;
+            this.direction = this.state.get() & BlockLogicWireRedstone.MASK_DIRECTION;
+        }
 
-        this.virtualPower = this.currentPower = this.state.get() & BlockLogicWireRedstone.MASK_POWER;
-        this.direction = this.state.get() & BlockLogicWireRedstone.MASK_DIRECTION;
+        this.connections = new WireConnectionManager(this);
         this.priority = priority();
     }
 
     @Override
-    Node set(TilePosc pos, BlockState state, boolean clearNeighbors) {
+    Node set(TilePosc pos, @NotNull BlockState state, boolean clearNeighbors) {
         throw new UnsupportedOperationException("Cannot update a WireNode!");
     }
 
@@ -82,17 +106,30 @@ public class WireNode extends Node {
         return this;
     }
 
-    boolean offerPower(int power, int iDir) {
+    //leave non-inline for future proofing (any other mediums added)
+    boolean is(Medium medium) {
+        return this.medium == medium;
+    }
+
+    /** Clamp a power level to what this node is actually able to hold. */
+    int clampPower(int power) {
+        return Math.min(power, this.powerCeiling);
+    }
+
+    boolean offerPower(int power, int iDir, boolean recordFlow) {
         if (this.removed || this.shouldBreak) {
             return false;
         }
+
         if (power == this.virtualPower) {
-            this.flowIn |= (1 << iDir);
+            if (recordFlow) {
+                this.flowIn |= (1 << iDir);
+            }
             return false;
         }
         if (power > this.virtualPower) {
             this.virtualPower = power;
-            this.flowIn = (1 << iDir);
+            this.flowIn = recordFlow ? (1 << iDir) : 0;
 
             return true;
         }
@@ -107,9 +144,23 @@ public class WireNode extends Node {
 
         this.state = WorldHelper.getBlockState(this.world, this.pos);
 
+        if (is(CONDUIT)) {
+            if (!this.state.is(Blocks.CONDUIT)) {
+                LOGGER.warn("Tried to update power of a conduit that is no longer there! This should never happen!", new RuntimeException());
+                return false; // we should never get here
+            }
+
+            int oldData = this.state.get();
+            int newData = BlockLogicConduit.withPower(oldData, MathHelper.clamp(this.virtualPower, BlockLogicWireRedstone.SIGNAL_MIN, BlockLogicWireRedstone.SIGNAL_MAX));
+            this.currentPower = BlockLogicConduit.getPower(newData);
+            this.state = this.state.set(newData);
+
+            return WorldHelper.setWireState(this.world, this.pos, this.state);
+        }
+
         if (!this.state.is(Blocks.WIRE_REDSTONE)) {
             LOGGER.warn("Tried to update power of non redstone dust! This should never happen!", new RuntimeException());
-            return false; // we should never get here
+            return false; // we should also never get here
         }
 
         if (this.shouldBreak) {
diff --git a/game/core/src/main/java/net/minecraft/core/entity/Entity.java b/game/core/src/main/java/net/minecraft/core/entity/Entity.java
index d23c84461..06ce3d305 100644
--- a/game/core/src/main/java/net/minecraft/core/entity/Entity.java
+++ b/game/core/src/main/java/net/minecraft/core/entity/Entity.java
@@ -109,6 +109,11 @@ public abstract class Entity
     public int pushesThisTick;
     public int acidJumpCooldown = 0;
 
+    // When true, move() writes the moved bounding box back into x/y/z even for a client-side Mob.
+    // Used while a client locally simulates a vehicle it is driving so position-derived
+    // logic like limbs, etc. tracks the real movement instead of staying frozen.
+    public boolean locallySimulated = false;
+
     // Vehicle
     public @Nullable IVehicle vehicle;
     private double entityRiderPitchDelta;
@@ -167,6 +172,7 @@ public abstract class Entity
 
     // Data
     public boolean sendAdditionalData = false;
+    public boolean additionalDataChanged = false;
     protected final @NotNull SynchedEntityData entityData = new SynchedEntityData();
 
     public Entity(final @NotNull World world) {
@@ -606,7 +612,7 @@ public abstract class Entity
             }
         }
         //fix for entity jitter when blasted by cannonballs (can cause some client-side jitter when entity is under a ceiling. unsure exactly why)
-        if (!this.world.isClientSide || this instanceof Player || !(this instanceof Mob)) {
+        if (!this.world.isClientSide || this instanceof Player || !(this instanceof Mob) || this.locallySimulated) {
             this.x = (this.bb.minX + this.bb.maxX) / 2D;
             this.y = (this.bb.minY + (double) this.heightOffset) - (double) this.ySlideOffset;
             this.z = (this.bb.minZ + this.bb.maxZ) / 2D;
@@ -1144,6 +1150,10 @@ public abstract class Entity
         return isPickable();
     }
 
+    public boolean passesThroughRiderTargeting(@Nullable ItemStack heldItem) {
+        return false;
+    }
+
     public boolean isPushable() {
         return false;
     }
@@ -1193,6 +1203,8 @@ public abstract class Entity
         tag.putShort("Air", (short) this.airSupply);
         tag.putBoolean("OnGround", this.onGround);
         tag.putFloat("PushTime", this.pushTime);
+        //MC-2025 fix by @theosib and @Kademlia. Persists the exact bounding box so it can be restored on load instead of being recomputed from position.
+        tag.put("AABB", newDoubleList(new double[]{this.bb.minX, this.bb.minY, this.bb.minZ, this.bb.maxX, this.bb.maxY, this.bb.maxZ}));
         addAdditionalSaveData(tag);
     }
 
@@ -1226,6 +1238,10 @@ public abstract class Entity
         setPos(this.x, this.y, this.z);
         setRot(this.yRot, this.xRot);
         readAdditionalSaveData(tag);
+        if (tag.containsKey("AABB")) {ListTag aabbTag = tag.getList("AABB");
+            this.bb.setMin(((DoubleTag) aabbTag.tagAt(0)).getValue(), ((DoubleTag) aabbTag.tagAt(1)).getValue(), ((DoubleTag) aabbTag.tagAt(2)).getValue());
+            this.bb.setMax(((DoubleTag) aabbTag.tagAt(3)).getValue(), ((DoubleTag) aabbTag.tagAt(4)).getValue(), ((DoubleTag) aabbTag.tagAt(5)).getValue());
+        }
     }
 
     public final @Nullable NamespaceID getDispatcherId() {
@@ -1585,12 +1601,14 @@ public abstract class Entity
         return true;
     }
 
-    public void handleSpecialVehicleControl() {
+    public void handleSpecialVehicleControl() {}
 
-    }
+    public void sendSpecialVehiclePacket() {}
 
-    public void sendSpecialVehiclePacket() {
+    public void handleControlDirect(double xd, double yd, double zd, float yRot) {}
 
+    public boolean relaysVehicleControl() {
+        return false;
     }
 
     public boolean deferVehicleBehavior() {
diff --git a/game/core/src/main/java/net/minecraft/core/entity/EntityFishingBobber.java b/game/core/src/main/java/net/minecraft/core/entity/EntityFishingBobber.java
index 3de7f1161..22b02aef4 100644
--- a/game/core/src/main/java/net/minecraft/core/entity/EntityFishingBobber.java
+++ b/game/core/src/main/java/net/minecraft/core/entity/EntityFishingBobber.java
@@ -177,9 +177,7 @@ public class EntityFishingBobber extends Entity {
                     double distance = MathHelper.sqrt(dx * dx + dy * dy + dz * dz);
                     if (distance > 10) {
                         double scale = 0.01D;
-                        this.hookedEntity.xd += dx * scale;
-                        this.hookedEntity.yd += dy * scale;
-                        this.hookedEntity.zd += dz * scale;
+                        this.hookedEntity.fling(dx * scale, dy * scale, dz * scale, 0f);
                     }
                     return;
                 }
@@ -374,9 +372,7 @@ public class EntityFishingBobber extends Entity {
             double dz = this.owner.z - this.z;
             double distance = MathHelper.sqrt(dx * dx + dy * dy + dz * dz);
             double scale = 0.1D;
-            this.hookedEntity.xd += dx * scale;
-            this.hookedEntity.yd += dy * scale + (double) MathHelper.sqrt(distance) * 0.08D;
-            this.hookedEntity.zd += dz * scale;
+            this.hookedEntity.fling(dx * scale, dy * scale + (double) MathHelper.sqrt(distance) * 0.08D, dz * scale, 0f);
             damage = 3;
         } else if (this.ticksCatchable > 0) {
             if (!this.world.isClientSide) {
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 67cee9925..9f1ca5610 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
@@ -4,6 +4,7 @@ import java.util.List;
 import java.util.Objects;
 
 import com.mojang.nbt.tags.CompoundTag;
+import net.minecraft.core.Global;
 import net.minecraft.core.achievement.Achievements;
 import net.minecraft.core.achievement.stat.StatList;
 import net.minecraft.core.block.Block;
@@ -20,6 +21,7 @@ import net.minecraft.core.item.ItemStack;
 import net.minecraft.core.item.tag.ItemTags;
 import net.minecraft.core.util.helper.DamageType;
 import net.minecraft.core.util.helper.Direction;
+import net.minecraft.core.util.helper.LightIndexHelper;
 import net.minecraft.core.util.helper.MathHelper;
 import net.minecraft.core.world.World;
 import net.minecraft.core.world.pos.TilePos;
@@ -38,6 +40,33 @@ public class EntityItem extends Entity
     public float bobOffset;
     public int basketPickupDelay = 0;
     public static boolean enableItemClumping = true;
+    private static final int BASE_DESPAWN_TICKS = 10 * 60 * 20;
+    private static final int BASE_FADE_TICKS = 2 * 60 * 20;
+    public static final float DESPAWN_ALPHA_THRESHOLD = 0.01f;
+    private static final int DESPAWN_FORCE_REMOVE_BUFFER = 20;
+
+    public static int getDespawnFadeTicks(final int lifetime) {
+        if (lifetime <= 0) {
+            return 0;
+        }
+        return Math.max(1, lifetime * BASE_FADE_TICKS / BASE_DESPAWN_TICKS);
+    }
+
+    public static float getDespawnFadeAlpha(final int age, final int lifetime, final float partialTick) {
+        if (lifetime < 0) {
+            return 1.0f;
+        }
+        final float adjustedAge = age + partialTick;
+        final int fadeWindow = getDespawnFadeTicks(lifetime);
+        final float fadeStart = lifetime - fadeWindow;
+        if (adjustedAge <= fadeStart) {
+            return 1.0f;
+        }
+        final float elapsed = adjustedAge - fadeStart;
+        final float toDeath = 1.0f - elapsed / fadeWindow;
+        final double period = 6.8 + 1.2 * toDeath;
+        return (float) (0.5 + 0.5 * Math.cos((elapsed * Math.PI) / period));
+    }
 
     public EntityItem(@NotNull World world, double x, double y, double z, @NotNull ItemStack itemstack) {
         super(world);
@@ -131,9 +160,13 @@ public class EntityItem extends Entity
         }
 
         this.age++;
-        if(this.age >= this.lifetime && this.lifetime >= 0 && !this.item.getItem().hasTag(ItemTags.IS_PERSISTENT)) // Negative lifetime indicates items should last forever
-        {
-            remove();
+        if (this.lifetime >= 0 && !this.item.getItem().hasTag(ItemTags.IS_PERSISTENT)) {
+            if (this.age >= this.lifetime) {
+                if (getDespawnFadeAlpha(this.age, this.lifetime, 0f) <= DESPAWN_ALPHA_THRESHOLD
+                    || this.age >= this.lifetime + DESPAWN_FORCE_REMOVE_BUFFER) {
+                    remove();
+                }
+            }
         }
     }
 
@@ -207,6 +240,9 @@ public class EntityItem extends Entity
 			player.inventory.insertItem(this.item, true);
 
 			if(this.item.stackSize < previousStackSize) {
+				if(this.item.stackSize > 0) {
+					this.additionalDataChanged = true;
+				}
 				if(this.item.itemID < 16384) {
                     Block<?> b = Blocks.blocksList[this.item.itemID];
 					if(b != null && b.getLogic() instanceof BlockLogicLog) {
@@ -369,7 +405,7 @@ public class EntityItem extends Entity
                 this.yd += 0.001;
             }
 
-            if(blockUnderItem == Blocks.MESH)
+            if(blockUnderItem == Blocks.MESH || blockUnderItem == Blocks.SPIKES)
             {
                 this.yd -= 0.02D;
             }
@@ -383,6 +419,7 @@ public class EntityItem extends Entity
                     }
                 }
             }
+            this.yd -= 0.02D;
         }
         //gravity out of water
         else
@@ -410,12 +447,34 @@ public class EntityItem extends Entity
     public boolean isBeingSiphoned(){
         return siphonDirection != Direction.NONE;
     }
-    
+
+    @Override
+    public byte calcLightIndex(float partialTick) {
+        if (Global.accessor.isFullbrightEnabled()) return LightIndexHelper.lightIndex2i(15, 15);
+        TilePos blockPos = new TilePos(this.x, this.y - this.heightOffset + this.ySlideOffset + this.bbHeight * 0.66D, this.z);
+
+        byte lightIndex = 0;
+        if (this.world.getBlockLitInteriorSurface(blockPos) || this.world.getBlockType(blockPos).isSolidRender()) {
+            TilePos queryPos = new TilePos();
+
+            lightIndex = LightIndexHelper.max(lightIndex, this.world.getSavedLightIndex(blockPos.up(queryPos)));
+            lightIndex = LightIndexHelper.max(lightIndex, this.world.getSavedLightIndex(blockPos.west(queryPos)));
+            lightIndex = LightIndexHelper.max(lightIndex, this.world.getSavedLightIndex(blockPos.east(queryPos)));
+            lightIndex = LightIndexHelper.max(lightIndex, this.world.getSavedLightIndex(blockPos.south(queryPos)));
+            lightIndex = LightIndexHelper.max(lightIndex, this.world.getSavedLightIndex(blockPos.north(queryPos)));
+        } else {
+            lightIndex = this.world.getSavedLightIndex(blockPos);
+        }
+
+        return lightIndex;
+    }
+
     private static void combineItems(EntityItem entityItem1, EntityItem entityItem2) {
     	entityItem1.item.stackSize += entityItem2.item.stackSize;
     	entityItem2.item.stackSize = 0;
     	entityItem1.age = Math.min(entityItem1.age, entityItem2.age);
     	entityItem1.pickupDelay = Math.max(entityItem1.pickupDelay, entityItem2.pickupDelay);
+    	entityItem1.additionalDataChanged = true;
     	entityItem2.remove();
     }
 }
diff --git a/game/core/src/main/java/net/minecraft/core/entity/animal/MobPig.java b/game/core/src/main/java/net/minecraft/core/entity/animal/MobPig.java
index de875eca7..26ae0ce10 100644
--- a/game/core/src/main/java/net/minecraft/core/entity/animal/MobPig.java
+++ b/game/core/src/main/java/net/minecraft/core/entity/animal/MobPig.java
@@ -5,6 +5,7 @@ import net.minecraft.core.achievement.Achievements;
 import net.minecraft.core.block.Blocks;
 import net.minecraft.core.block.tag.BlockTags;
 import net.minecraft.core.entity.AgedMob;
+import net.minecraft.core.entity.Entity;
 import net.minecraft.core.entity.EntityLightning;
 import net.minecraft.core.entity.MobAge;
 import net.minecraft.core.entity.monster.MobZombiePig;
@@ -13,6 +14,7 @@ import net.minecraft.core.item.ItemStack;
 import net.minecraft.core.item.Items;
 import com.mojang.nbt.tags.CompoundTag;
 import net.minecraft.core.util.collection.NamespaceID;
+import net.minecraft.core.util.helper.DamageType;
 import net.minecraft.core.util.helper.MathHelper;
 import net.minecraft.core.world.World;
 import net.minecraft.core.world.pos.TilePos;
@@ -26,6 +28,7 @@ public class MobPig
 //    implements AgedMob
 {
     public static final int DATA_SADDLE_ID = 16;
+    public static final int DATA_SIT_ID = 17;
 
     public List<WeightedRandomLootObject> burningMobDrops = new ArrayList<>();
 //    private final @NotNull MobAge age;
@@ -50,6 +53,7 @@ public class MobPig
     @Override
 	protected void defineSynchedData() {
         this.entityData.define(DATA_SADDLE_ID, (byte) 0, Byte.class);
+        this.entityData.define(DATA_SIT_ID, (byte) 0, Byte.class);
     }
 
     @Override
@@ -57,6 +61,7 @@ public class MobPig
     {
         super.addAdditionalSaveData(tag);
         tag.putBoolean("Saddle", getSaddled());
+        tag.putBoolean("Sit", getSitting());
 //        this.age.writeTag(tag);
     }
 
@@ -65,16 +70,208 @@ public class MobPig
     {
         super.readAdditionalSaveData(tag);
         setSaddled(tag.getBoolean("Saddle"));
+        setSitting(tag.getBoolean("Sit"));
 //        this.age.readTag(tag);
     }
 
+    @Override
+    public boolean save(@NotNull CompoundTag tag) {
+        if (this.passenger instanceof Player) {
+            return false;
+        }
+        return super.save(tag);
+    }
+
+    private static final double MAX_CONTROL_HORIZONTAL_STEP = 1.0D;
+    private static final double MAX_CONTROL_VERTICAL_STEP = 2.0D;
+
+    private double controlTargetX;
+    private double controlTargetY;
+    private double controlTargetZ;
+    private boolean hasControlTarget;
+    private float pendingFallDistance;
+
+    private double lastStandingY;
+    private boolean wasGrounded;
+    private boolean airborneFromJump;
+
+    private static final float JUMP_LANDING_KEEP = 0.1f;
+    private static final float WALL_HIT_SPEED_LOSS = 0.12f;
+
+    private static final float NORMAL_MOVE_SPEED = 0.7f;
+    private static final float PANIC_MOVE_SPEED = 1.3f;
+    private static final int PANIC_DURATION_TICKS = 60;
+    private int panicTicks = 0;
+
+    public float lookFadeAlpha = 1.0f;
+    public long lookFadeLastRenderNanos = 0L;
+
+    @Override
+    public void setPassenger(Entity passenger) {
+        super.setPassenger(passenger);
+        this.hasControlTarget = false;
+        this.airborneFromJump = false;
+        if (passenger != null) {
+            setSitting(false);
+            this.panicTicks = 0;
+        }
+        if (passenger == null) {
+            this.xd = 0.0D;
+            this.yd = 0.0D;
+            this.zd = 0.0D;
+            this.speedProgress = 0f;
+            this.fallDistance = 0f;
+            if (getSaddled()) {
+                setSitting(true);
+            }
+        }
+    }
+
     @Override
     public void onLivingUpdate() {
+        if (this.passenger instanceof Player driver) {
+            if (!driver.lerpVehicleMotion()) {
+                this.newPosRotationIncrements = 0;
+                boolean wasMultiplayerEntity = this.isMultiplayerEntity;
+                boolean wasLocallySimulated = this.locallySimulated;
+                this.isMultiplayerEntity = false;
+                this.locallySimulated = true;
+                super.onLivingUpdate();
+                this.isMultiplayerEntity = wasMultiplayerEntity;
+                this.locallySimulated = wasLocallySimulated;
+                driver.sendSpecialVehiclePacket();
+                return;
+            }
+            if (!this.world.isClientSide) {
+                this.target = null;
+                this.pathToEntity = null;
+                this.yBodyRot = this.yRot;
+                this.xRot = driver.xRot;
+                this.fallDistance = 0f;
+                if (this.hasControlTarget) {
+                    double trueY = this.bb.minY + (double) this.heightOffset;
+                    double dx = MathHelper.clamp(this.controlTargetX - this.x, -MAX_CONTROL_HORIZONTAL_STEP, MAX_CONTROL_HORIZONTAL_STEP);
+                    double dy = MathHelper.clamp(this.controlTargetY - trueY, -MAX_CONTROL_VERTICAL_STEP, MAX_CONTROL_VERTICAL_STEP);
+                    double dz = MathHelper.clamp(this.controlTargetZ - this.z, -MAX_CONTROL_HORIZONTAL_STEP, MAX_CONTROL_HORIZONTAL_STEP);
+                    move(dx, dy, dz);
+                }
+                this.xd = 0.0D;
+                this.yd = 0.0D;
+                this.zd = 0.0D;
+                return;
+            }
+        }
         super.onLivingUpdate();
 
 //        this.age.tick(this.world);
     }
 
+    @Override
+    public void handleControlDirect(double x, double y, double z, float yRot) {
+        this.yRot = yRot;
+        this.yBodyRot = yRot;
+        this.controlTargetX = x;
+        this.controlTargetY = y;
+        this.controlTargetZ = z;
+        this.hasControlTarget = true;
+    }
+
+    @Override
+    public boolean isInWall() {
+        if (!this.world.isClientSide && this.passenger instanceof Player) {
+            return false;
+        }
+        return super.isInWall();
+    }
+
+    @Override
+    public void move(double dx, double dy, double dz) {
+        if (!hasNoPhysics() && this.passenger instanceof Player rider && rider.bb.maxY > this.bb.maxY) {
+            final double pigHeight = this.bb.maxY - this.bb.minY;
+            this.bb.maxY = rider.bb.maxY;
+            super.move(dx, dy, dz);
+            this.bb.maxY = this.bb.minY + pigHeight;
+            return;
+        }
+        super.move(dx, dy, dz);
+    }
+
+    @Override
+    public boolean passesThroughRiderTargeting(ItemStack heldItem) {
+        boolean canFeed = heldItem != null
+            && (heldItem.itemID == Blocks.MUSHROOM_BROWN.id() || heldItem.itemID == Blocks.MUSHROOM_RED.id())
+            && getHealth() < getMaxHealth();
+        return !canFeed;
+    }
+
+    @Override
+    public boolean hurt(Entity attacker, int damage, DamageType type) {
+        if (attacker != null && attacker == this.passenger) {
+            return false;
+        }
+        boolean hurt = super.hurt(attacker, damage, type);
+        if (hurt) {
+            this.speedProgress = 0f;
+            if (!this.world.isClientSide && this.passenger == null && (getSitting() || this.panicTicks > 0)) {
+                setSitting(false);
+                this.panicTicks = PANIC_DURATION_TICKS;
+            }
+        }
+        return hurt;
+    }
+
+    @Override
+    public void handleEntityEvent(byte event, float attackedAtYaw) {
+        super.handleEntityEvent(event, attackedAtYaw);
+        if (event == 2) {
+            this.speedProgress = 0f;
+        }
+    }
+
+    @Override
+    protected void jump() {
+        super.jump();
+        if (this.passenger instanceof Player) {
+            this.airborneFromJump = true;
+        }
+    }
+
+    @Override
+    public void fling(double xd, double yd, double zd, float pushTime) {
+        super.fling(xd, yd, zd, pushTime);
+        if (!this.world.isClientSide && this.passenger instanceof Player rider) {
+            rider.onVehicleFlung(this);
+        }
+    }
+
+    @Override
+    public Entity ejectRider() {
+        Entity rider = super.ejectRider();
+        this.xd = 0.0D;
+        this.yd = 0.0D;
+        this.zd = 0.0D;
+        this.speedProgress = 0f;
+        this.fallDistance = 0f;
+        this.airborneFromJump = false;
+        if (getSaddled()) {
+            setSitting(true);
+        }
+        return rider;
+    }
+
+    /**
+     * Drops <= 0.5 of a block lose no speed, past that the speed loss starts at ~25% and curves up to ~85% at a full
+     * block, and capped at 95% around 1.5 blocks.
+     */
+    private static float landingSpeedKeep(float netDrop) {
+        if (netDrop <= 0.5f) {
+            return 1f;
+        }
+        float x = (netDrop - 0.5f) / 0.5f;
+        float loss = 0.25f + 0.6f * x * x;
+        return MathHelper.clamp(1f - loss, 0.05f, 1f);
+    }
+
     @Override
     protected void updateAI() {
         if (this.passenger instanceof Player) {
@@ -84,9 +281,27 @@ public class MobPig
             this.moveForward = 0f;
             this.moveStrafing = 0f;
 
+            if (this.onGround && !this.wasGrounded) {
+                float keep = this.airborneFromJump
+                    ? JUMP_LANDING_KEEP
+                    : landingSpeedKeep((float) (this.lastStandingY - this.y));
+                this.xd *= keep;
+                this.zd *= keep;
+                this.speedProgress *= keep;
+                this.airborneFromJump = false;
+            }
+            if (this.onGround) {
+                this.lastStandingY = this.y;
+            }
+            this.wasGrounded = this.onGround;
+
+            if (this.horizontalCollision) {
+                this.speedProgress = Math.max(0f, this.speedProgress - WALL_HIT_SPEED_LOSS);
+            }
+
             var friction = this.world.getBlockType(this.queryPos.set(MathHelper.floor(this.x), MathHelper.floor(this.y - 1), MathHelper.floor(this.z))).friction;
             final float accel = MathHelper.lerp(3, 10f * (1 + (0.6f - friction)), this.speedProgress) * (!this.onGround ? 0.25f : 1f) * (isInWater() ? 0.5f : 1f);
-            final float maxSpeed = MathHelper.lerp(0.15f, 0.5f * (1 - (0.6f - friction)/2.5f), this.speedProgress) * (!(this.onGround || this.yd < 0) ? 0.25f : 1f) * (isInWater() ? 0.5f : 1f);
+            final float maxSpeed = MathHelper.lerp(0.15f, 0.5f * (1 - (0.6f - friction)/2.5f), this.speedProgress) * (isInWater() ? 0.5f : 1f);
 
             double pXd = this.passenger.xd;
             double pZd = this.passenger.zd;
@@ -103,14 +318,17 @@ public class MobPig
                 pZd *= vel;
             }
 
+            double externalSpeed = Math.sqrt(this.xd * this.xd + this.zd * this.zd);
+            double effectiveMax = Math.max(maxSpeed, externalSpeed);
+
             this.xd += pXd * accel;
             this.zd += pZd * accel;
 
             double spdsqrd = this.xd * this.xd + this.zd * this.zd;
-            if (spdsqrd > maxSpeed * maxSpeed) {
+            if (spdsqrd > effectiveMax * effectiveMax) {
                 double speed = Math.sqrt(spdsqrd);
-                this.xd = (this.xd / speed) * maxSpeed;
-                this.zd = (this.zd / speed) * maxSpeed;
+                this.xd = (this.xd / speed) * effectiveMax;
+                this.zd = (this.zd / speed) * effectiveMax;
             }
 
             this.xRot = this.passenger.xRot;
@@ -119,7 +337,9 @@ public class MobPig
 
             this.isJumping = isInWater() || ((Player) this.passenger).isJumping();
 
-            if ((this.onGround || this.yd < 0) && !isInWater() && !this.isJumping) {
+            if (isInWater()) {
+                this.speedProgress = 0f;
+            } else if (this.onGround && !this.isJumping) {
                 if (pXd * pXd + pZd * pZd > 0.000005f) {
                     this.speedProgress += (1/200f) * (1 + (0.6f - friction) * 1.5f);
                     this.speedProgress *= (1.000f + 0.025f * (1 + (0.6f - friction) * 1.5f));
@@ -127,15 +347,35 @@ public class MobPig
                     this.speedProgress *= 0.9f;
                 }
                 this.speedProgress = MathHelper.clamp(this.speedProgress, 0, 1);
-            } else {
-                this.speedProgress = 0f;
+            }
+        } else if (this.panicTicks > 0) {
+            this.footSize = 0.5f;
+            this.panicTicks--;
+            this.moveSpeed = PANIC_MOVE_SPEED;
+            if (this.pathToEntity == null || this.random.nextInt(10) == 0) {
+                roamRandomPath();
+            }
+            super.updateAI();
+            if (this.panicTicks <= 0 && getSaddled() && this.passenger == null) {
+                setSitting(true);
             }
         } else {
             this.footSize = 0.5f;
+            this.moveSpeed = NORMAL_MOVE_SPEED;
             super.updateAI();
         }
     }
 
+    @Override
+    protected void checkForPlayerHoldingItem() {
+        if (this.panicTicks > 0) {
+            setTarget(null);
+            this.closestPlayer = null;
+            return;
+        }
+        super.checkForPlayerHoldingItem();
+    }
+
     @Override
     public void positionRider() {
         super.positionRider();
@@ -189,30 +429,40 @@ public class MobPig
     {
         if (super.interact(player)) return true;
         ItemStack heldItem = player.getHeldItem();
-        if (heldItem != null && (heldItem.itemID == Blocks.MUSHROOM_BROWN.id() || heldItem.itemID == Blocks.MUSHROOM_RED.id()) && heldItem.consumeItem(player)) {
+        if (heldItem != null && (heldItem.itemID == Blocks.MUSHROOM_BROWN.id() || heldItem.itemID == Blocks.MUSHROOM_RED.id())
+            && getHealth() < getMaxHealth() && heldItem.consumeItem(player)) {
             heal(4);
+
+            this.world.playSoundAtEntity(player, this, "random.bite",
+                0.2f + (this.random.nextFloat() - this.random.nextFloat()) * 0.1f,
+                0.8f + (this.random.nextFloat() - this.random.nextFloat()) * 0.1f);
             return true;
         }
-        if (!this.world.isClientSide) {
-            if (getSaddled()) {
-                if (player.isSneaking()) {
-                    if (this.passenger == null) {
-                        setSaddled(false);
-                        ItemStack toInsert = new ItemStack(Items.SADDLE);
-                        player.inventory.insertItem(toInsert, true);
-                        if (toInsert.stackSize > 0) {
-                            dropItem(toInsert, 0.0f);
-                        }
-                        return true;
-                    }
-                }
-                if (this.passenger == null || this.passenger == player) {
-                    player.startRiding(this);
-                    return true;
-                }
+        if (this.passenger == player) {
+            return false;
+        }
+        if (!getSaddled()) {
+            return false;
+        }
+        if (this.passenger != null) {
+            return false;
+        }
+        if (this.world.isClientSide) {
+            return true;
+        }
+        if (player.isSneaking()) {
+            setSaddled(false);
+            setSitting(false);
+            this.panicTicks = 0;
+            ItemStack toInsert = new ItemStack(Items.SADDLE);
+            player.inventory.insertItem(toInsert, true);
+            if (toInsert.stackSize > 0) {
+                dropItem(toInsert, 0.0f);
             }
+        } else {
+            player.startRiding(this);
         }
-        return false;
+        return true;
     }
 
     @Override
@@ -257,6 +507,29 @@ public class MobPig
         }
     }
 
+    public boolean getSitting()
+    {
+        return (this.entityData.getByte(DATA_SIT_ID) & 1) != 0;
+    }
+
+    public void setSitting(boolean flag)
+    {
+        this.entityData.set(DATA_SIT_ID, (byte) (flag ? 1 : 0));
+        if (flag) {
+            this.xd = 0.0D;
+            this.zd = 0.0D;
+            this.moveForward = 0f;
+            this.moveStrafing = 0f;
+            this.pathToEntity = null;
+            this.panicTicks = 0;
+        }
+    }
+
+    @Override
+    protected boolean isMovementCeased() {
+        return getSitting() || super.isMovementCeased();
+    }
+
     @Override
 	public void thunderHit(EntityLightning bolt)
     {
@@ -273,9 +546,33 @@ public class MobPig
 
     @Override
 	protected void causeFallDamage(float distance) {
+        if (this.passenger instanceof Player driver) {
+            if (this.world.isClientSide) {
+                this.pendingFallDistance = distance;
+            } else if (!driver.lerpVehicleMotion()) {
+                applyRiderFallDamage(distance);
+            }
+            return;
+        }
         super.causeFallDamage(distance);
-        if(distance > 5F && (this.passenger instanceof Player)) {
-            ((Player) this.passenger).triggerAchievement(Achievements.FLY_PIG);
+    }
+
+    public float consumePendingFallDistance() {
+        float distance = this.pendingFallDistance;
+        this.pendingFallDistance = 0f;
+        return distance;
+    }
+
+    public void applyRiderFallDamage(float distance) {
+        if (this.world.isClientSide) {
+            return;
+        }
+        if (distance > 5F && this.passenger instanceof Player rider) {
+            rider.triggerAchievement(Achievements.FLY_PIG);
+        }
+        int damage = (int) Math.ceil(distance - 3F);
+        if (damage > 0) {
+            hurt(null, damage, DamageType.FALL);
         }
     }
     @Override
diff --git a/game/core/src/main/java/net/minecraft/core/entity/player/Player.java b/game/core/src/main/java/net/minecraft/core/entity/player/Player.java
index 6edabf547..a06599cbe 100644
--- a/game/core/src/main/java/net/minecraft/core/entity/player/Player.java
+++ b/game/core/src/main/java/net/minecraft/core/entity/player/Player.java
@@ -127,6 +127,7 @@ public abstract class Player
     public float timeInPortal;
     public float prevTimeInPortal;
     public @Nullable EntityFishingBobber bobberEntity;
+    private @Nullable CompoundTag pendingVehicleTag;
     protected @Nullable ICarriable heldObject;
     public volatile String skinURL;
     public volatile String capeURL;
@@ -257,6 +258,15 @@ public abstract class Player
 
     @Override
     public void tick() {
+        if (this.pendingVehicleTag != null && !this.world.isClientSide) {
+            CompoundTag vehicleTag = this.pendingVehicleTag;
+            this.pendingVehicleTag = null;
+            Entity vehicle = EntityDispatcher.getInstance().createEntityFromNBT(vehicleTag, this.world);
+            if (vehicle instanceof MobPig pig) {
+                this.world.entityJoinedWorld(pig);
+                startRiding(pig);
+            }
+        }
         if (this.world.getDimension() == Dimension.NETHER) {
             addStat(Achievements.ENTER_NETHER, 1);
             if (random.nextInt(20) == 0) {
@@ -287,7 +297,7 @@ public abstract class Player
 
         final boolean wSneak = wantSneak();
 
-        if (wSneak || hasNoPhysics()) {
+        if (wSneak || hasNoPhysics() || this.vehicle != null) {
             this.sneaking = wSneak;
         } else {
             if (boundsClear(getBoundsForState(MOTION_STATE_DEFAULT))) {
@@ -373,6 +383,9 @@ public abstract class Player
         addMountedMovementStat(this.x - xdO, this.y - ydO, this.z - zdO);
     }
 
+    public void onVehicleFlung(Entity vehicle) {
+    }
+
     @Override
     public void resetPos() {
         setupScale();
@@ -955,6 +968,9 @@ public abstract class Player
             CompoundTag heldTag = tag.getCompound("HeldObject");
             this.heldObject = ICarriable.createAndLoadCarriable(this, heldTag);
         }
+        if (tag.containsKey("Vehicle")) {
+            this.pendingVehicleTag = tag.getCompound("Vehicle");
+        }
     }
 
     @Override
@@ -986,6 +1002,16 @@ public abstract class Player
             this.heldObject.writeToNBT(heldTag);
             tag.put("HeldObject", heldTag);
         }
+
+        if (this.vehicle instanceof MobPig pig) {
+            var vehicleId = pig.getDispatcherId();
+            if (vehicleId != null) {
+                CompoundTag vehicleTag = new CompoundTag();
+                vehicleTag.putString("id", vehicleId.toString());
+                pig.saveWithoutId(vehicleTag);
+                tag.put("Vehicle", vehicleTag);
+            }
+        }
     }
 
     @Override
@@ -1000,6 +1026,10 @@ public abstract class Player
     public void displayContainerScreen(Container container) {
     }
 
+    public void clearPendingVehicleTag() {
+        this.pendingVehicleTag = null;
+    }
+
     public void displayChestScreen(Container container, double x, double y, double z) {
     }
 
@@ -1180,12 +1210,12 @@ public abstract class Player
         return false;
     }
 
-    public boolean useCurrentItemOnEntity(Entity entity) {
-        boolean flag = false;
+    public boolean useCurrentItemOnEntity(@NotNull Entity entity) {
         if (entity.interact(this)) {
             swingItem();
-            flag = true;
+            return true;
         }
+        boolean flag = false;
         ItemStack itemstack = getCurrentEquippedItem();
         if (itemstack != null && (entity instanceof Mob)) {
             if (itemstack.useItemOnEntity(this, (Mob) entity)) {
@@ -1689,6 +1719,9 @@ public abstract class Player
     }
 
     public void setNoclip(final boolean noclip) {
+        if (noclip && this.vehicle != null) {
+            return;
+        }
         setNoPhysics(noclip);
         if (hasNoPhysics()) {
             this.yd += 0.079f;
diff --git a/game/core/src/main/java/net/minecraft/core/entity/vehicle/EntityBoat.java b/game/core/src/main/java/net/minecraft/core/entity/vehicle/EntityBoat.java
index 1536ee0d6..f0a9a8e52 100644
--- a/game/core/src/main/java/net/minecraft/core/entity/vehicle/EntityBoat.java
+++ b/game/core/src/main/java/net/minecraft/core/entity/vehicle/EntityBoat.java
@@ -396,8 +396,14 @@ public class EntityBoat extends Entity implements ICollidable {
         this.pendingZDChange = Math.sin(Math.toRadians(this.yRot)) * velOff;
     }
 
-    public void handleControlDirect(float xd, float zd, float yRot) {
-        this.yRot = (float) MathHelper.clamp(yRot, this.yRot - MAX_PACKET_ROTATION_CHANGE, this.yRot + MAX_PACKET_ROTATION_CHANGE);
+    @Override
+    public boolean relaysVehicleControl() {
+        return true;
+    }
+
+    @Override
+    public void handleControlDirect(double xd, double yd, double zd, float yRot) {
+        this.yRot = (float) MathHelper.clamp((double) yRot, this.yRot - MAX_PACKET_ROTATION_CHANGE, this.yRot + MAX_PACKET_ROTATION_CHANGE);
         this.xd = MathHelper.clamp(xd, this.xd - MAX_PACKET_SPEED_CHANGE, this.xd + MAX_PACKET_SPEED_CHANGE);
         this.zd = MathHelper.clamp(zd, this.zd - MAX_PACKET_SPEED_CHANGE, this.zd + MAX_PACKET_SPEED_CHANGE);
     }
diff --git a/game/core/src/main/java/net/minecraft/core/item/ItemSaddle.java b/game/core/src/main/java/net/minecraft/core/item/ItemSaddle.java
index 3675e69d9..bfc022998 100644
--- a/game/core/src/main/java/net/minecraft/core/item/ItemSaddle.java
+++ b/game/core/src/main/java/net/minecraft/core/item/ItemSaddle.java
@@ -16,6 +16,7 @@ public class ItemSaddle extends Item {
     public boolean useOnEntity(@NotNull ItemStack selfStack, @NotNull Player player, @NotNull Mob mob) {
         if(!player.isSneaking() && mob instanceof MobPig pig && !pig.getSaddled() && selfStack.consumeItem(player)) {
             pig.setSaddled(true);
+            pig.setSitting(true);
             return true;
         }
         return false;
diff --git a/game/core/src/main/java/net/minecraft/core/net/command/commands/CommandTeleport.java b/game/core/src/main/java/net/minecraft/core/net/command/commands/CommandTeleport.java
index d1f1e7ad8..171d645df 100644
--- a/game/core/src/main/java/net/minecraft/core/net/command/commands/CommandTeleport.java
+++ b/game/core/src/main/java/net/minecraft/core/net/command/commands/CommandTeleport.java
@@ -101,9 +101,19 @@ public class CommandTeleport implements CommandManager.CommandRegistry {
                             EntitySelector targetEntitySelector = c.getArgument("target", EntitySelector.class);
                             Entity targetEntity = targetEntitySelector.get(c.getSource()).get(0);
 
+                            double x = targetEntity.x;
+                            double y = targetEntity.y - targetEntity.heightOffset;
+                            double z = targetEntity.z;
+                            int targetDimension = targetEntity instanceof Player ? ((Player) targetEntity).dimension : targetEntity.world.dimension.id;
+
                             List<? extends Entity> entities = entitySelector.get(c.getSource());
                             for (Entity entity : entities) {
-                                entity.moveTo(targetEntity.x, targetEntity.y - targetEntity.heightOffset, targetEntity.z, entity.yRot, entity.xRot);
+                                if (entity instanceof Player player) {
+                                    if (player.dimension != targetDimension) source.movePlayerToDimension(player, targetDimension);
+                                    source.teleportPlayerToPos(player, x, y, z);
+                                } else {
+                                    entity.moveTo(x, y, z, entity.yRot, entity.xRot);
+                                }
                             }
 
                             if (entities.size() == 1) source.sendTranslatableMessage("command.commands.teleport.entity.success_single_entity", CommandHelper.getEntityName(entities.get(0)), CommandHelper.getEntityName(targetEntity));
diff --git a/game/core/src/main/java/net/minecraft/core/net/handler/PacketHandler.java b/game/core/src/main/java/net/minecraft/core/net/handler/PacketHandler.java
index e4ebde803..609a47978 100644
--- a/game/core/src/main/java/net/minecraft/core/net/handler/PacketHandler.java
+++ b/game/core/src/main/java/net/minecraft/core/net/handler/PacketHandler.java
@@ -211,6 +211,7 @@ public abstract class PacketHandler {
         handleInvalidPacket(packetSetHeldObject);
     }
 
+    @Deprecated
     public void handleContainerAck(@NotNull PacketContainerAck packetContainerAck) {
         handleInvalidPacket(packetContainerAck);
     }
@@ -219,8 +220,8 @@ public abstract class PacketHandler {
         handleInvalidPacket(packetAddPainting);
     }
 
-    public void handleBoatControl(@NotNull PacketBoatControl packetBoatControl) {
-        handleInvalidPacket(packetBoatControl);
+    public void handleVehicleControl(@NotNull PacketVehicleControl packetVehicleControl) {
+        handleInvalidPacket(packetVehicleControl);
     }
 
     public void handleBlockEvent(@NotNull PacketBlockEvent packetBlockEvent) {
diff --git a/game/core/src/main/java/net/minecraft/core/net/packet/Packet.java b/game/core/src/main/java/net/minecraft/core/net/packet/Packet.java
index 8e3aaf97f..c871d0372 100644
--- a/game/core/src/main/java/net/minecraft/core/net/packet/Packet.java
+++ b/game/core/src/main/java/net/minecraft/core/net/packet/Packet.java
@@ -279,7 +279,7 @@ public abstract class Packet
         addMapping(23, true, false, PacketAddEntity.class);
         addMapping(24, true, false, PacketAddMob.class);
         addMapping(25, true, false, PacketAddPainting.class);
-        addMapping(26, true, true, PacketBoatControl.class);
+        addMapping(26, true, true, PacketVehicleControl.class);
         addMapping(27, true, true, PacketSetHeldObject.class);
         addMapping(28, true, false, PacketSetEntityMotion.class);
         addMapping(29, true, false, PacketRemoveEntity.class);
diff --git a/game/core/src/main/java/net/minecraft/core/net/packet/PacketBoatControl.java b/game/core/src/main/java/net/minecraft/core/net/packet/PacketBoatControl.java
deleted file mode 100644
index ae29725cb..000000000
--- a/game/core/src/main/java/net/minecraft/core/net/packet/PacketBoatControl.java
+++ /dev/null
@@ -1,47 +0,0 @@
-package net.minecraft.core.net.packet;
-
-import net.minecraft.core.entity.Entity;
-import net.minecraft.core.net.handler.PacketHandler;
-
-import java.io.DataInputStream;
-import java.io.DataOutputStream;
-import java.io.IOException;
-
-public class PacketBoatControl extends Packet{
-    public int entityId;
-    public float targetXD;
-    public float targetZD;
-    public float targetYRot;
-    public PacketBoatControl(){}
-    public PacketBoatControl(Entity entity){
-        entityId = entity.id;
-        targetXD = (float) entity.xd;
-        targetZD = (float) entity.zd;
-        targetYRot = entity.yRot;
-    }
-    @Override
-    public void read(DataInputStream dis) throws IOException {
-        entityId = dis.readInt();
-        targetXD = dis.readFloat();
-        targetZD = dis.readFloat();
-        targetYRot = dis.readFloat();
-    }
-
-    @Override
-    public void write(DataOutputStream dos) throws IOException {
-        dos.writeInt(entityId);
-        dos.writeFloat(targetXD);
-        dos.writeFloat(targetZD);
-        dos.writeFloat(targetYRot);
-    }
-
-    @Override
-    public void handlePacket(PacketHandler packetHandler) {
-        packetHandler.handleBoatControl(this);
-    }
-
-    @Override
-    public int getEstimatedSize() {
-        return 12;
-    }
-}
diff --git a/game/core/src/main/java/net/minecraft/core/net/packet/PacketContainerAck.java b/game/core/src/main/java/net/minecraft/core/net/packet/PacketContainerAck.java
index fae9fde6b..7ed15eb4c 100644
--- a/game/core/src/main/java/net/minecraft/core/net/packet/PacketContainerAck.java
+++ b/game/core/src/main/java/net/minecraft/core/net/packet/PacketContainerAck.java
@@ -1,9 +1,11 @@
 package net.minecraft.core.net.packet;
 
 import net.minecraft.core.net.handler.PacketHandler;
+import org.jetbrains.annotations.NotNull;
 
 import java.io.*;
 
+@Deprecated
 public class PacketContainerAck extends Packet
 {
 
@@ -19,13 +21,13 @@ public class PacketContainerAck extends Packet
     }
 
     @Override
-	public void handlePacket(PacketHandler packetHandler)
+	public void handlePacket(@NotNull PacketHandler packetHandler)
     {
         packetHandler.handleContainerAck(this);
     }
 
     @Override
-	public void read(DataInputStream dis)
+	public void read(@NotNull DataInputStream dis)
         throws IOException
     {
         windowId = dis.readByte();
@@ -34,7 +36,7 @@ public class PacketContainerAck extends Packet
     }
 
     @Override
-	public void write(DataOutputStream dos)
+	public void write(@NotNull DataOutputStream dos)
         throws IOException
     {
         dos.writeByte(windowId);
diff --git a/game/core/src/main/java/net/minecraft/core/net/packet/PacketContainerClick.java b/game/core/src/main/java/net/minecraft/core/net/packet/PacketContainerClick.java
index 6fb4232e6..3afb71bb0 100644
--- a/game/core/src/main/java/net/minecraft/core/net/packet/PacketContainerClick.java
+++ b/game/core/src/main/java/net/minecraft/core/net/packet/PacketContainerClick.java
@@ -1,58 +1,72 @@
 package net.minecraft.core.net.packet;
 
+import com.mojang.nbt.tags.CompoundTag;
+import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap;
+import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
 import net.minecraft.core.InventoryAction;
 import net.minecraft.core.item.ItemStack;
 import net.minecraft.core.net.handler.PacketHandler;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 
 import java.io.*;
 
 public class PacketContainerClick extends Packet {
 
+	public int window_Id;
+	public InventoryAction action;
+	public int[] args;
+	public int stateId;
+	public Int2ObjectMap<ItemStack> changedSlots;
+	public ItemStack carriedItem;
+
 	public PacketContainerClick() {
 	}
 
-	public PacketContainerClick(int windowId, InventoryAction action, int[] args, ItemStack itemstack, short actionId) {
+	public PacketContainerClick(int windowId, InventoryAction action, int[] args, int stateId, Int2ObjectMap<ItemStack> changedSlots, ItemStack carriedItem) {
 		this.window_Id = windowId;
 		this.action = action;
 		this.args = args;
-		this.itemStack = itemstack;
-		this.actionId = actionId;
+		this.stateId = stateId;
+		this.changedSlots = changedSlots;
+		this.carriedItem = carriedItem;
 	}
 
 	@Override
-	public void handlePacket(PacketHandler packetHandler) {
+	public void handlePacket(@NotNull PacketHandler packetHandler) {
 		packetHandler.handleContainerClick(this);
 	}
 
 	@Override
-	public void read(DataInputStream dis) throws IOException {
+	public void read(@NotNull DataInputStream dis) throws IOException {
 		window_Id = dis.readByte();
 		action = InventoryAction.get(dis.readByte());
-		
+
 		int size = dis.readByte();
 		args = new int[size];
 		for(int i=0; i < size; i++) {
 			args[i] = dis.read();
 		}
-		
-		actionId = dis.readShort();
-		short word0 = dis.readShort();
-		if(word0 >= 0) {
-			byte byte0 = dis.readByte();
-			short word1 = dis.readShort();
-			itemStack = new ItemStack(word0, byte0, word1);
-		} else {
-			itemStack = null;
+
+		stateId = dis.readInt();
+
+		int changedCount = dis.readShort();
+		changedSlots = new Int2ObjectArrayMap<>(changedCount);
+		for(int i=0; i < changedCount; i++) {
+			int slot = dis.readShort();
+			changedSlots.put(slot, readStack(dis));
 		}
+
+		carriedItem = readStack(dis);
 	}
 
 	@Override
-	public void write(DataOutputStream dos)
+	public void write(@NotNull DataOutputStream dos)
         throws IOException
     {
         dos.writeByte(window_Id);
         dos.writeByte(action.getId());
-        
+
         if(args != null) {
         	if(args.length > 255) {
         		System.err.println("Too many args!");
@@ -68,27 +82,46 @@ public class PacketContainerClick extends Packet {
         }else {
         	dos.write(0);
         }
-        
-        dos.writeShort(actionId);
-        if(itemStack == null)
-        {
-            dos.writeShort(-1);
-        } else
-        {
-            dos.writeShort(itemStack.itemID);
-            dos.writeByte(itemStack.stackSize);
-            dos.writeShort(itemStack.getMetadata());
-        }
-    }
+
+		dos.writeInt(stateId);
+
+		if(changedSlots == null) {
+			dos.writeShort(0);
+		} else {
+			dos.writeShort(changedSlots.size());
+			for(Int2ObjectMap.Entry<ItemStack> entry : changedSlots.int2ObjectEntrySet()) {
+				dos.writeShort(entry.getIntKey());
+				writeStack(dos, entry.getValue());
+			}
+		}
+
+		writeStack(dos, carriedItem);
+	}
+
+	private static @Nullable ItemStack readStack(@NotNull DataInputStream dis) throws IOException {
+		short itemId = dis.readShort();
+		if(itemId >= 0) {
+			byte size = dis.readByte();
+			short meta = dis.readShort();
+			CompoundTag tag = readCompressedCompoundTag(dis);
+			return new ItemStack(itemId, size, meta, tag);
+		}
+		return null;
+	}
+
+	private static void writeStack(DataOutputStream dos, ItemStack stack) throws IOException {
+		if(stack == null) {
+			dos.writeShort(-1);
+		} else {
+			dos.writeShort(stack.itemID);
+			dos.writeByte(stack.stackSize);
+			dos.writeShort(stack.getMetadata());
+			writeCompressedCompoundTag(stack.getData(), dos);
+		}
+	}
 
 	@Override
 	public int getEstimatedSize() {
-		return 7 + (args != null ? args.length : 0);
+		return 11 + (args != null ? args.length : 0) + (changedSlots != null ? changedSlots.size() * 7 : 0);
 	}
-
-	public int window_Id;
-	public InventoryAction action;
-	public int[] args;
-	public short actionId;
-	public ItemStack itemStack;
 }
diff --git a/game/core/src/main/java/net/minecraft/core/net/packet/PacketContainerSetContent.java b/game/core/src/main/java/net/minecraft/core/net/packet/PacketContainerSetContent.java
index 29378c678..533f27578 100644
--- a/game/core/src/main/java/net/minecraft/core/net/packet/PacketContainerSetContent.java
+++ b/game/core/src/main/java/net/minecraft/core/net/packet/PacketContainerSetContent.java
@@ -3,6 +3,8 @@ package net.minecraft.core.net.packet;
 import com.mojang.nbt.tags.CompoundTag;
 import net.minecraft.core.item.ItemStack;
 import net.minecraft.core.net.handler.PacketHandler;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 
 import java.io.*;
 import java.util.List;
@@ -10,16 +12,20 @@ import java.util.List;
 public class PacketContainerSetContent extends Packet
 {
     public int windowId;
+    public int stateId;
     public ItemStack[] stackList;
+    public ItemStack carriedItem;
 
 
     public PacketContainerSetContent()
     {
     }
 
-    public PacketContainerSetContent(int i, List<ItemStack> list)
+    public PacketContainerSetContent(int windowId, int stateId, List<ItemStack> list, ItemStack carriedItem)
     {
-        windowId = i;
+        this.windowId = windowId;
+        this.stateId = stateId;
+        this.carriedItem = carriedItem != null ? carriedItem.copy() : null;
         stackList = new ItemStack[list.size()];
         for(int j = 0; j < stackList.length; j++)
         {
@@ -30,54 +36,60 @@ public class PacketContainerSetContent extends Packet
     }
 
     @Override
-	public void read(DataInputStream dis)
+	public void read(@NotNull DataInputStream dis)
         throws IOException
     {
         windowId = dis.readByte();
+        stateId = dis.readInt();
+        carriedItem = readStack(dis);
         short listSize = dis.readShort();
         stackList = new ItemStack[listSize];
         for(int i = 0; i < listSize; i++)
         {
-            short itemId = dis.readShort();
-            if(itemId >= 0)
-            {
-                byte size = dis.readByte();
-                short meta = dis.readShort();
-                CompoundTag tag = readCompressedCompoundTag(dis);
-                ItemStack s = new ItemStack(itemId, size, meta, tag);
-                stackList[i] = s;
-//                System.out.printf("Received|| Name:%s, ID:%s, CustomName:%s, Tag:%s\n", s.getItemKey(), s.itemID, s.getCustomName(), s.getData());
-            }
+            stackList[i] = readStack(dis);
         }
 
     }
 
     @Override
-	public void write(DataOutputStream dos)
+	public void write(@NotNull DataOutputStream dos)
         throws IOException
     {
         dos.writeByte(windowId);
+        dos.writeInt(stateId);
+        writeStack(dos, carriedItem);
         dos.writeShort(stackList.length);
         for(int i = 0; i < stackList.length; i++)
         {
-            if(stackList[i] == null)
-            {
-                dos.writeShort(-1);
-            } else
-            {
-                dos.writeShort((short) stackList[i].itemID);
-                dos.writeByte((byte) stackList[i].stackSize);
-                dos.writeShort((short) stackList[i].getMetadata());
-                writeCompressedCompoundTag(stackList[i].getData(), dos);
-//                ItemStack s = stackList[i];
-//                System.out.printf("Sent|| Name:%s, ID:%s, CustomName:%s, Tag:%s\n", s.getItemKey(), s.itemID, s.getCustomName(), s.getData());
-            }
+            writeStack(dos, stackList[i]);
         }
 
     }
 
+    private static @Nullable ItemStack readStack(@NotNull DataInputStream dis) throws IOException {
+        short itemId = dis.readShort();
+        if(itemId >= 0) {
+            byte size = dis.readByte();
+            short meta = dis.readShort();
+            CompoundTag tag = readCompressedCompoundTag(dis);
+            return new ItemStack(itemId, size, meta, tag);
+        }
+        return null;
+    }
+
+    private static void writeStack(DataOutputStream dos, ItemStack stack) throws IOException {
+        if(stack == null) {
+            dos.writeShort(-1);
+        } else {
+            dos.writeShort((short) stack.itemID);
+            dos.writeByte((byte) stack.stackSize);
+            dos.writeShort((short) stack.getMetadata());
+            writeCompressedCompoundTag(stack.getData(), dos);
+        }
+    }
+
     @Override
-	public void handlePacket(PacketHandler packetHandler)
+	public void handlePacket(@NotNull PacketHandler packetHandler)
     {
         packetHandler.handleContainerSetContent(this);
     }
@@ -85,6 +97,6 @@ public class PacketContainerSetContent extends Packet
     @Override
 	public int getEstimatedSize()
     {
-        return 3 + stackList.length * 5;
+        return 12 + stackList.length * 5;
     }
 }
diff --git a/game/core/src/main/java/net/minecraft/core/net/packet/PacketContainerSetSlot.java b/game/core/src/main/java/net/minecraft/core/net/packet/PacketContainerSetSlot.java
index 4d879790a..99bf584c3 100644
--- a/game/core/src/main/java/net/minecraft/core/net/packet/PacketContainerSetSlot.java
+++ b/game/core/src/main/java/net/minecraft/core/net/packet/PacketContainerSetSlot.java
@@ -3,6 +3,7 @@ package net.minecraft.core.net.packet;
 import net.minecraft.core.item.ItemStack;
 import com.mojang.nbt.tags.CompoundTag;
 import net.minecraft.core.net.handler.PacketHandler;
+import org.jetbrains.annotations.NotNull;
 
 import java.io.*;
 
@@ -14,23 +15,25 @@ public class PacketContainerSetSlot extends Packet
     }
 
     @Override
-	public void handlePacket(PacketHandler packetHandler)
+	public void handlePacket(@NotNull PacketHandler packetHandler)
     {
         packetHandler.handleContainerSetSlot(this);
     }
-    
-    public PacketContainerSetSlot(int i, int j, ItemStack itemstack)
+
+    public PacketContainerSetSlot(int windowId, int stateId, int slot, ItemStack itemstack)
     {
-        windowId = i;
-        itemSlot = j;
-        myItemStack = itemstack != null ? itemstack.copy() : itemstack;
+        this.windowId = windowId;
+        this.stateId = stateId;
+        this.itemSlot = slot;
+        this.myItemStack = itemstack != null ? itemstack.copy() : itemstack;
     }
 
     @Override
-	public void read(DataInputStream dis)
+	public void read(@NotNull DataInputStream dis)
         throws IOException
     {
         windowId = dis.readByte();
+        stateId = dis.readInt();
         itemSlot = dis.readShort();
         short word0 = dis.readShort();
         if(word0 >= 0)
@@ -46,10 +49,11 @@ public class PacketContainerSetSlot extends Packet
     }
 
     @Override
-	public void write(DataOutputStream dos)
+	public void write(@NotNull DataOutputStream dos)
         throws IOException
     {
         dos.writeByte(windowId);
+        dos.writeInt(stateId);
         dos.writeShort(itemSlot);
         if(myItemStack == null)
         {
@@ -66,10 +70,11 @@ public class PacketContainerSetSlot extends Packet
     @Override
 	public int getEstimatedSize()
     {
-        return 8;
+        return 12;
     }
 
     public int windowId;
+    public int stateId;
     public int itemSlot;
     public ItemStack myItemStack;
 }
diff --git a/game/core/src/main/java/net/minecraft/core/net/packet/PacketVehicleControl.java b/game/core/src/main/java/net/minecraft/core/net/packet/PacketVehicleControl.java
new file mode 100644
index 000000000..de104e8a0
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/net/packet/PacketVehicleControl.java
@@ -0,0 +1,71 @@
+package net.minecraft.core.net.packet;
+
+import net.minecraft.core.entity.Entity;
+import net.minecraft.core.net.handler.PacketHandler;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+
+/**
+ * Sent by the client driving a vehicle. The server applies it, and may send it to nearby players.
+ * <p>
+ * The meaning of the three values changes based on the vehicle:
+ * <li>{@code EntityBoat} treats xd/yd/zd as velocity</li>
+ * <li>{@code MobPig} treats xd/yd/zd as the pig's absolute position</li>
+ */
+public class PacketVehicleControl extends Packet {
+    public int entityId;
+    public double targetXD;
+    public double targetYD;
+    public double targetZD;
+    public float targetYRot;
+    public float fallDistance;
+
+    public PacketVehicleControl() {
+    }
+
+    public PacketVehicleControl(@NotNull Entity entity) {
+        this(entity.id, entity.xd, entity.yd, entity.zd, entity.yRot, 0f);
+    }
+
+    public PacketVehicleControl(int entityId, double targetXD, double targetYD, double targetZD, float targetYRot, float fallDistance) {
+        this.entityId = entityId;
+        this.targetXD = targetXD;
+        this.targetYD = targetYD;
+        this.targetZD = targetZD;
+        this.targetYRot = targetYRot;
+        this.fallDistance = fallDistance;
+    }
+
+    @Override
+    public void read(@NotNull DataInputStream dis) throws IOException {
+        entityId = dis.readInt();
+        targetXD = dis.readDouble();
+        targetYD = dis.readDouble();
+        targetZD = dis.readDouble();
+        targetYRot = dis.readFloat();
+        fallDistance = dis.readFloat();
+    }
+
+    @Override
+    public void write(@NotNull DataOutputStream dos) throws IOException {
+        dos.writeInt(entityId);
+        dos.writeDouble(targetXD);
+        dos.writeDouble(targetYD);
+        dos.writeDouble(targetZD);
+        dos.writeFloat(targetYRot);
+        dos.writeFloat(fallDistance);
+    }
+
+    @Override
+    public void handlePacket(@NotNull PacketHandler packetHandler) {
+        packetHandler.handleVehicleControl(this);
+    }
+
+    @Override
+    public int getEstimatedSize() {
+        return 4 + 8 * 3 + 4 + 4;
+    }
+}
diff --git a/game/core/src/main/java/net/minecraft/core/player/inventory/menu/MenuAbstract.java b/game/core/src/main/java/net/minecraft/core/player/inventory/menu/MenuAbstract.java
index f558bd76f..7f02e44c3 100644
--- a/game/core/src/main/java/net/minecraft/core/player/inventory/menu/MenuAbstract.java
+++ b/game/core/src/main/java/net/minecraft/core/player/inventory/menu/MenuAbstract.java
@@ -31,18 +31,17 @@ public abstract class MenuAbstract
 	public List<ItemStack> lastSlots;
 	public List<Slot> slots;
 	public int containerId;
-	private short changeUid;
+	public int stateId;
+	private boolean suppressRemoteUpdates;
 	protected List<ContainerListener> containerListeners;
-	private final Set<Player> unSynchedPlayers;
 
 	public MenuAbstract()
 	{
 		lastSlots = new ArrayList<>();
 		slots = new ArrayList<>();
 		containerId = 0;
-		changeUid = 0;
+		stateId = 0;
 		containerListeners = new ArrayList<>();
-		unSynchedPlayers = new HashSet<>();
 	}
 
 	protected final void addSlot(Slot slot)
@@ -62,6 +61,7 @@ public abstract class MenuAbstract
 		{
 			containerListeners.add(containerListener);
 			containerListener.updateCraftingInventory(this, getSlotStackList());
+			resetRemoteSlots();
 			broadcastChanges();
 		}
 	}
@@ -79,6 +79,11 @@ public abstract class MenuAbstract
 
 	public void broadcastChanges()
 	{
+		if(suppressRemoteUpdates)
+		{
+			return;
+		}
+		boolean bumpedState = false;
 		for(int i = 0; i < slots.size(); i++)
 		{
 
@@ -88,6 +93,11 @@ public abstract class MenuAbstract
 			{
 				continue;
 			}
+			if(!bumpedState)
+			{
+				incrementStateId();
+				bumpedState = true;
+			}
 			itemstack1 = itemstack != null ? itemstack.copy() : null;
 			lastSlots.set(i, itemstack1);
 			for (ContainerListener crafter : containerListeners) {
@@ -98,6 +108,36 @@ public abstract class MenuAbstract
 
 	}
 
+	public int getStateId()
+	{
+		return stateId;
+	}
+
+	public int incrementStateId()
+	{
+		this.stateId++;
+		return this.stateId;
+	}
+
+	public void setRemoteSlot(int index, ItemStack stack)
+	{
+		lastSlots.set(index, stack != null ? stack.copy() : null);
+	}
+
+	public void resetRemoteSlots()
+	{
+		for(int i = 0; i < slots.size(); i++)
+		{
+			ItemStack stack = slots.get(i).getItemStack();
+			lastSlots.set(i, stack != null ? stack.copy() : null);
+		}
+	}
+
+	public void setRemoteUpdatesSuppressed(boolean suppressed)
+	{
+		this.suppressRemoteUpdates = suppressed;
+	}
+
 	/**
 	 * @param container ContainerInventory
 	 * @param index integer index of slot in container
@@ -801,22 +841,6 @@ public abstract class MenuAbstract
 		broadcastChanges();
 	}
 
-	public boolean isSynched(Player entityplayer)
-	{
-		return !unSynchedPlayers.contains(entityplayer);
-	}
-
-	public void setSynched(Player entityplayer, boolean flag)
-	{
-		if(flag)
-		{
-			unSynchedPlayers.remove(entityplayer);
-		} else
-		{
-			unSynchedPlayers.add(entityplayer);
-		}
-	}
-
 	public void setItem(int i, ItemStack itemstack)
 	{
 		getSlot(i).set(itemstack);
@@ -835,20 +859,6 @@ public abstract class MenuAbstract
 	{
 	}
 
-	public short backup(ContainerInventory inventoryplayer)
-	{
-		changeUid++;
-		return changeUid;
-	}
-
-	public void deleteBackup(short word0)
-	{
-	}
-
-	public void rollbackToBackup(short word0)
-	{
-	}
-
 	public abstract boolean stillValid(@NotNull Player player);
 
 	/**
diff --git a/game/core/src/main/java/net/minecraft/core/player/inventory/menu/MenuInventoryCreative.java b/game/core/src/main/java/net/minecraft/core/player/inventory/menu/MenuInventoryCreative.java
index d28f2b8f8..d91a68214 100644
--- a/game/core/src/main/java/net/minecraft/core/player/inventory/menu/MenuInventoryCreative.java
+++ b/game/core/src/main/java/net/minecraft/core/player/inventory/menu/MenuInventoryCreative.java
@@ -155,6 +155,11 @@ public class MenuInventoryCreative extends MenuInventory
 		inventory.player.updateCreativeInventory(page, searchText);
 	}
 
+	public void broadcastInventoryStatus()
+	{
+		inventory.player.updateCreativeInventory(page, searchText);
+	}
+
 	public String getSearchText(){
 		return searchText;
 	}
diff --git a/game/core/src/main/java/net/minecraft/core/util/phys/BoundingVolume.java b/game/core/src/main/java/net/minecraft/core/util/phys/BoundingVolume.java
index 6f653673f..236a8860e 100644
--- a/game/core/src/main/java/net/minecraft/core/util/phys/BoundingVolume.java
+++ b/game/core/src/main/java/net/minecraft/core/util/phys/BoundingVolume.java
@@ -9,6 +9,9 @@ import java.util.ArrayList;
 import java.util.EnumSet;
 import java.util.List;
 
+import org.jetbrains.annotations.NotNull;
+import org.joml.primitives.AABBdc;
+
 public class BoundingVolume
 {
     private enum Line
@@ -79,6 +82,46 @@ public class BoundingVolume
         return volume;
     }
 
+    public static @NotNull BoundingVolume bakeFromBlockSpaceAabbs(AABBdc @NotNull ... boxes)
+    {
+        BoundingVolume vol = BoundingVolume.newEmptyVolume();
+        if (boxes.length == 0)
+        {
+            vol.bake();
+            return vol;
+        }
+        double inv = 1.0 / SIZE;
+        for (int iy = 0; iy < SIZE; iy++)
+        {
+            for (int iz = 0; iz < SIZE; iz++)
+            {
+                for (int ix = 0; ix < SIZE; ix++)
+                {
+                    double x = (ix + 0.5) * inv;
+                    double y = (iy + 0.5) * inv;
+                    double z = (iz + 0.5) * inv;
+                    for (AABBdc box : boxes)
+                    {
+                        if (pointInsideBlockSpaceAabb(box, x, y, z))
+                        {
+                            vol.fillVolume(ix, iy, iz, ix, iy, iz);
+                            break;
+                        }
+                    }
+                }
+            }
+        }
+        vol.bake();
+        return vol;
+    }
+
+    private static boolean pointInsideBlockSpaceAabb(@NotNull AABBdc box, double x, double y, double z)
+    {
+        return x >= box.minX() && x <= box.maxX()
+                && y >= box.minY() && y <= box.maxY()
+                && z >= box.minZ() && z <= box.maxZ();
+    }
+
     private boolean isCarved(int x, int y, int z)
     {
         if (x < 0 || x >= SIZE) return true;
diff --git a/game/core/src/main/java/net/minecraft/core/world/World.java b/game/core/src/main/java/net/minecraft/core/world/World.java
index e892660e7..5f4aa2f35 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
@@ -3,7 +3,6 @@ package net.minecraft.core.world;
 import com.mojang.logging.LogUtils;
 import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
 import it.unimi.dsi.fastutil.objects.ObjectArrayList;
-import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet;
 import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
 import net.minecraft.core.Global;
 import net.minecraft.core.NextTickListEntry;
@@ -14,7 +13,6 @@ import net.minecraft.core.block.material.Material;
 import net.minecraft.core.block.material.Materials;
 import net.minecraft.core.block.support.ISupport;
 import net.minecraft.core.block.tag.BlockTags;
-import net.minecraft.core.current.wire.ConduitHandler;
 import net.minecraft.core.current.wire.WireHandler;
 import net.minecraft.core.data.gamerule.GameRule;
 import net.minecraft.core.data.gamerule.GameRules;
@@ -49,7 +47,6 @@ import net.minecraft.core.world.chunk.provider.ChunkProvider;
 import net.minecraft.core.world.config.spawning.SpawnerConfig;
 import net.minecraft.core.world.lighting.LightingEngine;
 import net.minecraft.core.world.lighting.LightingEngineLegacy;
-import net.minecraft.core.world.particle.ParticleEmitterRegistry;
 import net.minecraft.core.world.pathfinder.Path;
 import net.minecraft.core.world.pathfinder.PathFinder;
 import net.minecraft.core.world.pos.*;
@@ -120,14 +117,13 @@ public abstract class World implements MutableWorldSource {
     private final @NotNull List<@NotNull Entity> entityBuffer = new ObjectArrayList<>();
     private final @NotNull Set<@NotNull NextTickListEntry> immediatelyUpdatedPositions = new ObjectOpenHashSet<>();
 
-    public final ParticleEmitterRegistry particleEmitters = new ParticleEmitterRegistry();
-
     public final @NotNull Random rand = new Random();
     private final AABBd workingBox = new AABBd();
     public final @NotNull Dimension dimension;
     public final boolean isNewWorld;
 
     public boolean scheduledUpdatesAreImmediate = false;
+    public boolean isDecorating = false;
     public int skyDarken = 0;
     protected int updateLCG = new Random().nextInt();
     public int lightningFlicker = 0;
@@ -156,7 +152,6 @@ public abstract class World implements MutableWorldSource {
     private final @NotNull WeatherManager weatherManager;
     private final @NotNull CommandManager commandManager = new CommandManager(isServer);
     private final @NotNull WireHandler wireHandler = new WireHandler(this);
-    private final @NotNull ConduitHandler conduitHandler = new ConduitHandler(this);
     private final @NotNull LightingEngine lightingEngine = new LightingEngineLegacy(this);
 
     protected World(
@@ -314,10 +309,6 @@ public abstract class World implements MutableWorldSource {
         return this.wireHandler;
     }
 
-    public @NotNull ConduitHandler getConduitHandler() {
-        return this.conduitHandler;
-    }
-
     protected abstract @NotNull ChunkProvider createChunkProvider(@NotNull LevelStorage levelStorage); /* {
         final ChunkLoader chunkLoader = this.levelStorage.getChunkLoader(this.dimension);
         return accessor.createChunkProvider(this, chunkLoader);
@@ -586,8 +577,8 @@ public abstract class World implements MutableWorldSource {
 
         final @NotNull ChunkPos chunkPos = new ChunkPos(minX, minZ);
 
-        for (; chunkPos.x <= maxX; chunkPos.x++) {
-            for (; chunkPos.z <= maxZ; chunkPos.z++) {
+        for (chunkPos.x = minX; chunkPos.x <= maxX; chunkPos.x++) {
+            for (chunkPos.z = minZ; chunkPos.z <= maxZ; chunkPos.z++) {
                 if (!this.isChunkLoaded(chunkPos)) {
                     return false;
                 }
@@ -633,12 +624,20 @@ public abstract class World implements MutableWorldSource {
         return this.getChunk(new ChunkPos(x, z));
     }
 
+    private boolean notifyDecorationWrite(final @NotNull Chunk chunk, final @NotNull TilePosc tilePos, final boolean changed) {
+        if (changed && this.isDecorating && chunk.sentToClients) {
+            this.markBlockNeedsUpdate(tilePos);
+        }
+        return changed;
+    }
+
     @Override
     public boolean setBlockTypeData(final @NotNull TilePosc tilePos, final @NotNull Block<?> block, final int data) {
         if (!tilePos.inBounds(this)) {
             return false;
         } else {
-            return this.getChunk(tilePos).setBlockIdData(new ChunkTilePos(tilePos), block.id(), data);
+            final Chunk chunk = this.getChunk(tilePos);
+            return this.notifyDecorationWrite(chunk, tilePos, chunk.setBlockIdData(new ChunkTilePos(tilePos), block.id(), data));
         }
     }
 
@@ -650,6 +649,7 @@ public abstract class World implements MutableWorldSource {
         final var prevBlock = c.getBlock(ctp);
         final var prevData = c.getBlockData(ctp);
         if (!c.setBlockIdDataRaw(ctp, block.id(), data)) return false;
+        this.notifyDecorationWrite(c, tilePos, true);
 
         this.replaceTileEntity(tilePos, entity);
         prevBlock.getLogic().onRemoved(this, tilePos, prevData);
@@ -671,7 +671,8 @@ public abstract class World implements MutableWorldSource {
         if (!tilePos.inBounds(this)) {
             return false;
         } else {
-            return this.getChunk(tilePos).setBlockIdDataRaw(new ChunkTilePos(tilePos), block.id(), data);
+            final Chunk chunk = this.getChunk(tilePos);
+            return this.notifyDecorationWrite(chunk, tilePos, chunk.setBlockIdDataRaw(new ChunkTilePos(tilePos), block.id(), data));
         }
     }
 
@@ -689,7 +690,8 @@ public abstract class World implements MutableWorldSource {
         if (!tilePos.inBounds(this)) {
             return false;
         } else {
-            return this.getChunk(tilePos).setBlockId(new ChunkTilePos(tilePos), block.id());
+            final Chunk chunk = this.getChunk(tilePos);
+            return this.notifyDecorationWrite(chunk, tilePos, chunk.setBlockId(new ChunkTilePos(tilePos), block.id()));
         }
     }
 
@@ -1637,20 +1639,21 @@ public abstract class World implements MutableWorldSource {
         }
 
         this.updatingTileEntities = true;
+        updateTileEntities();
+        this.updatingTileEntities = false;
+    }
+
+    protected void updateTileEntities() {
         for (int i = this.tileEntityList.size() - 1; i >= 0; i--) {
             final TileEntity tileEntity = this.tileEntityList.get(i);
-            if (!tileEntity.isInvalid() && this.isBlockLoaded(tileEntity.tilePos.x, tileEntity.tilePos.y, tileEntity.tilePos.z)) {
+            if (!tileEntity.isInvalid() && this.isBlockLoaded(tileEntity.tilePos)) {
                 tileEntity.tick();
             }
             if (tileEntity.isInvalid()) {
                 this.tileEntityList.remove(i);
-                final Chunk chunk = this.getChunkFromChunkCoords(Math.floorDiv(tileEntity.tilePos.x, Chunk.CHUNK_SIZE_X), Math.floorDiv(tileEntity.tilePos.z, Chunk.CHUNK_SIZE_Z));
-                if (chunk != null) {
-                    chunk.removeTileEntity(tileEntity);
-                }
+                this.getChunk(tileEntity.tilePos).removeTileEntity(tileEntity);
             }
         }
-        this.updatingTileEntities = false;
     }
 
     public void addAllBlockEntities(final Collection<TileEntity> blockEntities) {
@@ -2550,6 +2553,7 @@ public abstract class World implements MutableWorldSource {
         if (this.isBlockNormalCube(tilePos)) direct_sig: {
             if (block == Blocks.BLOCK_REDSTONE) break direct_sig;
             if (block == Blocks.PUMPKIN_REDSTONE) break direct_sig;
+            if (block == Blocks.MATCHER) break direct_sig;
             if (block == Blocks.MATCHER_ACTIVE) break direct_sig;
             if (this.hasDirectSignal(tilePos)) return true;
         }
@@ -2997,9 +3001,9 @@ public abstract class World implements MutableWorldSource {
     }
 
     public @NotNull EntityItem dropItem(double x, double y, double z, final @NotNull ItemStack itemStack, double radius) {
-        x += this.rand.nextFloat() * radius;
-        y += this.rand.nextFloat() * radius;
-        z += this.rand.nextFloat() * radius;
+        x += 0.5 + (this.rand.nextDouble() - 0.5) * radius;
+        y += 0.5 + this.rand.nextDouble() * radius * 0.25;
+        z += 0.5 + (this.rand.nextDouble() - 0.5) * radius;
 
         final @NotNull EntityItem item = new EntityItem(this, x, y, z, itemStack);
         item.pickupDelay = 10;
diff --git a/game/core/src/main/java/net/minecraft/core/world/chunk/Chunk.java b/game/core/src/main/java/net/minecraft/core/world/chunk/Chunk.java
index b3675369c..4d678cdf0 100644
--- a/game/core/src/main/java/net/minecraft/core/world/chunk/Chunk.java
+++ b/game/core/src/main/java/net/minecraft/core/world/chunk/Chunk.java
@@ -12,7 +12,6 @@ import net.minecraft.core.enums.LightLayer;
 import net.minecraft.core.util.helper.MathHelper;
 import net.minecraft.core.world.World;
 import net.minecraft.core.world.biome.Biome;
-import net.minecraft.core.world.particle.ParticleEmitterRange;
 import net.minecraft.core.world.pos.ChunkSectionTilePos;
 import net.minecraft.core.world.pos.ChunkPos;
 import net.minecraft.core.world.pos.ChunkPosc;
@@ -68,6 +67,7 @@ public class Chunk {
     public int averageBlockHeight;
     public boolean isTerrainPopulated = false;
     public boolean isModified = false;
+    public boolean sentToClients = false;
     public boolean neverSave;
     public boolean hasEntities = false;
     public long lastSaveTime = 0L;
@@ -684,42 +684,6 @@ public class Chunk {
         for (ChunkSection section : this.sections) {
             section.onLoad(this.world);
         }
-
-        if (!this.world.isClientSide) {
-            this.registerParticleEmittersFromLoadedBlocks();
-        }
-    }
-
-    /**
-     * Registers particle emitter blocks in this chunk on the server.
-     */
-    private void registerParticleEmittersFromLoadedBlocks() {
-        final ChunkSectionTilePos sectionTile = new ChunkSectionTilePos();
-        for (final ChunkSection section : this.sections) {
-            if (section.blocks == null) {
-                continue;
-            }
-            final int baseY = section.yPosition * ChunkSection.SECTION_SIZE_Y;
-            for (sectionTile.y = 0; sectionTile.y < ChunkSection.SECTION_SIZE_Y; sectionTile.y++) {
-                for (sectionTile.z = 0; sectionTile.z < CHUNK_SIZE_Z; sectionTile.z++) {
-                    for (sectionTile.x = 0; sectionTile.x < CHUNK_SIZE_X; sectionTile.x++) {
-                        final int id = section.getBlockId(sectionTile);
-                        if (id == 0) {
-                            continue;
-                        }
-                        final Block<?> block = Blocks.getBlock(id);
-                        final ParticleEmitterRange range = block.getLogic().getParticleEmitterRange();
-                        if (range == null) {
-                            continue;
-                        }
-                        final int wx = this.pos.x * CHUNK_SIZE_X + sectionTile.x;
-                        final int wy = baseY + sectionTile.y;
-                        final int wz = this.pos.z * CHUNK_SIZE_Z + sectionTile.z;
-                        this.world.particleEmitters.add(new TilePos(wx, wy, wz), range);
-                    }
-                }
-            }
-        }
     }
 
     public void onUnload() {
diff --git a/game/core/src/main/java/net/minecraft/core/world/generate/chunk/PositionSelectors.java b/game/core/src/main/java/net/minecraft/core/world/generate/chunk/PositionSelectors.java
index 59064ac5a..2f87042a9 100644
--- a/game/core/src/main/java/net/minecraft/core/world/generate/chunk/PositionSelectors.java
+++ b/game/core/src/main/java/net/minecraft/core/world/generate/chunk/PositionSelectors.java
@@ -8,6 +8,8 @@ import org.jetbrains.annotations.NotNull;
 import java.util.Random;
 
 public class PositionSelectors {
+    private static final int DECORATION_OFFSET = 8;
+
     public static class HeightRangeUniform implements PositionSelector {
 
         private final int range;
@@ -19,59 +21,59 @@ public class PositionSelectors {
         @Override
         public TilePos getValue(@NotNull World world, @NotNull Chunk chunk, @NotNull Random random, int minY, int maxY, int rangeY) {
 
-            int x = (chunk.pos.x() * 16) + random.nextInt(16);
-            int z = (chunk.pos.z() * 16) + random.nextInt(16);
+            int x = (chunk.pos.x() * 16) + random.nextInt(16) + DECORATION_OFFSET;
+            int z = (chunk.pos.z() * 16) + random.nextInt(16) + DECORATION_OFFSET;
             int y = minY + random.nextInt(rangeY / range);
             return new TilePos(x, y, z);
         }
     }
 
     public static PositionSelector MinY = (world, chunk, random, minY, maxY, rangeY) -> {
-        int x = (chunk.pos.x() * 16) + random.nextInt(16);
-        int z = (chunk.pos.z() * 16) + random.nextInt(16);
+        int x = (chunk.pos.x() * 16) + random.nextInt(16) + DECORATION_OFFSET;
+        int z = (chunk.pos.z() * 16) + random.nextInt(16) + DECORATION_OFFSET;
 
         return new TilePos(x, minY, z);
     };
 
 
     public static PositionSelector MaxY = (world, chunk, random, minY, maxY, rangeY) -> {
-        int x = (chunk.pos.x() * 16) + random.nextInt(16);
-        int z = (chunk.pos.z() * 16) + random.nextInt(16);
+        int x = (chunk.pos.x() * 16) + random.nextInt(16) + DECORATION_OFFSET;
+        int z = (chunk.pos.z() * 16) + random.nextInt(16) + DECORATION_OFFSET;
 
         return new TilePos(x, maxY, z);
     };
 
     public static PositionSelector HeightRangeUniform = (world, chunk, random, minY, maxY, rangeY) -> {
-        int x = (chunk.pos.x() * 16) + random.nextInt(16);
-        int z = (chunk.pos.z() * 16) + random.nextInt(16);
+        int x = (chunk.pos.x() * 16) + random.nextInt(16) + DECORATION_OFFSET;
+        int z = (chunk.pos.z() * 16) + random.nextInt(16) + DECORATION_OFFSET;
         int y = minY + random.nextInt(rangeY);
         return new TilePos(x, y, z);
     };
 
     public static PositionSelector HeightRangeUniformFromOcean = (world, chunk, random, minY, maxY, rangeY) -> {
-        int x = (chunk.pos.x() * 16) + random.nextInt(16);
-        int z = (chunk.pos.z() * 16) + random.nextInt(16);
+        int x = (chunk.pos.x() * 16) + random.nextInt(16) + DECORATION_OFFSET;
+        int z = (chunk.pos.z() * 16) + random.nextInt(16) + DECORATION_OFFSET;
         int y = world.getWorldType().getOceanY() + random.nextInt(rangeY - 8) + 4;
         return new TilePos(x, y, z);
     };
 
     public static PositionSelector UpperHeightRangeUniform = (world, chunk, random, minY, maxY, rangeY) -> {
-        int x = (chunk.pos.x() * 16) + random.nextInt(16);
-        int z = (chunk.pos.z() * 16) + random.nextInt(16);
+        int x = (chunk.pos.x() * 16) + random.nextInt(16) + DECORATION_OFFSET;
+        int z = (chunk.pos.z() * 16) + random.nextInt(16) + DECORATION_OFFSET;
         int y = rangeY / 2 + random.nextInt(rangeY - 8) + 4;
         return new TilePos(x, y, z);
     };
 
     public static PositionSelector HeightRangeBiasedTop = (world, chunk, random, minY, maxY, rangeY) -> {
-        int x = (chunk.pos.x() * 16) + random.nextInt(16);
-        int z = (chunk.pos.z() * 16) + random.nextInt(16);
+        int x = (chunk.pos.x() * 16) + random.nextInt(16) + DECORATION_OFFSET;
+        int z = (chunk.pos.z() * 16) + random.nextInt(16) + DECORATION_OFFSET;
         int y = minY + random.nextInt(random.nextInt(rangeY - (rangeY / 16)) + (rangeY / 16));
         return new TilePos(x, y, z);
     };
 
     public static PositionSelector SurfaceBlock = (world, chunk, random, minY, maxY, rangeY) -> {
-        int x = (chunk.pos.x() * 16) + random.nextInt(16);
-        int z = (chunk.pos.z() * 16) + random.nextInt(16);
+        int x = (chunk.pos.x() * 16) + random.nextInt(16) + DECORATION_OFFSET;
+        int z = (chunk.pos.z() * 16) + random.nextInt(16) + DECORATION_OFFSET;
         int y = world.getHeightValue(x, z);
         return new TilePos(x, y, z);
     };
diff --git a/game/core/src/main/java/net/minecraft/core/world/particle/ParticleEmitterRange.java b/game/core/src/main/java/net/minecraft/core/world/particle/ParticleEmitterRange.java
deleted file mode 100644
index 5ce6e423d..000000000
--- a/game/core/src/main/java/net/minecraft/core/world/particle/ParticleEmitterRange.java
+++ /dev/null
@@ -1,34 +0,0 @@
-package net.minecraft.core.world.particle;
-
-public enum ParticleEmitterRange {
-    // BASIC fixed 16-block radius
-    BASIC(0.0f),
-
-    // SHORT scales to 25% of the players' particle render distance
-    SHORT(0.25f),
-
-    // MEDIUM scales to 50%
-    MEDIUM(0.5f),
-
-    // FAR scales to 75%
-    FAR(0.75f),
-
-    // EXTREME uses 100%
-    EXTREME(1.0f);
-
-    public final float multiplier;
-
-    ParticleEmitterRange(float multiplier) {
-        this.multiplier = multiplier;
-    }
-
-    public double getMaxDistanceSq(int renderDistanceChunks) {
-        if (this == BASIC) {
-            return 16.0 * 16.0;
-        }
-
-        double maxBlocks = (renderDistanceChunks * 16.0) * this.multiplier;
-        maxBlocks = Math.max(maxBlocks, 16.0);
-        return maxBlocks * maxBlocks;
-    }
-}
diff --git a/game/core/src/main/java/net/minecraft/core/world/particle/ParticleEmitterRegistry.java b/game/core/src/main/java/net/minecraft/core/world/particle/ParticleEmitterRegistry.java
deleted file mode 100644
index 14016cc2d..000000000
--- a/game/core/src/main/java/net/minecraft/core/world/particle/ParticleEmitterRegistry.java
+++ /dev/null
@@ -1,161 +0,0 @@
-package net.minecraft.core.world.particle;
-
-import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
-import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
-import net.minecraft.core.block.Block;
-import net.minecraft.core.entity.player.Player;
-import net.minecraft.core.world.World;
-import net.minecraft.core.world.pos.TilePos;
-import net.minecraft.core.world.pos.TilePosc;
-import org.jetbrains.annotations.NotNull;
-
-import java.util.concurrent.ConcurrentLinkedQueue;
-
-public final class ParticleEmitterRegistry {
-
-    private record EmitterNode(TilePos pos, ParticleEmitterRange type) { }
-
-    private final Long2ObjectOpenHashMap<EmitterNode> emitters = new Long2ObjectOpenHashMap<>();
-
-    private final ConcurrentLinkedQueue<EmitterNode> pendingAdds = new ConcurrentLinkedQueue<>();
-    private final ConcurrentLinkedQueue<Long> pendingRemoves = new ConcurrentLinkedQueue<>();
-
-    private long getPosHash(int x, int y, int z) {
-        long hash = x & 0x3FFFFFF;
-        hash |= ((long)(z & 0x3FFFFFF)) << 26;
-        hash |= ((long)(y & 0xFFF)) << 52;
-        return hash;
-    }
-
-    public void add(TilePos pos) {
-        this.add(pos, ParticleEmitterRange.BASIC);
-    }
-
-    public void add(@NotNull TilePos pos, ParticleEmitterRange type) {
-        this.pendingAdds.add(new EmitterNode(new TilePos(pos), type));
-    }
-
-    public void add(TilePosc pos) {
-        this.add(pos, ParticleEmitterRange.BASIC);
-    }
-
-    public void add(@NotNull TilePosc pos, ParticleEmitterRange type) {
-        this.pendingAdds.add(new EmitterNode(new TilePos(pos), type));
-    }
-
-    public void remove(@NotNull TilePos pos) {
-        this.pendingRemoves.add(getPosHash(pos.x(), pos.y(), pos.z()));
-    }
-
-    public void remove(@NotNull TilePosc pos) {
-        this.pendingRemoves.add(getPosHash(pos.x(), pos.y(), pos.z()));
-    }
-
-    private void drainPendingQueues() {
-        while (!this.pendingRemoves.isEmpty()) {
-            this.emitters.remove(this.pendingRemoves.poll().longValue());
-        }
-        while (!this.pendingAdds.isEmpty()) {
-            EmitterNode node = this.pendingAdds.poll();
-            this.emitters.put(getPosHash(node.pos.x(), node.pos.y(), node.pos.z()), node);
-        }
-    }
-
-    public void tick(World world, Player player, int renderDistanceChunks) {
-        this.drainPendingQueues();
-
-        if (this.emitters.isEmpty()) return;
-
-        var iterator = this.emitters.long2ObjectEntrySet().fastIterator();
-
-        while (iterator.hasNext()) {
-            Long2ObjectMap.Entry<EmitterNode> entry = iterator.next();
-            EmitterNode node = entry.getValue();
-            TilePos pos = node.pos;
-            ParticleEmitterRange type = node.type;
-
-            if (!world.isBlockLoaded(pos)) {
-                iterator.remove();
-                continue;
-            }
-
-            double dx = pos.x() - player.x;
-            double dy = pos.y() - player.y;
-            double dz = pos.z() - player.z;
-            double distSq = dx * dx + dy * dy + dz * dz;
-
-            if (distSq > type.getMaxDistanceSq(renderDistanceChunks)) continue;
-
-            int throttle = 4;
-            if (distSq > 4096) {
-                throttle = 64;
-            } else if (distSq > 1024) {
-                throttle = 16;
-            }
-
-            if (world.rand.nextInt(throttle) == 0) {
-                Block<?> block = world.getBlockType(pos);
-
-                if (block.getLogic().getParticleEmitterRange() != null) {
-                    block.animationTick(world, pos, world.rand);
-                } else {
-                    iterator.remove();
-                }
-            }
-        }
-    }
-
-    /**
-     * runs/ticks emitters if any player is within range.
-     */
-    public void tickServer(World world, Iterable<? extends Player> players, int renderDistanceChunks) {
-        this.drainPendingQueues();
-
-        if (this.emitters.isEmpty()) return;
-
-        var iterator = this.emitters.long2ObjectEntrySet().fastIterator();
-
-        while (iterator.hasNext()) {
-            Long2ObjectMap.Entry<EmitterNode> entry = iterator.next();
-            EmitterNode node = entry.getValue();
-            TilePos pos = node.pos;
-            ParticleEmitterRange type = node.type;
-
-            if (!world.isBlockLoaded(pos)) {
-                iterator.remove();
-                continue;
-            }
-
-            double maxDistSq = type.getMaxDistanceSq(renderDistanceChunks);
-            double distSq = Double.POSITIVE_INFINITY;
-            for (Player player : players) {
-                double dx = pos.x() - player.x;
-                double dy = pos.y() - player.y;
-                double dz = pos.z() - player.z;
-                double d = dx * dx + dy * dy + dz * dz;
-                if (d <= maxDistSq && d < distSq) {
-                    distSq = d;
-                }
-            }
-
-            if (distSq == Double.POSITIVE_INFINITY) continue;
-
-            int throttle = 4;
-            if (distSq > 4096) {
-                throttle = 64;
-            } else if (distSq > 1024) {
-                throttle = 16;
-            }
-
-            if (world.rand.nextInt(throttle) == 0) {
-                Block<?> block = world.getBlockType(pos);
-
-                if (block.getLogic().getParticleEmitterRange() != null) {
-                    block.animationTick(world, pos, world.rand);
-                } else {
-                    iterator.remove();
-                }
-            }
-        }
-    }
-}
\ No newline at end of file
diff --git a/game/core/src/main/resources/assets/minecraft/lang/en_US/gui.lang b/game/core/src/main/resources/assets/minecraft/lang/en_US/gui.lang
index de2a55a7a..d97a06674 100644
--- a/game/core/src/main/resources/assets/minecraft/lang/en_US/gui.lang
+++ b/game/core/src/main/resources/assets/minecraft/lang/en_US/gui.lang
@@ -292,6 +292,7 @@ gui.options.page.debug.button.crash.hard=Crash Hard
 gui.options.page.debug.button.music.skip=Force Ambient Music
 gui.options.page.debug.button.garbage_collect=Run Garbage Collector
 gui.options.page.debug.button.reload_commands=Reload Commands
+gui.options.page.debug.button.reset_hud_onboarding=Reset HUD Editor Onboarding
 
 gui.options.page.hidden.title=Hidden Options
 gui.options.page.hidden.category.general=General
diff --git a/game/server/src/main/java/net/minecraft/server/MinecraftServer.java b/game/server/src/main/java/net/minecraft/server/MinecraftServer.java
index 0c5ac011d..971a45f1e 100644
--- a/game/server/src/main/java/net/minecraft/server/MinecraftServer.java
+++ b/game/server/src/main/java/net/minecraft/server/MinecraftServer.java
@@ -134,7 +134,6 @@ public class MinecraftServer
     public int sleepPercentage = 100;
     public int maxPlayers = 20;
     public int viewDistance = 10;
-    public int particleRenderDistance = 10;
     public Gamemode defaultGamemode;
     public static String statsToken;
     public static boolean statsStatus;
@@ -243,7 +242,6 @@ public class MinecraftServer
         NetworkManager.TIMEOUT_TIME = propertyManager.getIntProperty("login-timeout-time", 1800 * 50);
         maxPlayers = propertyManager.getIntProperty("max-players", 20);
         viewDistance = propertyManager.getIntProperty("view-distance", 10);
-        particleRenderDistance = Math.max(1, Math.min(24, propertyManager.getIntProperty("particle-render-distance", 10)));
         language = propertyManager.getStringProperty("language", "en_US");
         disablePhotoMode = propertyManager.getBooleanProperty("disable-photomode", false);
         name = propertyManager.getStringProperty("name", "");
diff --git a/game/server/src/main/java/net/minecraft/server/entity/EntityTrackerEntryImpl.java b/game/server/src/main/java/net/minecraft/server/entity/EntityTrackerEntryImpl.java
index ddcd470ec..8bcccd211 100644
--- a/game/server/src/main/java/net/minecraft/server/entity/EntityTrackerEntryImpl.java
+++ b/game/server/src/main/java/net/minecraft/server/entity/EntityTrackerEntryImpl.java
@@ -6,9 +6,12 @@ import net.minecraft.core.entity.player.Player;
 import net.minecraft.core.item.ItemStack;
 import net.minecraft.core.net.entity.EntityTrackerEntry;
 import net.minecraft.core.net.entity.NetEntityHandler;
+import net.minecraft.core.block.entity.TileEntity;
 import net.minecraft.core.net.packet.PacketMoveEntity;
 import net.minecraft.core.net.packet.Packet;
+import net.minecraft.core.net.packet.PacketSetRiding;
 import net.minecraft.core.net.packet.PacketSleep;
+import net.minecraft.core.world.IVehicle;
 import net.minecraft.core.net.packet.PacketSetEntityMotion;
 import net.minecraft.core.net.packet.PacketRemoveEntity;
 import net.minecraft.core.net.packet.PacketTeleportEntity;
@@ -19,6 +22,7 @@ import net.minecraft.core.net.packet.PacketSetEquippedItem;
 import net.minecraft.core.util.helper.MathHelper;
 import net.minecraft.core.world.data.SynchedEntityData;
 import net.minecraft.server.entity.player.PlayerServer;
+import org.jetbrains.annotations.NotNull;
 
 import java.util.HashSet;
 import java.util.List;
@@ -43,6 +47,7 @@ public class EntityTrackerEntryImpl implements EntityTrackerEntry {
     private boolean firstUpdateDone;
     private final boolean shouldSendMotionUpdates;
     private int ticksSinceLastTeleport;
+    private IVehicle lastTrackedVehicle;
     private final SynchedEntityData synchedEntityData;
     public boolean playerEntitiesUpdated;
     public Set<PlayerServer> trackedPlayers;
@@ -62,6 +67,7 @@ public class EntityTrackerEntryImpl implements EntityTrackerEntry {
         encodedPosZ = MathHelper.floor(entity.z * 32D);
         encodedRotationYaw = MathHelper.floor_float((entity.yRot * 256F) / 360F);
         encodedRotationPitch = MathHelper.floor_float((entity.xRot * 256F) / 360F);
+        lastTrackedVehicle = entity.vehicle;
     }
 
     @Override
@@ -97,7 +103,7 @@ public class EntityTrackerEntryImpl implements EntityTrackerEntry {
             int dy = entityY - encodedPosY;
             int dz = entityZ - encodedPosZ;
             Packet packet = null;
-            boolean hasMoved = java.lang.Math.abs(entityX) >= 8 || java.lang.Math.abs(entityY) >= 8 || java.lang.Math.abs(entityZ) >= 8;
+            boolean hasMoved = java.lang.Math.abs(dx) >= 2 || java.lang.Math.abs(dy) >= 2 || java.lang.Math.abs(dz) >= 2;
             boolean hasLooked = java.lang.Math.abs(entityYaw - encodedRotationYaw) >= 8 || java.lang.Math.abs(entityPitch - encodedRotationPitch) >= 8;
             if(dx < -128 || dx >= 128 || dy < -128 || dy >= 128 || dz < -128 || dz >= 128 || ticksSinceLastTeleport > 400) {
                 ticksSinceLastTeleport = 0;
@@ -149,6 +155,24 @@ public class EntityTrackerEntryImpl implements EntityTrackerEntry {
             sendPacketToTrackedPlayersAndTrackedEntity(new PacketEntityNickname(trackedEntity.id, ((Mob) trackedEntity).nickname, ((Mob) trackedEntity).chatColor));
             trackedEntity.hadNicknameSet = false;
         }
+        if (trackedEntity.additionalDataChanged) {
+            sendPacketToTrackedPlayers(new PacketEntityTagData(trackedEntity));
+            trackedEntity.additionalDataChanged = false;
+        }
+
+        if (trackedEntity.vehicle != lastTrackedVehicle) {
+            lastTrackedVehicle = trackedEntity.vehicle;
+            sendPacketToTrackedPlayers(createRidingPacket());
+        }
+    }
+
+    private @NotNull PacketSetRiding createRidingPacket() {
+        if (trackedEntity.vehicle instanceof Entity) {
+            return new PacketSetRiding(trackedEntity, (Entity) trackedEntity.vehicle);
+        } else if (trackedEntity.vehicle instanceof TileEntity tileEntity) {
+            return new PacketSetRiding(trackedEntity, tileEntity.tilePos.x, tileEntity.tilePos.y, tileEntity.tilePos.z);
+        }
+        return new PacketSetRiding(trackedEntity, null); // dismount
     }
 
     public void sendPacketToTrackedPlayers(Packet packet) {
@@ -178,8 +202,10 @@ public class EntityTrackerEntryImpl implements EntityTrackerEntry {
         if(playerServer == trackedEntity) {
             return;
         }
-        double dx = playerServer.x - (double)(encodedPosX / 32);
-        double dz = playerServer.z - (double)(encodedPosZ / 32);
+
+        double dx = playerServer.x - trackedEntity.x;
+        double dz = playerServer.z - trackedEntity.z;
+
         if(dx >= (double)(-trackingDistance) && dx <= (double)trackingDistance && dz >= (double)(-trackingDistance) && dz <= (double)trackingDistance) {
             if(!trackedPlayers.contains(playerServer)) {
                 trackedPlayers.add(playerServer);
@@ -203,6 +229,12 @@ public class EntityTrackerEntryImpl implements EntityTrackerEntry {
                         playerServer.playerNetServerHandler.sendPacket(new PacketSleep(trackedEntity, 0, MathHelper.floor(trackedEntity.x), MathHelper.floor(trackedEntity.y), MathHelper.floor(trackedEntity.z)));
                     }
                 }
+                if (trackedEntity.vehicle != null) {
+                    playerServer.playerNetServerHandler.sendPacket(createRidingPacket());
+                }
+                if (trackedEntity.passenger != null) {
+                    playerServer.playerNetServerHandler.sendPacket(new PacketSetRiding(trackedEntity.passenger, trackedEntity));
+                }
             }
         } else if(trackedPlayers.contains(playerServer)) {
             trackedPlayers.remove(playerServer);
diff --git a/game/server/src/main/java/net/minecraft/server/entity/player/PlayerServer.java b/game/server/src/main/java/net/minecraft/server/entity/player/PlayerServer.java
index a9dd6427b..676660862 100644
--- a/game/server/src/main/java/net/minecraft/server/entity/player/PlayerServer.java
+++ b/game/server/src/main/java/net/minecraft/server/entity/player/PlayerServer.java
@@ -6,6 +6,7 @@ import net.minecraft.core.block.Blocks;
 import net.minecraft.core.block.entity.*;
 import net.minecraft.core.crafting.ContainerListener;
 import net.minecraft.core.entity.*;
+import net.minecraft.core.entity.animal.MobPig;
 import net.minecraft.core.entity.player.Player;
 import net.minecraft.core.entity.projectile.ProjectileArrow;
 import net.minecraft.core.enums.EnumSleepStatus;
@@ -22,6 +23,7 @@ import net.minecraft.core.world.ICarriable;
 import net.minecraft.core.world.IVehicle;
 import net.minecraft.core.world.chunk.Chunk;
 import net.minecraft.core.world.chunk.ChunkSection;
+import net.minecraft.core.world.pos.ChunkPos;
 import net.minecraft.core.world.pos.TilePos;
 import net.minecraft.core.world.pos.TilePosc;
 import net.minecraft.server.entity.EntityTrackerImpl;
@@ -183,17 +185,7 @@ public class PlayerServer extends Player implements ContainerListener {
         playerController.tick();
         ticksOfInvuln--;
         containerMenu.broadcastChanges();
-
-        ItemStack heldItem = inventory.getHeldItemStack();
-        if (!ItemStack.areItemStacksEqual(heldItem, lastHeldItem))
-        {
-            if (playerNetServerHandler != null)
-            {
-                playerNetServerHandler.sendPacket(new PacketContainerSetSlot(-1, -1, heldItem));
-            }
-            lastHeldItem = heldItem == null ? null : heldItem.copy();
-        }
-
+        broadcastCarried(containerMenu);
         for (int i = 0; i < 5; i++)
         {
             ItemStack itemstack = getEquipmentInSlot(i);
@@ -310,7 +302,7 @@ public class PlayerServer extends Player implements ContainerListener {
                 {
                     usePersonalCraftingInventory();
                 }
-                if (vehicle != null)
+                if (vehicle != null && !(vehicle instanceof MobPig))
                 {
                     startRiding(vehicle);
                 }
@@ -372,6 +364,10 @@ public class PlayerServer extends Player implements ContainerListener {
                 {
                     WorldServer worldserver = mcServer.getDimensionWorld(dimension);
                     loadedChunks.remove(chunkCoord);
+                    ChunkPos sentChunkPos = new ChunkPos(chunkCoord.x, chunkCoord.z);
+                    if (worldserver.isChunkLoaded(sentChunkPos)) {
+                        worldserver.getChunk(sentChunkPos, true).sentToClients = true;
+                    }
                     playerNetServerHandler.sendPacket(new PacketBlockRegionUpdate(chunkCoord.x * Chunk.CHUNK_SIZE_X, 0, chunkCoord.z * Chunk.CHUNK_SIZE_Z, Chunk.CHUNK_SIZE_X, Chunk.CHUNK_SECTIONS * ChunkSection.SECTION_SIZE_Y, Chunk.CHUNK_SIZE_Z, worldserver));
                     List<TileEntity> list = worldserver.getBlockEntitiesWithinBounds(chunkCoord.x * Chunk.CHUNK_SIZE_X, 0, chunkCoord.z * Chunk.CHUNK_SIZE_Z, chunkCoord.x * Chunk.CHUNK_SIZE_X + Chunk.CHUNK_SIZE_X, Chunk.CHUNK_SECTIONS * ChunkSection.SECTION_SIZE_Y, chunkCoord.z * Chunk.CHUNK_SIZE_Z + Chunk.CHUNK_SIZE_Z);
                     for (int j = 0; j < list.size(); j++)
@@ -616,17 +612,9 @@ public class PlayerServer extends Player implements ContainerListener {
         {
             return;
         }
-        if (isChangingQuantityOnly)
+        if (playerNetServerHandler != null)
         {
-            return;
-        }
-        else
-        {
-            if (playerNetServerHandler != null)
-            {
-                playerNetServerHandler.sendPacket(new PacketContainerSetSlot(container.containerId, i, itemstack));
-            }
-            return;
+            playerNetServerHandler.sendPacket(new PacketContainerSetSlot(container.containerId, container.getStateId(), i, itemstack));
         }
     }
 
@@ -638,13 +626,12 @@ public class PlayerServer extends Player implements ContainerListener {
     @Override
 	public void updateCraftingInventory(MenuAbstract container, List<ItemStack> list)
     {
-
-        //playernetserverhandler returning false?
+        ItemStack held = inventory.getHeldItemStack();
         if (playerNetServerHandler != null)
         {
-            playerNetServerHandler.sendPacket(new PacketContainerSetContent(container.containerId, list));
-//            playerNetServerHandler.sendPacket(new ContainerSetSlotPacket(-1, -1, inventory.getHeldItemStack()));
+            playerNetServerHandler.sendPacket(new PacketContainerSetContent(container.containerId, container.getStateId(), list, held));
         }
+        remoteCarried = held == null ? null : held.copy();
     }
 
     @Override
@@ -659,23 +646,39 @@ public class PlayerServer extends Player implements ContainerListener {
         closeCraftingGui();
     }
 
-    public void updateHeldItem()
+    public void setRemoteCarried(ItemStack stack)
     {
-        if (isChangingQuantityOnly)
+        remoteCarried = stack == null ? null : stack.copy();
+    }
+
+    public void broadcastCarried(MenuAbstract container)
+    {
+        if (playerNetServerHandler == null)
         {
             return;
         }
-        else
+        ItemStack held = inventory.getHeldItemStack();
+        if (ItemStack.areItemStacksEqual(held, remoteCarried))
         {
-            playerNetServerHandler.sendPacket(new PacketContainerSetSlot(-1, -1, inventory.getHeldItemStack()));
             return;
         }
+        container.incrementStateId();
+        playerNetServerHandler.sendPacket(new PacketContainerSetSlot(-1, container.getStateId(), -1, held));
+        remoteCarried = held == null ? null : held.copy();
+    }
+
+    public void broadcastFullState(MenuAbstract container)
+    {
+        container.incrementStateId();
+        updateCraftingInventory(container, container.getSlotStackList());
+        container.resetRemoteSlots();
     }
 
     public void closeCraftingGui()
     {
         containerMenu.onCraftGuiClosed(this);
         containerMenu = inventoryMenu;
+        broadcastFullState(containerMenu);
     }
 
     public void setMovementType(float f, float f1, boolean flag, boolean flag1, float f2, float f3)
@@ -758,6 +761,11 @@ public class PlayerServer extends Player implements ContainerListener {
         this.playerNetServerHandler.sendPacket(new PacketEntityFling(this.id, this.xd, this.yd, this.zd, this.pushTime, this.pushesThisTick));
     }
 
+    @Override
+    public void onVehicleFlung(final Entity vehicle) {
+        this.playerNetServerHandler.sendPacket(new PacketEntityFling(vehicle.id, vehicle.xd, vehicle.yd, vehicle.zd, vehicle.pushTime, vehicle.pushesThisTick));
+    }
+
     @Override
     public void setHeldObject(@Nullable ICarriable heldObject) {
         super.setHeldObject(heldObject);
@@ -816,7 +824,6 @@ public class PlayerServer extends Player implements ContainerListener {
     private ItemStack[] playerInventory = {
             null, null, null, null, null
     };
-    private ItemStack lastHeldItem;
     private int currentWindowId;
-    public boolean isChangingQuantityOnly;
+    private ItemStack remoteCarried;
 }
diff --git a/game/server/src/main/java/net/minecraft/server/net/PlayerList.java b/game/server/src/main/java/net/minecraft/server/net/PlayerList.java
index 8719b4eab..5c0b843da 100644
--- a/game/server/src/main/java/net/minecraft/server/net/PlayerList.java
+++ b/game/server/src/main/java/net/minecraft/server/net/PlayerList.java
@@ -6,6 +6,8 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
 import net.minecraft.core.achievement.Achievements;
 import net.minecraft.core.block.entity.TileEntity;
 import net.minecraft.core.data.registry.Registries;
+import net.minecraft.core.entity.Entity;
+import net.minecraft.core.entity.animal.MobPig;
 import net.minecraft.core.entity.player.Player;
 import net.minecraft.core.util.helper.DyeColor;
 import net.minecraft.core.util.helper.UUIDHelper;
@@ -122,6 +124,19 @@ public class PlayerList
         world.getChunkProvider().prepareChunk((int) player.x >> 4, (int) player.z >> 4);
     }
 
+    public void resendChunksAfterTeleport(PlayerServer player)
+    {
+        int viewChunkX = (int) player.viewingX >> 4;
+        int viewChunkZ = (int) player.viewingZ >> 4;
+        int chunkX = (int) player.x >> 4;
+        int chunkZ = (int) player.z >> 4;
+        if (viewChunkX == chunkX && viewChunkZ == chunkZ)
+        {
+            return;
+        }
+        syncPlayerDimension(player);
+    }
+
     public int getMaxTrackingDistance()
     {
         return playerManagers.get(0).getMaxTrackingDistance();
@@ -163,8 +178,13 @@ public class PlayerList
 
     public void playerLoggedOut(PlayerServer entityplayermp)
     {
+        Entity mount = entityplayermp.vehicle instanceof MobPig ? (Entity) entityplayermp.vehicle : null;
         playerIo.save(entityplayermp);
-        server.getDimensionWorld(entityplayermp.dimension).setEntityDead(entityplayermp);
+        WorldServer worldserver = server.getDimensionWorld(entityplayermp.dimension);
+        worldserver.setEntityDead(entityplayermp);
+        if (mount != null) {
+            worldserver.setEntityDead(mount);
+        }
         playerEntities.remove(entityplayermp);
         getPlayerManager(entityplayermp.dimension).removePlayer(entityplayermp);
     }
@@ -217,12 +237,15 @@ public class PlayerList
         newPlayer.id = previousPlayer.id;
         newPlayer.chatColor = previousPlayer.chatColor;
         newPlayer.nickname = previousPlayer.nickname;
-        newPlayer.gamemode = previousPlayer.gamemode;
+        newPlayer.setGamemodeOnLogin(previousPlayer.gamemode);
+        newPlayer.rotationLock = previousPlayer.rotationLock;
+        newPlayer.rotationLockHorizontal = previousPlayer.rotationLockHorizontal;
+        newPlayer.rotationLockVertical = previousPlayer.rotationLockVertical;
+        newPlayer.placementModeOverride = previousPlayer.placementModeOverride;
         newPlayer.playerNetServerHandler = previousPlayer.playerNetServerHandler;
 		newPlayer.inventory.transferAllContents(previousPlayer.inventory);
         newPlayer.playerConfig.config = previousPlayer.playerConfig.config;
         WorldServer worldserver = server.getDimensionWorld(previousPlayer.dimension);
-        //entityplayermp1.setGamemodeOnLogin(entityplayermp.gamemode);
         if (chunkcoordinates != null)
         {
             final TilePos bedCoordinates = Player.getValidBedSpawnCoordinates(this.server.getDimensionWorld(previousPlayer.dimension), chunkcoordinates);
@@ -260,10 +283,15 @@ public class PlayerList
         Dimension lastDim = Dimension.getDimensionList().get(playerServer.dimension);
         Dimension newDim = Dimension.getDimensionList().get(targetDim);
 
+        Entity mount = playerServer.vehicle instanceof MobPig ? (Entity) playerServer.vehicle : null;
+
         playerServer.dimension = targetDim;
         WorldServer worldserver1 = server.getDimensionWorld(playerServer.dimension);
         playerServer.playerNetServerHandler.sendPacket(new PacketRespawn((byte) playerServer.dimension, (byte) Registries.WORLD_TYPES.getNumericIdOfItem(worldserver1.getWorldType())));
         worldserver.removePlayer(playerServer);
+        if (mount != null) {
+            worldserver.removePlayer(mount);
+        }
         playerServer.removed = false;
         double x = playerServer.x;
         double y = playerServer.y;
@@ -306,6 +334,16 @@ public class PlayerList
         setTime(playerServer, worldserver1);
         initializePlayerObject(playerServer);
         playerServer.playerNetServerHandler.sendPacket(new PacketGameRule(server.getDimensionWorld(0).getLevelData().getGameRules()));
+        playerServer.playerNetServerHandler.sendPacket(new PacketSetHotbarOffset(playerServer.inventory.getHotbarOffset()));
+        playerServer.playerNetServerHandler.sendPacket(new PacketSetCarriedItem(playerServer.inventory.getCurrentSlot()));
+
+        if (mount != null && playerServer.isAlive()) {
+            mount.removed = false;
+            mount.world = worldserver1;
+            mount.moveTo(playerServer.x, playerServer.y, playerServer.z, playerServer.yRot, mount.xRot);
+            worldserver1.entityJoinedWorld(mount);
+            playerServer.playerNetServerHandler.sendPacket(new PacketSetRiding(playerServer, mount));
+        }
     }
 
     public void onTick()
diff --git a/game/server/src/main/java/net/minecraft/server/net/command/ConsoleCommandSource.java b/game/server/src/main/java/net/minecraft/server/net/command/ConsoleCommandSource.java
index 930940cd7..57c76d7fe 100644
--- a/game/server/src/main/java/net/minecraft/server/net/command/ConsoleCommandSource.java
+++ b/game/server/src/main/java/net/minecraft/server/net/command/ConsoleCommandSource.java
@@ -121,16 +121,18 @@ public class ConsoleCommandSource implements CommandSource, IServerCommandSource
 
     @Override
     public void teleportPlayerToPos(Player player, double x, double y, double z) {
-        if (player instanceof PlayerServer)
+        if (player instanceof PlayerServer) {
             ((PlayerServer) player).playerNetServerHandler.teleport(x, y, z);
-        else throw new IllegalStateException("Player is not an instance of PlayerServer");
+            this.server.playerList.resendChunksAfterTeleport((PlayerServer) player);
+        } else throw new IllegalStateException("Player is not an instance of PlayerServer");
     }
 
     @Override
     public void teleportPlayerToPosAndRot(Player player, double x, double y, double z, float yaw, float pitch) {
-        if (player instanceof PlayerServer)
+        if (player instanceof PlayerServer) {
             ((PlayerServer) player).playerNetServerHandler.teleportAndRotate(x, y, z, yaw, pitch);
-        else throw new IllegalStateException("Player is not an instance of PlayerServer");
+            this.server.playerList.resendChunksAfterTeleport((PlayerServer) player);
+        } else throw new IllegalStateException("Player is not an instance of PlayerServer");
     }
 
     @Override
diff --git a/game/server/src/main/java/net/minecraft/server/net/command/ServerCommandSource.java b/game/server/src/main/java/net/minecraft/server/net/command/ServerCommandSource.java
index 9475420f2..b07067416 100644
--- a/game/server/src/main/java/net/minecraft/server/net/command/ServerCommandSource.java
+++ b/game/server/src/main/java/net/minecraft/server/net/command/ServerCommandSource.java
@@ -125,16 +125,18 @@ public class ServerCommandSource implements CommandSource, IServerCommandSource
 
     @Override
     public void teleportPlayerToPos(Player player, double x, double y, double z) {
-        if (player instanceof PlayerServer)
+        if (player instanceof PlayerServer) {
             ((PlayerServer) player).playerNetServerHandler.teleport(x, y, z);
-        else throw new IllegalStateException("Player is not an instance of PlayerServer");
+            this.server.playerList.resendChunksAfterTeleport((PlayerServer) player);
+        } else throw new IllegalStateException("Player is not an instance of PlayerServer");
     }
 
     @Override
     public void teleportPlayerToPosAndRot(Player player, double x, double y, double z, float yaw, float pitch) {
-        if (player instanceof PlayerServer)
+        if (player instanceof PlayerServer) {
             ((PlayerServer) player).playerNetServerHandler.teleportAndRotate(x, y, z, yaw, pitch);
-        else throw new IllegalStateException("Player is not an instance of PlayerServer");
+            this.server.playerList.resendChunksAfterTeleport((PlayerServer) player);
+        } else throw new IllegalStateException("Player is not an instance of PlayerServer");
     }
 
     @Override
diff --git a/game/server/src/main/java/net/minecraft/server/net/handler/PacketHandlerServer.java b/game/server/src/main/java/net/minecraft/server/net/handler/PacketHandlerServer.java
index 2bb059989..ae6bce942 100644
--- a/game/server/src/main/java/net/minecraft/server/net/handler/PacketHandlerServer.java
+++ b/game/server/src/main/java/net/minecraft/server/net/handler/PacketHandlerServer.java
@@ -2,14 +2,13 @@ package net.minecraft.server.net.handler;
 
 import com.mojang.brigadier.exceptions.CommandSyntaxException;
 import com.mojang.logging.LogUtils;
-import it.unimi.dsi.fastutil.ints.Int2ShortArrayMap;
-import it.unimi.dsi.fastutil.ints.Int2ShortMap;
+import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
 import net.minecraft.core.block.entity.TileEntity;
 import net.minecraft.core.block.entity.TileEntityFlag;
 import net.minecraft.core.block.entity.TileEntityMobSpawner;
 import net.minecraft.core.block.entity.TileEntitySign;
 import net.minecraft.core.entity.Entity;
-import net.minecraft.core.entity.vehicle.EntityBoat;
+import net.minecraft.core.entity.animal.MobPig;
 import net.minecraft.core.enums.ArtType;
 import net.minecraft.core.enums.EnumSignPicture;
 import net.minecraft.core.enums.PlacementMode;
@@ -26,6 +25,7 @@ import net.minecraft.core.world.pos.TilePos;
 import net.minecraft.server.net.command.ServerCommandSource;
 import net.minecraft.core.net.handler.PacketHandler;
 import net.minecraft.core.net.packet.*;
+import net.minecraft.core.player.inventory.menu.MenuAbstract;
 import net.minecraft.core.player.inventory.menu.MenuFlag;
 import net.minecraft.core.player.inventory.menu.MenuInventoryCreative;
 import net.minecraft.core.player.inventory.slot.Slot;
@@ -62,13 +62,11 @@ public class PacketHandlerServer extends PacketHandler
     private double lastPosY;
     private double lastPosZ;
     private boolean hasMoved;
-    private final Int2ShortMap guiIdMap;
 
 
     public PacketHandlerServer(MinecraftServer minecraftserver, NetworkManager networkManager, PlayerServer player) {
         this.connectionClosed = false;
         this.hasMoved = true;
-        this.guiIdMap = new Int2ShortArrayMap();
         this.mcServer = minecraftserver;
         this.netManager = networkManager;
         networkManager.setNetHandler(this);
@@ -434,18 +432,19 @@ public class PacketHandlerServer extends PacketHandler
         if (itemstack != null && itemstack.stackSize <= 0) {
             this.playerEntity.inventory.mainInventory[this.playerEntity.inventory.getCurrentSlot()] = null;
         }
-        this.playerEntity.isChangingQuantityOnly = true;
         this.playerEntity.inventory.mainInventory[this.playerEntity.inventory.getCurrentSlot()] = ItemStack.copyItemStack(this.playerEntity.inventory.mainInventory[this.playerEntity.inventory.getCurrentSlot()]);
-        Slot slot = this.playerEntity.containerMenu.getSlotFor(this.playerEntity.inventory, this.playerEntity.inventory.getCurrentSlot());
-        this.playerEntity.containerMenu.broadcastChanges();
-        this.playerEntity.isChangingQuantityOnly = false;
+        MenuAbstract menu = this.playerEntity.containerMenu;
+        Slot slot = menu.getSlotFor(this.playerEntity.inventory, this.playerEntity.inventory.getCurrentSlot());
         if (slot == null) {
-            LOGGER.warn("Couldn't find slot for index '{}' in {}'s inventory while they were in the {} container!", this.playerEntity.inventory.getCurrentSlot(), this.playerEntity.username, this.playerEntity.containerMenu.getClass().getSimpleName());
+            LOGGER.warn("Couldn't find slot for index '{}' in {}'s inventory while they were in the {} container!", this.playerEntity.inventory.getCurrentSlot(), this.playerEntity.username, menu.getClass().getSimpleName());
             return;
         }
-        if (!ItemStack.areItemStacksEqual(this.playerEntity.inventory.getCurrentItem(), packet.itemStack)) {
-            sendPacket(new PacketContainerSetSlot(this.playerEntity.containerMenu.containerId, slot.index, this.playerEntity.inventory.getCurrentItem()));
+        ItemStack current = this.playerEntity.inventory.getCurrentItem();
+        if (!ItemStack.areItemStacksEqual(current, packet.itemStack)) {
+            menu.incrementStateId();
+            sendPacket(new PacketContainerSetSlot(menu.containerId, menu.getStateId(), slot.index, current));
         }
+        menu.setRemoteSlot(slot.index, current);
     }
 
     @Override
@@ -570,13 +569,21 @@ public class PacketHandlerServer extends PacketHandler
     }
 
     @Override
-    public void handleBoatControl(@NotNull PacketBoatControl packetBoatControl) {
-        if (!canInteract() || Double.isNaN(packetBoatControl.targetXD) || Double.isNaN(packetBoatControl.targetZD))
+    public void handleVehicleControl(@NotNull PacketVehicleControl packetVehicleControl) {
+        if (!canInteract()
+            || Double.isNaN(packetVehicleControl.targetXD)
+            || Double.isNaN(packetVehicleControl.targetYD)
+            || Double.isNaN(packetVehicleControl.targetZD))
             return;
-        if (this.playerEntity.vehicle != null && this.playerEntity.vehicle instanceof EntityBoat boat) {
-            boat.handleControlDirect(packetBoatControl.targetXD, packetBoatControl.targetZD, packetBoatControl.targetYRot);
-            packetBoatControl.entityId = boat.id;
-            this.mcServer.playerList.sendPacketToOtherPlayersAroundPoint(this.playerEntity, boat.x, boat.y, boat.z, 128, boat.world.dimension.id, packetBoatControl);
+        if (this.playerEntity.vehicle instanceof Entity vehicle) {
+            vehicle.handleControlDirect(packetVehicleControl.targetXD, packetVehicleControl.targetYD, packetVehicleControl.targetZD, packetVehicleControl.targetYRot);
+            if (vehicle instanceof MobPig pig && packetVehicleControl.fallDistance > 0f) {
+                pig.applyRiderFallDamage(packetVehicleControl.fallDistance);
+            }
+            if (vehicle.relaysVehicleControl()) {
+                packetVehicleControl.entityId = vehicle.id;
+                this.mcServer.playerList.sendPacketToOtherPlayersAroundPoint(this.playerEntity, vehicle.x, vehicle.y, vehicle.z, 128, vehicle.world.dimension.id, packetVehicleControl);
+            }
         }
     }
 
@@ -661,34 +668,38 @@ public class PacketHandlerServer extends PacketHandler
 
     @Override
     public void handleContainerClick(@NotNull PacketContainerClick packetContainerClick) {
-        if (this.playerEntity.containerMenu.containerId == packetContainerClick.window_Id && this.playerEntity.containerMenu.isSynched(this.playerEntity)) {
-            ItemStack itemstack = this.playerEntity.containerMenu.clicked(packetContainerClick.action, packetContainerClick.args, this.playerEntity);
-            if (ItemStack.areItemStacksEqual(packetContainerClick.itemStack, itemstack)) {
-                this.playerEntity.playerNetServerHandler.sendPacket(new PacketContainerAck(packetContainerClick.window_Id, packetContainerClick.actionId, true));
-                this.playerEntity.isChangingQuantityOnly = true;
-                this.playerEntity.containerMenu.broadcastChanges();
-                this.playerEntity.updateHeldItem();
-                this.playerEntity.isChangingQuantityOnly = false;
-            } else {
-                this.guiIdMap.put(this.playerEntity.containerMenu.containerId, packetContainerClick.actionId);
-                this.playerEntity.playerNetServerHandler.sendPacket(new PacketContainerAck(packetContainerClick.window_Id, packetContainerClick.actionId, false));
-                this.playerEntity.containerMenu.setSynched(this.playerEntity, false);
-                ArrayList<ItemStack> arraylist = new ArrayList<>();
-                for (int i = 0; i < this.playerEntity.containerMenu.slots.size(); i++) {
-                    arraylist.add(this.playerEntity.containerMenu.slots.get(i).getItemStack());
-                }
+        MenuAbstract menu = this.playerEntity.containerMenu;
+        if (menu.containerId != packetContainerClick.window_Id) {
+            return;
+        }
+
+        boolean stale = packetContainerClick.stateId != menu.getStateId();
 
-                this.playerEntity.updateCraftingInventory(this.playerEntity.containerMenu, arraylist);
+        menu.setRemoteUpdatesSuppressed(true);
+        menu.clicked(packetContainerClick.action, packetContainerClick.args, this.playerEntity);
+        menu.setRemoteUpdatesSuppressed(false);
+
+        if (packetContainerClick.changedSlots != null) {
+            for (Int2ObjectMap.Entry<ItemStack> entry : packetContainerClick.changedSlots.int2ObjectEntrySet()) {
+                int slot = entry.getIntKey();
+                if (slot >= 0 && slot < menu.slots.size()) {
+                    menu.setRemoteSlot(slot, entry.getValue());
+                }
             }
         }
+        this.playerEntity.setRemoteCarried(packetContainerClick.carriedItem);
+
+        if (stale) {
+            this.playerEntity.broadcastFullState(menu);
+        } else {
+            menu.broadcastChanges();
+            this.playerEntity.broadcastCarried(menu);
+        }
     }
 
     @Override
     public void handleContainerAck(@NotNull PacketContainerAck packet) {
-        short short1 = this.guiIdMap.get(this.playerEntity.containerMenu.containerId);
-        if (packet.shortWindowId == short1 && this.playerEntity.containerMenu.containerId == packet.windowId && !this.playerEntity.containerMenu.isSynched(this.playerEntity)) {
-            this.playerEntity.containerMenu.setSynched(this.playerEntity, true);
-        }
+        //reconciliation of state ids and delta corrections render container ack deprecated
     }
 
     @Override
diff --git a/game/server/src/main/java/net/minecraft/server/player/PlayerManager.java b/game/server/src/main/java/net/minecraft/server/player/PlayerManager.java
index 5db1056dd..ebdc7e658 100644
--- a/game/server/src/main/java/net/minecraft/server/player/PlayerManager.java
+++ b/game/server/src/main/java/net/minecraft/server/player/PlayerManager.java
@@ -1,6 +1,7 @@
 package net.minecraft.server.player;
 
 import java.util.ArrayList;
+import java.util.Comparator;
 import java.util.List;
 
 import com.mojang.logging.LogUtils;
@@ -160,13 +161,14 @@ public class PlayerManager
         {
             return;
         }
+        List<ChunkCoordinate> chunksToAdd = new ArrayList<>();
         for(int _cx = chunkX - viewRadius; _cx <= chunkX + viewRadius; _cx++)
         {
             for(int _cz = chunkZ - viewRadius; _cz <= chunkZ + viewRadius; _cz++)
             {
                 if(!chunkWithinViewRadius(_cx, _cz, playerChunkX, playerChunkZ)) // If chunk was not in previous render radius add the chunk to the player
                 {
-                    getPlayerInstance(_cx, _cz, true).addPlayer(player);
+                    chunksToAdd.add(new ChunkCoordinate(_cx, _cz));
                 }
                 if(chunkWithinViewRadius(_cx - cDX, _cz - cDZ, chunkX, chunkZ))
                 {
@@ -181,6 +183,16 @@ public class PlayerManager
 
         }
 
+        chunksToAdd.sort(Comparator.comparingInt(c -> {
+            int ddx = c.x - chunkX;
+            int ddz = c.z - chunkZ;
+            return ddx * ddx + ddz * ddz;
+        }));
+        for(ChunkCoordinate chunk : chunksToAdd)
+        {
+            getPlayerInstance(chunk.x, chunk.z, true).addPlayer(player);
+        }
+
         player.viewingX = player.x;
         player.viewingZ = player.z;
     }
diff --git a/game/server/src/main/java/net/minecraft/server/world/WorldServer.java b/game/server/src/main/java/net/minecraft/server/world/WorldServer.java
index 04cc95bc3..216a26b2b 100644
--- a/game/server/src/main/java/net/minecraft/server/world/WorldServer.java
+++ b/game/server/src/main/java/net/minecraft/server/world/WorldServer.java
@@ -278,10 +278,6 @@ public class WorldServer extends World {
     public void tick() {
         super.tick();
 
-        if (!this.players.isEmpty()) {
-            this.particleEmitters.tickServer(this, this.players, this.mcServer.particleRenderDistance);
-        }
-
         if (System.currentTimeMillis() - lastWeatherSend > 1000) {
             mcServer.playerList.sendPacketToAllPlayers(new PacketWeatherStatus(dimension.id, getCurrentWeather() != null ? getCurrentWeather().getId() : -1, getWeatherManager().getNextWeather() != null ? getWeatherManager().getNextWeather().getId() : -1, getWeatherManager().getWeatherDuration(), getWeatherManager().getWeatherIntensity(), getWeatherManager().getWeatherPower()));
             lastWeatherSend = System.currentTimeMillis();
diff --git a/game/server/src/main/java/net/minecraft/server/world/chunk/provider/ChunkProviderServer.java b/game/server/src/main/java/net/minecraft/server/world/chunk/provider/ChunkProviderServer.java
index 5652e2676..92fef2466 100644
--- a/game/server/src/main/java/net/minecraft/server/world/chunk/provider/ChunkProviderServer.java
+++ b/game/server/src/main/java/net/minecraft/server/world/chunk/provider/ChunkProviderServer.java
@@ -27,6 +27,8 @@ public class ChunkProviderServer
     private final Map<ChunkPos, Chunk> chunkMap;
     private final List<Chunk> chunkList;
     private final WorldServer world;
+    private boolean decorating = false;
+    private boolean warnedRecursiveGeneration = false;
 
     public ChunkProviderServer(WorldServer world, ChunkLoader chunkLoader, ChunkGenerator chunkGenerator) {
         this.droppedChunksSet = new HashSet<>();
@@ -159,6 +161,13 @@ public class ChunkProviderServer
     public @NotNull Chunk provideChunk(@NotNull ChunkPosc chunkPos, boolean priority) {
         Chunk chunk = this.chunkMap.get(chunkPos);
         if (chunk == null) {
+            if (this.decorating) {
+                if (!this.warnedRecursiveGeneration) {
+                    this.warnedRecursiveGeneration = true;
+                    LOGGER.warn("Blocked recursive chunk generation at X:{}, Z:{} during decoration!", chunkPos.x(), chunkPos.z(), new Throwable("Stack trace"));
+                }
+                return this.emptyChunk;
+            }
             if (this.world.findingSpawnPoint || this.chunkLoadOverride) {
                 return prepareChunk(chunkPos, true);
             } else {
@@ -203,7 +212,13 @@ public class ChunkProviderServer
         if (!chunk.isTerrainPopulated) {
             chunk.isTerrainPopulated = true;
             if (this.chunkGenerator != null) {
-                this.chunkGenerator.decorate(chunk);
+                this.decorating = true;
+                this.warnedRecursiveGeneration = false;
+                try {
+                    this.chunkGenerator.decorate(chunk);
+                } finally {
+                    this.decorating = false;
+                }
                 chunk.setChunkModified();
             }
         }
diff --git a/util/datagen/src/main/java/net/minecraft/datagen/TrommelGenerator.java b/util/datagen/src/main/java/net/minecraft/datagen/TrommelGenerator.java
index 095655ba5..81de6c796 100644
--- a/util/datagen/src/main/java/net/minecraft/datagen/TrommelGenerator.java
+++ b/util/datagen/src/main/java/net/minecraft/datagen/TrommelGenerator.java
@@ -51,7 +51,7 @@ public class TrommelGenerator {
 
         RecipeBuilder.Trommel(RecipesGenerator.CORE_NAMESPACE)
             .setInput(Blocks.DIRT_SCORCHED_RICH)
-            .addEntry(new WeightedRandomLootObject(new ItemStack(Items.ORE_RAW_GOLD), 1), 10)
+            .addEntry(new WeightedRandomLootObject(new ItemStack(Items.ORE_RAW_GOLD), 1, 2), 10)
             .addEntry(new WeightedRandomLootObject(new ItemStack(Items.ORE_RAW_IRON), 1, 2), 15)
             .addEntry(new WeightedRandomLootObject(new ItemStack(Items.QUARTZ), 1, 4), 25)
             .addEntry(new WeightedRandomLootObject(new ItemStack(Items.OLIVINE), 1, 4), 25)
@@ -80,24 +80,24 @@ public class TrommelGenerator {
             .addEntry(new WeightedRandomLootObject(new ItemStack(Items.DUST_GLOWSTONE), 1, 6), 5)
             .addEntry(new WeightedRandomLootObject(new ItemStack(Items.NETHERCOAL), 1), 2)
             .addEntry(new WeightedRandomLootObject(new ItemStack(Items.AMMO_PEBBLE), 1, 2), 15)
-            .create("soul_sand");
+            .create("soulsand");
 
         RecipeBuilder.Trommel(RecipesGenerator.CORE_NAMESPACE)
             .setInput(Blocks.BRIMSAND)
             .addEntry(new WeightedRandomLootObject(new ItemStack(Items.AMMO_PEBBLE), 2, 3), 35)
             .addEntry(new WeightedRandomLootObject(new ItemStack(Items.GUNPOWDER), 1, 2), 25)
-            .addEntry(new WeightedRandomLootObject(new ItemStack(Items.BONE), 1, 2), 10)
+            .addEntry(new WeightedRandomLootObject(new ItemStack(Items.BONE), 1, 2), 5)
             .addEntry(new WeightedRandomLootObject(new ItemStack(Items.DUST_GLOWSTONE), 1, 2), 10)
             .addEntry(new WeightedRandomLootObject(new ItemStack(Items.FLINT), 1, 2), 5)
-            .addEntry(new WeightedRandomLootObject(new ItemStack(Items.SULFUR), 1, 2), 5)
+            .addEntry(new WeightedRandomLootObject(new ItemStack(Items.SULFUR), 1, 2), 10)
             .addEntry(new WeightedRandomLootObject(new ItemStack(Items.NETHERCOAL), 1), 5)
             .addEntry(new WeightedRandomLootObject(new ItemStack(Items.ORE_RAW_GOLD), 1), 0.5)
             .create("brimsand");
 
         RecipeBuilder.Trommel(RecipesGenerator.CORE_NAMESPACE)
             .setInput(Blocks.SULFUR)
-            .addEntry(new WeightedRandomLootObject(new ItemStack(Items.SULFUR), 2, 5), 30)
-            .addEntry(new WeightedRandomLootObject(new ItemStack(Items.DUST_GLOWSTONE), 1, 3), 20)
+            .addEntry(new WeightedRandomLootObject(new ItemStack(Items.DUST_GLOWSTONE), 2, 5), 30)
+            .addEntry(new WeightedRandomLootObject(new ItemStack(Items.SULFUR), 2, 4), 20)
             .addEntry(new WeightedRandomLootObject(new ItemStack(Items.BONE), 1, 2), 10)
             .addEntry(new WeightedRandomLootObject(new ItemStack(Items.FLINT), 1, 2), 5)
             .addEntry(new WeightedRandomLootObject(new ItemStack(Items.ORE_RAW_IRON), 1, 2), 4)
