diff --git a/game/client/src/main/java/net/minecraft/client/gui/TooltipElement.java b/game/client/src/main/java/net/minecraft/client/gui/TooltipElement.java
index 5940c6dc8..a8a760262 100644
--- a/game/client/src/main/java/net/minecraft/client/gui/TooltipElement.java
+++ b/game/client/src/main/java/net/minecraft/client/gui/TooltipElement.java
@@ -7,7 +7,9 @@ import com.google.gson.JsonParser;
 import com.google.gson.stream.JsonReader;
 import com.mojang.logging.LogUtils;
 import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.guidebook.SlotGuidebook;
 import net.minecraft.client.option.GameSettings;
+import net.minecraft.client.option.enums.DescriptionPromptEnum;
 import net.minecraft.client.render.font.FontRenderer;
 import net.minecraft.client.render.renderer.BlendFactor;
 import net.minecraft.client.render.renderer.CompareFunc;
@@ -17,20 +19,18 @@ import net.minecraft.client.render.texture.stitcher.IconCoordinate;
 import net.minecraft.client.render.texture.stitcher.TextureRegistry;
 import net.minecraft.client.render.texturepack.TexturePack;
 import net.minecraft.client.render.texturepack.TexturePackList;
-import net.minecraft.client.option.enums.DescriptionPromptEnum;
 import net.minecraft.core.WeightedRandomBag;
-import net.minecraft.core.data.registry.recipe.entry.RecipeEntryTrommel;
 import net.minecraft.core.data.registry.recipe.RecipeSymbol;
+import net.minecraft.core.data.registry.recipe.entry.RecipeEntryTrommel;
 import net.minecraft.core.item.ItemStack;
-import net.minecraft.core.player.inventory.slot.Slot;
-import net.minecraft.core.player.inventory.slot.SlotResult;
 import net.minecraft.core.lang.I18n;
 import net.minecraft.core.net.command.TextFormatting;
-import net.minecraft.client.gui.guidebook.SlotGuidebook;
+import net.minecraft.core.player.inventory.slot.Slot;
+import net.minecraft.core.player.inventory.slot.SlotResult;
 import net.minecraft.core.util.helper.MathHelper;
+import net.minecraft.core.util.helper.NBTFormatHelper;
 import org.jetbrains.annotations.NotNull;
 import org.lwjgl.input.Keyboard;
-
 import org.slf4j.Logger;
 
 import java.io.IOException;
@@ -51,54 +51,61 @@ public class TooltipElement extends Gui {
     Minecraft mc;
     FontRenderer fontRenderer;
 
-    public TooltipElement(Minecraft mc) {
+    private static final int SCREEN_MARGIN = 12;
+
+    public TooltipElement(@NotNull Minecraft mc) {
         this.mc = mc;
         this.fontRenderer = mc.font;
     }
 
-    public static final int BASE_PADDING = 3;
-    public static final int LINE_SPACING = 3;
-
     public void render(final CharSequence string, final int x, final int y, final int offsetX, final int offsetY) {
         render(string, x, y, offsetX, offsetY, -1, -1, true);
     }
 
-    public void render(final CharSequence chars, int x, int y, int offsetX, int offsetY, int maxWidth, int maxHeight, boolean canOffset) {
-        GLRenderer.pushFrame();
-        GLRenderer.setDepthFunc(CompareFunc.ALWAYS);
+    public void render(final CharSequence chars, int mouseX, int mouseY, int offsetX, int offsetY, int maxWidth, int maxHeight, boolean canOffset) {
         maxWidth = maxWidth == -1 ? Integer.MAX_VALUE : maxWidth;
-        maxHeight = maxHeight == -1 ? Integer.MAX_VALUE : maxHeight;
-
         final List<String> lines = new ArrayList<>();
         this.fontRenderer.splitCharsIntoLines(chars, maxWidth, lines);
-        int width = 0;
-        for (final String line : lines) {
-            width = Math.max(width, this.fontRenderer.stringWidth(line) - 1);
-        }
 
-        final int height = MathHelper.ceil(this.fontRenderer.heightOfLines(lines, maxWidth) - 1);
+        if (lines.isEmpty()) return;
 
-        // Flip tooltip X if it goes offscreen to the right
-        if (canOffset && x + offsetX + width > this.mc.resolution.getScaledWidthScreenCoords()) {
-            offsetX = -offsetX - width;
+        // raw dimensions
+        int rawWidth = 0;
+        for (final String line : lines) {
+            rawWidth = Math.max(rawWidth, this.fontRenderer.stringWidth(line) - 1);
         }
+        int rawHeight = MathHelper.ceil(this.fontRenderer.heightOfLines(lines, maxWidth) - 1);
+        int padding = getPadding();
 
-        if (canOffset) {
-            // Move tooltip up when it goes offscreen to the bottom
-            offsetY -= Math.max(0, y + height + offsetY - this.mc.resolution.getScaledHeightScreenCoords());
-        }
+        // screen bounds
+        int screenW = this.mc.resolution.getScaledWidthScreenCoords();
+        int screenH = this.mc.resolution.getScaledHeightScreenCoords();
 
+        int finalX = mouseX + offsetX;
+        int finalY = mouseY + offsetY;
 
-        x += offsetX;
-        y += offsetY;
+        if (canOffset) {
+            if (finalX + rawWidth > screenW - SCREEN_MARGIN) {
+                finalX = mouseX - rawWidth - offsetX;
+            }
+            if (finalY + rawHeight > screenH - SCREEN_MARGIN) {
+                finalY = screenH - rawHeight - SCREEN_MARGIN;
+            }
 
-        final int padding = getPadding();
+            finalX = MathHelper.clamp(finalX, SCREEN_MARGIN, screenW - rawWidth - SCREEN_MARGIN);
+            finalY = MathHelper.clamp(finalY, SCREEN_MARGIN, screenH - rawHeight - SCREEN_MARGIN);
+        }
 
-        drawBackground(x, y, x + width, y + height);
-//        drawRect(x, y, x + width, y + height, 0xFFFFFFFF);
+        // render
+        GLRenderer.pushFrame();
+        try {
+            GLRenderer.setDepthFunc(CompareFunc.ALWAYS);
+            drawBackground(finalX, finalY, finalX + rawWidth, finalY + rawHeight);
 
-        this.fontRenderer.renderWidthConstrained(chars, x + padding, y + padding, maxWidth).call();
-        GLRenderer.popFrame();
+            this.fontRenderer.renderWidthConstrained(chars, finalX + padding, finalY + padding, maxWidth).call();
+        } finally {
+            GLRenderer.popFrame();
+        }
     }
 
     public int getPadding() {
@@ -123,7 +130,7 @@ public class TooltipElement extends Gui {
         return drawBackground(x, y, x + width, y + height);
     }
 
-    private int[] drawBackground(final int minX, final int minY, final int maxX, final int maxY) {
+    private int @NotNull [] drawBackground(final int minX, final int minY, final int maxX, final int maxY) {
         final Style style = styleMap.getOrDefault(GameSettings.TOOLTIP_STYLE.value, defaultStyle);
         if (style != null) {
             GLRenderer.pushFrame();
@@ -150,21 +157,23 @@ public class TooltipElement extends Gui {
         return new int[]{minX, minY};
     }
 
+    public String getTooltipText(ItemStack itemStack, boolean showDescription) {
+        return getTooltipText(itemStack, showDescription, null);
+    }
+
     public String getTooltipText(ItemStack itemStack, boolean showDescription, Slot slot) {
         I18n trans = I18n.getInstance();
         StringBuilder text = new StringBuilder();
+
         boolean discovered = true;
-        boolean ctrlPressed = false;
-        boolean shiftPressed = false;
+        boolean ctrlPressed = Keyboard.isKeyDown(Keyboard.KEY_LCONTROL) || Keyboard.isKeyDown(Keyboard.KEY_RCONTROL);
+        boolean shiftPressed = Keyboard.isKeyDown(Keyboard.KEY_LSHIFT) || Keyboard.isKeyDown(Keyboard.KEY_RSHIFT);
         boolean debug = GameSettings.SHOW_ITEM_DEBUG_INFO.value;
 
         if (slot != null) {
             discovered = slot.getIsDiscovered(this.mc.thePlayer);
 
             if (slot instanceof SlotResult) {
-                ctrlPressed = Keyboard.isKeyDown(Keyboard.KEY_LCONTROL) || Keyboard.isKeyDown(Keyboard.KEY_RCONTROL);
-                shiftPressed = Keyboard.isKeyDown(Keyboard.KEY_LSHIFT) || Keyboard.isKeyDown(Keyboard.KEY_RSHIFT);
-
                 showDescription = false;
                 if (GameSettings.SWAP_CRAFTING_BUTTONS.value) {
                     boolean temp = shiftPressed;
@@ -175,42 +184,49 @@ public class TooltipElement extends Gui {
         }
 
         if (discovered) {
-            text.append(TextFormatting.get(itemStack.getDisplayColor()));
-            text.append(itemStack.getDisplayName());
-            if (itemStack.getData().containsKey("tileEntityData"))
-                text.append(" ").append(TextFormatting.MAGENTA).append(TextFormatting.ITALIC).append("+NBT").append(TextFormatting.RESET).append(TextFormatting.get(itemStack.getDisplayColor()));
-
+            text.append(TextFormatting.get(itemStack.getDisplayColor()))
+                    .append(itemStack.getDisplayName())
+                    .append(TextFormatting.RESET);
+
+            if (itemStack.getData().containsKey("tileEntityData")) {
+                if (debug) {
+                    text.append(" ")
+                        .append(TextFormatting.MAGENTA)
+                        .append(TextFormatting.ITALIC)
+                        .append("+NBT")
+                        .append(TextFormatting.RESET)
+                        .append(TextFormatting.get(itemStack.getDisplayColor()));
+                } else {
+                    text.append(" ")
+                        .append(TextFormatting.MAGENTA)
+                        .append("•")
+                        .append(TextFormatting.RESET);
+                }
+            }
 
             if (debug) {
                 text.append(" #").append(itemStack.itemID).append(":").append(itemStack.getMetadata());
             }
 
-            if (slot instanceof SlotGuidebook) {
-                if (((SlotGuidebook) slot).recipe != null) {
-                    if (((SlotGuidebook) slot).recipe instanceof RecipeEntryTrommel) {
-                        RecipeSymbol input = (RecipeSymbol) ((SlotGuidebook) slot).recipe.getInput();
-                        if (!input.matches(slot.getItemStack())) {
-                            WeightedRandomBag<?> bag = (WeightedRandomBag<?>) ((SlotGuidebook) slot).recipe.getOutput();
-                            double percent = bag.getAsPercentage(slot.getItemStack());
-                            text.append('\n').append(TextFormatting.LIGHT_GRAY).append(String.format("%.2f", percent)).append("%");
-                        }
-                    }
+            if (slot instanceof SlotGuidebook slotGuidebook && slotGuidebook.recipe instanceof RecipeEntryTrommel) {
+                RecipeSymbol input = (RecipeSymbol) slotGuidebook.recipe.getInput();
+                if (!input.matches(slot.getItemStack())) {
+                    WeightedRandomBag<?> bag = (WeightedRandomBag<?>) slotGuidebook.recipe.getOutput();
+                    double percent = bag.getAsPercentage(slot.getItemStack());
+                    text.append('\n').append(TextFormatting.LIGHT_GRAY).append(String.format("%.2f", percent)).append("%");
                 }
             }
 
             if (debug) {
                 text.append('\n').append(TextFormatting.LIGHT_GRAY).append(itemStack.getItem().namespaceID);
                 text.append('\n').append(TextFormatting.LIGHT_GRAY).append(itemStack.getItemKey());
-                text.append('\n').append(TextFormatting.LIGHT_GRAY).append("NBT: ").append(itemStack.getData().getValue().size()).append(" Tags");
 
                 if (slot instanceof SlotGuidebook slotGuidebook) {
                     if (slotGuidebook.recipe != null) {
                         text.append('\n').append(TextFormatting.GRAY).append(slotGuidebook.recipe);
                     }
-                    if (slotGuidebook.symbol != null) {
-                        if (slotGuidebook.symbol.getItemGroup() != null) {
-                            text.append('\n').append(TextFormatting.GRAY).append("Accepts any: ").append(slotGuidebook.symbol.getItemGroup());
-                        }
+                    if (slotGuidebook.symbol != null && slotGuidebook.symbol.getItemGroup() != null) {
+                        text.append('\n').append(TextFormatting.GRAY).append("Accepts any: ").append(slotGuidebook.symbol.getItemGroup());
                     }
                 }
             }
@@ -222,23 +238,32 @@ public class TooltipElement extends Gui {
                 text.append('\n').append(TextFormatting.LIGHT_GRAY).append(remainingUses + 1).append(" / ").append(durability + 1);
             }
 
-            if (showDescription) {
-                text.append('\n').append(formatDescription(itemStack.getItemDescription(), 16));
+            if (showDescription && !debug) {
+                text.append('\n').append(formatDescription(itemStack.getItemDescription()));
             } else if (!(slot instanceof SlotResult) && GameSettings.ITEM_DESCRIPTIONS.value != DescriptionPromptEnum.NEVER_PROMPT) {
-                text.append('\n').append(formatDescription(trans.translateKeyAndFormat("gui.tooltip.prompt.description", GameSettings.KEY_DESCRIPTION.getKeyName()), 16));
+                text.append('\n').append(formatDescription(trans.translateKeyAndFormat("gui.tooltip.prompt.description", GameSettings.KEY_DESCRIPTION.getKeyName())));
             }
 
             if (slot instanceof SlotResult) {
                 if (shiftPressed && !ctrlPressed) {
                     text.append('\n').append(TextFormatting.LIGHT_GRAY).append("Craft Stack");
-                }
-                if (ctrlPressed && !shiftPressed) {
+                } else if (ctrlPressed && !shiftPressed) {
                     text.append('\n').append(TextFormatting.LIGHT_GRAY).append("Craft Once");
-                }
-                if (ctrlPressed && shiftPressed) {
+                } else if (ctrlPressed) {
                     text.append('\n').append(TextFormatting.LIGHT_GRAY).append("Craft All");
                 }
             }
+
+            if (debug && !itemStack.getData().getValue().isEmpty()) {
+                if (ctrlPressed) {
+                    text.append('\n').append(NBTFormatHelper.formatNbtTooltip(itemStack.getData().toString()));
+                } else {
+                    int tagCount = itemStack.getData().getValue().size();
+                    String tagWord = tagCount == 1 ? " Tag" : " Tags";
+                    text.append('\n').append(TextFormatting.GRAY).append(tagCount).append(tagWord).append(" <CTRL>");
+                }
+            }
+
         } else {
             String translatedName = itemStack.getDisplayName();
             for (char c : translatedName.toCharArray()) {
@@ -258,21 +283,15 @@ public class TooltipElement extends Gui {
         return text.toString();
     }
 
-    public String getTooltipText(ItemStack itemStack, boolean showDescription) {
-        return getTooltipText(itemStack, showDescription, null);
-    }
-
-    private static String formatDescription(String description, int preferredLineLength) {
+    private static @NotNull String formatDescription(@NotNull String description) {
         StringBuilder string = new StringBuilder();
 
         string.append(TextFormatting.LIGHT_GRAY);
 
         int lineLength = 0;
-        for (int i = 0; i < description.length(); i++) {
-            char c = description.charAt(i);
-
+        for (char c : description.toCharArray()) {
             if (c == ' ') {
-                if (lineLength > preferredLineLength) {
+                if (lineLength > 16) {
                     lineLength = 0;
                     string.append("\n").append(TextFormatting.LIGHT_GRAY);
                 } else {
diff --git a/game/client/src/main/java/net/minecraft/client/gui/hud/component/HudComponentDynamicTooltip.java b/game/client/src/main/java/net/minecraft/client/gui/hud/component/HudComponentDynamicTooltip.java
index df19902d8..897b8b483 100644
--- a/game/client/src/main/java/net/minecraft/client/gui/hud/component/HudComponentDynamicTooltip.java
+++ b/game/client/src/main/java/net/minecraft/client/gui/hud/component/HudComponentDynamicTooltip.java
@@ -9,9 +9,9 @@ import net.minecraft.client.option.Option;
 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.Mob;
 import net.minecraft.core.item.ItemStack;
 import net.minecraft.client.util.helper.Colors;
+import net.minecraft.core.net.command.TextFormatting;
 import net.minecraft.core.util.helper.Time;
 import org.jetbrains.annotations.NotNull;
 
@@ -23,6 +23,7 @@ public class HudComponentDynamicTooltip extends HudComponentMovable {
     private String itemString;
     private int itemColor;
     private double itemTime = 0.0;
+    private boolean itemHasNbt = false;
 
     private String systemString;
     private int systemColor;
@@ -58,7 +59,11 @@ public class HudComponentDynamicTooltip extends HudComponentMovable {
 
     @Override
     public int getTrueXSize() {
-        return mc.font.stringWidth(getDisplayText());
+        int width = mc.font.stringWidth(getDisplayText());
+        if (this.itemTime > 0.0 && this.itemHasNbt) {
+            width += mc.font.stringWidth(" •");
+        }
+        return width;
     }
 
     private String getDisplayText() {
@@ -107,7 +112,7 @@ public class HudComponentDynamicTooltip extends HudComponentMovable {
             activeTime = this.itemTime;
         }
 
-        if (activeString == null || activeTime <= 0.0) return;
+        if (activeString == null) return;
 
         double alpha = (activeTime < this.fadeTime) ? (activeTime / this.fadeTime) : 1.0;
         int a = (int) (alpha * 255);
@@ -125,6 +130,15 @@ public class HudComponentDynamicTooltip extends HudComponentMovable {
 
         mc.font.render(activeString, x, y).setShadow().setColor(rgb).call();
 
+        if (activeString.equals(this.itemString) && this.itemHasNbt) {
+            int nameWidth = mc.font.stringWidth(activeString);
+
+            int magenta = Colors.allChatColors[TextFormatting.MAGENTA.id].getARGB();
+            int bulletColor = (magenta & 0x00FFFFFF) | (a << 24);
+
+            mc.font.render(" •", x + nameWidth, y).setShadow().setColor(bulletColor).call();
+        }
+
         GLRenderer.disableState(State.BLEND);
     }
 
@@ -142,10 +156,12 @@ public class HudComponentDynamicTooltip extends HudComponentMovable {
     private void updateItemString(ItemStack item) {
         if (item == null) {
             this.itemString = null;
+            this.itemHasNbt = false;
             return;
         }
         this.itemString = item.getDisplayName();
         this.itemColor = Colors.allChatColors[item.getDisplayColor()].getARGB();
+        this.itemHasNbt = item.getData().containsKey("tileEntityData");
     }
 
     private int getItemID(ItemStack stack) {
diff --git a/game/client/src/main/java/net/minecraft/client/player/controller/PlayerController.java b/game/client/src/main/java/net/minecraft/client/player/controller/PlayerController.java
index 2c6b86736..118ad0166 100644
--- a/game/client/src/main/java/net/minecraft/client/player/controller/PlayerController.java
+++ b/game/client/src/main/java/net/minecraft/client/player/controller/PlayerController.java
@@ -20,6 +20,7 @@ import net.minecraft.core.item.Item;
 import net.minecraft.core.item.ItemStack;
 import net.minecraft.core.item.tag.ItemTags;
 import net.minecraft.core.util.helper.Side;
+import net.minecraft.core.world.ICarrySource;
 import net.minecraft.core.world.World;
 import net.minecraft.core.world.pos.TilePosc;
 import org.jetbrains.annotations.NotNull;
@@ -279,12 +280,8 @@ public abstract class PlayerController {
         final @NotNull Block<?> block = world.getBlockType(tilePos);
 
         if (player.getHeldObject() == null) {
-            if (player.isSneaking() && itemStack == null) { // If crouched and hand empty try to pickup block entity
-                final @Nullable TileEntity tileEntity = world.getTileEntity(tilePos);
-                if (tileEntity != null && tileEntity.canBeCarried(world, player)) {
-                    player.setHeldObject(tileEntity.pickup(world, player));
-                    return true;
-                }
+            if (player.isSneaking() && itemStack == null) {
+                if (ICarrySource.tryPickUpFromWorld(world, player, tilePos) != null) return true;
             }
             if (!player.isSneaking() || itemStack == null) {  // If standing or hand empty perform block right click method
                 if (block.onInteracted(world, tilePos, player, side, xPlaced, yPlaced)) {
diff --git a/game/client/src/main/java/net/minecraft/client/render/block/model/generic/BlockModelGenericBed.java b/game/client/src/main/java/net/minecraft/client/render/block/model/generic/BlockModelGenericBed.java
index b97790131..da3036cf0 100644
--- a/game/client/src/main/java/net/minecraft/client/render/block/model/generic/BlockModelGenericBed.java
+++ b/game/client/src/main/java/net/minecraft/client/render/block/model/generic/BlockModelGenericBed.java
@@ -21,7 +21,7 @@ public class BlockModelGenericBed<T extends BlockLogic> extends BlockModelGeneri
 
     @Override
     public boolean renderAttached(@NotNull TessellatorGeneral tessellator, @NotNull WorldSource worldSource, @NotNull TilePosc tilePos, boolean cullFaces, @Nullable IconCoordinate overrideTexture) {
-        int direction = BlockLogicBed.getDirection(worldSource.getBlockData(tilePos));
+        int direction = BlockLogicBed.DIRECTION.get(worldSource.getBlockData(tilePos));
         return switch (direction) {
             case 0 -> getModel(worldSource, tilePos).renderAttached(this, tessellator, worldSource, tilePos, 0, 3, 0, 0, 0, 0, false, cullFaces, overrideTexture);
             case 1 -> getModel(worldSource, tilePos).renderAttached(this, tessellator, worldSource, tilePos, 0, 2, 0, 0, 0, 0, false, cullFaces, overrideTexture);
@@ -32,6 +32,6 @@ public class BlockModelGenericBed<T extends BlockLogic> extends BlockModelGeneri
 
     @Override
     public @NotNull StaticBlockModel getModelFromData(int data) {
-        return BlockLogicBed.isBlockHeadOfBed(data) ? this.head : this.staticModel;
+        return BlockLogicBed.IS_HEAD.bool(data) ? this.head : this.staticModel;
     }
 }
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 af1e997ee..271270fd2 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
@@ -16,7 +16,7 @@ import net.minecraft.client.render.tessellator.TessellatorGeneral;
 import net.minecraft.client.render.texture.stitcher.TextureRegistry;
 import net.minecraft.client.render.tileentity.TileEntityRenderer;
 import net.minecraft.core.block.entity.TileEntity;
-import net.minecraft.core.block.motion.CarriedBlock;
+import net.minecraft.core.block.motion.ICarriedBlock;
 import net.minecraft.core.entity.player.Player;
 import net.minecraft.core.player.gamemode.Gamemodes;
 import net.minecraft.core.util.helper.MathHelper;
@@ -68,7 +68,7 @@ public class MobRendererPlayer extends MobRendererBipedArmored<Player> {
     }
 
     public void drawHeldObject(final Player player, final float partialTick) {
-        if (player.getHeldObject() instanceof CarriedBlock carriedBlock) {
+        if (player.getHeldObject() instanceof ICarriedBlock carriedBlock) {
             if (this.container == null || this.container.world != player.world) {
                 this.container = new BlocksContainer(player.world);
             }
@@ -91,18 +91,18 @@ public class MobRendererPlayer extends MobRendererBipedArmored<Player> {
 
             this.container.setLightReferenceEntity(player);
             this.container.partialTick = partialTick;
-            this.container.setBlock(blockPos.x, blockPos.y, blockPos.z, carriedBlock.blockId, carriedBlock.metadata, carriedBlock.entity);
+            this.container.setBlock(blockPos.x, blockPos.y, blockPos.z, carriedBlock.block().id(), carriedBlock.metadata(), carriedBlock.entity());
             BlockModelDispatcher.getInstance().getDispatch(carriedBlock.block()).renderNoCulling(GLRenderer.getTessellator(), this.container, blockPos);
             tessellator.draw();
             tessellator.setTranslation(0, 0, 0);
             this.container.setLightReferenceEntity(null);
             this.container.clear();
 
-            final TileEntityRenderer<TileEntity> renderer = TileEntityRenderDispatcher.instance.getRenderer(carriedBlock.entity);
+            final TileEntityRenderer<TileEntity> renderer = TileEntityRenderDispatcher.instance.getRenderer(carriedBlock.entity());
             if (renderer != null) {
-                carriedBlock.entity.worldObj = player.world;
-                renderer.doRender(tessellator, carriedBlock.entity, -0.5, -0.5, -0.5, partialTick);
-                carriedBlock.entity.worldObj = null;
+                carriedBlock.entity().worldObj = player.world;
+                renderer.doRender(tessellator, carriedBlock.entity(), -0.5, -0.5, -0.5, partialTick);
+                carriedBlock.entity().worldObj = null;
             }
 
             GLRenderer.popFrame();
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 32cf8f1b6..e8f021467 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
@@ -11,6 +11,7 @@ import net.minecraft.core.enums.EnumDropCause;
 import net.minecraft.core.enums.EnumSleepStatus;
 import net.minecraft.core.item.Items;
 import net.minecraft.core.item.ItemStack;
+import net.minecraft.core.util.PackedField;
 import net.minecraft.core.util.helper.Direction;
 import net.minecraft.core.util.helper.Side;
 import net.minecraft.core.world.World;
@@ -38,58 +39,50 @@ public class BlockLogicBed
         int data = world.getBlockData(tilePos);
         final @NotNull TilePosc headPos;
         final @NotNull TilePosc footPos;
-        if (!isBlockHeadOfBed(data)) {
-            final int dir = getDirection(data);
+        if (!IS_HEAD.bool(data)) {
+            final int dir = DIRECTION.get(data);
             headPos = tilePos;
             footPos = tilePos.add(headBlockToFootBlockMap[dir].getDirection(), new TilePos());
+            if (world.getBlockType(footPos) != this.block) return true;
         } else {
-            final int dir = getDirection(data);
-            headPos = tilePos.sub(headBlockToFootBlockMap[dir].getDirection(), new TilePos());
+            final int dir = DIRECTION.get(data);
             footPos = tilePos;
-        }
-
-        // Check if bed is not complete
-        if (!isBlockHeadOfBed(data)) {
-            if (world.getBlockType(footPos) != this.block) {
-                return true;
-            }
-            data = world.getBlockData(tilePos);
+            headPos = tilePos.sub(headBlockToFootBlockMap[dir].getDirection(), new TilePos());
+            if (world.getBlockType(headPos) != this.block) return true;
         }
 
         // Explode in invalid dimensions
         if (!world.getWorldType().mayRespawn()) {
-            world.setBlockTypeNotify(tilePos, Blocks.AIR);
-            if (world.getBlockType(footPos) == this.block) {
-                world.setBlockTypeNotify(footPos, Blocks.AIR);
-            }
+            world.setBlockType(headPos, Blocks.AIR);
+            assert world.getBlockType(footPos) == this.block; // made sure from above
+            world.setBlockType(footPos, Blocks.AIR);
+
+            // prevents dupe... tho the dupe would need to be pretty funny to work
+            world.notifyBlockChange(headPos, Blocks.AIR); 
+            world.notifyBlockChange(footPos, Blocks.AIR); 
+
             player.addStat(Achievements.SLEEP_NETHER, 1);
             world.createExplosion(null, tilePos.x() + 0.5F, tilePos.y() + 0.5F, tilePos.z() + 0.5F, 5F, true, false);
             return true;
         }
 
         // Check if bed is occupied, and set occupied
-        if (isBedOccupied(data)) {
-            @Nullable Player sleepingPlayer = null;
+        if (IS_OCCUPIED.bool(data)) {
+            // @Nullable Player sleepingPlayer = null;
             for (final @NotNull Player p : world.players) {
-                if (p.isPlayerSleeping()) {
-                    final @NotNull TilePos pos = Objects.requireNonNull(p.bedTilePos);
-                    if (pos.equals(footPos)) {
-                        sleepingPlayer = p;
-                    }
+                if (!p.isPlayerSleeping()) continue;
+                final var otherBedPos = Objects.requireNonNull(p.bedTilePos);
+                if (otherBedPos.equals(footPos)) {
+                    player.sendTranslatedChatMessage("bed.occupied");
+                    return true;
                 }
             }
-            if (sleepingPlayer == null) {
-                setBedOccupied(world, footPos, false);
-            } else {
-                player.sendTranslatedChatMessage("bed.occupied");
-                return true;
-            }
+            world.setBlockData(footPos, IS_OCCUPIED.set(data, 0));
         }
-
+        
         // Sleep
         if (player.sleepInBedAt(footPos.x(), footPos.y(), footPos.z()) == EnumSleepStatus.OK) {
-            setBedOccupied(world, footPos, true);
-            return true;
+            world.setBlockData(footPos, IS_OCCUPIED.set(data, 1));
         }
 
         return true;
@@ -112,51 +105,41 @@ public class BlockLogicBed
 
     @Override
     public void onNeighborChanged(final @NotNull World world, final @NotNull TilePosc tilePos, final @NotNull Block<?> block) {
-        final var state = new State(world, tilePos);
-        if (!state.hasOtherHalf(world, tilePos)) {
+        final var otherHalf = otherHalf(world, tilePos, world.getBlockData(tilePos));
+        if (otherHalf == null) {
             world.setBlockTypeNotify(tilePos, Blocks.AIR);
         }
     }
 
     @Override
     public void onRemoved(final @NotNull World world, final @NotNull TilePosc tilePos, final int data) {
-        final var state = new State(data, world, tilePos);
-        if (state.hasOtherHalf(world, tilePos)) {
+        final var otherHalf = otherHalf(world, tilePos, data);
+        if (otherHalf != null) {
             // eventhough onNeighborChanged would normally be able to handle the removal of
             // the other half, however piston will break this half without emitting neighbor
             // update untill it finishes extending, destroying the other half before which
             // would result in a dupe.
-            world.setBlockTypeNotify(state.otherPos(tilePos), Blocks.AIR);
+            world.setBlockTypeNotify(otherHalf, Blocks.AIR);
         }
     }
 
-    protected static class State {
-        final boolean isHead;
-        final @NotNull Direction h2fDir;
+    public static final PackedField DIRECTION = new PackedField(0, 2); 
+    public static final PackedField IS_OCCUPIED = new PackedField(2, 1); 
+    public static final PackedField IS_HEAD = new PackedField(3, 1); 
 
-        public State(int data, final @NotNull World world, @NotNull TilePosc tilePos) {
-            isHead = isBlockHeadOfBed(data);
-            h2fDir = headBlockToFootBlockMap[getDirection(data)].getDirection();
-        }
-
-        public State(final @NotNull World world, @NotNull TilePosc tilePos) {
-            this(world.getBlockData(tilePos), world, tilePos);
-        }    
-
-        public @NotNull TilePos otherPos(@NotNull TilePosc tilePos) {
-            return (isHead)
-                ? tilePos.sub(h2fDir, new TilePos())
-                : tilePos.add(h2fDir, new TilePos());
-        }
+    public static @Nullable TilePos otherHalf(final @NotNull World world, @NotNull TilePosc tilePos, int data) {
+        final var isHead = IS_HEAD.bool(data);
+        final var dir = DIRECTION.get(data);
+        final var direction = headBlockToFootBlockMap[DIRECTION.get(data)].getDirection();
+        final var otherPos = (isHead)
+            ? tilePos.sub(direction, new TilePos())
+            : tilePos.add(direction, new TilePos());
 
-        public boolean hasOtherHalf(final @NotNull World world, @NotNull TilePosc tilePos) {
-            final var otherPos = otherPos(tilePos);
-            if (!(world.getBlockType(otherPos).getLogic() instanceof BlockLogicBed)) return false;
-            final var otherState = new State(world, otherPos);
-            if (this.isHead == otherState.isHead) return false;
-            if (this.h2fDir != otherState.h2fDir) return false;
-            return true;
-        }
+        if (!(world.getBlockType(otherPos).getLogic() instanceof BlockLogicBed)) return null;
+        final int otherData = world.getBlockData(otherPos);
+        if (isHead == IS_HEAD.bool(otherData)) return null;
+        if (dir != DIRECTION.get(otherData)) return null;
+        return otherPos;
     }
 
     @Override
@@ -169,35 +152,8 @@ public class BlockLogicBed
         return Material.PISTON_DESTROY_ON_PUSH;
     }
 
-    public static final int MASK_IS_HEAD = 0b0000_1000;
-    public static final int MASK_IS_OCCUPIED = 0b0000_0100;
-    public static final int MASK_DIRECTION = 0b0000_0011;
-
-    public static boolean isBlockHeadOfBed(final int meta) {
-        return (meta & MASK_IS_HEAD) != 0;
-    }
-
-    public static boolean isBedOccupied(final int meta) {
-        return (meta & MASK_IS_OCCUPIED) != 0;
-    }
-
-    public static int getDirection(final int meta) {
-        return meta & MASK_DIRECTION;
-    }
-
-    public static void setBedOccupied(final @NotNull World world, final @NotNull TilePosc tilePos, final boolean flag) {
-        int l = world.getBlockData(tilePos);
-        if (flag) {
-            l |= MASK_IS_OCCUPIED;
-        } else {
-            l &= ~MASK_IS_OCCUPIED;
-        }
-        world.setBlockDataNotify(tilePos, l);
-    }
-
     public static @Nullable TilePos getNearestEmptyTilePos(final @NotNull World world, final @NotNull TilePos tilePos, int attempts) {
-        final int data = world.getBlockData(tilePos);
-        final int direction = getDirection(data);
+        final int direction = DIRECTION.get(world.getBlockData(tilePos));
 
         for (int d = 0; d <= 1; d++) {
             final @NotNull TilePos minPos = new TilePos(
@@ -230,29 +186,10 @@ public class BlockLogicBed
         return null;
     }
 
-    public static final int[] headOfBedMap = {
-        3, 4, 2, 5
+    public static final @NotNull Side @NotNull [] headBlockToFootBlockMap = {
+        Side.SOUTH,
+        Side.WEST,
+        Side.NORTH,
+        Side.EAST
     };
-    public static final int[] footToHeadMap = {
-        2, 3, 0, 1
-    };
-    public static final int[][] bedDirection = {
-        {
-            1, 0, 3, 2, 5, 4
-        }, {
-        1, 0, 5, 4, 2, 3
-    }, {
-        1, 0, 2, 3, 4, 5
-    }, {
-        1, 0, 4, 5, 3, 2
-    }
-    };
-
-    public static final @NotNull Side @NotNull [] headBlockToFootBlockMap =
-        {
-            Side.SOUTH,
-            Side.WEST,
-            Side.NORTH,
-            Side.EAST
-        };
 }
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 a64af88db..d7163bb7e 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
@@ -27,6 +27,7 @@ import org.joml.primitives.AABBdc;
 
 import java.util.function.Supplier;
 
+// TODO this needs massive refactor
 public class BlockLogicDoor
     extends BlockLogic
     implements IPaintable {
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 84b830ae8..7e8b34df3 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
@@ -42,22 +42,24 @@ public class BlockLogicDoorPainted
     @Override
     public void removeDye(final @NotNull World world, final @NotNull TilePosc tilePos) {
         final int data = world.getBlockData(tilePos);
-        world.setBlockTypeDataNotify(tilePos, this.isTop ? Blocks.DOOR_PLANKS_OAK_TOP : Blocks.DOOR_PLANKS_OAK_BOTTOM, data & 0xF);
+        world.setBlockTypeData(tilePos, this.isTop ? Blocks.DOOR_PLANKS_OAK_TOP : Blocks.DOOR_PLANKS_OAK_BOTTOM, data & 0xF);
         if (this.isTop) {
             world.setBlockTypeDataNotify(tilePos.down(new TilePos()), Blocks.DOOR_PLANKS_OAK_BOTTOM, data & 0xF);
         } else {
             world.setBlockTypeDataNotify(tilePos.up(new TilePos()), Blocks.DOOR_PLANKS_OAK_TOP, data & 0xF);
         }
+        world.notifyBlockChange(tilePos, this.block);
     }
-
+    
     @Override
     public void setColor(final @NotNull World world, final @NotNull TilePosc tilePos, final @NotNull DyeColor color) {
         final int data = world.getBlockData(tilePos);
-        world.setBlockDataNotify(tilePos, data & 0xF | this.toMetadata(color));
+        world.setBlockData(tilePos, data & 0xF | this.toMetadata(color));
         if (this.isTop) {
             world.setBlockDataNotify(tilePos.down(new TilePos()), data & 0xF | this.toMetadata(color));
         } else {
             world.setBlockDataNotify(tilePos.up(new TilePos()), data & 0xF | this.toMetadata(color));
         }
+        world.notifyBlockChange(tilePos, this.block);
     }
 }
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 558b84a1c..89f9f5c6c 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
@@ -41,7 +41,7 @@ public abstract class BlockLogicFluid extends BlockLogic {
         return (float) (data + 1) / 8F;
     }
 
-    protected int getDepth(final WorldSource worldSource, final @NotNull TilePosc tilePos) {
+    public int getDepth(final WorldSource worldSource, final @NotNull TilePosc tilePos) {
         if (worldSource.getBlockMaterial(tilePos) != this.material) {
             return -1;
         } else {
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 d8ac4e7b0..5a927c736 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
@@ -48,13 +48,11 @@ public class BlockLogicFluidFlowing extends BlockLogicFluid {
             if (newFlowDecay >= 8 || surroundingFlowDecay < 0) {
                 newFlowDecay = -1;
             }
-            if (getDepth(world, tilePos.up(queryPos)) >= 0) {
-                int flowDecayAbove = getDepth(world, tilePos.up(queryPos));
-                if (flowDecayAbove >= 8) {
-                    newFlowDecay = flowDecayAbove;
-                } else {
-                    newFlowDecay = flowDecayAbove + 8;
-                }
+            int flowDecayAbove = getDepth(world, tilePos.up(queryPos));
+            if (flowDecayAbove >= 8) {
+                newFlowDecay = flowDecayAbove;
+            } else if (flowDecayAbove >= 0) {
+                newFlowDecay = flowDecayAbove + 8;
             }
             // Become source block
             if (this.maxCount >= 2 && this.fluid.canBecomeSource(this, world, tilePos, rand)) {
@@ -71,7 +69,7 @@ public class BlockLogicFluidFlowing extends BlockLogicFluid {
                 } else {
                     world.setBlockDataNotify(tilePos, localFlowDecay);
                     world.scheduleBlockUpdate(tilePos, this.block, tickDelay());
-                    world.notifyBlocksOfNeighborChange(tilePos, this.block);
+                    world.notifyBlocksOfNeighborChange(tilePos, this.block); // ?
                 }
             } else {
                 setFluidStill(world, tilePos);
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicFurnace.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicFurnace.java
index d85e55d52..1b9e33702 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicFurnace.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicFurnace.java
@@ -20,7 +20,6 @@ import java.util.Random;
 
 public class BlockLogicFurnace extends BlockLogicRotatable {
     private static final Logger LOGGER = LogUtils.getLogger();
-    public static boolean keepFurnaceInventory = false;
 
     protected final boolean isActive;
 
@@ -78,9 +77,7 @@ public class BlockLogicFurnace extends BlockLogicRotatable {
     }
 
     public static void updateFurnaceBlockState(@NotNull World world, @NotNull TilePos tilePos, boolean lit) {
-        int meta = world.getBlockData(tilePos);
-        TileEntity tileEntity = world.getTileEntity(tilePos);
-        if (tileEntity == null) {
+        if (!(world.getTileEntity(tilePos) instanceof TileEntityFurnace)) {
             String msg = "Furnace is missing Tile Entity at " + tilePos + ", block will be removed!";
             if (Global.BUILD_CHANNEL.isUnstableBuild()) {
                 throw new RuntimeException(msg);
@@ -89,16 +86,11 @@ public class BlockLogicFurnace extends BlockLogicRotatable {
             LOGGER.warn(msg);
             return;
         }
-        keepFurnaceInventory = true;
-        if (lit) {
-            world.setBlockTypeNotify(tilePos, Blocks.FURNACE_STONE_ACTIVE);
-        } else {
-            world.setBlockTypeNotify(tilePos, Blocks.FURNACE_STONE_IDLE);
-        }
-        keepFurnaceInventory = false;
-        world.setBlockDataNotify(tilePos, meta);
-        tileEntity.validate();
-        world.setTileEntity(tilePos, tileEntity);
+        final int meta = world.getBlockData(tilePos);
+        final var block = (lit)
+            ? Blocks.FURNACE_STONE_ACTIVE
+            : Blocks.FURNACE_STONE_IDLE;
+        world.setBlockTypeDataRaw(tilePos, block, meta);
+        world.notifyBlockChange(tilePos, block);
     }
-
 }
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicFurnaceBlast.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicFurnaceBlast.java
index 211ee3511..d5610e1ea 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicFurnaceBlast.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicFurnaceBlast.java
@@ -23,7 +23,6 @@ public class BlockLogicFurnaceBlast extends BlockLogicRotatable
     private static final Logger LOGGER = LogUtils.getLogger();
 
     protected final boolean isActive;
-    public static boolean keepFurnaceInventory = false;
 
     public BlockLogicFurnaceBlast(Block<?> block, boolean active)
     {
@@ -81,27 +80,21 @@ public class BlockLogicFurnaceBlast extends BlockLogicRotatable
     }
 
     public static void updateFurnaceBlockState(@NotNull World world, @NotNull TilePos tilePos, boolean lit) {
-		int meta = world.getBlockData(tilePos);
-		TileEntity tileEntity = world.getTileEntity(tilePos);
-		if(tileEntity == null) {
-			String msg = "Blast Furnace is missing Tile Entity at " + tilePos + ", block will be removed!";
+        if(!(world.getTileEntity(tilePos) instanceof TileEntityFurnaceBlast)) {
+            String msg = "Blast Furnace is missing Tile Entity at " + tilePos + ", block will be removed!";
 			if(Global.BUILD_CHANNEL.isUnstableBuild()) {
-				throw new RuntimeException(msg);
+                throw new RuntimeException(msg);
 			}
 			world.setBlockTypeNotify(tilePos, Blocks.AIR);
             LOGGER.warn(msg);
 			return;
 		}
-		keepFurnaceInventory = true;
-		if(lit) {
-			world.setBlockTypeNotify(tilePos, Blocks.FURNACE_BLAST_ACTIVE);
-		} else {
-			world.setBlockTypeNotify(tilePos, Blocks.FURNACE_BLAST_IDLE);
-		}
-		keepFurnaceInventory = false;
-		world.setBlockDataNotify(tilePos, meta);
-		tileEntity.validate();
-		world.setTileEntity(tilePos, tileEntity);
+        final int meta = world.getBlockData(tilePos);
+        final var block = (lit)
+            ? Blocks.FURNACE_BLAST_ACTIVE
+            : Blocks.FURNACE_BLAST_IDLE;
+		world.setBlockTypeDataRaw(tilePos, block, meta);
+        world.notifyBlockChange(tilePos, block);
 	}
 
 }
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 a546093c9..d21de68de 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
@@ -44,13 +44,10 @@ public class BlockLogicJukebox extends BlockLogic {
         }
     }
 
-    public void ejectRecord(@NotNull World world, @NotNull TilePosc tilePos)
-    {
-        if(world.isClientSide)
-        {
-            return;
+    public void ejectRecord(@NotNull World world, @NotNull TilePosc tilePos) {
+        if(world.isClientSide) return;
+        if (world.getTileEntity(tilePos) instanceof TileEntityJukebox jukebox) {
+            jukebox.dropContents(world, tilePos.x(), tilePos.y(), tilePos.z());
         }
-        TileEntity tileEntity = world.getTileEntity(tilePos);
-        if (tileEntity != null) tileEntity.dropContents(world, tilePos.x(), tilePos.y(), tilePos.z());
     }
 }
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 a6371b971..7969695e9 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
@@ -12,6 +12,7 @@ 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;
 
@@ -74,7 +75,7 @@ public class BlockLogicLadder extends BlockLogic implements ISupportable {
         return true;
     }
 
-    public Side getSideForPlacement(@NotNull World world, @NotNull TilePos tilePos, @NotNull Side clickedSide) {
+    public @Nullable Side getSideForPlacement(@NotNull World world, @NotNull TilePos tilePos, @NotNull Side clickedSide) {
         TilePosc p = tilePos;
         if (!world.canPlaceInsideBlock(tilePos)) {
             p = p.add(clickedSide.getDirection(), new TilePos());
@@ -97,7 +98,7 @@ public class BlockLogicLadder extends BlockLogic implements ISupportable {
     /**
      * Returns the side of the block the ladder is attached to
      */
-    public Side getSideFromMeta(int meta) {
+    public @NotNull Side getSideFromMeta(int meta) {
         if (meta == 2) return Side.NORTH;
         if (meta == 3) return Side.SOUTH;
         if (meta == 4) return Side.WEST;
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 5a0ab3cdd..32902034e 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
@@ -135,13 +135,13 @@ public class BlockLogicPressurePlate<T extends Entity> extends BlockLogic implem
         }
         if (isSteppedOn && !isPressed) {
             world.setBlockDataNotify(tilePos, meta | MASK_POWERED);
-            world.notifyBlocksOfNeighborChange(tilePos, this.block);
+            // world.notifyBlocksOfNeighborChange(tilePos, this.block);
             world.notifyBlocksOfNeighborChange(tilePos.add(dir, new TilePos()), this.block);
             world.markBlocksDirty(tilePos, tilePos);
             world.playSoundEffect(null, SoundCategory.WORLD_SOUNDS, (double) tilePos.x() + 0.5D, (double) tilePos.y() + 0.1D, (double) tilePos.z() + 0.5D, "random.click", 0.3F, 0.6F);
         } else if (!isSteppedOn && isPressed) {
             world.setBlockDataNotify(tilePos, meta & ~MASK_POWERED);
-            world.notifyBlocksOfNeighborChange(tilePos, this.block);
+            // world.notifyBlocksOfNeighborChange(tilePos, this.block);
             world.notifyBlocksOfNeighborChange(tilePos.add(dir, new TilePos()), this.block);
             world.markBlocksDirty(tilePos, tilePos);
             world.playSoundEffect(null, SoundCategory.WORLD_SOUNDS, (double) tilePos.x() + 0.5D, (double) tilePos.y() + 0.1D, (double) tilePos.z() + 0.5D, "random.click", 0.3F, 0.5F);
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicRailDetector.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicRailDetector.java
index 0276813f0..c20b3a53b 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicRailDetector.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicRailDetector.java
@@ -13,6 +13,7 @@ import net.minecraft.core.world.pos.TilePosc;
 import org.jetbrains.annotations.NotNull;
 import org.joml.primitives.AABBd;
 
+// TODO detector rail has some freaky behaviors, investigate and fix
 public class BlockLogicRailDetector extends BlockLogicRail {
     public BlockLogicRailDetector(@NotNull Block<?> block) {
         super(block, true);
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicSponge.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicSponge.java
index facc4d952..171e60515 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicSponge.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicSponge.java
@@ -31,22 +31,23 @@ public class BlockLogicSponge extends BlockLogic {
                 world.spawnParticle("largesmoke", (double) tilePos.x() + Math.random(), (double) tilePos.y() + Math.random(), (double) tilePos.z() + Math.random(), 0.0D, 0.0D, 0.0D, 0, false);
             }
         }
-        final byte radius = 2;
+        // TODO this sends far more updates than needed
         if (!this.isWet && inWater(world, tilePos)) {
+            world.setBlockTypeNotify(tilePos, Blocks.SPONGE_WET);
             TilePos queryPos = new TilePos();
-            for (queryPos.x = tilePos.x() - radius; queryPos.x <= tilePos.x() + radius; queryPos.x++) {
-                for (queryPos.y = tilePos.y() - radius; queryPos.y <= tilePos.y() + radius; queryPos.y++) {
-                    for (queryPos.z = tilePos.z() - radius; queryPos.z <= tilePos.z() + radius; queryPos.z++) {
+            for (queryPos.x = tilePos.x() - RADIUS; queryPos.x <= tilePos.x() + RADIUS; queryPos.x++) {
+                for (queryPos.y = tilePos.y() - RADIUS; queryPos.y <= tilePos.y() + RADIUS; queryPos.y++) {
+                    for (queryPos.z = tilePos.z() - RADIUS; queryPos.z <= tilePos.z() + RADIUS; queryPos.z++) {
                         if (world.getBlockType(queryPos).hasTag(BlockTags.IS_WATER)) {
                             world.setBlockTypeNotify(queryPos, Blocks.AIR);
                         }
                     }
                 }
             }
-            world.setBlockTypeNotify(tilePos, Blocks.SPONGE_WET);
         }
     }
 
+    private static final int RADIUS = 2;
 
     public boolean inWater(@NotNull World world, @NotNull TilePosc tilePos) {
         TilePos queryPos = new TilePos();
@@ -71,11 +72,10 @@ public class BlockLogicSponge extends BlockLogic {
     @Override
     public void onRemoved(@NotNull World world, @NotNull TilePosc tilePos, int data) {
         if (!this.isWet) {
-            byte radius = 2;
             TilePos queryPos = new TilePos(tilePos);
-            for (queryPos.x = tilePos.x() - radius; queryPos.x <= tilePos.x() + radius; queryPos.x++) {
-                for (queryPos.y = tilePos.y() - radius; queryPos.y <= tilePos.y() + radius; queryPos.y++) {
-                    for (queryPos.z = tilePos.z() - radius; queryPos.z <= tilePos.z() + radius; queryPos.z++) {
+            for (queryPos.x = tilePos.x() - RADIUS; queryPos.x <= tilePos.x() + RADIUS; queryPos.x++) {
+                for (queryPos.y = tilePos.y() - RADIUS; queryPos.y <= tilePos.y() + RADIUS; queryPos.y++) {
+                    for (queryPos.z = tilePos.z() - RADIUS; queryPos.z <= tilePos.z() + RADIUS; queryPos.z++) {
                         world.notifyBlocksOfNeighborChange(queryPos, world.getBlockType(queryPos));
                     }
 
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicTrommel.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicTrommel.java
index 37191017c..b48bc5088 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicTrommel.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicTrommel.java
@@ -18,10 +18,11 @@ import org.slf4j.Logger;
 
 import java.util.Random;
 
+// TODO refactor reuse
+
 public class BlockLogicTrommel extends BlockLogicRotatable
 {
     private static final Logger LOGGER = LogUtils.getLogger();
-    public static boolean keepTrommelInventory = false;
     private final boolean isActive;
 
 	public BlockLogicTrommel(@NotNull Block<?> block, @NotNull Material material, boolean isActive)
@@ -62,9 +63,7 @@ public class BlockLogicTrommel extends BlockLogicRotatable
     
     public static void updateTrommelBlockState(@NotNull World world, @NotNull TilePos tilepos, boolean lit)
     {
-        int data = world.getBlockData(tilepos);
-        TileEntity tileEntity = world.getTileEntity(tilepos);
-        if(tileEntity == null) {
+        if(!(world.getTileEntity(tilepos) instanceof TileEntityTrommel)) {
             String msg = "Trommel is missing Tile Entity at " + tilepos + ", block will be removed!";
             if(Global.BUILD_CHANNEL.isUnstableBuild()) {
                 throw new RuntimeException(msg);
@@ -73,17 +72,9 @@ public class BlockLogicTrommel extends BlockLogicRotatable
             LOGGER.warn(msg);
             return;
         }
-        keepTrommelInventory = true;
-        if(lit)
-        {
-            world.setBlockTypeNotify(tilepos, Blocks.TROMMEL_ACTIVE);
-        } else
-        {
-            world.setBlockTypeNotify(tilepos, Blocks.TROMMEL_IDLE);
-        }
-        keepTrommelInventory = false;
-        world.setBlockDataNotify(tilepos, data);
-        tileEntity.validate();
-        world.setTileEntity(tilepos, tileEntity);
+        final int data = world.getBlockData(tilepos);
+        final var block = (lit) ? Blocks.TROMMEL_ACTIVE : Blocks.TROMMEL_IDLE;
+        world.setBlockTypeDataRaw(tilepos, block, data);
+        world.notifyBlockChange(tilepos, block);
     }
 }
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 df435d221..b7a2a0605 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
@@ -15,6 +15,8 @@ import net.minecraft.core.util.helper.Side;
 import net.minecraft.core.world.ICarriable;
 import net.minecraft.core.world.World;
 import net.minecraft.core.world.pos.TilePos;
+import net.minecraft.core.world.pos.TilePosc;
+
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
@@ -133,6 +135,7 @@ public abstract class TileEntity implements ICarriable
 
     }
 
+    // TODO TilePos refactor
     public void dropContents(World world, int x, int y, int z) {
 
     }
@@ -185,13 +188,7 @@ public abstract class TileEntity implements ICarriable
         carriedBlock.block().dropBlockWithCause(world, EnumDropCause.WORLD, holderX, holderY, holderZ, carriedBlock.metadata, this, null); // TODO this might be scuffed since the position won't have correlated to a real block
     }
 
-    @Override
-    public boolean canBeCarried(World world, Entity potentialHolder) {
-        return false;
-    }
-
-    @Override
-    public ICarriable pickup(World world, Entity holder) {
+    protected @NotNull ICarriable pickup(@NotNull World world, @NotNull Entity holder, @NotNull TilePosc tilePos_) {
         Block<?> currentBlock = world.getBlock(tilePos.x, tilePos.y, tilePos.z);
         int currentMeta = world.getBlockMetadata(tilePos.x, tilePos.y, tilePos.z);
         world.removeBlockTileEntity(tilePos.x, tilePos.y, tilePos.z);
diff --git a/game/core/src/main/java/net/minecraft/core/block/entity/TileEntityActivator.java b/game/core/src/main/java/net/minecraft/core/block/entity/TileEntityActivator.java
index 247dc4f2f..374e034f4 100644
--- a/game/core/src/main/java/net/minecraft/core/block/entity/TileEntityActivator.java
+++ b/game/core/src/main/java/net/minecraft/core/block/entity/TileEntityActivator.java
@@ -11,13 +11,17 @@ import net.minecraft.core.entity.player.Player;
 import net.minecraft.core.item.ItemStack;
 import net.minecraft.core.player.inventory.container.Container;
 import net.minecraft.core.util.helper.Direction;
+import net.minecraft.core.world.ICarriable;
+import net.minecraft.core.world.ICarrySource;
 import net.minecraft.core.world.World;
+import net.minecraft.core.world.pos.TilePosc;
+
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
 import java.util.Random;
 
-public class TileEntityActivator extends TileEntity implements Container {
+public class TileEntityActivator extends TileEntity implements Container, ICarrySource {
     public static final int CONTAINER_SIZE = 9;
     private ItemStack[] dispenserContents;
     private final Random random;
@@ -228,10 +232,11 @@ public class TileEntityActivator extends TileEntity implements Container {
     }
 
     @Override
-    public boolean canBeCarried(World world, Entity potentialHolder) {
-        return true;
+    public @NotNull ICarriable pickup(@NotNull World world, @NotNull Entity holder, @NotNull TilePosc tilePos_) {
+        return super.pickup(world, holder, tilePos_);
     }
 
+
     @Override
     public CarriedBlock getCarriedEntry(World world, Entity holder, Block<?> currentBlock, int currentMeta) {
         return super.getCarriedEntry(world, holder, currentBlock, BlockLogicRotatable.setDirection(currentMeta, Direction.NORTH) /*Reset rotation, primarily to make it render in hand properly*/);
diff --git a/game/core/src/main/java/net/minecraft/core/block/entity/TileEntityBasket.java b/game/core/src/main/java/net/minecraft/core/block/entity/TileEntityBasket.java
index 67bee6f83..67f4f074d 100644
--- a/game/core/src/main/java/net/minecraft/core/block/entity/TileEntityBasket.java
+++ b/game/core/src/main/java/net/minecraft/core/block/entity/TileEntityBasket.java
@@ -8,21 +8,24 @@ import net.minecraft.core.block.Blocks;
 import net.minecraft.core.entity.Entity;
 import net.minecraft.core.entity.EntityItem;
 import net.minecraft.core.entity.player.Player;
-import net.minecraft.core.world.save.conversion.ItemConverters;
 import net.minecraft.core.item.Item;
 import net.minecraft.core.item.ItemStack;
 import com.mojang.nbt.tags.CompoundTag;
 import com.mojang.nbt.tags.ListTag;
 import net.minecraft.core.net.packet.Packet;
 import net.minecraft.core.net.packet.PacketTileEntityData;
+import net.minecraft.core.world.ICarriable;
+import net.minecraft.core.world.ICarrySource;
 import net.minecraft.core.world.World;
+import net.minecraft.core.world.pos.TilePosc;
+
 import org.jetbrains.annotations.NotNull;
 import org.joml.primitives.AABBd;
 import org.joml.primitives.AABBdc;
 
 import static net.minecraft.core.world.save.conversion.ItemConverters.legacyBucketConverter;
 
-public class TileEntityBasket extends TileEntity {
+public class TileEntityBasket extends TileEntity implements ICarrySource {
     private int numUnitsInside = 0;
     private final Object2IntMap<BasketEntry> contents = new Object2IntArrayMap<>();
 
@@ -239,8 +242,8 @@ public class TileEntityBasket extends TileEntity {
     }
 
     @Override
-    public boolean canBeCarried(final World world, final Entity potentialHolder) {
-        return true;
+    public @NotNull ICarriable pickup(@NotNull World world, @NotNull Entity holder, @NotNull TilePosc tilePos_) {
+        return super.pickup(world, holder, tilePos_);
     }
 
     private static final class BasketEntry {
diff --git a/game/core/src/main/java/net/minecraft/core/block/entity/TileEntityChest.java b/game/core/src/main/java/net/minecraft/core/block/entity/TileEntityChest.java
index 3102e18bd..4b05280a8 100644
--- a/game/core/src/main/java/net/minecraft/core/block/entity/TileEntityChest.java
+++ b/game/core/src/main/java/net/minecraft/core/block/entity/TileEntityChest.java
@@ -13,13 +13,17 @@ import com.mojang.nbt.tags.CompoundTag;
 import com.mojang.nbt.tags.ListTag;
 import net.minecraft.core.player.inventory.InventorySorter;
 import net.minecraft.core.util.helper.Direction;
+import net.minecraft.core.world.ICarriable;
+import net.minecraft.core.world.ICarrySource;
 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.slf4j.Logger;
 
 public class TileEntityChest extends TileEntity
-    implements Container
+    implements Container, ICarrySource
 {
     private static final Logger LOGGER = LogUtils.getLogger();
 
@@ -152,8 +156,8 @@ public class TileEntityChest extends TileEntity
     }
 
     @Override
-    public boolean canBeCarried(World world, Entity potentialHolder) {
-        return true;
+    public @NotNull ICarriable pickup(@NotNull World world, @NotNull Entity holder, @NotNull TilePosc tilePos_) {
+        return super.pickup(world, holder, tilePos_);
     }
 
     @Override
diff --git a/game/core/src/main/java/net/minecraft/core/block/entity/TileEntityDispenser.java b/game/core/src/main/java/net/minecraft/core/block/entity/TileEntityDispenser.java
index ffb16000b..91bdcd264 100644
--- a/game/core/src/main/java/net/minecraft/core/block/entity/TileEntityDispenser.java
+++ b/game/core/src/main/java/net/minecraft/core/block/entity/TileEntityDispenser.java
@@ -12,14 +12,18 @@ import net.minecraft.core.player.inventory.container.Container;
 import com.mojang.nbt.tags.CompoundTag;
 import com.mojang.nbt.tags.ListTag;
 import net.minecraft.core.util.helper.Direction;
+import net.minecraft.core.world.ICarriable;
+import net.minecraft.core.world.ICarrySource;
 import net.minecraft.core.world.World;
+import net.minecraft.core.world.pos.TilePosc;
+
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
 import java.util.Random;
 
 public class TileEntityDispenser extends TileEntity
-    implements Container
+    implements Container, ICarrySource
 {
 
     public TileEntityDispenser()
@@ -215,8 +219,8 @@ public class TileEntityDispenser extends TileEntity
     }
 
     @Override
-    public boolean canBeCarried(World world, Entity potentialHolder) {
-        return true;
+    public @NotNull ICarriable pickup(@NotNull World world, @NotNull Entity holder, @NotNull TilePosc tilePos_) {
+        return super.pickup(world, holder, tilePos_);
     }
 
     @Override
diff --git a/game/core/src/main/java/net/minecraft/core/block/entity/TileEntityFurnace.java b/game/core/src/main/java/net/minecraft/core/block/entity/TileEntityFurnace.java
index 8e56b16a6..006d87b58 100644
--- a/game/core/src/main/java/net/minecraft/core/block/entity/TileEntityFurnace.java
+++ b/game/core/src/main/java/net/minecraft/core/block/entity/TileEntityFurnace.java
@@ -13,13 +13,15 @@ import net.minecraft.core.entity.Entity;
 import net.minecraft.core.entity.EntityItem;
 import net.minecraft.core.entity.player.Player;
 import net.minecraft.core.item.ItemBucket;
-import net.minecraft.core.item.Items;
 import net.minecraft.core.item.ItemStack;
 import net.minecraft.core.net.packet.PacketTileEntityData;
 import net.minecraft.core.net.packet.Packet;
 import net.minecraft.core.player.inventory.container.Container;
 import net.minecraft.core.util.helper.Side;
+import net.minecraft.core.world.ICarriable;
+import net.minecraft.core.world.ICarrySource;
 import net.minecraft.core.world.World;
+import net.minecraft.core.world.pos.TilePosc;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
@@ -27,7 +29,7 @@ import java.util.List;
 import java.util.Random;
 
 public class TileEntityFurnace extends TileEntity
-        implements Container
+        implements Container, ICarrySource
 {
     public static final int SLOT_INGREDIENT = 0;
     public static final int SLOT_FUEL = 1;
@@ -333,40 +335,36 @@ public class TileEntityFurnace extends TileEntity
     @Override
     public void dropContents(World world, int x, int y, int z) {
         super.dropContents(world, x, y, z);
-        if(!BlockLogicFurnace.keepFurnaceInventory)
+        label0:
+        for(int l = 0; l < this.getContainerSize(); l++)
         {
-            label0:
-            for(int l = 0; l < this.getContainerSize(); l++)
+            ItemStack itemstack = this.getItem(l);
+            if(itemstack == null)
             {
-                ItemStack itemstack = this.getItem(l);
-                if(itemstack == null)
+                continue;
+            }
+            float f = random.nextFloat() * 0.8F + 0.1F;
+            float f1 = random.nextFloat() * 0.8F + 0.1F;
+            float f2 = random.nextFloat() * 0.8F + 0.1F;
+            do
+            {
+                if(itemstack.stackSize <= 0)
                 {
-                    continue;
+                    continue label0;
                 }
-                float f = random.nextFloat() * 0.8F + 0.1F;
-                float f1 = random.nextFloat() * 0.8F + 0.1F;
-                float f2 = random.nextFloat() * 0.8F + 0.1F;
-                do
+                int i1 = random.nextInt(21) + 10;
+                if(i1 > itemstack.stackSize)
                 {
-                    if(itemstack.stackSize <= 0)
-                    {
-                        continue label0;
-                    }
-                    int i1 = random.nextInt(21) + 10;
-                    if(i1 > itemstack.stackSize)
-                    {
-                        i1 = itemstack.stackSize;
-                    }
-                    itemstack.stackSize -= i1;
-                    EntityItem entityItem = new EntityItem(world, (float) x + f, (float) y + f1, (float) z + f2, new ItemStack(itemstack.itemID, i1, itemstack.getMetadata()));
-                    float f3 = 0.05F;
-                    entityItem.xd = (float)random.nextGaussian() * f3;
-                    entityItem.yd = (float)random.nextGaussian() * f3 + 0.2F;
-                    entityItem.zd = (float)random.nextGaussian() * f3;
-                    world.entityJoinedWorld(entityItem);
-                } while(true);
-            }
-
+                    i1 = itemstack.stackSize;
+                }
+                itemstack.stackSize -= i1;
+                EntityItem entityItem = new EntityItem(world, (float) x + f, (float) y + f1, (float) z + f2, new ItemStack(itemstack.itemID, i1, itemstack.getMetadata()));
+                float f3 = 0.05F;
+                entityItem.xd = (float)random.nextGaussian() * f3;
+                entityItem.yd = (float)random.nextGaussian() * f3 + 0.2F;
+                entityItem.zd = (float)random.nextGaussian() * f3;
+                world.entityJoinedWorld(entityItem);
+            } while(true);
         }
     }
 
@@ -398,8 +396,8 @@ public class TileEntityFurnace extends TileEntity
     }
 
     @Override
-    public boolean canBeCarried(World world, Entity potentialHolder) {
-        return true;
+    public @NotNull ICarriable pickup(@NotNull World world, @NotNull Entity holder, @NotNull TilePosc tilePos_) {
+        return super.pickup(world, holder, tilePos_);
     }
 
     @Override
diff --git a/game/core/src/main/java/net/minecraft/core/block/entity/TileEntityFurnaceBlast.java b/game/core/src/main/java/net/minecraft/core/block/entity/TileEntityFurnaceBlast.java
index 5414b53e6..61cfb1c7b 100644
--- a/game/core/src/main/java/net/minecraft/core/block/entity/TileEntityFurnaceBlast.java
+++ b/game/core/src/main/java/net/minecraft/core/block/entity/TileEntityFurnaceBlast.java
@@ -17,8 +17,12 @@ import net.minecraft.core.net.packet.Packet;
 import net.minecraft.core.net.packet.PacketTileEntityData;
 import net.minecraft.core.player.inventory.container.Container;
 import net.minecraft.core.util.helper.Side;
+import net.minecraft.core.world.ICarriable;
+import net.minecraft.core.world.ICarrySource;
 import net.minecraft.core.world.World;
 import net.minecraft.core.world.pos.TilePos;
+import net.minecraft.core.world.pos.TilePosc;
+
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
@@ -26,7 +30,7 @@ import java.util.List;
 import java.util.Random;
 
 public class TileEntityFurnaceBlast extends TileEntity
-    implements Container
+    implements Container, ICarrySource
 {
     public static final int SLOT_LEFT = 0;
     public static final int SLOT_RIGHT = 1;
@@ -295,32 +299,30 @@ public class TileEntityFurnaceBlast extends TileEntity
     @Override
     public void dropContents(final World world, final int x, final int y, final int z) {
         super.dropContents(world, x, y, z);
-        if (!BlockLogicFurnaceBlast.keepFurnaceInventory) {
-slotLoop:   for (int slot = 0; slot < this.getContainerSize(); slot++) {
-                final ItemStack item = this.getItem(slot);
-                if (item == null) {
-                    continue;
-                }
-                final float rx = this.random.nextFloat() * 0.8F + 0.1F;
-                final float ry = this.random.nextFloat() * 0.8F + 0.1F;
-                final float rz = this.random.nextFloat() * 0.8F + 0.1F;
-                do {
-                    if (item.stackSize <= 0) {
-                        continue slotLoop;
-                    }
-                    int stackSize = this.random.nextInt(21) + 10;
-                    if (stackSize > item.stackSize) {
-                        stackSize = item.stackSize;
-                    }
-                    item.stackSize -= stackSize;
-                    final EntityItem entityItem = new EntityItem(world, (float) x + rx, (float) y + ry, (float) z + rz, new ItemStack(item.itemID, stackSize, item.getMetadata()));
-                    final float velocityScale = 0.05F;
-                    entityItem.xd = (float) this.random.nextGaussian() * velocityScale;
-                    entityItem.yd = (float) this.random.nextGaussian() * velocityScale + 0.2F;
-                    entityItem.zd = (float) this.random.nextGaussian() * velocityScale;
-                    world.entityJoinedWorld(entityItem);
-                } while (true);
+        slotLoop: for (int slot = 0; slot < this.getContainerSize(); slot++) {
+            final ItemStack item = this.getItem(slot);
+            if (item == null) {
+                continue;
             }
+            final float rx = this.random.nextFloat() * 0.8F + 0.1F;
+            final float ry = this.random.nextFloat() * 0.8F + 0.1F;
+            final float rz = this.random.nextFloat() * 0.8F + 0.1F;
+            do {
+                if (item.stackSize <= 0) {
+                    continue slotLoop;
+                }
+                int stackSize = this.random.nextInt(21) + 10;
+                if (stackSize > item.stackSize) {
+                    stackSize = item.stackSize;
+                }
+                item.stackSize -= stackSize;
+                final EntityItem entityItem = new EntityItem(world, (float) x + rx, (float) y + ry, (float) z + rz, new ItemStack(item.itemID, stackSize, item.getMetadata()));
+                final float velocityScale = 0.05F;
+                entityItem.xd = (float) this.random.nextGaussian() * velocityScale;
+                entityItem.yd = (float) this.random.nextGaussian() * velocityScale + 0.2F;
+                entityItem.zd = (float) this.random.nextGaussian() * velocityScale;
+                world.entityJoinedWorld(entityItem);
+            } while (true);
         }
     }
 
@@ -352,8 +354,8 @@ slotLoop:   for (int slot = 0; slot < this.getContainerSize(); slot++) {
     }
 
     @Override
-    public boolean canBeCarried(final World world, final Entity potentialHolder) {
-        return true;
+    public @NotNull ICarriable pickup(@NotNull World world, @NotNull Entity holder, @NotNull TilePosc tilePos_) {
+        return super.pickup(world, holder, tilePos_);
     }
 
     @Override
diff --git a/game/core/src/main/java/net/minecraft/core/block/entity/TileEntityJukebox.java b/game/core/src/main/java/net/minecraft/core/block/entity/TileEntityJukebox.java
index 8151eceaf..1e6c941c6 100644
--- a/game/core/src/main/java/net/minecraft/core/block/entity/TileEntityJukebox.java
+++ b/game/core/src/main/java/net/minecraft/core/block/entity/TileEntityJukebox.java
@@ -39,19 +39,25 @@ public class TileEntityJukebox extends TileEntity {
 
     @Override
     public void dropContents(World world, int x, int y, int z) {
-        if (record != 0) {
-            float f = 0.7F;
-            double d = (double) (world.rand.nextFloat() * f) + (double) (1.0F - f) * 0.5D;
-            double d1 = (double) (world.rand.nextFloat() * f) + (double) (1.0F - f) * 0.2D + 0.6D;
-            double d2 = (double) (world.rand.nextFloat() * f) + (double) (1.0F - f) * 0.5D;
-            EntityItem item = new EntityItem(world, (double) x + d, (double) y + d1, (double) z + d2, new ItemStack(record, 1, 0));
-            item.pickupDelay = 10;
-            world.entityJoinedWorld(item);
-
-            world.playBlockEvent(LevelListener.EVENT_JUKEBOX_TOGGLE, x, y, z, 0);
-            world.playRecord(null, null, x, y, z);
-            record = 0;
-            setChanged();
+        if (this.record == 0) return;
+        final float scale = 0.7F;
+        final double dx,dy,dz;
+        dx = (double) (world.rand.nextFloat() * scale) + (double) (1.0F - scale) * 0.5D;
+        dy = (double) (world.rand.nextFloat() * scale) + (double) (1.0F - scale) * 0.2D + 0.6D;
+        dz = (double) (world.rand.nextFloat() * scale) + (double) (1.0F - scale) * 0.5D;
+        final var item = new EntityItem(
+            world,
+            (double) x + dx, (double) y + dy, (double) z + dz,
+            new ItemStack(this.record, 1, 0)
+        );
+        item.pickupDelay = 10;
+        world.entityJoinedWorld(item);
+        world.playBlockEvent(LevelListener.EVENT_JUKEBOX_TOGGLE, x, y, z, 0);
+        world.playRecord(null, null, x, y, z);
+        this.record = 0;
+        setChanged();
+        if (this.worldObj != null) {
+            // fix falling block mutating data of other blocks
             world.setBlockMetadataWithNotify(x, y, z, 0);
         }
     }
diff --git a/game/core/src/main/java/net/minecraft/core/block/entity/TileEntitySensor.java b/game/core/src/main/java/net/minecraft/core/block/entity/TileEntitySensor.java
index c9fa51b5b..b22191f02 100644
--- a/game/core/src/main/java/net/minecraft/core/block/entity/TileEntitySensor.java
+++ b/game/core/src/main/java/net/minecraft/core/block/entity/TileEntitySensor.java
@@ -150,16 +150,11 @@ public class TileEntitySensor extends TileEntity {
                 this.carriedBlock.blockId = Blocks.MOTION_SENSOR_IDLE.id();
             }
         } else {
-            int meta = world.getBlockMetadata(this.tilePos.x, this.tilePos.y, this.tilePos.z);
-            int blockId;
-            if (active) {
-                blockId = Blocks.MOTION_SENSOR_ACTIVE.id();
-            } else {
-                blockId = Blocks.MOTION_SENSOR_IDLE.id();
-            }
-            world.setBlock(this.tilePos.x, this.tilePos.y, this.tilePos.z, blockId);
-            world.setBlockMetadata(this.tilePos.x, this.tilePos.y, this.tilePos.z, meta);
-            world.notifyBlockChange(this.tilePos.x, this.tilePos.y, this.tilePos.z, blockId);
+            int meta = world.getBlockData(this.tilePos);
+            final var block = (active)
+                ? Blocks.MOTION_SENSOR_ACTIVE
+                : Blocks.MOTION_SENSOR_IDLE;
+            world.setBlockTypeDataNotify(this.tilePos, block, meta);
         }
     }
 }
diff --git a/game/core/src/main/java/net/minecraft/core/block/entity/TileEntityTrommel.java b/game/core/src/main/java/net/minecraft/core/block/entity/TileEntityTrommel.java
index 2c305f854..21f4555e3 100644
--- a/game/core/src/main/java/net/minecraft/core/block/entity/TileEntityTrommel.java
+++ b/game/core/src/main/java/net/minecraft/core/block/entity/TileEntityTrommel.java
@@ -19,13 +19,15 @@ import net.minecraft.core.entity.monster.MobSlime;
 import net.minecraft.core.entity.player.Player;
 import net.minecraft.core.item.Item;
 import net.minecraft.core.item.ItemBucket;
-import net.minecraft.core.item.Items;
 import net.minecraft.core.item.ItemStack;
 import net.minecraft.core.player.inventory.container.Container;
 import net.minecraft.core.util.helper.Direction;
 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.ICarriable;
+import net.minecraft.core.world.ICarrySource;
+import net.minecraft.core.world.pos.TilePosc;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 import org.slf4j.Logger;
@@ -36,7 +38,7 @@ import java.util.Map;
 import java.util.Random;
 
 public class TileEntityTrommel extends TileEntity
-        implements Container
+        implements Container, ICarrySource
 {
     private static final Logger LOGGER = LogUtils.getLogger();
 
@@ -432,37 +434,34 @@ public class TileEntityTrommel extends TileEntity
     @Override
     public void dropContents(World world, int x, int y, int z) {
         super.dropContents(world, x, y, z);
-        if (!BlockLogicTrommel.keepTrommelInventory)
+        if(world.getTileEntity(x, y, z) != null)
         {
-            if(world.getTileEntity(x, y, z) != null)
+            label0:
+            for (int l = 0; l < getContainerSize(); l++)
             {
-                label0:
-                for (int l = 0; l < getContainerSize(); l++)
-                {
-                    ItemStack itemstack = getItem(l);
-                    if (itemstack == null) {
-                        continue;
-                    }
-                    float f = this.random.nextFloat() * 0.8F + 0.1F;
-                    float f1 = this.random.nextFloat() * 0.8F + 0.1F;
-                    float f2 = this.random.nextFloat() * 0.8F + 0.1F;
-                    do {
-                        if (itemstack.stackSize <= 0) {
-                            continue label0;
-                        }
-                        int i1 = this.random.nextInt(21) + 10;
-                        if (i1 > itemstack.stackSize) {
-                            i1 = itemstack.stackSize;
-                        }
-                        itemstack.stackSize -= i1;
-                        EntityItem item = new EntityItem(world, (float) x + f, (float) y + f1, (float) z + f2, new ItemStack(itemstack.itemID, i1, itemstack.getMetadata()));
-                        float f3 = 0.05F;
-                        item.xd = (float) this.random.nextGaussian() * f3;
-                        item.yd = (float) this.random.nextGaussian() * f3 + 0.2F;
-                        item.zd = (float) this.random.nextGaussian() * f3;
-                        world.entityJoinedWorld(item);
-                    } while (true);
+                ItemStack itemstack = getItem(l);
+                if (itemstack == null) {
+                    continue;
                 }
+                float f = this.random.nextFloat() * 0.8F + 0.1F;
+                float f1 = this.random.nextFloat() * 0.8F + 0.1F;
+                float f2 = this.random.nextFloat() * 0.8F + 0.1F;
+                do {
+                    if (itemstack.stackSize <= 0) {
+                        continue label0;
+                    }
+                    int i1 = this.random.nextInt(21) + 10;
+                    if (i1 > itemstack.stackSize) {
+                        i1 = itemstack.stackSize;
+                    }
+                    itemstack.stackSize -= i1;
+                    EntityItem item = new EntityItem(world, (float) x + f, (float) y + f1, (float) z + f2, new ItemStack(itemstack.itemID, i1, itemstack.getMetadata()));
+                    float f3 = 0.05F;
+                    item.xd = (float) this.random.nextGaussian() * f3;
+                    item.yd = (float) this.random.nextGaussian() * f3 + 0.2F;
+                    item.zd = (float) this.random.nextGaussian() * f3;
+                    world.entityJoinedWorld(item);
+                } while (true);
             }
         }
     }
@@ -573,8 +572,8 @@ public class TileEntityTrommel extends TileEntity
     }
 
     @Override
-    public boolean canBeCarried(World world, Entity potentialHolder) {
-        return true;
+    public @NotNull ICarriable pickup(@NotNull World world, @NotNull Entity holder, @NotNull TilePosc tilePos_) {
+        return super.pickup(world, holder, tilePos_);
     }
 
     @Override
diff --git a/game/core/src/main/java/net/minecraft/core/block/motion/CarriedBlock.java b/game/core/src/main/java/net/minecraft/core/block/motion/CarriedBlock.java
index 316bef176..88391617f 100644
--- a/game/core/src/main/java/net/minecraft/core/block/motion/CarriedBlock.java
+++ b/game/core/src/main/java/net/minecraft/core/block/motion/CarriedBlock.java
@@ -13,10 +13,12 @@ import net.minecraft.core.util.helper.MathHelper;
 import net.minecraft.core.util.helper.Side;
 import net.minecraft.core.world.ICarriable;
 import net.minecraft.core.world.World;
+import net.minecraft.core.world.pos.TilePosc;
+
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
-public class CarriedBlock implements ICarriable {
+public class CarriedBlock implements ICarriable, ICarriedBlock {
     public int blockId; // TODO replace with direct block ref
     public int metadata;
     public @Nullable TileEntity entity;
@@ -64,12 +66,13 @@ public class CarriedBlock implements ICarriable {
 
             Block<?> currentBlock = world.getBlock(x, y, z);
             if (currentBlock != null && !currentBlock.hasTag(BlockTags.PLACE_OVERWRITES)) return false;
-            world.setBlockAndMetadataWithNotify(x, y, z, blockId, metadata);
+            world.setBlockAndMetadata(x, y, z, blockId, metadata);
 
             Block<?> b = world.getBlock(x, y, z);
             if (b != null && holder instanceof Mob) {
                 b.onBlockPlacedByMob(world, x, y, z, side, (Mob) holder, xPlaced, yPlaced);
             }
+            world.notifyBlockChange(x, y, z, blockId);
             return true;
         }
     }
@@ -96,16 +99,6 @@ public class CarriedBlock implements ICarriable {
         }
     }
 
-    @Override
-    public boolean canBeCarried(World world, Entity potentialHolder) {
-        return true;
-    }
-
-    @Override
-    public ICarriable pickup(World world, Entity holder) {
-        return this;
-    }
-
     @Override
     public void writeToNBT(CompoundTag tag) {
         if (entity != null) {
@@ -135,4 +128,14 @@ public class CarriedBlock implements ICarriable {
         carriedBlock.readFromNBT(tag);
         return carriedBlock;
     }
+
+    @Override
+    public int metadata() {
+        return metadata;
+    }
+
+    @Override
+    public @Nullable TileEntity entity() {
+        return entity;
+    }
 }
diff --git a/game/core/src/main/java/net/minecraft/core/block/motion/ICarriedBlock.java b/game/core/src/main/java/net/minecraft/core/block/motion/ICarriedBlock.java
new file mode 100644
index 000000000..15551993c
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/block/motion/ICarriedBlock.java
@@ -0,0 +1,15 @@
+package net.minecraft.core.block.motion;
+
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import net.minecraft.core.block.Block;
+import net.minecraft.core.block.entity.TileEntity;
+
+public interface ICarriedBlock {
+    @NotNull Block<?> block();
+    int metadata();
+    default @Nullable TileEntity entity() {
+        return null;
+    }
+}
diff --git a/game/core/src/main/java/net/minecraft/core/block/piston/TileEntityMovingPistonBlock.java b/game/core/src/main/java/net/minecraft/core/block/piston/TileEntityMovingPistonBlock.java
index c4dfde503..0b7d5745b 100644
--- a/game/core/src/main/java/net/minecraft/core/block/piston/TileEntityMovingPistonBlock.java
+++ b/game/core/src/main/java/net/minecraft/core/block/piston/TileEntityMovingPistonBlock.java
@@ -216,10 +216,4 @@ public class TileEntityMovingPistonBlock extends TileEntity
         compoundTag.putFloat("progress", this.progressO);
         compoundTag.putBoolean("extending", this.extending);
     }
-
-    @Override
-    public boolean canBeCarried(World world, Entity potentialHolder) {
-        // Explicitly defined as false since the MovingPistonBlock should *never* be carried
-        return false;
-    }
 }
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 6a57f5029..b5ba3bd35 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
@@ -1141,7 +1141,7 @@ public abstract class Player
         setSize(0.2F, 0.2F);
         if (this.world.isBlockLoaded(x, y, z)) {
             int meta = this.world.getBlockMetadata(x, y, z);
-            int dir = BlockLogicBed.getDirection(meta);
+            int dir = BlockLogicBed.DIRECTION.get(meta);
             float xOff = 0.5F;
             float zOff = 0.5F;
 
@@ -1197,7 +1197,8 @@ public abstract class Player
         TilePos bedPos = this.bedTilePos;
         Block<?> b;
         if (bedPos != null && (b = this.world.getBlockType(bedPos)) != null && b.getLogic() instanceof BlockLogicBed) {
-            BlockLogicBed.setBedOccupied(this.world, bedPos, false);
+            final var data = this.world.getBlockData(bedPos);
+            this.world.setBlockData(bedPos, BlockLogicBed.IS_OCCUPIED.set(data, 0));
             TilePos emptyPos = BlockLogicBed.getNearestEmptyTilePos(this.world, bedPos, 0);
             if (emptyPos == null) {
                 emptyPos = bedPos.up(new TilePos());
@@ -1236,9 +1237,8 @@ public abstract class Player
 
     public float getBedOrientationInDegrees() {
         if (this.bedTilePos != null) {
-            int i = this.world.getBlockMetadata(this.bedTilePos.x, this.bedTilePos.y, this.bedTilePos.z);
-            int j = BlockLogicBed.getDirection(i);
-            switch (j) {
+            int data = this.world.getBlockData(this.bedTilePos);
+            switch (BlockLogicBed.DIRECTION.get(data)) {
                 case 0: // '\0'
                     return 90F;
 
diff --git a/game/core/src/main/java/net/minecraft/core/item/ItemBed.java b/game/core/src/main/java/net/minecraft/core/item/ItemBed.java
index 22efccf38..23cdecf3c 100644
--- a/game/core/src/main/java/net/minecraft/core/item/ItemBed.java
+++ b/game/core/src/main/java/net/minecraft/core/item/ItemBed.java
@@ -20,22 +20,38 @@ public class ItemBed extends Item {
 
     @Override
     public boolean onUseOnBlock(@NotNull ItemStack selfStack, @NotNull World world, @Nullable Player player, @NotNull TilePosc blockPos, @NotNull Side side, double xHit, double yHit) {
-        TilePosc pos = blockPos;
-        if (!world.canPlaceInsideBlock(pos)) {
-            pos = pos.add(side.getDirection(), new TilePos());
+        if (selfStack.stackSize <= 0) return false;
+        TilePosc pos1 = blockPos;
+        if (!world.canPlaceInsideBlock(pos1)) {
+            pos1 = new TilePos(pos1).add(side.getDirection());
         }
         Direction dir = player == null ? Direction.NORTH : player.getHorizontalPlacementDirection(null).getOpposite();
-        TilePos pos2 = pos.sub(dir, new TilePos());
-        if (world.isAirBlock(pos) && world.isAirBlock(pos2) && world.canPlaceOnSurfaceOfBlock(pos.down(new TilePos())) && world.canPlaceOnSurfaceOfBlock(pos2.down(new TilePos()))) {
-            if (world.canBlockIdBePlacedAt(Blocks.BED.id(), pos, false, side) && selfStack.consumeItem(player)) {
-                world.playBlockSoundEffect(player, pos.x() + 0.5F, pos.y() + 0.5F, pos.z() + 0.5F, Blocks.BED, EnumBlockSoundEffectType.PLACE);
-                world.setBlockTypeData(pos, Blocks.BED, dir.getHorizontalIndex());
-                world.setBlockTypeData(pos2, Blocks.BED, dir.getHorizontalIndex() | BlockLogicBed.MASK_IS_HEAD);
-                world.notifyBlockChange(pos, Blocks.BED);
-                world.notifyBlockChange(pos2, Blocks.BED);
-                return true;
-            }
+        final TilePos pos2 = new TilePos(pos1).sub(dir);
+        
+        if (!world.canPlaceInsideBlock(pos1) || !world.canPlaceInsideBlock(pos2)) return false;
+        
+        final TilePos qpos = new TilePos();
+        if (!world.canPlaceOnSurfaceOfBlock(qpos.set(pos1).down())) return false;
+        if (!world.canPlaceOnSurfaceOfBlock(qpos.set(pos2).down())) return false;
+        
+        if (!world.canBlockIdBePlacedAt(Blocks.BED.id(), pos1, false, side)) return false;
+        // if (!world.canBlockIdBePlacedAt(Blocks.BED.id(), pos2, false, side)) return false;
+        
+        
+        final boolean isPlaced1 = 
+        world.setBlockTypeData(pos1, Blocks.BED, dir.getHorizontalIndex());
+        final boolean isPlaced2 = isPlaced1 &&
+        world.setBlockTypeData(pos2, Blocks.BED, dir.getHorizontalIndex() | BlockLogicBed.IS_HEAD.set(0, 1));
+        if (!isPlaced2) {
+            if (isPlaced1) world.setBlockType(pos1, Blocks.AIR);
+            return false;
         }
-        return false;
+        selfStack.consumeItem(player);
+
+        world.notifyBlockChange(pos1, Blocks.BED);
+        world.notifyBlockChange(pos2, Blocks.BED);
+
+        world.playBlockSoundEffect(player, pos1.x() + 0.5F, pos1.y() + 0.5F, pos1.z() + 0.5F, Blocks.BED, EnumBlockSoundEffectType.PLACE);
+        return true;
     }
 }
diff --git a/game/core/src/main/java/net/minecraft/core/item/ItemDoor.java b/game/core/src/main/java/net/minecraft/core/item/ItemDoor.java
index aa381176a..d6fb92c73 100644
--- a/game/core/src/main/java/net/minecraft/core/item/ItemDoor.java
+++ b/game/core/src/main/java/net/minecraft/core/item/ItemDoor.java
@@ -65,16 +65,17 @@ public class ItemDoor extends Item {
             return false;
         }
 
-        world.noNeighborUpdate = true;
-        world.setBlockTypeDataNotify(blockPos, this.doorBlockBottom, meta);
-        world.setBlockTypeDataNotify(blockPos.up(new TilePos()), this.doorBlockTop, meta);
-        world.noNeighborUpdate = false;
-        world.notifyBlocksOfNeighborChange(blockPos, this.doorBlockBottom);
-        world.notifyBlocksOfNeighborChange(blockPos.up(new TilePos()), this.doorBlockTop);
-        world.playBlockSoundEffect(player, blockPos.x() + 0.5F, blockPos.y() + 0.5F, blockPos.z() + 0.5F, this.doorBlockBottom, EnumBlockSoundEffectType.PLACE);
+        world.setBlockTypeData(blockPos, this.doorBlockBottom, meta);
+        world.setBlockTypeData(blockPos.up(new TilePos()), this.doorBlockTop, meta);
+        selfStack.consumeItem(player);
+
         this.doorBlockBottom.onPlacedByMob(world, blockPos, side, player, xHit, yHit);
         this.doorBlockTop.onPlacedByMob(world, blockPos.up(new TilePos()), side, player, xHit, yHit);
-        selfStack.consumeItem(player);
+
+        world.notifyBlockChange(blockPos, this.doorBlockBottom);
+        world.notifyBlockChange(blockPos.up(new TilePos()), this.doorBlockTop);
+
+        world.playBlockSoundEffect(player, blockPos.x() + 0.5F, blockPos.y() + 0.5F, blockPos.z() + 0.5F, this.doorBlockBottom, EnumBlockSoundEffectType.PLACE);
         return true;
     }
 
@@ -118,15 +119,16 @@ public class ItemDoor extends Item {
             return;
         }
 
-        world.noNeighborUpdate = true;
-        world.setBlockTypeDataNotify(blockPos, this.doorBlockBottom, meta);
-        world.setBlockTypeDataNotify(blockPos.up(new TilePos()), this.doorBlockTop, meta);
-        world.noNeighborUpdate = false;
-        world.notifyBlocksOfNeighborChange(blockPos, this.doorBlockBottom);
-        world.notifyBlocksOfNeighborChange(blockPos.up(new TilePos()), this.doorBlockTop);
-        world.playBlockSoundEffect(null, blockPos.x() + 0.5F, blockPos.y() + 0.5F, blockPos.z() + 0.5F, this.doorBlockBottom, EnumBlockSoundEffectType.PLACE);
+        world.setBlockTypeData(blockPos, this.doorBlockBottom, meta);
+        world.setBlockTypeData(blockPos.up(new TilePos()), this.doorBlockTop, meta);
+        selfStack.consumeItem(null);
+
         this.doorBlockBottom.onPlacedOnSide(world, blockPos, direction.getSide(), 0.5, 0.5);
         this.doorBlockTop.onPlacedOnSide(world, blockPos.up(new TilePos()), direction.getSide(), 0.5, 0.5);
-        selfStack.consumeItem(null);
+
+        world.notifyBlockChange(blockPos, this.doorBlockBottom);
+        world.notifyBlockChange(blockPos.up(new TilePos()), this.doorBlockTop);
+        
+        world.playBlockSoundEffect(null, blockPos.x() + 0.5F, blockPos.y() + 0.5F, blockPos.z() + 0.5F, this.doorBlockBottom, EnumBlockSoundEffectType.PLACE);
     }
 }
diff --git a/game/core/src/main/java/net/minecraft/core/item/ItemFlag.java b/game/core/src/main/java/net/minecraft/core/item/ItemFlag.java
index fb4726bc3..c00177e8f 100644
--- a/game/core/src/main/java/net/minecraft/core/item/ItemFlag.java
+++ b/game/core/src/main/java/net/minecraft/core/item/ItemFlag.java
@@ -67,7 +67,7 @@ public class ItemFlag extends Item {
         }
 
         world.playBlockSoundEffect(player, blockPos.x() + 0.5F, blockPos.y() + 0.5F, blockPos.z() + 0.5F, Blocks.FLAG, EnumBlockSoundEffectType.PLACE);
-        world.setBlockTypeNotify(blockPos, Blocks.FLAG);
+        world.setBlockType(blockPos, Blocks.FLAG);
 
         selfStack.consumeItem(player);
         if (world.getTileEntity(blockPos) instanceof TileEntityFlag flag) {
@@ -80,6 +80,7 @@ public class ItemFlag extends Item {
                 player.displayFlagEditorScreen(flag);
             }
         }
+        world.notifyBlockChange(blockPos, Blocks.FLAG);
         return true;
     }
 
diff --git a/game/core/src/main/java/net/minecraft/core/item/ItemPlaceable.java b/game/core/src/main/java/net/minecraft/core/item/ItemPlaceable.java
index bc5ba2bdb..bbe148998 100644
--- a/game/core/src/main/java/net/minecraft/core/item/ItemPlaceable.java
+++ b/game/core/src/main/java/net/minecraft/core/item/ItemPlaceable.java
@@ -38,7 +38,7 @@ public class ItemPlaceable extends Item {
             return false;
         }
         if (world.canBlockIdBePlacedAt(this.blockToPlace.id(), blockPos, false, side) && selfStack.consumeItem(player)) {
-            if (world.setBlockTypeNotify(blockPos, this.blockToPlace)) {
+            if (world.setBlockType(blockPos, this.blockToPlace)) {
                 if (player == null) {
                     this.blockToPlace.onPlacedOnSide(world, blockPos, side, xHit, yHit);
                 } else {
@@ -50,6 +50,10 @@ public class ItemPlaceable extends Item {
                         tileEntity.readAdditionalData(selfStack.getData().getCompound("tileEntityData"));
                     }
                 }
+                // notify self change after onPlacedByMob
+                // this should fix almost all bugs related to piston intercepting placement logic,
+                // with out change to individual onPlaced<_> logics.
+                world.notifyBlockChange(blockPos, this.blockToPlace);
                 world.playBlockSoundEffect(player, blockPos.x() + 0.5F, blockPos.y() + 0.5F, blockPos.z() + 0.5F, this.blockToPlace, EnumBlockSoundEffectType.PLACE);
                 return true;
             }
diff --git a/game/core/src/main/java/net/minecraft/core/item/ItemRope.java b/game/core/src/main/java/net/minecraft/core/item/ItemRope.java
index a4f2f8511..53bf94e5f 100644
--- a/game/core/src/main/java/net/minecraft/core/item/ItemRope.java
+++ b/game/core/src/main/java/net/minecraft/core/item/ItemRope.java
@@ -48,12 +48,13 @@ public class ItemRope extends ItemPlaceable {
             return false;
         }
         if (world.canBlockIdBePlacedAt(this.blockToPlace.id(), bp, false, side) && selfStack.consumeItem(player)) {
-            if (world.setBlockTypeDataNotify(bp, this.blockToPlace, placeOnSide ? BlockLogicRope.MASK_ROPE_CONNECT : 0)) {
+            if (world.setBlockTypeData(bp, this.blockToPlace, placeOnSide ? BlockLogicRope.MASK_ROPE_CONNECT : 0)) {
                 if (player == null) {
                     this.blockToPlace.onPlacedOnSide(world, bp, side, xHit, yHit);
                 } else {
                     this.blockToPlace.onPlacedByMob(world, bp, side, player, xHit, yHit);
                 }
+                world.notifyBlockChange(bp, this.blockToPlace);
                 world.playBlockSoundEffect(player, bp.x + 0.5F, bp.y + 0.5F, bp.z + 0.5F, this.blockToPlace, EnumBlockSoundEffectType.PLACE);
                 return true;
             }
diff --git a/game/core/src/main/java/net/minecraft/core/item/ItemSign.java b/game/core/src/main/java/net/minecraft/core/item/ItemSign.java
index 36a62aa52..66a35f6c4 100644
--- a/game/core/src/main/java/net/minecraft/core/item/ItemSign.java
+++ b/game/core/src/main/java/net/minecraft/core/item/ItemSign.java
@@ -1,6 +1,7 @@
 package net.minecraft.core.item;
 
 
+import net.minecraft.core.block.Block;
 import net.minecraft.core.block.Blocks;
 import net.minecraft.core.block.entity.TileEntitySign;
 import net.minecraft.core.entity.player.Player;
@@ -33,14 +34,16 @@ public class ItemSign extends Item {
             return false;
         }
 
+        final Block<?> blockPlaced;
         if (side == Side.TOP) {
-            world.playBlockSoundEffect(player, blockPos.x() + 0.5F, blockPos.y() + 0.5F, blockPos.z() + 0.5F, Blocks.SIGN_POST_PLANKS_OAK, EnumBlockSoundEffectType.PLACE);
-            world.setBlockTypeDataNotify(blockPos, Blocks.SIGN_POST_PLANKS_OAK, MathHelper.floor((double) (((player.yRot + 180F) * 16F) / 360F) + 0.5D) & 0xf);
+            blockPlaced = Blocks.SIGN_POST_PLANKS_OAK;
+            world.setBlockTypeData(blockPos, blockPlaced, MathHelper.floor((double) (((player.yRot + 180F) * 16F) / 360F) + 0.5D) & 0xf);
         } else {
-            world.playBlockSoundEffect(player, blockPos.x() + 0.5F, blockPos.y() + 0.5F, blockPos.z() + 0.5F, Blocks.SIGN_WALL_PLANKS_OAK, EnumBlockSoundEffectType.PLACE);
-            world.setBlockTypeDataNotify(blockPos, Blocks.SIGN_WALL_PLANKS_OAK, side.getId());
+            blockPlaced = Blocks.SIGN_WALL_PLANKS_OAK;
+            world.setBlockTypeData(blockPos, blockPlaced, side.getId());
         }
         selfStack.consumeItem(player);
+        world.playBlockSoundEffect(player, blockPos.x() + 0.5F, blockPos.y() + 0.5F, blockPos.z() + 0.5F, blockPlaced, EnumBlockSoundEffectType.PLACE);
         if (world.getTileEntity(blockPos) instanceof TileEntitySign sign) {
             if (selfStack.getData().containsKey("tileEntityData")) {
                 sign.readAdditionalData(selfStack.getData().getCompound("tileEntityData"));
@@ -49,6 +52,7 @@ public class ItemSign extends Item {
                 player.displaySignEditorScreen(sign);
             }
         }
+        world.notifyBlockChange(blockPos, blockPlaced);
         return true;
     }
 }
diff --git a/game/core/src/main/java/net/minecraft/core/item/ItemSignPainted.java b/game/core/src/main/java/net/minecraft/core/item/ItemSignPainted.java
index a431013e7..f73ba4d5a 100644
--- a/game/core/src/main/java/net/minecraft/core/item/ItemSignPainted.java
+++ b/game/core/src/main/java/net/minecraft/core/item/ItemSignPainted.java
@@ -1,5 +1,6 @@
 package net.minecraft.core.item;
 
+import net.minecraft.core.block.Block;
 import net.minecraft.core.block.Blocks;
 import net.minecraft.core.block.entity.TileEntitySign;
 import net.minecraft.core.entity.player.Player;
@@ -31,16 +32,18 @@ public class ItemSignPainted extends ItemSign {
         if (blockPos.y() < 0 || blockPos.y() >= world.getHeightBlocks() || !Blocks.SIGN_POST_PLANKS_OAK_PAINTED.canPlaceAt(world, blockPos)) {
             return false;
         }
-
+        
+        final Block<?> blockPlaced;
         if (side == Side.TOP) {
             int direction = MathHelper.floor((double) (((player.yRot + 180F) * 16F) / 360F) + 0.5D) & 0xf;
-            world.playBlockSoundEffect(player, blockPos.x() + 0.5F, blockPos.y() + 0.5F, blockPos.z() + 0.5F, Blocks.SIGN_POST_PLANKS_OAK_PAINTED, EnumBlockSoundEffectType.PLACE);
-            world.setBlockTypeDataNotify(blockPos, Blocks.SIGN_POST_PLANKS_OAK_PAINTED, Blocks.SIGN_POST_PLANKS_OAK_PAINTED.getLogic().toMetadata(DyeColor.colorFromItemMeta(selfStack.getMetadata())) | direction);
+            blockPlaced = Blocks.SIGN_POST_PLANKS_OAK_PAINTED;
+            world.setBlockTypeData(blockPos, blockPlaced, Blocks.SIGN_POST_PLANKS_OAK_PAINTED.getLogic().toMetadata(DyeColor.colorFromItemMeta(selfStack.getMetadata())) | direction);
         } else {
-            world.playBlockSoundEffect(player, blockPos.x() + 0.5F, blockPos.y() + 0.5F, blockPos.z() + 0.5F, Blocks.SIGN_WALL_PLANKS_OAK_PAINTED, EnumBlockSoundEffectType.PLACE);
-            world.setBlockTypeDataNotify(blockPos, Blocks.SIGN_WALL_PLANKS_OAK_PAINTED, Blocks.SIGN_POST_PLANKS_OAK_PAINTED.getLogic().toMetadata(DyeColor.colorFromItemMeta(selfStack.getMetadata())) | side.getId());
+            blockPlaced = Blocks.SIGN_WALL_PLANKS_OAK_PAINTED;
+            world.setBlockTypeData(blockPos, blockPlaced, Blocks.SIGN_POST_PLANKS_OAK_PAINTED.getLogic().toMetadata(DyeColor.colorFromItemMeta(selfStack.getMetadata())) | side.getId());
         }
         selfStack.consumeItem(player);
+        world.playBlockSoundEffect(player, blockPos.x() + 0.5F, blockPos.y() + 0.5F, blockPos.z() + 0.5F, blockPlaced, EnumBlockSoundEffectType.PLACE);
         if (world.getTileEntity(blockPos) instanceof TileEntitySign sign) {
             if (selfStack.getData().containsKey("tileEntityData")) {
                 sign.readAdditionalData(selfStack.getData().getCompound("tileEntityData"));
@@ -49,6 +52,7 @@ public class ItemSignPainted extends ItemSign {
                 player.displaySignEditorScreen(sign);
             }
         }
+        world.notifyBlockChange(blockPos, blockPlaced);
         return true;
     }
 
diff --git a/game/core/src/main/java/net/minecraft/core/item/ItemStatue.java b/game/core/src/main/java/net/minecraft/core/item/ItemStatue.java
index b60c5d684..59e3381a7 100644
--- a/game/core/src/main/java/net/minecraft/core/item/ItemStatue.java
+++ b/game/core/src/main/java/net/minecraft/core/item/ItemStatue.java
@@ -36,13 +36,10 @@ public class ItemStatue extends Item {
             return false;
         }
 
-        world.noNeighborUpdate = true;
-        world.setBlockTypeDataNotify(blockPos, this.statueBlockBottom, MathHelper.floor((double) (((player.yRot + 180F) * 16F) / 360F) + 0.5D) & 0xf);
-        world.setBlockTypeDataNotify(blockPos.up(new TilePos()), this.statueBlockTop, MathHelper.floor((double) (((player.yRot + 180F) * 16F) / 360F) + 0.5D) & 0xf);
-        world.noNeighborUpdate = false;
-        world.notifyBlocksOfNeighborChange(blockPos, this.statueBlockBottom);
-        world.notifyBlocksOfNeighborChange(blockPos.up(new TilePos()), this.statueBlockTop);
-        world.playBlockSoundEffect(player, blockPos.x() + 0.5F, blockPos.y() + 0.5F, blockPos.z() + 0.5F, this.statueBlockBottom, EnumBlockSoundEffectType.PLACE);
+        world.setBlockTypeData(blockPos, this.statueBlockBottom, MathHelper.floor((double) (((player.yRot + 180F) * 16F) / 360F) + 0.5D) & 0xf);
+        world.setBlockTypeData(blockPos.up(new TilePos()), this.statueBlockTop, MathHelper.floor((double) (((player.yRot + 180F) * 16F) / 360F) + 0.5D) & 0xf);
+        selfStack.consumeItem(player);
+        
         this.statueBlockBottom.onPlacedByMob(world, blockPos, side, player, xHit, yHit);
         this.statueBlockTop.onPlacedByMob(world, blockPos.up(new TilePos()), side, player, xHit, yHit);
 
@@ -52,8 +49,11 @@ public class ItemStatue extends Item {
                 tileEntity.readAdditionalData(selfStack.getData().getCompound("tileEntityData"));
             }
         }
+        world.notifyBlockChange(blockPos, this.statueBlockBottom);
+        world.notifyBlockChange(blockPos.up(new TilePos()), this.statueBlockTop);
+        
+        world.playBlockSoundEffect(player, blockPos.x() + 0.5F, blockPos.y() + 0.5F, blockPos.z() + 0.5F, this.statueBlockBottom, EnumBlockSoundEffectType.PLACE);
 
-        selfStack.consumeItem(player);
         return true;
     }
 
diff --git a/game/core/src/main/java/net/minecraft/core/item/block/ItemBlock.java b/game/core/src/main/java/net/minecraft/core/item/block/ItemBlock.java
index 18351c234..7169216c3 100644
--- a/game/core/src/main/java/net/minecraft/core/item/block/ItemBlock.java
+++ b/game/core/src/main/java/net/minecraft/core/item/block/ItemBlock.java
@@ -2,6 +2,7 @@ package net.minecraft.core.item.block;
 
 import net.minecraft.core.block.Block;
 import net.minecraft.core.block.BlockLogic;
+import net.minecraft.core.block.Blocks;
 import net.minecraft.core.block.entity.TileEntity;
 import net.minecraft.core.block.entity.TileEntityActivator;
 import net.minecraft.core.block.tag.BlockTags;
@@ -33,44 +34,58 @@ public class ItemBlock<T extends BlockLogic> extends Item {
     }
 
     @Override
-    public boolean onUseOnBlock(@NotNull ItemStack selfStack, @NotNull World world, @Nullable Player player, @NotNull TilePosc blockPos, @NotNull Side side, double xHit, double yHit) {
+    public boolean onUseOnBlock(
+        @NotNull ItemStack selfStack, @NotNull World world, @Nullable Player player,
+        @NotNull TilePosc blockPos, @NotNull Side side, double xHit, double yHit
+    ) {
+        final int meta = getPlacedData(selfStack, world, player, blockPos, side, xHit, yHit);
+        return this.placeWithMeta(selfStack, world, player, blockPos, meta, side, xHit, yHit);
+    }
+
+    public final boolean placeWithMeta(
+        @NotNull ItemStack selfStack, @NotNull World world, @Nullable Player player,
+        @NotNull TilePosc blockPos, int meta, @NotNull Side side, double xHit, double yHit
+    ) {
         if (selfStack.stackSize <= 0) {
             return false;
         }
-        if (!world.canPlaceInsideBlock(blockPos)) {
-            blockPos = blockPos.add(side.getDirection(), new TilePos());
-        }
-        if (blockPos.y() < 0 || blockPos.y() >= world.getHeightBlocks()) {
-            return false;
+        final boolean placeNextToExisting = !world.canPlaceInsideBlock(blockPos) ||
+            (this.block.hasTag(BlockTags.PLACE_OVERWRITES) && this.block == world.getBlockType(blockPos));
+
+        if (placeNextToExisting) blockPos = blockPos.add(side.getDirection(), new TilePos());
+
+        if (!world.canBlockIdBePlacedAt(this.block.id(), blockPos, false, side)) return false;
+        
+        if (!world.setBlockTypeData(blockPos, this.block, meta)) return false;
+        if (!selfStack.consumeItem(player)) return false; // should always be true
+
+        if (player == null) {
+            this.block.onPlacedOnSide(world, blockPos, side, xHit, yHit);
+        } else {
+            this.block.onPlacedByMob(world, blockPos, side, player, xHit, yHit);
         }
-        if (world.canBlockIdBePlacedAt(this.block.id(), blockPos, false, side) && selfStack.consumeItem(player)) {
-            int meta = getPlacedData(selfStack, world, player, blockPos, side, xHit, yHit);
-            if (world.setBlockTypeDataNotify(blockPos, this.block, meta)) {
-                if (player == null) {
-                    this.block.onPlacedOnSide(world, blockPos, side, xHit, yHit);
-                } else {
-                    this.block.onPlacedByMob(world, blockPos, side, player, xHit, yHit);
-                }
-                if (this.block.isEntityTile && selfStack.getData().containsKey("tileEntityData")) {
-                    TileEntity tileEntity = world.getTileEntity(blockPos);
-                    if (tileEntity != null) {
-                        tileEntity.readAdditionalData(selfStack.getData().getCompound("tileEntityData"));
-                    }
-                }
-                world.playBlockSoundEffect(player, blockPos.x() + 0.5F, blockPos.y() + 0.5F, blockPos.z() + 0.5F, this.block, EnumBlockSoundEffectType.PLACE);
-                return true;
-            } else if (player == null || player.getGamemode().hasBlockConsumption()) {
-                selfStack.stackSize += 1;
+
+        if (this.block.isEntityTile && selfStack.getData().containsKey("tileEntityData")) {
+            final TileEntity tileEntity = world.getTileEntity(blockPos);
+            if (tileEntity != null) {
+                tileEntity.readAdditionalData(selfStack.getData().getCompound("tileEntityData"));
             }
         }
-        return false;
+
+        world.notifyBlockChange(blockPos, this.block);
+        world.playBlockSoundEffect(
+            player,
+            blockPos.x() + 0.5F, blockPos.y() + 0.5F, blockPos.z() + 0.5F,
+            this.block, EnumBlockSoundEffectType.PLACE
+        );
+        return true;
     }
 
     @Override
     public void onUseByActivator(@NotNull ItemStack selfStack, @NotNull World world, @NotNull TileEntityActivator activator, @NotNull Random random, @NotNull TilePosc blockPos, @NotNull Direction direction, double offX, double offY, double offZ) {
-        blockPos = blockPos.add(direction, new TilePos());
-        Block<?> b = world.getBlockType(blockPos);
-        if (b == null || BlockTags.PLACE_OVERWRITES.appliesTo(b)) {
+        blockPos = new TilePos(blockPos).add(direction);
+        @NotNull Block<?> b = world.getBlockType(blockPos);
+        if (b == Blocks.AIR || BlockTags.PLACE_OVERWRITES.appliesTo(b)) {
             onUseOnBlock(selfStack, world, null, blockPos, direction.getSide(), 0.5, 0.5);
         }
     }
diff --git a/game/core/src/main/java/net/minecraft/core/item/block/ItemBlockAlgae.java b/game/core/src/main/java/net/minecraft/core/item/block/ItemBlockAlgae.java
index 8694aaf19..829a4db67 100644
--- a/game/core/src/main/java/net/minecraft/core/item/block/ItemBlockAlgae.java
+++ b/game/core/src/main/java/net/minecraft/core/item/block/ItemBlockAlgae.java
@@ -2,11 +2,11 @@ package net.minecraft.core.item.block;
 
 import net.minecraft.core.block.Block;
 import net.minecraft.core.block.BlockLogic;
+import net.minecraft.core.block.BlockLogicFluid;
 import net.minecraft.core.block.Blocks;
+import net.minecraft.core.block.FluidWater;
 import net.minecraft.core.block.entity.TileEntityActivator;
-import net.minecraft.core.block.tag.BlockTags;
 import net.minecraft.core.entity.player.Player;
-import net.minecraft.core.enums.EnumBlockSoundEffectType;
 import net.minecraft.core.item.ItemStack;
 import net.minecraft.core.util.helper.Direction;
 import net.minecraft.core.util.helper.Side;
@@ -34,40 +34,39 @@ public class ItemBlockAlgae<T extends BlockLogic> extends ItemBlock<T> {
 
     @Override
     public @Nullable ItemStack onUse(@NotNull ItemStack selfStack, @NotNull World world, @NotNull Player player) {
-        double reachDistance = player.getGamemode().getBlockReachDistance();
-        HitResult rayCast = player.rayCast(reachDistance, 1f, true, false, false);
-        if (rayCast == null) {
-            return selfStack;
-        }
-        if (rayCast instanceof HitResult.Tile hitTile) {
-            TilePos blockPos = new TilePos(hitTile.tilePos);
-            if (world.getBlockType(blockPos) == Blocks.FLUID_WATER_STILL) {
-                blockPos.y++;
-            } else {
-                return selfStack;
-            }
+        final double reachDistance = player.getGamemode().getBlockReachDistance();
+        final HitResult rayCast = player.rayCast(reachDistance, 1f, true, false, false);
+        if (!(rayCast instanceof HitResult.Tile hitTile)) return selfStack;
 
-            if (world.getBlockType(blockPos) != Blocks.AIR) {
-                return selfStack;
-            }
-            if (selfStack.consumeItem(player) && world.setBlockTypeNotify(blockPos, this.block)) {
-                this.block.onPlacedByMob(world, blockPos, Side.NONE, player, 0.5, 0.5);
-                world.playBlockSoundEffect(player, blockPos.x + 0.5F, blockPos.y + 0.5F, blockPos.z + 0.5F, this.block, EnumBlockSoundEffectType.PLACE);
-                player.swingItem();
-            }
-        }
+        final var pos = new TilePos(hitTile.tilePos);
+        if (!testPlace(world, pos)) return selfStack;
+
+        super.onUseOnBlock(selfStack, world, player, pos, Side.NONE, 0.5, 0.5);
         return selfStack;
     }
-
+    
     @Override
     public void onUseByActivator(@NotNull ItemStack selfStack, @NotNull World world, @NotNull TileEntityActivator activator, @NotNull Random random, @NotNull TilePosc blockPos, @NotNull Direction direction, double offX, double offY, double offZ) {
-        blockPos = blockPos.add(direction, new TilePos());
-        TilePos up = blockPos.up(new TilePos());
-        Block<?> b = world.getBlockType(blockPos);
-        Block<?> bAbove = world.getBlockType(up);
-        if (b == Blocks.FLUID_WATER_STILL && (bAbove == null || bAbove.hasTag(BlockTags.PLACE_OVERWRITES))) {
-            world.setBlockTypeNotify(up, this.block);
-            this.block.onPlacedOnSide(world, up, Side.NONE, 0.5, 0.5);
-        }
+        
+        final var pos = new TilePos(blockPos).add(direction);
+        if (!testPlace(world, pos)) return;
+        
+        if (!world.setBlockType(pos, this.block)) return;
+        selfStack.consumeItem(null);
+
+        this.block.onPlacedOnSide(world, pos, Side.NONE, 0.5, 0.5);
+        world.notifyBlockChange(pos, this.block);
+    }
+
+    private static final boolean testPlace(@NotNull World world, @NotNull TilePos toMove) {
+        final TilePos origin = new TilePos(toMove);
+
+        @NotNull Block<?> up = world.getBlockType(toMove.up());
+        if (up != Blocks.AIR) return false;
+
+        if (!(world.getBlockType(origin).getLogic() instanceof BlockLogicFluid fluid)) return false;
+        if (!(fluid.fluid instanceof FluidWater)) return false;
+        if (fluid.getDepth(world, origin) != 0) return false;
+        return true;
     }
 }
diff --git a/game/core/src/main/java/net/minecraft/core/item/block/ItemBlockLadder.java b/game/core/src/main/java/net/minecraft/core/item/block/ItemBlockLadder.java
index 99ea24a7b..e7a7a2cca 100644
--- a/game/core/src/main/java/net/minecraft/core/item/block/ItemBlockLadder.java
+++ b/game/core/src/main/java/net/minecraft/core/item/block/ItemBlockLadder.java
@@ -3,7 +3,6 @@ package net.minecraft.core.item.block;
 import net.minecraft.core.block.Block;
 import net.minecraft.core.block.BlockLogicLadder;
 import net.minecraft.core.entity.player.Player;
-import net.minecraft.core.enums.EnumBlockSoundEffectType;
 import net.minecraft.core.item.ItemStack;
 import net.minecraft.core.util.helper.Side;
 import net.minecraft.core.world.World;
@@ -20,55 +19,23 @@ public class ItemBlockLadder<T extends BlockLogicLadder> extends ItemBlock<T> {
 
     @Override
     public boolean onUseOnBlock(@NotNull ItemStack selfStack, @NotNull World world, @Nullable Player player, @NotNull TilePosc blockPos, @NotNull Side side, double xHit, double yHit) {
-        TilePos bp = new TilePos(blockPos);
-        Block<?> clickedBlock = world.getBlockType(bp);
-
-        BlockLogicLadder ladder = this.block.getLogic();
-
-        if (clickedBlock == this.block && player != null && !player.isSneaking()) {
-            Side clickedLadderSide = ladder.getSideFromMeta(world.getBlockData(bp));
-            while (world.getBlockType(bp) == this.block && ladder.getSideFromMeta(world.getBlockData(bp)) == clickedLadderSide)
-                bp.y--;
-            int meta = ladder.getMetaForSide(clickedLadderSide);
-            if (ladder.canExistAt(world, new TilePos(bp), meta)) {
-                return placeBlock(selfStack, world, bp, meta, player, clickedLadderSide, 0.5, 0.5);
+        final TilePos bp = new TilePos(blockPos);
+        final @NotNull BlockLogicLadder ladder = this.block.getLogic();
+
+        final Side pside;
+        if (player == null || player.isSneaking() || world.getBlockType(bp) != this.block) {
+            if (!world.canPlaceInsideBlock(bp)) bp.add(side.getDirection());
+            pside = ladder.getSideForPlacement(world, bp, side);
+        } else {
+            xHit = 0.5;
+            yHit = 0.5;
+            pside = ladder.getSideFromMeta(world.getBlockData(bp));
+            while (world.getBlockType(bp) == this.block && ladder.getSideFromMeta(world.getBlockData(bp)) == pside) {
+                bp.down();
             }
-
-            return false;
-        }
-
-        if (!world.canPlaceInsideBlock(bp)) {
-            bp = bp.add(side.getDirection(), new TilePos());
-        }
-
-        Side sideForPlacement = ladder.getSideForPlacement(world, bp, side);
-        if (sideForPlacement == null) {
-            return false;
-        }
-
-        int meta = ladder.getMetaForSide(sideForPlacement);
-        if (ladder.canExistAt(world, bp, meta)) {
-            return placeBlock(selfStack, world, bp, meta, player, sideForPlacement, xHit, yHit);
         }
-
-        return false;
-    }
-
-
-    public boolean placeBlock(@NotNull ItemStack selfStack, @NotNull World world, @NotNull TilePos tilePos, int meta, @Nullable Player player, @NotNull Side side, double xHit, double yHit) {
-        if (world.canPlaceInsideBlock(tilePos) && world.canBlockIdBePlacedAt(this.block.id(), tilePos, false, side) && selfStack.consumeItem(player)) {
-            if (world.setBlockTypeDataNotify(tilePos, this.block, meta)) {
-                if (player == null) {
-                    this.block.onPlacedOnSide(world, tilePos, side, xHit, yHit);
-                } else {
-                    this.block.onPlacedByMob(world, tilePos, side, player, xHit, yHit);
-                }
-                world.playBlockSoundEffect(player, tilePos.x + 0.5f, tilePos.y + 0.5f, tilePos.z + 0.5f, this.block, EnumBlockSoundEffectType.PLACE);
-                return true;
-            }
-        }
-        return false;
+        final var meta = ladder.getMetaForSide(pside);
+        if (!ladder.canExistAt(world, bp, meta)) return false;
+        return super.placeWithMeta(selfStack, world, player, bp, meta, pside, xHit, yHit);
     }
-
-
 }
diff --git a/game/core/src/main/java/net/minecraft/core/item/block/ItemBlockLayer.java b/game/core/src/main/java/net/minecraft/core/item/block/ItemBlockLayer.java
index b5c06c363..0d4f4bf49 100644
--- a/game/core/src/main/java/net/minecraft/core/item/block/ItemBlockLayer.java
+++ b/game/core/src/main/java/net/minecraft/core/item/block/ItemBlockLayer.java
@@ -2,7 +2,6 @@ package net.minecraft.core.item.block;
 
 import net.minecraft.core.block.Block;
 import net.minecraft.core.block.BlockLogicLayerBase;
-import net.minecraft.core.block.tag.BlockTags;
 import net.minecraft.core.entity.player.Player;
 import net.minecraft.core.enums.EnumBlockSoundEffectType;
 import net.minecraft.core.item.ItemStack;
@@ -12,8 +11,8 @@ 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.Vector3d;
 import org.joml.primitives.AABBd;
-import org.joml.primitives.AABBdc;
 
 
 public class ItemBlockLayer<T extends BlockLogicLayerBase> extends ItemBlock<T> {
@@ -21,89 +20,51 @@ public class ItemBlockLayer<T extends BlockLogicLayerBase> extends ItemBlock<T>
         super(block);
     }
 
+    // TODO: sideways placement into layer
     @Override
     public boolean onUseOnBlock(@NotNull ItemStack selfStack, @NotNull World world, @Nullable Player player, @NotNull TilePosc tilePos, @NotNull Side side, double xHit, double yHit) {
-        Block<?> targetBlock = world.getBlockType(tilePos);
-        int meta = world.getBlockData(tilePos);
-        if (targetBlock != this.block && targetBlock != null && targetBlock.hasTag(BlockTags.PLACE_OVERWRITES)) {
-            targetBlock = null;
-            meta = 0;
-        }
-        if (selfStack.stackSize <= 0 || tilePos.y() == world.getHeightBlocks() - 1 && this.block.getMaterial().isSolid()) {
+        if (selfStack.stackSize <= 0) return false; 
+        if (tilePos.y() == world.getHeightBlocks() - 1 && this.block.getMaterial().isSolid()) {
             return false;
         }
-
-        // Check if we're placing "inside" a layer
-        if (targetBlock == this.block && side == Side.TOP) {
-            final BlockLogicLayerBase blockLayer = this.block.getLogic();
-            final int newMeta = meta + 1;
-
-            final AABBdc bbBox = new AABBd(tilePos.x(), tilePos.y(), tilePos.z(), tilePos.x() + 1f, tilePos.y() + (2 * (newMeta + 1)) / 16.0f, tilePos.z() + 1f);
-            if (!world.checkIfAABBIsClear(bbBox)) {
-                return false;
-            }
-
-            if (newMeta < 7) {
-                world.setBlockTypeDataNotify(tilePos, this.block, newMeta);
-            } else {
-                if (blockLayer.fullBlock != null) {
-                    world.setBlockTypeNotify(tilePos, blockLayer.fullBlock);
-                } else {
-                    world.setBlockTypeNotify(tilePos.up(new TilePos()), this.block);
-                }
-            }
-            world.playBlockSoundEffect(player, tilePos.x() + 0.5F, tilePos.y() + 0.5F, tilePos.z() + 0.5F, this.block, EnumBlockSoundEffectType.PLACE);
-            selfStack.consumeItem(player);
-            return true;
-
+        final var targetBlock = world.getBlockType(tilePos);
+        if (targetBlock != this.block || side != Side.TOP) {
+            // you can no longer stack layers by placing on
+            // the top face of a partial block under the layers :3
+            return super.placeWithMeta(selfStack, world, player, tilePos, 0, side, xHit, yHit);
         }
 
-        // Offset position
-        if (targetBlock != null) {
-            tilePos = tilePos.add(side.getDirection(), new TilePos());
-            targetBlock = world.getBlockType(tilePos);
-            meta = world.getBlockData(tilePos);
-        }
+        final @NotNull BlockLogicLayerBase blockLayer = this.block.getLogic();
+        final int meta = world.getBlockData(tilePos) + 1;
 
-        // Check if the block we're placing into is a layer
-        if (targetBlock == this.block) {
-            final BlockLogicLayerBase blockLayer = this.block.getLogic();
-            final int newMeta = meta + 1;
+        final var bbmin = new Vector3d(tilePos.vec());
+        final var bbmax = new Vector3d(bbmin).add(1, (meta + 1) / 8.0f, 1);
+        if (!world.checkIfAABBIsClear(new AABBd(bbmin, bbmax))) return false;
 
-            final AABBdc bbBox = new AABBd(tilePos.x(), tilePos.y(), tilePos.z(), tilePos.x() + 1f, tilePos.y() + (2 * (newMeta + 1)) / 16.0f, tilePos.z() + 1f);
-            if (!world.checkIfAABBIsClear(bbBox)) {
-                return false;
-            }
+        final boolean placed;
+        final @NotNull Block<?> placedBlock;
+        final var placedPos = new TilePos(tilePos);
 
-            if (newMeta < 7) {
-                world.setBlockTypeDataNotify(tilePos, this.block, newMeta);
-            } else {
-                if (blockLayer.fullBlock != null) {
-                    world.setBlockTypeNotify(tilePos, blockLayer.fullBlock);
-                } else {
-                    world.setBlockTypeNotify(tilePos.up(new TilePos()), this.block);
-                }
-            }
-            world.playBlockSoundEffect(player, tilePos.x() + 0.5F, tilePos.y() + 0.5F, tilePos.z() + 0.5F, this.block, EnumBlockSoundEffectType.PLACE);
-            selfStack.consumeItem(player);
-            return true;
+        if (meta < 7) {
+            placedBlock = this.block;
+            placed = world.setBlockTypeData(placedPos, placedBlock, meta);
+        } else if (blockLayer.fullBlock != null) {
+            placedBlock = blockLayer.fullBlock;
+            placed = world.setBlockType(placedPos, placedBlock);
+        } else {
+            placedBlock = this.block;
+            // TODO this can place inside entities
+            placed = world.setBlockType(placedPos.up(), placedBlock);
         }
+        if (!placed) return false;
+        selfStack.consumeItem(player);
 
-        // Regular placement, as all else fails
-        if (world.canBlockIdBePlacedAt(this.block.id(), tilePos, false, side)) {
-            int placeMeta = getPlacedData(selfStack, world, player, tilePos, side, xHit, yHit);
-            if (world.setBlockTypeDataNotify(tilePos, this.block, placeMeta)) {
-                if (player == null) {
-                    this.block.onPlacedOnSide(world, tilePos, side, xHit, yHit);
-                } else {
-                    this.block.onPlacedByMob(world, tilePos, Side.NONE, player, xHit, yHit);
-                }
-                world.playBlockSoundEffect(player, tilePos.x() + 0.5F, tilePos.y() + 0.5F, tilePos.z() + 0.5F, this.block, EnumBlockSoundEffectType.PLACE);
-                selfStack.consumeItem(player);
-                return true;
-            }
-        }
-        return false;
+        placedBlock.onPlacedByMob(world, tilePos, side, player, xHit, yHit);
+        world.notifyBlockChange(placedPos, placedBlock);
 
+        world.playBlockSoundEffect(player,
+            placedPos.x() + 0.5F, placedPos.y() + 0.5F, placedPos.z() + 0.5F,
+            this.block, EnumBlockSoundEffectType.PLACE);
+        return true;
     }
 }
diff --git a/game/core/src/main/java/net/minecraft/core/item/block/ItemBlockLayerLeaves.java b/game/core/src/main/java/net/minecraft/core/item/block/ItemBlockLayerLeaves.java
index 7761fa869..160c93803 100644
--- a/game/core/src/main/java/net/minecraft/core/item/block/ItemBlockLayerLeaves.java
+++ b/game/core/src/main/java/net/minecraft/core/item/block/ItemBlockLayerLeaves.java
@@ -3,8 +3,6 @@ package net.minecraft.core.item.block;
 import net.minecraft.core.block.Block;
 import net.minecraft.core.block.BlockLogicLayerBase;
 import net.minecraft.core.block.BlockLogicLayerLeaves;
-import net.minecraft.core.block.BlockLogicLeavesBase;
-import net.minecraft.core.block.tag.BlockTags;
 import net.minecraft.core.entity.player.Player;
 import net.minecraft.core.enums.EnumBlockSoundEffectType;
 import net.minecraft.core.item.ItemStack;
@@ -14,102 +12,60 @@ 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.Vector3d;
 import org.joml.primitives.AABBd;
-import org.joml.primitives.AABBdc;
 
 public class ItemBlockLayerLeaves<T extends BlockLogicLayerLeaves> extends ItemBlock<T> {
     public ItemBlockLayerLeaves(@NotNull Block<T> block) {
         super(block);
     }
 
+    // TODO: refactor to reuse ItemBlockLayer?
     @Override
-    public boolean onUseOnBlock(@NotNull ItemStack selfStack, @NotNull World world, @Nullable Player player, @NotNull TilePosc blockPos, @NotNull Side side, double xHit, double yHit) {
-        Block<?> targetBlock = world.getBlockType(blockPos);
-        int meta = world.getBlockData(blockPos);
-        if (targetBlock != this.block && targetBlock != null && targetBlock.hasTag(BlockTags.PLACE_OVERWRITES)) {
-            targetBlock = null;
-            meta = 0;
-        }
-        if (selfStack.stackSize <= 0 || blockPos.y() == world.getHeightBlocks() - 1 && this.block.getMaterial().isSolid()) {
+    public boolean onUseOnBlock(@NotNull ItemStack selfStack, @NotNull World world, @Nullable Player player, @NotNull TilePosc tilePos, @NotNull Side side, double xHit, double yHit) {
+        if (selfStack.stackSize <= 0) return false; 
+        if (tilePos.y() == world.getHeightBlocks() - 1 && this.block.getMaterial().isSolid()) {
             return false;
         }
-
-        // Check if we're placing "inside" a layer
-        if (targetBlock == this.block && side == Side.TOP) {
-            final BlockLogicLayerBase blockLayer = this.block.getLogic();
-            final int newMeta = meta + 1;
-
-            final AABBdc bbBox = new AABBd(blockPos.x(), blockPos.y(), blockPos.z(), blockPos.x() + 1f, blockPos.y() + (2 * (newMeta + 1)) / 16.0f, blockPos.z() + 1f);
-            if (!world.checkIfAABBIsClear(bbBox)) {
-                return false;
-            }
-
-            if ((newMeta & 0b00001111) < 7) {
-                world.setBlockTypeDataNotify(blockPos, this.block, BlockLogicLayerLeaves.setPermanent(newMeta, true));
-            } else {
-                if (blockLayer.fullBlock != null) {
-                    world.setBlockTypeDataNotify(blockPos, blockLayer.fullBlock, BlockLogicLeavesBase.setPermanent(0, true));
-                } else {
-                    world.setBlockTypeDataNotify(blockPos.up(new TilePos()), this.block, BlockLogicLayerLeaves.setPermanent(0, true));
-                }
-            }
-            world.playBlockSoundEffect(player, blockPos.x() + 0.5F, blockPos.y() + 0.5F, blockPos.z() + 0.5F, this.block, EnumBlockSoundEffectType.PLACE);
-            selfStack.consumeItem(player);
-            return true;
-        }
-
-        // Offset position
-        if (targetBlock != null) {
-            blockPos = blockPos.add(side.getDirection(), new TilePos());
-            targetBlock = world.getBlockType(blockPos);
-            meta = world.getBlockData(blockPos);
+        final var targetBlock = world.getBlockType(tilePos);
+        if (targetBlock != this.block || side != Side.TOP) {
+            // you can no longer stack layers by placing on
+            // the top face of a partial block under the layers :3
+            return super.placeWithMeta(selfStack, world, player, tilePos, 
+                BlockLogicLayerLeaves.setPermanent(0, true), side, xHit, yHit);
         }
 
-        // Check if the block we're placing into is a layer
-        if (targetBlock == this.block) {
-            final BlockLogicLayerBase blockLayer = this.block.getLogic();
-            final int newMeta = meta + 1;
+        final @NotNull BlockLogicLayerBase blockLayer = this.block.getLogic();
+        final int meta = world.getBlockData(tilePos) + 1;
 
-            final AABBdc bbBox = new AABBd(blockPos.x(), blockPos.y(), blockPos.z(), blockPos.x() + 1f, blockPos.y() + (2 * (newMeta + 1)) / 16.0f, blockPos.z() + 1f);
-            if (!world.checkIfAABBIsClear(bbBox)) {
-                return false;
-            }
+        final var bbmin = new Vector3d(tilePos.vec());
+        final var bbmax = new Vector3d(bbmin).add(1, (meta + 1) / 8.0f, 1);
+        if (!world.checkIfAABBIsClear(new AABBd(bbmin, bbmax))) return false;
 
-            if ((newMeta & 0b00001111) < 7) {
-                world.setBlockTypeDataNotify(blockPos, this.block, BlockLogicLayerLeaves.setPermanent(newMeta, true));
-            } else {
-                if (blockLayer.fullBlock != null) {
-                    world.setBlockTypeDataNotify(blockPos, blockLayer.fullBlock, BlockLogicLeavesBase.setPermanent(0, true));
-                } else {
-                    world.setBlockTypeDataNotify(blockPos.up(new TilePos()), this.block, BlockLogicLayerLeaves.setPermanent(0, true));
-                }
-            }
-            world.playBlockSoundEffect(player, blockPos.x() + 0.5F, blockPos.y() + 0.5F, blockPos.z() + 0.5F, this.block, EnumBlockSoundEffectType.PLACE);
-            selfStack.consumeItem(player);
-            return true;
-        }
+        final boolean placed;
+        final @NotNull Block<?> placedBlock;
+        final var placedPos = new TilePos(tilePos);
 
-        // Regular placement, as all else fails
-        if (world.canBlockIdBePlacedAt(this.block.id(), blockPos, false, side)) {
-            int placeMeta = getPlacedData(selfStack, world, player, blockPos, side, xHit, yHit);
-            if (world.setBlockTypeDataNotify(blockPos, this.block, placeMeta)) {
-                if (player == null) {
-                    this.block.onPlacedOnSide(world, blockPos, side, xHit, yHit);
-                } else {
-                    this.block.onPlacedByMob(world, blockPos, side, player, xHit, yHit);
-                }
-                world.playBlockSoundEffect(player, blockPos.x() + 0.5F, blockPos.y() + 0.5F, blockPos.z() + 0.5F, this.block, EnumBlockSoundEffectType.PLACE);
-                selfStack.consumeItem(player);
-                return true;
-            }
+        if ((meta & 0x0F) < 7) {
+            placedBlock = this.block;
+            placed = world.setBlockTypeData(placedPos, placedBlock, BlockLogicLayerLeaves.setPermanent(meta, true));
+        } else if (blockLayer.fullBlock != null) {
+            placedBlock = blockLayer.fullBlock;
+            placed = world.setBlockTypeData(placedPos, placedBlock, BlockLogicLayerLeaves.setPermanent(0, true));
+        } else {
+            placedBlock = this.block;
+            // TODO this can place inside entities
+            placed = world.setBlockTypeData(placedPos.up(), placedBlock, BlockLogicLayerLeaves.setPermanent(0, true));
         }
-        return false;
+        if (!placed) return false;
+        selfStack.consumeItem(player);
 
-    }
+        placedBlock.onPlacedByMob(world, tilePos, side, player, xHit, yHit);
+        world.notifyBlockChange(placedPos, placedBlock);
 
-    @Override
-    public int getPlacedData(@NotNull ItemStack selfStack, @NotNull World world, @Nullable Player player, @NotNull TilePosc blockPos, @NotNull Side side, double xHit, double yHit) {
-        return BlockLogicLayerLeaves.MASK_PERMANENT;
+        world.playBlockSoundEffect(player,
+            placedPos.x() + 0.5F, placedPos.y() + 0.5F, placedPos.z() + 0.5F,
+            this.block, EnumBlockSoundEffectType.PLACE);
+        return true;
     }
-
 }
diff --git a/game/core/src/main/java/net/minecraft/core/item/block/ItemBlockSlab.java b/game/core/src/main/java/net/minecraft/core/item/block/ItemBlockSlab.java
index 009657755..7ba51df41 100644
--- a/game/core/src/main/java/net/minecraft/core/item/block/ItemBlockSlab.java
+++ b/game/core/src/main/java/net/minecraft/core/item/block/ItemBlockSlab.java
@@ -2,7 +2,6 @@ package net.minecraft.core.item.block;
 
 import net.minecraft.core.block.Block;
 import net.minecraft.core.block.BlockLogic;
-import net.minecraft.core.block.Blocks;
 import net.minecraft.core.block.tag.BlockTags;
 import net.minecraft.core.entity.player.Player;
 import net.minecraft.core.enums.EnumBlockSoundEffectType;
@@ -13,8 +12,8 @@ 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.Vector3d;
 import org.joml.primitives.AABBd;
-import org.joml.primitives.AABBdc;
 
 public class ItemBlockSlab<T extends BlockLogic> extends ItemBlock<T> {
     public ItemBlockSlab(@NotNull Block<T> block) {
@@ -23,69 +22,53 @@ public class ItemBlockSlab<T extends BlockLogic> extends ItemBlock<T> {
 
     @Override
     public boolean onUseOnBlock(@NotNull ItemStack selfStack, @NotNull World world, @Nullable Player player, @NotNull TilePosc blockPos, @NotNull Side side, double xHit, double yHit) {
-        @NotNull Block<?> block = world.getBlockType(blockPos);
-        int meta = world.getBlockData(blockPos) & 0b11;
-        int otherMeta = world.getBlockData(blockPos) & ~0b11;
-        if (world.getBlockType(blockPos).hasTag(BlockTags.PLACE_OVERWRITES)) {
-            block = Blocks.AIR;
-            meta = 0;
-        }
-        if (selfStack.stackSize <= 0 || blockPos.y() == world.getHeightBlocks() - 1 && this.block.getMaterial().isSolid()) {
+        if (selfStack.stackSize <= 0) return false;
+        if (blockPos.y() == world.getHeightBlocks() - 1 && this.block.getMaterial().isSolid()) {
             return false;
         }
 
-        // Check if we're placing "inside" a slab
-        if (block == this.block && otherMeta == selfStack.getMetadata() && (side == Side.TOP || side == Side.BOTTOM)) {
-            if ((side == Side.TOP && meta == 0) || (side == Side.BOTTOM && meta == 2)) {
-                AABBdc bbBox = new AABBd(blockPos.x(), blockPos.y(), blockPos.z(), blockPos.x() + 1f, blockPos.y() + 1f, blockPos.z() + 1f);
-                if (!world.checkIfAABBIsClear(bbBox)) {
-                    return false;
-                }
-
-                world.setBlockDataNotify(blockPos, 1 | selfStack.getMetadata());
-                world.playBlockSoundEffect(player, blockPos.x() + 0.5F, blockPos.y() + 0.5F, blockPos.z() + 0.5F, this.block, EnumBlockSoundEffectType.PLACE);
-                selfStack.consumeItem(player);
-                return true;
-            }
+        var block = world.getBlockType(blockPos);
+        if (block.hasTag(BlockTags.PLACE_OVERWRITES)) {
+            return super.onUseOnBlock(selfStack, world, player, blockPos, side, xHit, yHit);
         }
 
-        // Offset position
-        if (block != Blocks.AIR) {
-            blockPos = blockPos.add(side.getDirection(), new TilePos());
-            block = world.getBlockType(blockPos);
-            meta = world.getBlockData(blockPos) & 0b11;
-            otherMeta = world.getBlockData(blockPos) & ~0b11;
+        final int selfMeta = selfStack.getMetadata();
+        final var shiftPos = new TilePos(blockPos);
+        
+        int i;
+        for (i = 0; i < 2; i++) {
+            block = world.getBlockType(shiftPos);
+            final var blockMeta = world.getBlockData(shiftPos);
+            final boolean canPlaceInto = 
+                block == this.block &&
+                (blockMeta & ~0b11) == selfMeta &&
+                switch (blockMeta & 0b11) {
+                    default -> false;
+                    case 0 -> yHit >= 0.5f;
+                    case 2 -> yHit <= 0.5f;
+                };
+            if (canPlaceInto) break;
+            shiftPos.add(side);
+            yHit -= side.getOffsetY();
+        } if (i == 2) { // for else
+            return super.onUseOnBlock(selfStack, world, player, blockPos, side, xHit, yHit);
         }
 
-        // Check if the block we're placing into is a slab
-        if (block == this.block && otherMeta == selfStack.getMetadata()) {
-            if ((yHit > 0.5f && meta == 0) || (yHit <= 0.5f && meta == 2) || (side == Side.BOTTOM && meta == 0) || (side == Side.TOP && meta == 2)) {
-                AABBdc bbBox = new AABBd(blockPos.x(), blockPos.y(), blockPos.z(), blockPos.x() + 1f, blockPos.y() + 1f, blockPos.z() + 1f);
-                if (!world.checkIfAABBIsClear(bbBox)) {
-                    return false;
-                }
+        final var bbmin = new Vector3d(shiftPos.vec());
+        final var bbmax = new Vector3d(bbmin).add(1,1,1);
+        if (!world.checkIfAABBIsClear(new AABBd(bbmin, bbmax))) return false;
 
-                world.setBlockDataNotify(blockPos, 1 | selfStack.getMetadata());
-                world.playBlockSoundEffect(player, blockPos.x() + 0.5F, blockPos.y() + 0.5F, blockPos.z() + 0.5F, this.block, EnumBlockSoundEffectType.PLACE);
-                selfStack.consumeItem(player);
-                return true;
-            }
-        }
+        if (!world.setBlockData(shiftPos, 1 | selfStack.getMetadata())) return false;
+        selfStack.consumeItem(player);
 
-        // Regular placement, as all else fails
-        if (world.canBlockIdBePlacedAt(this.block.id(), blockPos, false, side)) {
-            int placeMeta = getPlacedData(selfStack, world, player, blockPos, side, xHit, yHit);
-            if (world.setBlockTypeDataNotify(blockPos, this.block, placeMeta)) {
-                if (player == null) {
-                    this.block.onPlacedOnSide(world, blockPos, side, xHit, yHit);
-                } else {
-                    this.block.onPlacedByMob(world, blockPos, side, player, xHit, yHit);
-                }
-                world.playBlockSoundEffect(player, blockPos.x() + 0.5F, blockPos.y() + 0.5F, blockPos.z() + 0.5F, this.block, EnumBlockSoundEffectType.PLACE);
-                selfStack.consumeItem(player);
-                return true;
-            }
-        }
-        return false;
+        // this invokes modelBlock's on place logic. is this a good idea?
+        this.block.onPlacedByWorld(world, shiftPos); 
+
+        world.notifyBlockChange(shiftPos, block);
+        world.playBlockSoundEffect(player,
+            shiftPos.x() + 0.5F, shiftPos.y() + 0.5F, shiftPos.z() + 0.5F,
+            this.block, EnumBlockSoundEffectType.PLACE);
+        
+        return true;
     }
 }
diff --git a/game/core/src/main/java/net/minecraft/core/util/PackedField.java b/game/core/src/main/java/net/minecraft/core/util/PackedField.java
new file mode 100644
index 000000000..9b7791a95
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/util/PackedField.java
@@ -0,0 +1,37 @@
+package net.minecraft.core.util;
+
+public final class PackedField {
+    final int offset;
+    public final int mask;
+
+    public PackedField(int offset, int size) {
+        assert offset >= 0 && size >= 0 && offset + size < 32;
+        this.offset = offset;
+        this.mask = ((1 << size) - 1) << offset;
+    }
+
+    public PackedField(int mask) {
+        this.offset = 0;
+        this.mask = mask;
+    }
+
+    public int get(int src) {
+        return (src & mask) >> offset;
+    }
+
+    public int getNoShift(int src) {
+        return src & mask;
+    }
+
+    public boolean bool(int src) {
+        return get(src) != 0;
+    }
+
+    public int set(int src, int val) {
+        return (src &~ mask) | ((val << offset) & mask);
+    }
+
+    public int setNoShift(int src, int val) {
+        return (src &~ mask) | (val & mask);
+    }
+}
diff --git a/game/core/src/main/java/net/minecraft/core/util/helper/NBTFormatHelper.java b/game/core/src/main/java/net/minecraft/core/util/helper/NBTFormatHelper.java
new file mode 100644
index 000000000..ba891371a
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/util/helper/NBTFormatHelper.java
@@ -0,0 +1,402 @@
+package net.minecraft.core.util.helper;
+
+import net.minecraft.core.item.ItemStack;
+import net.minecraft.core.net.command.TextFormatting;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Arrays;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class NBTFormatHelper {
+
+    //removes version and expanded tags, modifies id, damage, and tile entity data
+    private static final Pattern VERSION_PATTERN = Pattern.compile("\"?(Version)\"?\\s*:\\s*([^,;{}]+|\"[^\"]*\")\\s*[;,]?\\s*");
+    private static final Pattern EXPANDED_PATTERN = Pattern.compile("\"?(Expanded)\"?\\s*:\\s*([^,;{}]+|\"[^\"]*\")\\s*[;,]?\\s*");
+    private static final Pattern ID_PATTERN = Pattern.compile("\"?id\"?\\s*:\\s*(-?\\d+)");
+    private static final Pattern DAMAGE_PATTERN = Pattern.compile("\"?Damage\"?\\s*:\\s*(-?\\d+)");
+    private static final Pattern TILE_ENTITY_DATA_PATTERN = Pattern.compile("\"?tileEntityData\"?\\s*:\\s*(?=\\{)");
+
+    //key patterns to match/look for
+    private static final Pattern EMPTY_KEY_PATTERN = Pattern.compile("\"\"\\s*:\\s*");
+    private static final Pattern QUOTED_KEY_PATTERN = Pattern.compile("\"([^\"]+)\"\\s*:");
+
+    //formatNBTTooltip strips out a lot of whitespace, this char is used to replace it if it needs to be preserved
+    private static final char VISIBLE_SPACE = '\u00A0';
+
+    public static @NotNull String formatNbtTooltip(String nbtString) {
+        nbtString = VERSION_PATTERN.matcher(nbtString).replaceAll("");
+        nbtString = EXPANDED_PATTERN.matcher(nbtString).replaceAll("");
+        nbtString = processTopLevelTileEntityData(nbtString);
+        nbtString = resolveEmptyKeys(nbtString);
+        nbtString = QUOTED_KEY_PATTERN.matcher(nbtString).replaceAll("$1:");
+
+        boolean[] inlineMask = getInlineMask(nbtString);
+        StringBuilder stringBuilder = new StringBuilder(nbtString.length() * 2);
+
+        int indentLevel = 0;
+        boolean inQuotes = false;
+        boolean isKey = true;
+        boolean colorNeedsUpdate = true;
+        boolean[] inObjectStack = new boolean[128];
+
+        TextFormatting syntaxColor = TextFormatting.GRAY;
+        TextFormatting contentColor = TextFormatting.LIGHT_GRAY;
+        TextFormatting titleColor = TextFormatting.MAGENTA;
+
+        for (int i = 0; i < nbtString.length(); i++) {
+            char charAt = nbtString.charAt(i);
+            boolean inline = inlineMask[i];
+
+            if (charAt == '"') {
+                if (!inQuotes) {
+                    stringBuilder.append(contentColor).append(charAt);
+                    inQuotes = true;
+                } else {
+                    stringBuilder.append(charAt);
+                    inQuotes = false;
+                    colorNeedsUpdate = true;
+                }
+                continue;
+            }
+
+            if (inQuotes) {
+                stringBuilder.append(charAt);
+                continue;
+            }
+
+            if (charAt == ' ' || charAt == '\n' || charAt == '\r' || charAt == '\t') continue;
+
+            if (charAt == '{' || charAt == '[' || charAt == '(') {
+                indentLevel++;
+                if (indentLevel >= inObjectStack.length) {
+                    inObjectStack = Arrays.copyOf(inObjectStack, inObjectStack.length * 2);
+                }
+
+                boolean isObj = (charAt == '{' || charAt == '(');
+                inObjectStack[indentLevel] = isObj;
+                isKey = isObj;
+
+                stringBuilder.append(syntaxColor).append(charAt);
+                colorNeedsUpdate = true;
+
+                char nextChar = nextNonWhitespace(nbtString, i + 1);
+                if (isMatchingClose(charAt, nextChar)) {
+                    stringBuilder.append(nextChar);
+                    while (i + 1 < nbtString.length() && Character.isWhitespace(nbtString.charAt(i + 1))) i++;
+                    i++;
+                    indentLevel--;
+                } else {
+                    stringBuilder.append(inline ? " " : "\n" + getIndent(indentLevel));
+                }
+            } else if (charAt == '}' || charAt == ']' || charAt == ')') {
+                indentLevel = Math.max(0, indentLevel - 1);
+                stringBuilder.append(inline ? " " : "\n" + getIndent(indentLevel));
+                stringBuilder.append(syntaxColor).append(charAt);
+                colorNeedsUpdate = true;
+            } else if (charAt == ',' || charAt == ';') {
+                stringBuilder.append(syntaxColor).append(charAt);
+                colorNeedsUpdate = true;
+                isKey = indentLevel > 0 && inObjectStack[indentLevel];
+
+                char nextChar = nextNonWhitespace(nbtString, i + 1);
+                if (nextChar != '}' && nextChar != ']' && nextChar != ')') {
+                    if (!inline) {
+                        stringBuilder.append('\n').append(getIndent(indentLevel));
+                    } else {
+                        stringBuilder.append(' ');
+                    }
+                }
+            } else if (charAt == ':') {
+                stringBuilder.append(syntaxColor).append(": ");
+                colorNeedsUpdate = true;
+                isKey = false;
+            } else {
+                if (colorNeedsUpdate) {
+                    stringBuilder.append(isKey ? titleColor : contentColor);
+                    colorNeedsUpdate = false;
+                }
+                stringBuilder.append(charAt);
+            }
+        }
+
+        return stringBuilder.toString();
+    }
+
+    private static @NotNull String resolveEmptyKeys(@NotNull String nbt) {
+        Matcher keyMatcher = EMPTY_KEY_PATTERN.matcher(nbt);
+        StringBuilder result = new StringBuilder(nbt.length());
+        int lastEnd = 0;
+
+        while (keyMatcher.find()) {
+            int matchStart = keyMatcher.start();
+            int matchEnd = keyMatcher.end();
+            result.append(nbt, lastEnd, matchStart);
+
+            int openBrace = nbt.indexOf('{', matchEnd);
+
+            if (openBrace != -1 && isOnlyWhitespace(nbt, matchEnd, openBrace)) {
+                int closeBrace = findMatchingBrace(nbt, openBrace);
+                if (closeBrace != -1) {
+                    CharSequence block = nbt.subSequence(openBrace, closeBrace + 1);
+                    Matcher idMatcher = ID_PATTERN.matcher(block);
+
+                    if (idMatcher.find()) {
+                        try {
+                            int id = Integer.parseInt(idMatcher.group(1));
+                            int damage = 0;
+                            Matcher dmgMatcher = DAMAGE_PATTERN.matcher(block);
+                            if (dmgMatcher.find()) {
+                                damage = Integer.parseInt(dmgMatcher.group(1));
+                            }
+
+                            String itemKey = "Item" + VISIBLE_SPACE + VISIBLE_SPACE + id;
+                            try {
+                                ItemStack stack = new ItemStack(id, 1, damage);
+                                itemKey = stack.getItemKey();
+                            } catch (Exception ignored) {}
+
+                            result.append(itemKey).append(":");
+                            lastEnd = matchEnd;
+                            continue;
+                        } catch (NumberFormatException ignored) {}
+                    }
+                }
+            }
+
+            result.append("Tag:");
+            lastEnd = matchEnd;
+        }
+
+        result.append(nbt, lastEnd, nbt.length());
+        return result.toString();
+    }
+
+    private static @NotNull String processTopLevelTileEntityData(@NotNull String nbt) {
+        Matcher matcher = TILE_ENTITY_DATA_PATTERN.matcher(nbt);
+        StringBuilder result = new StringBuilder(nbt.length());
+        int searchStart = 0;
+
+        while (matcher.find(searchStart)) {
+            int openBrace = matcher.end();
+            int closeBrace = findMatchingBrace(nbt, openBrace);
+
+            if (closeBrace != -1) {
+                result.append(nbt, searchStart, openBrace + 1);
+
+                String inner = nbt.substring(openBrace + 1, closeBrace);
+                result.append(collapseAllTileEntityData(inner));
+
+                searchStart = closeBrace;
+            } else {
+                result.append(nbt, searchStart, openBrace);
+                searchStart = openBrace;
+            }
+        }
+
+        result.append(nbt, searchStart, nbt.length());
+        return result.toString();
+    }
+
+    private static @NotNull String collapseAllTileEntityData(@NotNull String nbt) {
+        Matcher matcher = TILE_ENTITY_DATA_PATTERN.matcher(nbt);
+        StringBuilder result = new StringBuilder(nbt.length());
+        int searchStart = 0;
+
+        while (matcher.find(searchStart)) {
+            int openBrace = matcher.end();
+            int closeBrace = findMatchingBrace(nbt, openBrace);
+
+            if (closeBrace != -1) {
+                int tags = countTags(nbt, openBrace, closeBrace);
+                String tagWord = tags == 1 ? "Tag" : "Tags";
+                String replacement = "<" + tags + VISIBLE_SPACE + tagWord + ">";
+
+                result.append(nbt, searchStart, openBrace);
+                result.append(replacement);
+
+                searchStart = closeBrace + 1;
+            } else {
+                result.append(nbt, searchStart, openBrace);
+                searchStart = openBrace;
+            }
+        }
+
+        result.append(nbt, searchStart, nbt.length());
+        return result.toString();
+    }
+
+    private static boolean @NotNull [] getInlineMask(@NotNull String nbt) {
+        boolean[] mask = new boolean[nbt.length()];
+
+        for (int i = 0; i < nbt.length(); i++) {
+            char charAt = nbt.charAt(i);
+            if (charAt == '{' || charAt == '[') {
+                int closeIndex = findMatchingBrace(nbt, i);
+                if (closeIndex != -1) {
+                    boolean isFlat = true;
+                    boolean inQuotes = false;
+                    boolean escaped = false;
+
+                    for (int j = i + 1; j < closeIndex; j++) {
+                        char inner = nbt.charAt(j);
+                        if (escaped) {
+                            escaped = false;
+                            continue;
+                        }
+                        if (inner == '\\') {
+                            escaped = true;
+                            continue;
+                        }
+                        if (inner == '"') {
+                            inQuotes = !inQuotes;
+                        } else if (!inQuotes) {
+                            if (inner == '{' || inner == '[' || inner == '}' || inner == ']') {
+                                isFlat = false;
+                                break;
+                            }
+                        }
+                    }
+
+                    if (isFlat) {
+                        for (int j = i; j <= closeIndex; j++) {
+                            mask[j] = true;
+                        }
+                    }
+                }
+            }
+        }
+
+        String target = VISIBLE_SPACE + "Tag";
+        int start = 0;
+
+        while (true) {
+            int index = nbt.indexOf(target, start);
+            if (index == -1) break;
+
+            int openBrace = -1;
+            int depth = 0;
+            boolean inQuotes = false;
+            for (int i = index; i >= 0; i--) {
+                char charAt = nbt.charAt(i);
+                if (charAt == '"') inQuotes = !inQuotes;
+                if (!inQuotes) {
+                    if (charAt == '}' || charAt == ']' || charAt == ')') depth++;
+                    else if (charAt == '{' || charAt == '[' || charAt == '(') {
+                        if (depth == 0) {
+                            openBrace = i;
+                            break;
+                        } else {
+                            depth--;
+                        }
+                    }
+                }
+            }
+
+            if (openBrace != -1) {
+                int closeBrace = findMatchingBrace(nbt, openBrace);
+                if (closeBrace != -1) {
+                    for (int i = openBrace; i <= closeBrace; i++) {
+                        mask[i] = true;
+                    }
+                }
+            }
+            start = index + target.length();
+        }
+        return mask;
+    }
+
+    private static int findMatchingBrace(@NotNull String str, int startIndex) {
+        int depth = 0;
+        boolean inQuotes = false;
+        boolean escaped = false;
+
+        for (int i = startIndex; i < str.length(); i++) {
+            char charAt = str.charAt(i);
+
+            if (escaped) {
+                escaped = false;
+                continue;
+            }
+            if (charAt == '\\') {
+                escaped = true;
+                continue;
+            }
+            if (charAt == '"') {
+                inQuotes = !inQuotes;
+            } else if (!inQuotes) {
+                if (charAt == '{') depth++;
+                else if (charAt == '}') {
+                    depth--;
+                    if (depth == 0) return i;
+                }
+            }
+        }
+        return -1;
+    }
+
+    private static int countTags(String nbt, int openBrace, int closeBrace) {
+        int depth = 0;
+        int tags = 0;
+        boolean inQuotes = false;
+        boolean escaped = false;
+        boolean hasContent = false;
+
+        for (int i = openBrace; i <= closeBrace; i++) {
+            char charAt = nbt.charAt(i);
+
+            if (escaped) {
+                escaped = false;
+                continue;
+            }
+            if (charAt == '\\') {
+                escaped = true;
+                continue;
+            }
+            if (charAt == '"') {
+                inQuotes = !inQuotes;
+            } else if (!inQuotes) {
+                if (charAt == '{' || charAt == '[') depth++;
+                else if (charAt == '}' || charAt == ']') {
+                    depth--;
+                    if (depth == 0 && charAt == '}') {
+                        if (hasContent) tags++;
+                        break;
+                    }
+                } else if (charAt == ',' && depth == 1) {
+                    tags++;
+                    hasContent = false;
+                } else if (!Character.isWhitespace(charAt)) {
+                    hasContent = true;
+                }
+            }
+        }
+        return tags;
+    }
+
+    private static boolean isOnlyWhitespace(String str, int start, int end) {
+        for (int i = start; i < end; i++) {
+            if (!Character.isWhitespace(str.charAt(i))) return false;
+        }
+        return true;
+    }
+
+    private static char nextNonWhitespace(@NotNull String str, int startIndex) {
+        for (int i = startIndex; i < str.length(); i++) {
+            char charAt = str.charAt(i);
+            if (!Character.isWhitespace(charAt)) return charAt;
+        }
+        return '\0';
+    }
+
+    private static boolean isMatchingClose(char open, char close) {
+        return (open == '{' && close == '}') ||
+                (open == '[' && close == ']') ||
+                (open == '(' && close == ')');
+    }
+
+    private static @NotNull String getIndent(int level) {
+        if (level <= 0) return "";
+        return "  ".repeat(level);
+    }
+}
\ No newline at end of file
diff --git a/game/core/src/main/java/net/minecraft/core/world/ICarriable.java b/game/core/src/main/java/net/minecraft/core/world/ICarriable.java
index a7aefa966..d2a19fd8d 100644
--- a/game/core/src/main/java/net/minecraft/core/world/ICarriable.java
+++ b/game/core/src/main/java/net/minecraft/core/world/ICarriable.java
@@ -25,15 +25,9 @@ public interface ICarriable {
      */
     void drop(World world, Entity holder);
 
-    /**
-     * Called before an entity tries to pick up the object
-     */
-    boolean canBeCarried(World world, Entity potentialHolder);
-
-    /**
-     * Called by an entity when attempting to pick up the object
-     */
-    ICarriable pickup(World world, Entity holder);
+    // moved to ICarriableSource
+    // boolean canBeCarried(World world, Entity potentialHolder);
+    // ICarriable pickup(World world, Entity holder, TilePosc tilePos);
 
     void writeToNBT(CompoundTag tag);
 
diff --git a/game/core/src/main/java/net/minecraft/core/world/ICarrySource.java b/game/core/src/main/java/net/minecraft/core/world/ICarrySource.java
new file mode 100644
index 000000000..4a12e9ec3
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/world/ICarrySource.java
@@ -0,0 +1,25 @@
+package net.minecraft.core.world;
+
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import net.minecraft.core.entity.Entity;
+import net.minecraft.core.world.pos.TilePosc;
+
+public interface ICarrySource {
+    @NotNull ICarriable pickup(@NotNull World world, @NotNull Entity holder, @NotNull TilePosc tilePos);
+
+    public static @Nullable ICarrySource getFromWorld(@NotNull World world, @NotNull TilePosc tilePos) {
+        if (world.getTileEntity(tilePos) instanceof ICarrySource carryTE) return carryTE;
+        if (world.getBlockType(tilePos).getLogic() instanceof ICarrySource carryTile) return carryTile;
+        return null;
+    }
+
+    public static @Nullable ICarrySource tryPickUpFromWorld(@NotNull World world, @NotNull Entity holder, @NotNull TilePosc tilePos) {
+        final @Nullable var carry = getFromWorld(world, tilePos);
+        if (carry != null) {
+            holder.setHeldObject(carry.pickup(world, holder, tilePos));
+        }
+        return carry;
+    }
+}
diff --git a/game/core/src/main/java/net/minecraft/core/world/pos/TilePosc.java b/game/core/src/main/java/net/minecraft/core/world/pos/TilePosc.java
index 8824a90f0..5c72e0ff5 100644
--- a/game/core/src/main/java/net/minecraft/core/world/pos/TilePosc.java
+++ b/game/core/src/main/java/net/minecraft/core/world/pos/TilePosc.java
@@ -7,9 +7,9 @@ import net.minecraft.core.util.helper.Side;
 import net.minecraft.core.world.WorldSource;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
-import org.joml.Vector3d;
 import org.joml.Vector3dc;
 import org.joml.Vector3fc;
+import org.joml.Vector3i;
 import org.joml.Vector3ic;
 
 /**
@@ -22,6 +22,10 @@ public interface TilePosc {
 
     int z();
 
+    default @NotNull Vector3ic vec() {
+        return new Vector3i(x(), y(), z());
+    }
+
     int get(int component);
 
     int get(@NotNull Axis axis);
diff --git a/game/server/src/main/java/net/minecraft/server/world/ServerPlayerController.java b/game/server/src/main/java/net/minecraft/server/world/ServerPlayerController.java
index 896e67870..17046ee13 100644
--- a/game/server/src/main/java/net/minecraft/server/world/ServerPlayerController.java
+++ b/game/server/src/main/java/net/minecraft/server/world/ServerPlayerController.java
@@ -10,8 +10,10 @@ import net.minecraft.core.item.Item;
 import net.minecraft.core.item.ItemStack;
 import net.minecraft.core.net.packet.PacketBlockUpdate;
 import net.minecraft.core.util.helper.Side;
+import net.minecraft.core.world.ICarrySource;
 import net.minecraft.core.world.LevelListener;
 import net.minecraft.core.world.World;
+import net.minecraft.core.world.pos.TilePos;
 import net.minecraft.server.entity.player.PlayerServer;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
@@ -164,14 +166,11 @@ public class ServerPlayerController
     public boolean useOrPlaceItemStackOnTile(Player entityplayer, World world, ItemStack itemstack, int blockX, int blockY, int blockZ, Side side, double xPlaced, double yPlaced)
     {
         if (!entityplayer.getGamemode().canInteract()) return false;
-        int blockId = world.getBlockId(blockX, blockY, blockZ);
+        final var pos = new TilePos(blockX, blockY, blockZ);
+        int blockId = world.getBlockType(pos).id();
         if (player.getHeldObject() == null) {
-            if (player.isSneaking() && itemstack == null) { // If crouched and hand empty try to pickup block entity
-                TileEntity tileEntity = world.getTileEntity(blockX, blockY, blockZ);
-                if (tileEntity != null && tileEntity.canBeCarried(world, player)) {
-                    player.setHeldObject(tileEntity.pickup(world, player));
-                    return true;
-                }
+            if (player.isSneaking() && itemstack == null) {
+                if (ICarrySource.tryPickUpFromWorld(world, player, pos) != null) return true;
             }
             if (!player.isSneaking() || itemstack == null) {  // If standing or hand empty perform block right click method
                 if (blockId > 0 && Blocks.blocksList[blockId].onBlockRightClicked(world, blockX, blockY, blockZ, player, side, xPlaced, yPlaced)) {
diff --git a/gradle.properties b/gradle.properties
index 37ab4b219..7b5e78a90 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -3,6 +3,6 @@ org.gradle.daemon = true
 org.gradle.caching = true
 
 bta.group = net.minecraft
-bta.version = 7.4
+bta.version = 8.0
 bta.assetPackFormat = 4
 bta.buildChannel = BuildChannel.DEVELOPMENT
\ No newline at end of file
diff --git a/util/nbt/src/main/java/com/mojang/nbt/tags/ListTag.java b/util/nbt/src/main/java/com/mojang/nbt/tags/ListTag.java
index 0b4ffe9f0..e9854d0cf 100644
--- a/util/nbt/src/main/java/com/mojang/nbt/tags/ListTag.java
+++ b/util/nbt/src/main/java/com/mojang/nbt/tags/ListTag.java
@@ -108,7 +108,7 @@ public class ListTag
     @Override
     public String toString()
     {
-        if (getValue().size() > 16)
+        if (getValue().size() > 27)
         {
             return "\"" + getTagName() + "\": [ " + getValue().size() + " elements ]";
         }
