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 535d28847..5deb76c64 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
@@ -30,7 +30,6 @@ public abstract class TileEntity implements ICarriable
     public TileEntity()
     {
     }
-
     public void tick()
     {
     }
@@ -159,29 +158,24 @@ public abstract class TileEntity implements ICarriable
 
     @Override
     public void drop(World world, Entity holder) {
+        if (world.isClientSide) return;
+        if (ICarriable.tryPlaceAroundHolder(this, world, holder)) return;
+
         final int holderX = MathHelper.floor(holder.x);
         final int holderY = MathHelper.floor(holder.y);
         final int holderZ = MathHelper.floor(holder.z);
-        for (int _y = holderY - 1; _y <= holderY + 1; _y++) {
-            for (int _x = holderX - 1; _x <= holderX + 1; _x++) {
-                for (int _z = holderZ - 1; _z <= holderZ + 1; _z++) {
-                    if (tryPlace(world, holder, _x, _y - 1, _z, Side.TOP, 0, 0)) {
-                        return;
-                    }
-                }
-            }
-        }
-        if (world.isClientSide) return;
-        dropContents(world, holderX, holderY, holderZ);
+        this.dropContents(world, holderX, holderY, holderZ);
         assert carriedBlock != null;
         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
     }
 
+    // TODO: clean this up in the future tileEntity refactor
     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);
         world.setBlockRaw(tilePos.x, tilePos.y, tilePos.z, 0);
+        world.markBlockDirty(tilePos);
         final boolean isSignal = this.getBlock().isSignalSource();
         world.notifyBlocksInRadiusOfNeighborChange((isSignal) ? 2 : 1, tilePos, currentBlock);
         this.worldObj = null;
diff --git a/game/core/src/main/java/net/minecraft/core/block/piston/BlockLogicPistonBase.java b/game/core/src/main/java/net/minecraft/core/block/piston/BlockLogicPistonBase.java
index d066e7073..762cb1c4d 100644
--- a/game/core/src/main/java/net/minecraft/core/block/piston/BlockLogicPistonBase.java
+++ b/game/core/src/main/java/net/minecraft/core/block/piston/BlockLogicPistonBase.java
@@ -109,7 +109,7 @@ public class BlockLogicPistonBase extends BlockLogic {
         case EVENT_DETACH:
             break;
         case EVENT_DETACH_EXTEND:
-            world.setBlockData(tilePos, IS_EXTENDED.set(data, isOut ? 1 : 0));
+            world.setBlockDataNotify(tilePos, IS_EXTENDED.set(data, isOut ? 1 : 0));
             break;
         case EVENT_EXTEND:
             if (isOut) {
@@ -135,15 +135,16 @@ public class BlockLogicPistonBase extends BlockLogic {
             assert false;
             return;
         case EVENT_DETACH:
-            world.playBlockEvent(tilePos, LevelListener.EVENT_DISPENSER_PARTICLES, DIRECTION.get(data));
+            this.emitParticles(world, tilePos, Direction.fromId(DIRECTION.get(data)));
             sound = (isOut) ? "tile.piston.detached" : "tile.piston.attached";
-            volumn = 1.0F;
+            volumn = 0.5F;
             pitch  = 0.6F;
             break;
         case EVENT_DETACH_EXTEND:
-            world.playBlockEvent(tilePos, LevelListener.EVENT_DISPENSER_PARTICLES, DIRECTION.get(data));
+            this.emitParticles(world, tilePos, Direction.fromId(DIRECTION.get(data)));
             sound = (isOut) ? "tile.piston.detached.out" : "tile.piston.detached.in";
-            volumn = pitch = 1.0F;
+            volumn = 0.2F;
+            pitch  = 0.5F;
             break;
         case EVENT_EXTEND:
             sound = (isOut) ? "tile.piston.out" : "tile.piston.in";
@@ -154,7 +155,7 @@ public class BlockLogicPistonBase extends BlockLogic {
 
         world.playSoundEffect(null, SoundCategory.WORLD_SOUNDS, x, y, z, sound, volumn, pitch);
     }
-    
+
     private static boolean hasNeighborSignal(@NotNull World world, @NotNull TilePosc tilePos, @NotNull Direction dir) {
         final var queryPos = new TilePos();
         for (final var search : Direction.all) {
@@ -170,6 +171,37 @@ public class BlockLogicPistonBase extends BlockLogic {
         return false;
     }
 
+    private void emitParticles(@NotNull World world, @NotNull TilePosc pos, @NotNull Direction direction) {
+        final var random = world.rand;
+
+        var thickness = HEAD_BLOCK_MAP[this.typeId].getLogic().headThickness;
+
+        final double dx,dy,dz;
+        dx = direction.offsetX();
+        dy = direction.offsetY();
+        dz = direction.offsetZ();
+        
+        final double ox,oy,oz;
+        ox = pos.x() + 0.5 + dx * (0.5-thickness);
+        oy = pos.y() + 0.5 + dy * (0.5-thickness);
+        oz = pos.z() + 0.5 + dz * (0.5-thickness);
+
+        for (int i = 0; i < 8; i++) {
+            final double v = random.nextDouble() * 0x1p-5;
+
+            final double x,y,z;
+            x = ox + random.nextDouble() * 0x1p-2 * dx + random.nextDouble() * 0x1p-4 * (dy + dz);
+            y = oy + random.nextDouble() * 0x1p-2 * dy + random.nextDouble() * 0x1p-4 * (dx + dz);
+            z = oz + random.nextDouble() * 0x1p-2 * dz + random.nextDouble() * 0x1p-4 * (dx + dy);
+
+            final double vx,vy,vz;
+            vx = dx * v + random.nextGaussian() * 0x1p-7;
+            vy = dy * v + random.nextGaussian() * 0x1p-7;
+            vz = dz * v + random.nextGaussian() * 0x1p-7;
+            world.spawnParticle("smoke", x,y,z, vx, vy, vz, 0, 16.0, false);
+        }
+    }
+
     // Piston tests:
     // =================
 
@@ -385,6 +417,7 @@ public class BlockLogicPistonBase extends BlockLogic {
         final var moving = new TileEntityMovingPistonBlock(
             MovingType.RETRACTING_PISTON,
             this.block, dir.id, null, dir);
+        world.setBlockTypeRaw(tilePos, Blocks.AIR);
         world.setBlockTypeDataEntity(tilePos, Blocks.PISTON_MOVING, data, moving);
 
         if (this.canPull) {
@@ -397,7 +430,7 @@ public class BlockLogicPistonBase extends BlockLogic {
         // logically, retraction should only update head immediately, and base 3 ticks after
         // but this emulates the old behavior
         world.notifyBlocksOfNeighborChange(tilePos, this.block);
-        world.notifyBlocksOfNeighborChange(headPos, Blocks.AIR);
+        world.notifyBlocksOfNeighborChange(headPos, null);
     }
 
     private static void pull(@NotNull World world, @NotNull TilePosc headPos, @NotNull Direction dir) {
@@ -418,6 +451,7 @@ public class BlockLogicPistonBase extends BlockLogic {
 
         if (Movability.test(world, src) != Movability.MOVES) {
             world.removeTileEntity(tar);
+            world.setBlockType(tar, Blocks.AIR);
             return;
         }
         
@@ -477,16 +511,11 @@ public class BlockLogicPistonBase extends BlockLogic {
     @Override
     public @NotNull AABBdc getBoundsFromState(@NotNull WorldSource source, @NotNull TilePosc tilePos) {
         int data = getValidatedBaseData(source, tilePos, this);
-        if (!IS_DETACHED_OR_EXTENDED.bool(data)) return new AABBd(0.0F, 0.0F, 0.0F, 1.0F, 1.0F, 1.0F);
+        if (!IS_DETACHED_OR_EXTENDED.bool(data)) return this.bounds;
 
         final var dir = Direction.fromId(DIRECTION.get(data));
         final var thickness = HEAD_BLOCK_MAP[this.typeId].getLogic().headThickness;
-
-        final var min = new Vector3d(0);
-        final var max = new Vector3d(1);
-
-        dir.offsetScaled(dir.isPositive() ? max : min, (dir.isNegative()) ? thickness : -thickness);
-        return new AABBd(min, max);
+        return getBaseCollision(dir, thickness);
     }
 
     @Override
@@ -494,6 +523,12 @@ public class BlockLogicPistonBase extends BlockLogic {
         return false;
     }
 
+    @Override
+    public boolean renderAsNormalBlockOnCondition(@NotNull WorldSource source, @NotNull TilePosc tilePos) {
+        int data = getValidatedBaseData(source, tilePos, this);
+        return !IS_DETACHED_OR_EXTENDED.bool(data);
+    }
+
     private static final ISupport[] SUPPORT_LUT;
     static {
         final var F = FullSupport.INSTANCE;
diff --git a/game/core/src/main/java/net/minecraft/core/block/piston/BlockLogicPistonMoving.java b/game/core/src/main/java/net/minecraft/core/block/piston/BlockLogicPistonMoving.java
index 9cf4d026d..67f43c127 100644
--- a/game/core/src/main/java/net/minecraft/core/block/piston/BlockLogicPistonMoving.java
+++ b/game/core/src/main/java/net/minecraft/core/block/piston/BlockLogicPistonMoving.java
@@ -4,6 +4,7 @@ import net.minecraft.core.block.Blocks;
 import net.minecraft.core.block.entity.TileEntity;
 import net.minecraft.core.block.Block;
 import net.minecraft.core.block.BlockLogic;
+import net.minecraft.core.block.material.Material;
 import net.minecraft.core.block.material.Materials;
 import net.minecraft.core.entity.player.Player;
 import net.minecraft.core.enums.EnumDropCause;
@@ -13,6 +14,9 @@ import net.minecraft.core.world.WorldSource;
 import net.minecraft.core.world.World;
 import net.minecraft.core.world.pos.TilePosc;
 
+import static net.minecraft.core.block.piston.PistonCommon.HEAD_BLOCK_MAP;
+import static net.minecraft.core.block.piston.PistonCommon.getBaseCollision;
+
 import java.util.List;
 
 import org.jetbrains.annotations.NotNull;
@@ -82,17 +86,15 @@ public class BlockLogicPistonMoving extends BlockLogic {
         @NotNull World world, @NotNull TilePosc tilePos,
         @NotNull AABBdc aabb, @NotNull List<@NotNull AABBdc> aabbList
     ) {
-        if (!(world.getTileEntity(tilePos) instanceof TileEntityMovingPistonBlock moving)) {
-            super.getCollisionAABBs(world, tilePos, aabb, aabbList);
-            return;
-        }
-        if (moving.isExtending || !moving.isSourcePiston) {
-            super.getCollisionAABBs(world, tilePos, aabb, aabbList);
-            return;
-        }
         aabbList.add(this.getCollisionAABB(world, tilePos));
-        // prevents entities from phasing into piston base
-        aabbList.add(new AABBd(0, 0, 0, 1, 1, 1).translate(tilePos.x(), tilePos.y(), tilePos.z()));
+
+        if (!(world.getTileEntity(tilePos) instanceof TileEntityMovingPistonBlock moving)) return;
+
+        final boolean isRetractingPiston = !moving.isExtending && moving.isSourcePiston;
+        if (!isRetractingPiston || !(moving.movingBlock.getLogic() instanceof BlockLogicPistonBase base)) return;
+
+        final var thickness = HEAD_BLOCK_MAP[base.typeId].getLogic().headThickness;        
+        aabbList.add(getBaseCollision(moving.direction, thickness).translate(tilePos.x(), tilePos.y(), tilePos.z()));
         return;
     }
 
@@ -111,4 +113,9 @@ public class BlockLogicPistonMoving extends BlockLogic {
         }
         return super.getBoundsFromState(source, tilePos);
     }
+
+    @Override
+    public int getPistonPushReaction(@NotNull World world, @NotNull TilePosc tilePos) {
+        return Material.PISTON_CANT_PUSH;
+    }
 }
diff --git a/game/core/src/main/java/net/minecraft/core/block/piston/CarriedPistonHead.java b/game/core/src/main/java/net/minecraft/core/block/piston/CarriedPistonHead.java
index a67feeed1..51abfd297 100644
--- a/game/core/src/main/java/net/minecraft/core/block/piston/CarriedPistonHead.java
+++ b/game/core/src/main/java/net/minecraft/core/block/piston/CarriedPistonHead.java
@@ -8,8 +8,10 @@ import net.minecraft.core.block.Block;
 import net.minecraft.core.block.motion.ICarriedBlock;
 import net.minecraft.core.entity.Entity;
 import net.minecraft.core.entity.Mob;
+import net.minecraft.core.enums.EnumDropCause;
 import net.minecraft.core.enums.PlacementMode;
 import net.minecraft.core.util.helper.Direction;
+import net.minecraft.core.util.helper.MathHelper;
 import net.minecraft.core.util.helper.Side;
 import net.minecraft.core.world.ICarriable;
 import net.minecraft.core.world.World;
@@ -83,6 +85,16 @@ public class CarriedPistonHead implements ICarriable, ICarriedBlock {
 
     @Override
     public void drop(World world, Entity holder) {
+        if (world.isClientSide) return;
+        if (ICarriable.tryPlaceAroundHolder(this, world, holder)) return;
+
+        final int holderX = MathHelper.floor(holder.x);
+        final int holderY = MathHelper.floor(holder.y);
+        final int holderZ = MathHelper.floor(holder.z);
+
+        this.block().dropWithCause(
+            world, EnumDropCause.WORLD, new TilePos(holderX, holderY, holderZ),
+            this.metadata(), null, null);
     }
 
     @Override
diff --git a/game/core/src/main/java/net/minecraft/core/block/piston/PistonCommon.java b/game/core/src/main/java/net/minecraft/core/block/piston/PistonCommon.java
index ebf72e971..568e7f29c 100644
--- a/game/core/src/main/java/net/minecraft/core/block/piston/PistonCommon.java
+++ b/game/core/src/main/java/net/minecraft/core/block/piston/PistonCommon.java
@@ -1,6 +1,8 @@
 package net.minecraft.core.block.piston;
 
 import org.jetbrains.annotations.NotNull;
+import org.joml.Vector3d;
+import org.joml.primitives.AABBd;
 
 import net.minecraft.core.block.Block;
 import net.minecraft.core.block.Blocks;
@@ -26,8 +28,8 @@ public abstract class PistonCommon {
     
     public static final PackedField NEW_FORMAT_SIGNATURE = new PackedField(7, 1);
 
-    public static final PackedField IS_STICKY   = new PackedField(4, 1);
-    public static final PackedField IS_STEEL    = new PackedField(5, 1);
+    public static final PackedField IS_STICKY   = new PackedField(3, 1);
+    public static final PackedField IS_STEEL    = new PackedField(4, 1);
 
     public static final PackedField IS_DETACHED_OR_EXTENDED = new PackedField(
         IS_DETACHED.mask | IS_EXTENDED.mask
@@ -153,6 +155,13 @@ public abstract class PistonCommon {
             ? getValidatedBaseData(world, bpos, base)
             : DATA_INVALID; 
     }
+    
+    public static @NotNull AABBd getBaseCollision(@NotNull Direction dir, double headThickness) {
+        final var min = new Vector3d(0);
+        final var max = new Vector3d(1);
+        dir.offsetScaled(dir.isPositive() ? max : min, -headThickness);
+        return new AABBd(min, max);
+    }
 
     public static enum MovingType {
         EXTENDING(true, false),
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 8822b3578..cdde69de5 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
@@ -6,9 +6,13 @@ import net.minecraft.core.block.Block;
 import net.minecraft.core.block.Blocks;
 import net.minecraft.core.util.helper.Direction;
 import com.mojang.nbt.tags.CompoundTag;
+
+import net.minecraft.core.world.pos.ChunkPos;
 import net.minecraft.core.world.pos.TilePos;
+
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
+import org.joml.Vector3d;
 import org.joml.primitives.AABBd;
 import org.joml.primitives.AABBdc;
 
@@ -30,10 +34,16 @@ public class TileEntityMovingPistonBlock extends TileEntity
     float progressCur = 0F, progressPrev = 0F;
 
     // these would otherwise be reallocated each tick, might as well store them here
-    @NotNull AABBd collision = new AABBd(); 
-    @NotNull AABBd bounds = new AABBd();
+    final @NotNull AABBd bounds = new AABBd();
+    final @NotNull AABBd collision = new AABBd(); 
+    final @NotNull AABBd sweptCollision = new AABBd();
+    final @NotNull Vector3d directionScaled = new Vector3d();
+
+    // awkward
+    // TODO provide an actual initiallization event that is guaranteed to be called after
+    // TODO   tile entity is valid (i.e. not .validate())
+    private boolean isCollisionInited = false;
 
-    
     public @NotNull Block<?> movingBlock() { return this.movingBlock; }
     public int movingBlockData() { return this.movingBlockData; }
     public @Nullable TileEntity movingTileEntity() { return this.movingTileEntity; }
@@ -65,72 +75,63 @@ public class TileEntityMovingPistonBlock extends TileEntity
         this.direction = direction;
         this.isExtending = type.isExtending;
         this.isSourcePiston = type.isSourcePiston;
+
+        this.movingDirection().getOffsetScaled(this.directionScaled, PROGRESS_DELTA);
     }
 
-    private final void initAABBs() {
-        double dx,dy,dz;
-        final var sign = (this.isExtending) ? -1 : 1;
-        dx = sign * this.direction.offsetX();
-        dy = sign * this.direction.offsetY();
-        dz = sign * this.direction.offsetZ();
+    @Override
+    public void validate() {
+        super.validate();
+        
+        if (this.worldObj == null) return;
+        if (!this.worldObj.isChunkLoaded(new ChunkPos(this.tilePos))) return;
+
+        this.initCollision();
+    }
+
+    private final void initCollision() {
+        assert this.worldObj != null;
+        assert this.worldObj.isChunkLoaded(new ChunkPos(this.tilePos));
 
+        final var dir = this.movingDirection();
+
+        final var off = dir.opposite().getOffset(new Vector3d());
         this.bounds
-            .set(this.movingBlock.getBoundsFromState(this.worldObj, tilePos))
-            .translate(dx, dy, dz);
+            .set(this.movingBlock.getBoundsFromState(this.worldObj, this.tilePos))
+            .translate(off);
 
         var collision = this.movingBlock.getCollisionAABB(this.worldObj, this.tilePos);
         if (collision == null) collision = new AABBd(0,0,0,0,0,0);
-
         this.collision
             .set(collision)
-            .translate(dx, dy, dz);
-    }
+            .translate(off);
 
-    private final void translateAABBs(float scale) {
-        final float dx,dy,dz;
-        scale = (this.isExtending) ? scale : -scale;
-        dx = scale * this.direction.offsetX();
-        dy = scale * this.direction.offsetY();
-        dz = scale * this.direction.offsetZ();
-        this.bounds.translate(dx, dy, dz);
-        this.collision.translate(dx, dy, dz);
+        this.isCollisionInited = true;
     }
 
+
     @Override
     public void tick() {
+        if (!this.isCollisionInited) this.initCollision();
         this.progressPrev = this.progressCur;
-        if (this.progressCur == 0.0f) this.initAABBs();
         if (this.progressCur > (1.0F - 0x1p-12F)) {
             this.finalTick();
         } else {
             this.progressCur += PROGRESS_DELTA;
-            this.translateAABBs(PROGRESS_DELTA);
-            this.moveCollidedEntities(PROGRESS_DELTA + 0x1p-7F); 
-        }
-    }
 
-    private final void moveCollidedEntities(float scale) {
-        assert this.worldObj != null;
+            this.sweptCollision.set(this.collision);
+            this.sweptCollision.translate(this.directionScaled);
+            this.sweptCollision.union(this.collision);
 
-        final var dir = this.movingDirection();
-        final float dx,dy,dz;
-        dx = dir.offsetX() * scale;
-        dy = dir.offsetY() * scale;
-        dz = dir.offsetZ() * scale;
-
-        final int axis = dir.id >> 1; // y z x
-        final boolean neg = (dir.id & 1) == 0;
-        final double lim = 0x1p-4;
-
-        final var entities = this.worldObj.getEntitiesWithinAABBExcludingEntity(null, this.collision);
-        for (final @NotNull var e : entities) {
-            if (e.noPhysics) continue;
-            e.move(dx, dy, dz);
-            switch (axis) {
-                case 2 -> e.xd = (neg) ? Math.min(e.xd, -lim) : Math.max(e.xd, lim);
-                case 0 -> e.yd = (neg) ? Math.min(e.yd, -lim) : Math.max(e.yd, lim);
-                case 1 -> e.zd = (neg) ? Math.min(e.zd, -lim) : Math.max(e.zd, lim);
+            final var entities = this.worldObj.getEntitiesWithinAABBExcludingEntity(null, this.sweptCollision);
+            for (final var e : entities) {
+                if (e.noPhysics) continue;
+                e.move(this.directionScaled.x, this.directionScaled.y, this.directionScaled.z);
             }
+
+            // translate block collision after entities are moved
+            this.collision.translate(this.directionScaled);
+            this.bounds.translate(this.directionScaled);
         }
     }
 
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 fc9c59284..453204786 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
@@ -41,8 +41,10 @@ public class ItemDoor extends Item {
         Direction leftDir = player.getHorizontalPlacementDirection(side).rotateY(1);
         int meta = leftDir.legacyHorizontalIndex();
 
-        int isSolidBlockLeft = (isSupported(world, blockPos, leftDir, false) ? 1 : 0) + (isSupported(world, blockPos.up(new TilePos()), leftDir, false) ? 1 : 0);
-        int isSolidBlockRight = (isSupported(world, blockPos, leftDir.opposite(), true) ? 1 : 0) + (isSupported(world, blockPos.up(new TilePos()), leftDir.opposite(), true) ? 1 : 0);
+        final var upperPos = blockPos.up(new TilePos());
+
+        int isSolidBlockLeft = (isSupported(world, blockPos, leftDir, false) ? 1 : 0) + (isSupported(world, upperPos, leftDir, false) ? 1 : 0);
+        int isSolidBlockRight = (isSupported(world, blockPos, leftDir.opposite(), true) ? 1 : 0) + (isSupported(world, upperPos, leftDir.opposite(), true) ? 1 : 0);
         boolean isDoorLeft = world.getBlockType(blockPos.add(leftDir, new TilePos())) == this.doorBlockBottom || world.getBlockType(blockPos.add(leftDir, new TilePos()).up(new TilePos())) == this.doorBlockTop;
         boolean isDoorRight = world.getBlockType(blockPos.sub(leftDir, new TilePos())) == this.doorBlockBottom || world.getBlockType(blockPos.sub(leftDir, new TilePos()).up(new TilePos())) == this.doorBlockTop;
         boolean isMirrored = false;
@@ -68,15 +70,17 @@ public class ItemDoor extends Item {
         }
 
         world.setBlockTypeData(blockPos, this.doorBlockBottom, meta);
-        world.setBlockTypeData(blockPos.up(new TilePos()), this.doorBlockTop, meta);
+        world.setBlockTypeData(upperPos, 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);
+        this.doorBlockTop.onPlacedByMob(world, upperPos, side, player, xHit, yHit);
 
         // this is technically incorrect as both positions are updated as bottom door,
         // but does it really matter?
         world.notifyBlocksInCapsuleOfNeighborChange(Direction.UP, blockPos, this.doorBlockBottom);
+        world.markBlockNeedsUpdate(blockPos);
+        world.markBlockNeedsUpdate(upperPos);
 
         world.playBlockSoundEffect(player, blockPos.x() + 0.5F, blockPos.y() + 0.5F, blockPos.z() + 0.5F, this.doorBlockBottom, EnumBlockSoundEffectType.PLACE);
         return true;
diff --git a/game/core/src/main/java/net/minecraft/core/util/helper/Direction.java b/game/core/src/main/java/net/minecraft/core/util/helper/Direction.java
index a34f6a9d6..ea3aacaa6 100644
--- a/game/core/src/main/java/net/minecraft/core/util/helper/Direction.java
+++ b/game/core/src/main/java/net/minecraft/core/util/helper/Direction.java
@@ -11,12 +11,12 @@ import net.minecraft.core.entity.Mob;
 import net.minecraft.core.world.pos.TilePos;
 
 public enum Direction {
-    DOWN  (0b000, "north"),
-    UP    (0b001, "east"),
-    NORTH (0b010, "south"),
-    SOUTH (0b011, "west"),
-    WEST  (0b100, "up"),
-    EAST  (0b101, "down"),
+    DOWN  (0b000, "down"),
+    UP    (0b001, "up"),
+    NORTH (0b010, "north"),
+    SOUTH (0b011, "south"),
+    WEST  (0b100, "west"),
+    EAST  (0b101, "east"),
 
     /**
      * Method implementations might not guarantee correct behaviour (error safety) when passed
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 da753bdeb..d1b45581e 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
@@ -5,15 +5,21 @@ import com.mojang.nbt.tags.CompoundTag;
 import net.minecraft.core.block.motion.CarriedBlock;
 import net.minecraft.core.block.piston.CarriedPistonHead;
 import net.minecraft.core.entity.Entity;
+import net.minecraft.core.enums.EnumDropCause;
+import net.minecraft.core.util.helper.MathHelper;
 import net.minecraft.core.util.helper.Side;
+import net.minecraft.core.world.pos.TilePosc;
+
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 import org.slf4j.Logger;
 
 public interface ICarriable {
     Logger LOGGER = LogUtils.getLogger();
     /**
      * Called every tick an entity holds this object
-    */
-   void heldTick(World world, Entity holder);
+     */
+    void heldTick(World world, Entity holder);
    
     // TODO TilePos refactor
     /**
@@ -27,6 +33,25 @@ public interface ICarriable {
      */
     void drop(World world, Entity holder);
 
+    static boolean tryPlaceAroundHolder(
+        @NotNull ICarriable self,
+        @NotNull World world, @NotNull Entity holder
+    ) {
+        final int holderX = MathHelper.floor(holder.x);
+        final int holderY = MathHelper.floor(holder.y);
+        final int holderZ = MathHelper.floor(holder.z);
+        for (int y = holderY - 1; y <= holderY + 1; y++) {
+            for (int x = holderX - 1; x <= holderX + 1; x++) {
+                for (int z = holderZ - 1; z <= holderZ + 1; z++) {
+                    if (self.tryPlace(world, holder, x, y - 1, z, Side.TOP, 0, 0)) {
+                        return true;
+                    }
+                }
+            }
+        }
+        return false;
+    }
+
     // moved to ICarriableSource
     // boolean canBeCarried(World world, Entity potentialHolder);
     // ICarriable pickup(World world, Entity holder, TilePosc tilePos);
diff --git a/game/core/src/main/resources/assets/minecraft/lang/en_US/tile.lang b/game/core/src/main/resources/assets/minecraft/lang/en_US/tile.lang
index 0d97b30e8..f337b2865 100644
--- a/game/core/src/main/resources/assets/minecraft/lang/en_US/tile.lang
+++ b/game/core/src/main/resources/assets/minecraft/lang/en_US/tile.lang
@@ -1539,7 +1539,7 @@ tile.boulder.sulfuric.name=Sulfuric Boulder
 tile.boulder.sulfuric.desc=A large sulfuric rock.
 
 tile.brimthaw.name=Brimthaw
-tile.brimthaw.desc=Brimstone laced with glowing rock.
+tile.brimthaw.desc=Acid that's been partially cooled. Melts in corrosive liquid.
 
 tile.ember.name=Ember
 tile.ember.desc=Smoldering, explosive remnants of a forgotten world.
diff --git a/game/core/src/main/resources/assets/minecraft/sounds/sounds.json b/game/core/src/main/resources/assets/minecraft/sounds/sounds.json
index 5181f4807..a431e1e15 100644
--- a/game/core/src/main/resources/assets/minecraft/sounds/sounds.json
+++ b/game/core/src/main/resources/assets/minecraft/sounds/sounds.json
@@ -716,24 +716,16 @@
       "tile/piston/in.ogg"
     ]
   },
-  "tile.piston.detached.in": {
-    "subtitle": "subtitles.tile.piston.detached.in",
+  "tile.piston.detached.out": {
+    "subtitle": "subtitles.tile.piston.detached.out",
     "sounds": [
-      {
-        "name": "random.click",
-        "type": "event",
-        "pitch": 0.85
-      }
+      "tile/piston/out.ogg"
     ]
   },
-  "tile.piston.detached.out": {
-    "subtitle": "subtitles.tile.piston.detached.out",
+  "tile.piston.detached.in": {
+    "subtitle": "subtitles.tile.piston.detached.in",
     "sounds": [
-      {
-        "name": "random.click",
-        "type": "event",
-        "pitch": 0.8
-      }
+      "tile/piston/in.ogg"
     ]
   },
   "mob.cow": {
