diff --git a/src/main/java/com/lambda/mixin/MinecraftClientMixin.java b/src/main/java/com/lambda/mixin/MinecraftClientMixin.java index 081e51286..e2fd9030a 100644 --- a/src/main/java/com/lambda/mixin/MinecraftClientMixin.java +++ b/src/main/java/com/lambda/mixin/MinecraftClientMixin.java @@ -24,15 +24,21 @@ import com.lambda.event.events.TickEvent; import com.lambda.gui.DearImGui; import com.lambda.module.modules.player.Interact; +import com.lambda.module.modules.player.PacketMine; +import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod; +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; import net.minecraft.client.MinecraftClient; -import net.minecraft.client.RunArgs; import net.minecraft.client.gui.screen.Screen; import net.minecraft.client.gui.screen.ingame.ScreenHandlerProvider; +import net.minecraft.client.network.ClientPlayerEntity; import net.minecraft.client.network.ClientPlayerInteractionManager; -import net.minecraft.client.util.Window; +import net.minecraft.client.render.WorldRenderer; +import net.minecraft.client.sound.SoundManager; import net.minecraft.util.thread.ThreadExecutor; +import net.minecraft.util.Hand; +import net.minecraft.util.hit.HitResult; import org.jetbrains.annotations.Nullable; -import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; @@ -40,35 +46,60 @@ import org.spongepowered.asm.mixin.injection.Redirect; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -@Mixin(MinecraftClient.class) +@Mixin(value = MinecraftClient.class, priority = Integer.MAX_VALUE) public class MinecraftClientMixin { @Shadow @Nullable public Screen currentScreen; + @Shadow + @Nullable + public HitResult crosshairTarget; @Inject(method = "close", at = @At("HEAD")) void closeImGui(CallbackInfo ci) { DearImGui.INSTANCE.destroy(); } - @Inject(method = "tick", at = @At("HEAD")) - void onTickPre(CallbackInfo ci) { - EventFlow.post(new TickEvent.Pre()); + @WrapMethod(method = "render") + void onLoopTick(boolean tick, Operation original) { + EventFlow.post(TickEvent.Render.Pre.INSTANCE); + original.call(tick); + EventFlow.post(TickEvent.Render.Post.INSTANCE); } - @Inject(method = "tick", at = @At("RETURN")) - void onTickPost(CallbackInfo ci) { - EventFlow.post(new TickEvent.Post()); + @WrapMethod(method = "tick") + void onTick(Operation original) { + EventFlow.post(TickEvent.Pre.INSTANCE); + original.call(); + EventFlow.post(TickEvent.Post.INSTANCE); } - @Inject(method = "render", at = @At("HEAD")) - void onLoopTickPre(CallbackInfo ci) { - EventFlow.post(new TickEvent.Render.Pre()); + @WrapOperation(method = "tick", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/network/ClientPlayerInteractionManager;tick()V")) + void onNetwork(ClientPlayerInteractionManager instance, Operation original) { + EventFlow.post(TickEvent.Network.Pre.INSTANCE); + original.call(instance); + EventFlow.post(TickEvent.Network.Post.INSTANCE); } - @Inject(method = "render", at = @At("RETURN")) - void onLoopTickPost(CallbackInfo ci) { - EventFlow.post(new TickEvent.Render.Post()); + @WrapOperation(method = "tick", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/MinecraftClient;handleInputEvents()V")) + void onInput(MinecraftClient instance, Operation original) { + EventFlow.post(TickEvent.Input.Pre.INSTANCE); + original.call(instance); + EventFlow.post(TickEvent.Input.Post.INSTANCE); + } + + @WrapOperation(method = "tick", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/WorldRenderer;tick()V")) + void onWorldRenderer(WorldRenderer instance, Operation original) { + EventFlow.post(TickEvent.WorldRender.Pre.INSTANCE); + original.call(instance); + EventFlow.post(TickEvent.WorldRender.Post.INSTANCE); + } + + @WrapOperation(method = "tick", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/sound/SoundManager;tick(Z)V")) + void onSound(SoundManager instance, boolean paused, Operation original) { + EventFlow.post(TickEvent.Sound.Pre.INSTANCE); + original.call(instance, paused); + EventFlow.post(TickEvent.Sound.Post.INSTANCE); } @Inject(at = @At(value = "INVOKE", target = "Lorg/slf4j/Logger;info(Ljava/lang/String;)V", shift = At.Shift.AFTER, remap = false), method = "stop") @@ -100,8 +131,16 @@ private void onScreenRemove(@Nullable Screen screen, CallbackInfo ci) { } } + @Redirect(method = "doAttack()Z", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/network/ClientPlayerEntity;swingHand(Lnet/minecraft/util/Hand;)V")) + private void redirectHandSwing(ClientPlayerEntity instance, Hand hand) { + if (this.crosshairTarget == null) return; + if (this.crosshairTarget.getType() != HitResult.Type.BLOCK || PacketMine.INSTANCE.isDisabled()) { + instance.swingHand(hand); + } + } + @Redirect(method = "doItemUse", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/network/ClientPlayerInteractionManager;isBreakingBlock()Z")) - boolean injectMultiActon(ClientPlayerInteractionManager instance) { + boolean redirectMultiActon(ClientPlayerInteractionManager instance) { if (instance == null) return true; if (Interact.INSTANCE.isEnabled() && Interact.getMultiAction()) return false; diff --git a/src/main/java/com/lambda/mixin/baritone/MixinBaritonePlayerContext.java b/src/main/java/com/lambda/mixin/baritone/MixinBaritonePlayerContext.java index b541ccde1..830eebb56 100644 --- a/src/main/java/com/lambda/mixin/baritone/MixinBaritonePlayerContext.java +++ b/src/main/java/com/lambda/mixin/baritone/MixinBaritonePlayerContext.java @@ -20,7 +20,7 @@ import baritone.Baritone; import baritone.api.utils.Rotation; import baritone.utils.player.BaritonePlayerContext; -import com.lambda.interaction.request.rotation.RotationManager; +import com.lambda.interaction.request.rotating.RotationManager; import com.lambda.util.BaritoneUtils; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; @@ -42,7 +42,7 @@ void syncRotationWithBaritone(CallbackInfoReturnable cir) { RotationManager rm = RotationManager.INSTANCE; cir.setReturnValue(new Rotation( - (float) rm.getCurrentRotation().getYaw(), (float) rm.getCurrentRotation().getPitch()) + (float) rm.getActiveRotation().getYaw(), (float) rm.getActiveRotation().getPitch()) ); } } diff --git a/src/main/java/com/lambda/mixin/baritone/MixinLookBehavior.java b/src/main/java/com/lambda/mixin/baritone/MixinLookBehavior.java index 22235558b..599f7906a 100644 --- a/src/main/java/com/lambda/mixin/baritone/MixinLookBehavior.java +++ b/src/main/java/com/lambda/mixin/baritone/MixinLookBehavior.java @@ -21,7 +21,7 @@ import baritone.api.event.events.RotationMoveEvent; import baritone.api.utils.Rotation; import baritone.behavior.LookBehavior; -import com.lambda.interaction.request.rotation.RotationManager; +import com.lambda.interaction.request.rotating.RotationManager; import com.lambda.util.BaritoneUtils; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; diff --git a/src/main/java/com/lambda/mixin/entity/ClientPlayerEntityMixin.java b/src/main/java/com/lambda/mixin/entity/ClientPlayerEntityMixin.java index cd7c80905..0ea94de7c 100644 --- a/src/main/java/com/lambda/mixin/entity/ClientPlayerEntityMixin.java +++ b/src/main/java/com/lambda/mixin/entity/ClientPlayerEntityMixin.java @@ -1,5 +1,5 @@ /* - * Copyright 2024 Lambda + * Copyright 2025 Lambda * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -23,9 +23,11 @@ import com.lambda.event.events.PlayerEvent; import com.lambda.event.events.TickEvent; import com.lambda.interaction.PlayerPacketManager; -import com.lambda.interaction.request.rotation.RotationManager; +import com.lambda.interaction.request.rotating.RotationManager; import com.lambda.module.modules.player.PortalGui; import com.lambda.module.modules.render.ViewModel; +import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod; +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; import com.mojang.authlib.GameProfile; import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.screen.Screen; @@ -51,6 +53,9 @@ @Mixin(value = ClientPlayerEntity.class, priority = Integer.MAX_VALUE) public abstract class ClientPlayerEntityMixin extends AbstractClientPlayerEntity { + @Shadow + public Input input; + @Shadow private boolean autoJumpEnabled; @@ -90,6 +95,7 @@ void onMove(MovementType movementType, Vec3d movement, CallbackInfo ci) { @Redirect(method = "tickMovement", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/input/Input;tick()V")) void processMovement(Input input) { input.tick(); + RotationManager.processRotations(); RotationManager.BaritoneProcessor.processPlayerMovement(input); EventFlow.post(new MovementEvent.InputUpdate(input)); } @@ -134,14 +140,11 @@ void sendBegin(CallbackInfo ci) { autoJumpEnabled = Lambda.getMc().options.getAutoJump().getValue(); } - @Inject(method = "tick", at = @At(value = "HEAD")) - void onTickPre(CallbackInfo ci) { - EventFlow.post(new TickEvent.Player.Pre()); - } - - @Inject(method = "tick", at = @At(value = "RETURN")) - void onTickPost(CallbackInfo ci) { - EventFlow.post(new TickEvent.Player.Post()); + @WrapMethod(method = "tick") + void onTick(Operation original) { + EventFlow.post(TickEvent.Player.Pre.INSTANCE); + original.call(); + EventFlow.post(TickEvent.Player.Post.INSTANCE); } @Redirect(method = "tick", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/network/ClientPlayerEntity;getYaw()F")) @@ -155,15 +158,10 @@ float fixHeldItemPitch(ClientPlayerEntity instance) { } @Inject(method = "swingHand", at = @At("HEAD"), cancellable = true) - void onSwingHandPre(Hand hand, CallbackInfo ci) { + void onSwing(Hand hand, CallbackInfo ci) { if (EventFlow.post(new PlayerEvent.SwingHand(hand)).isCanceled()) ci.cancel(); } - @Inject(method = "updateHealth", at = @At("HEAD")) - public void damage(float health, CallbackInfo ci) { - EventFlow.post(new PlayerEvent.Damage(health)); - } - @Redirect(method = "swingHand", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/network/AbstractClientPlayerEntity;swingHand(Lnet/minecraft/util/Hand;)V")) private void adjustSwing(AbstractClientPlayerEntity instance, Hand hand) { ViewModel viewModel = ViewModel.INSTANCE; @@ -176,6 +174,11 @@ private void adjustSwing(AbstractClientPlayerEntity instance, Hand hand) { viewModel.adjustSwing(hand, instance); } + @Inject(method = "updateHealth", at = @At("HEAD")) + public void damage(float health, CallbackInfo ci) { + EventFlow.post(new PlayerEvent.Damage(health)); + } + /** * Prevents the game from closing Guis when the player is in a nether portal *
{@code
diff --git a/src/main/java/com/lambda/mixin/entity/EntityMixin.java b/src/main/java/com/lambda/mixin/entity/EntityMixin.java
index 1413e9703..5e6708562 100644
--- a/src/main/java/com/lambda/mixin/entity/EntityMixin.java
+++ b/src/main/java/com/lambda/mixin/entity/EntityMixin.java
@@ -21,7 +21,7 @@
 import com.lambda.event.EventFlow;
 import com.lambda.event.events.EntityEvent;
 import com.lambda.event.events.PlayerEvent;
-import com.lambda.interaction.request.rotation.RotationManager;
+import com.lambda.interaction.request.rotating.RotationManager;
 import com.lambda.util.math.Vec2d;
 import net.minecraft.entity.Entity;
 import net.minecraft.entity.MovementType;
diff --git a/src/main/java/com/lambda/mixin/entity/LivingEntityMixin.java b/src/main/java/com/lambda/mixin/entity/LivingEntityMixin.java
index 27c1d7e90..44637b7f6 100644
--- a/src/main/java/com/lambda/mixin/entity/LivingEntityMixin.java
+++ b/src/main/java/com/lambda/mixin/entity/LivingEntityMixin.java
@@ -20,8 +20,8 @@
 import com.lambda.Lambda;
 import com.lambda.event.EventFlow;
 import com.lambda.event.events.MovementEvent;
-import com.lambda.interaction.request.rotation.RotationManager;
 import com.lambda.module.modules.render.ViewModel;
+import com.lambda.interaction.request.rotating.RotationManager;
 import net.minecraft.entity.LivingEntity;
 import net.minecraft.util.math.MathHelper;
 import net.minecraft.util.math.Vec3d;
@@ -128,7 +128,7 @@ private float rotBody(LivingEntity entity) {
             return entity.getYaw();
         }
 
-        Float yaw = RotationManager.getRenderYaw();
+        Float yaw = RotationManager.getHeadYaw();
         return (yaw == null) ? entity.getYaw() : yaw;
     }
 
@@ -159,7 +159,7 @@ private float rotHead(LivingEntity entity) {
             return entity.getYaw();
         }
 
-        Float yaw = RotationManager.getRenderYaw();
+        Float yaw = RotationManager.getHeadYaw();
         return (yaw == null) ? entity.getYaw() : yaw;
     }
 
diff --git a/src/main/java/com/lambda/mixin/entity/PlayerEntityMixin.java b/src/main/java/com/lambda/mixin/entity/PlayerEntityMixin.java
index 91257a61e..457719846 100644
--- a/src/main/java/com/lambda/mixin/entity/PlayerEntityMixin.java
+++ b/src/main/java/com/lambda/mixin/entity/PlayerEntityMixin.java
@@ -20,7 +20,7 @@
 import com.lambda.Lambda;
 import com.lambda.event.EventFlow;
 import com.lambda.event.events.MovementEvent;
-import com.lambda.interaction.request.rotation.RotationManager;
+import com.lambda.interaction.request.rotating.RotationManager;
 import net.minecraft.entity.player.PlayerEntity;
 import org.spongepowered.asm.mixin.Mixin;
 import org.spongepowered.asm.mixin.injection.At;
@@ -42,7 +42,7 @@ private float injectHeadYaw(PlayerEntity instance) {
             return instance.getYaw();
         }
 
-        Float yaw = RotationManager.getRenderYaw();
+        Float yaw = RotationManager.getHeadYaw();
         return (yaw != null) ? yaw : instance.getYaw();
     }
 
diff --git a/src/main/java/com/lambda/mixin/entity/PlayerInventoryMixin.java b/src/main/java/com/lambda/mixin/entity/PlayerInventoryMixin.java
index 73d59a1ef..aadc2041a 100644
--- a/src/main/java/com/lambda/mixin/entity/PlayerInventoryMixin.java
+++ b/src/main/java/com/lambda/mixin/entity/PlayerInventoryMixin.java
@@ -17,38 +17,30 @@
 
 package com.lambda.mixin.entity;
 
-import net.minecraft.client.MinecraftClient;
-import net.minecraft.client.network.ClientPlayerInteractionManager;
-import net.minecraft.entity.player.PlayerEntity;
+import com.lambda.interaction.request.hotbar.HotbarManager;
+import com.lambda.interaction.request.hotbar.HotbarRequest;
+import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
 import net.minecraft.entity.player.PlayerInventory;
-import net.minecraft.item.ItemStack;
-import net.minecraft.util.collection.DefaultedList;
-import org.spongepowered.asm.mixin.Final;
+import org.objectweb.asm.Opcodes;
 import org.spongepowered.asm.mixin.Mixin;
-import org.spongepowered.asm.mixin.Shadow;
 import org.spongepowered.asm.mixin.injection.At;
 import org.spongepowered.asm.mixin.injection.Inject;
 import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
 
-import static net.minecraft.entity.player.PlayerInventory.isValidHotbarIndex;
-
 @Mixin(PlayerInventory.class)
 public class PlayerInventoryMixin {
-    @Shadow @Final private DefaultedList main;
-
-    @Shadow @Final public PlayerEntity player;
-
-    @Inject(method = "getSelectedStack", at = @At(value = "HEAD"), cancellable = true)
-    public void handleSpoofedMainHandStack(CallbackInfoReturnable cir) {
-        MinecraftClient mc = MinecraftClient.getInstance();
-        ClientPlayerInteractionManager interaction = mc.interactionManager;
-
-        if (player != mc.player || interaction == null) return;
-
-        int actualSlot = interaction.lastSelectedSlot;
+    @SuppressWarnings({"MixinAnnotationTarget", "UnresolvedMixinReference"})
+    @ModifyExpressionValue(method = "*", at = @At(value = "FIELD", target = "Lnet/minecraft/entity/player/PlayerInventory;selectedSlot:I", opcode = Opcodes.GETFIELD))
+    private int modifySelectedSlot(int original) {
+        final HotbarRequest hotbarRequest = HotbarManager.INSTANCE.getActiveRequest();
+        if (hotbarRequest == null) return original;
+        return hotbarRequest.getSlot();
+    }
 
-        cir.setReturnValue(
-                isValidHotbarIndex(actualSlot) ? main.get(actualSlot) : ItemStack.EMPTY
-        );
+    @Inject(method = "getSelectedSlot", at = @At("HEAD"), cancellable = true)
+    private void redirectGetSelectedSlot(CallbackInfoReturnable cir) {
+        final HotbarRequest hotbarRequest = HotbarManager.INSTANCE.getActiveRequest();
+        if (hotbarRequest == null) return;
+        cir.setReturnValue(hotbarRequest.getSlot());
     }
 }
diff --git a/src/main/java/com/lambda/mixin/render/CameraMixin.java b/src/main/java/com/lambda/mixin/render/CameraMixin.java
index 5a269a13d..f1f8f9fb5 100644
--- a/src/main/java/com/lambda/mixin/render/CameraMixin.java
+++ b/src/main/java/com/lambda/mixin/render/CameraMixin.java
@@ -17,7 +17,7 @@
 
 package com.lambda.mixin.render;
 
-import com.lambda.interaction.request.rotation.RotationManager;
+import com.lambda.interaction.request.rotating.RotationManager;
 import com.lambda.module.modules.player.Freecam;
 import com.lambda.module.modules.render.CameraTweaks;
 import com.lambda.module.modules.render.FreeLook;
diff --git a/src/main/java/com/lambda/mixin/render/LivingEntityRendererMixin.java b/src/main/java/com/lambda/mixin/render/LivingEntityRendererMixin.java
index d83981903..50aaebc9f 100644
--- a/src/main/java/com/lambda/mixin/render/LivingEntityRendererMixin.java
+++ b/src/main/java/com/lambda/mixin/render/LivingEntityRendererMixin.java
@@ -17,13 +17,11 @@
 
 package com.lambda.mixin.render;
 
-import com.lambda.interaction.request.rotation.RotationManager;
+import com.lambda.interaction.request.rotating.RotationManager;
 import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
 import net.minecraft.client.render.entity.LivingEntityRenderer;
-import net.minecraft.entity.LivingEntity;
 import org.spongepowered.asm.mixin.Mixin;
 import org.spongepowered.asm.mixin.injection.At;
-import org.spongepowered.asm.mixin.injection.Redirect;
 
 import java.util.Objects;
 
@@ -48,6 +46,6 @@ public class LivingEntityRendererMixin {
      */
     @ModifyExpressionValue(method = "updateRenderState(Lnet/minecraft/entity/LivingEntity;Lnet/minecraft/client/render/entity/state/LivingEntityRenderState;F)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/LivingEntity;getLerpedPitch(F)F"))
     private float injectRotationPitch(float original) {
-        return Objects.requireNonNullElse(RotationManager.getRenderPitch(), original);
+        return Objects.requireNonNullElse(RotationManager.getHeadPitch(), original);
     }
 }
diff --git a/src/main/kotlin/com/lambda/command/commands/TransferCommand.kt b/src/main/kotlin/com/lambda/command/commands/TransferCommand.kt
index b07f9ddd6..599b54c1f 100644
--- a/src/main/kotlin/com/lambda/command/commands/TransferCommand.kt
+++ b/src/main/kotlin/com/lambda/command/commands/TransferCommand.kt
@@ -52,7 +52,7 @@ object TransferCommand : LambdaCommand(
                         val selection = selectStack(count) {
                             isItem(stack(ctx).value().item)
                         }
-                        containerWithMaterial(selection).forEachIndexed { i, container ->
+                        selection.containerWithMaterial().forEachIndexed { i, container ->
                             builder.suggest("\"${i + 1}. ${container.name}\"", container.description(selection))
                         }
                         builder.buildFuture()
diff --git a/src/main/kotlin/com/lambda/config/AbstractSetting.kt b/src/main/kotlin/com/lambda/config/AbstractSetting.kt
index 81d2d9c42..7f585cb73 100644
--- a/src/main/kotlin/com/lambda/config/AbstractSetting.kt
+++ b/src/main/kotlin/com/lambda/config/AbstractSetting.kt
@@ -150,6 +150,10 @@ abstract class AbstractSetting(
         listeners.add(ValueListener(false, block))
     }
 
+    fun group(path: List, vararg continuation: NamedEnum) = apply {
+        groups.add(path + continuation)
+    }
+
     fun group(vararg path: NamedEnum) = apply {
         groups.add(path.toList())
     }
diff --git a/src/main/kotlin/com/lambda/config/Configurable.kt b/src/main/kotlin/com/lambda/config/Configurable.kt
index c77d0127a..2937bb779 100644
--- a/src/main/kotlin/com/lambda/config/Configurable.kt
+++ b/src/main/kotlin/com/lambda/config/Configurable.kt
@@ -257,7 +257,7 @@ abstract class Configurable(
     inline fun  setting(
         name: String,
         immutableList: Set,
-        defaultValue: Set,
+        defaultValue: Set = immutableList,
         description: String = "",
         noinline visibility: () -> Boolean = { true },
     ) = SetSetting(
diff --git a/src/main/kotlin/com/lambda/config/groups/BreakSettings.kt b/src/main/kotlin/com/lambda/config/groups/BreakSettings.kt
new file mode 100644
index 000000000..eb2ddfdbc
--- /dev/null
+++ b/src/main/kotlin/com/lambda/config/groups/BreakSettings.kt
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2025 Lambda
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see .
+ */
+
+package com.lambda.config.groups
+
+import com.lambda.config.Configurable
+import com.lambda.event.events.TickEvent
+import com.lambda.interaction.request.breaking.BreakConfig
+import com.lambda.interaction.request.breaking.BreakConfig.AnimationMode
+import com.lambda.interaction.request.breaking.BreakConfig.BreakConfirmationMode
+import com.lambda.interaction.request.breaking.BreakConfig.BreakMode
+import com.lambda.interaction.request.breaking.BreakConfig.SortMode
+import com.lambda.interaction.request.breaking.BreakConfig.SwingMode
+import com.lambda.util.BlockUtils.allSigns
+import com.lambda.util.NamedEnum
+import java.awt.Color
+
+class BreakSettings(
+    c: Configurable,
+    groupPath: List = emptyList(),
+    vis: () -> Boolean = { true },
+) : BreakConfig {
+    enum class Group(override val displayName: String) : NamedEnum {
+        General("General"),
+        Cosmetic("Cosmetic")
+    }
+
+    // General
+    override val breakMode by c.setting("Break Mode", BreakMode.Packet, visibility = vis).group(groupPath, Group.General)
+    override val sorter by c.setting("Sorter", SortMode.Closest, "The order in which breaks are performed", visibility = vis).group(groupPath, Group.General)
+    override val rebreak by c.setting("Rebreak", true, "Re-breaks blocks after they've been broken once", visibility = vis).group(groupPath, Group.General)
+
+    // Double break
+    override val doubleBreak by c.setting("Double Break", true, "Allows breaking two blocks at once", visibility = vis).group(groupPath, Group.General)
+    override val unsafeCancels by c.setting("Unsafe Cancels", true, "Allows cancelling block breaking even if the server might continue breaking sever side, potentially causing unexpected state changes", visibility = vis).group(groupPath, Group.General)
+
+    // Fixes / Delays
+    override val breakThreshold by c.setting("Break Threshold", 0.70f, 0.1f..1.0f, 0.01f, "The break amount at which the block is considered broken", visibility = vis).group(groupPath, Group.General)
+    override val fudgeFactor by c.setting("Fudge Factor", 2, 0..5, 1, "The amount of ticks to give double, aka secondary breaks extra for the server to recognise the break", visibility = vis).group(groupPath, Group.General)
+//    override val desyncFix by c.setting("Desync Fix", false, "Predicts if the players breaking will be slowed next tick as block break packets are processed using the players next position") { vis() && page == Page.General }
+    override val breakDelay by c.setting("Break Delay", 0, 0..6, 1, "The delay between breaking blocks", " ticks", visibility = vis).group(groupPath, Group.General)
+
+    // Timing
+    override val breakStageMask by c.setting("Break Stage Mask", setOf(TickEvent.Input.Post, TickEvent.Player.Post), description = "The sub-tick timing at which break actions can be performed", visibility = vis).group(groupPath, Group.General)
+
+    // Swap
+    override val swapMode by c.setting("Swap Mode", BreakConfig.SwapMode.End, "Decides when to swap to the best suited tool when breaking a block", visibility = vis).group(groupPath, Group.General)
+
+    // Swing
+    override val swing by c.setting("Swing Mode", SwingMode.Constant, "The times at which to swing the players hand", visibility = vis).group(groupPath, Group.General)
+    override val swingType by c.setting("Break Swing Type", BuildConfig.SwingType.Vanilla, "The style of swing") { vis() && swing != SwingMode.None }.group(groupPath, Group.General)
+
+    // Rotate
+    override val rotateForBreak by c.setting("Rotate For Break", false, "Rotate towards block while breaking", visibility = vis).group(groupPath, Group.General)
+
+    // Pending / Post
+    override val breakConfirmation by c.setting("Break Confirmation", BreakConfirmationMode.BreakThenAwait, "The style of confirmation used when breaking", visibility = vis).group(groupPath, Group.General)
+    override val breaksPerTick by c.setting("Breaks Per Tick", 5, 1..30, 1, "Maximum instant block breaks per tick", visibility = vis).group(groupPath, Group.General)
+    override val maxPendingBreaks by c.setting("Max Pending Breaks", 15, 1..30, 1, "The maximum amount of pending breaks", visibility = vis).group(groupPath, Group.General)
+
+    // Block
+    override val avoidLiquids by c.setting("Avoid Liquids", true, "Avoids breaking blocks that would cause liquid to spill", visibility = vis).group(groupPath, Group.General)
+    override val avoidSupporting by c.setting("Avoid Supporting", true, "Avoids breaking the block supporting the player", visibility = vis).group(groupPath, Group.General)
+    override val breakWeakBlocks by c.setting("Break Weak Blocks", false, "Break blocks that dont have structural integrity (e.g: grass)", visibility = vis).group(groupPath, Group.General)
+    override val ignoredBlocks by c.setting("Ignored Blocks", allSigns, description = "Blocks that wont be broken", visibility = vis).group(groupPath, Group.General)
+
+    // Tool
+    override val suitableToolsOnly by c.setting("Suitable Tools Only", false, "Places a restriction to only use tools suitable for the given block", visibility = vis).group(groupPath, Group.General)
+    override val forceSilkTouch by c.setting("Force Silk Touch", false, "Force silk touch when breaking blocks", visibility = vis).group(groupPath, Group.General)
+    override val forceFortunePickaxe by c.setting("Force Fortune Pickaxe", false, "Force fortune pickaxe when breaking blocks", visibility = vis).group(groupPath, Group.General)
+    override val minFortuneLevel by c.setting("Min Fortune Level", 1, 1..3, 1, "The minimum fortune level to use") { vis() && forceFortunePickaxe }.group(groupPath, Group.General)
+
+    // Cosmetics
+    override val sounds by c.setting("Break Sounds", true, "Plays the breaking sounds", visibility = vis).group(groupPath, Group.Cosmetic)
+    override val particles by c.setting("Particles", true, "Renders the breaking particles", visibility = vis).group(groupPath, Group.Cosmetic)
+    override val breakingTexture by c.setting("Breaking Overlay", true, "Overlays the breaking texture at its different stages", visibility = vis).group(groupPath, Group.Cosmetic)
+    // Modes
+    override val renders by c.setting("Renders", true, "Enables the render settings for breaking progress", visibility = vis).group(groupPath, Group.Cosmetic)
+    override val animation by c.setting("Animation", AnimationMode.Out, "The style of animation used for the box") { vis() && renders }.group(groupPath, Group.Cosmetic)
+    // Fill
+    override val fill by c.setting("Fill", true, "Renders the sides of the box to display break progress") { vis() && renders }.group(groupPath, Group.Cosmetic)
+    override val dynamicFillColor by c.setting("Dynamic Colour", true, "Enables fill color interpolation from start to finish for fill when breaking a block") { vis() && renders && fill }.group(groupPath, Group.Cosmetic)
+    override val staticFillColor by c.setting("Fill Color", Color(255, 0, 0, 60).brighter(), "The color of the fill") { vis() && renders && !dynamicFillColor && fill }.group(groupPath, Group.Cosmetic)
+    override val startFillColor by c.setting("Start Fill Color", Color(255, 0, 0, 60).brighter(), "The color of the fill at the start of breaking") { vis()  && renders && dynamicFillColor && fill }.group(groupPath, Group.Cosmetic)
+    override val endFillColor by c.setting("End Fill Color", Color(0, 255, 0, 60).brighter(), "The color of the fill at the end of breaking") { vis() && renders && dynamicFillColor && fill }.group(groupPath, Group.Cosmetic)
+    // Outline
+    override val outline by c.setting("Outline", true, "Renders the lines of the box to display break progress") { vis() && renders }.group(groupPath, Group.Cosmetic)
+    override val outlineWidth by c.setting("Outline Width", 2, 0..5, 1, "The width of the outline") { vis() && renders && outline }.group(groupPath, Group.Cosmetic)
+    override val dynamicOutlineColor by c.setting("Dynamic Outline Color", true, "Enables color interpolation from start to finish for the outline when breaking a block") { vis() && renders && outline }.group(groupPath, Group.Cosmetic)
+    override val staticOutlineColor by c.setting("Outline Color", Color.RED.brighter(), "The Color of the outline at the start of breaking") { vis() && renders && !dynamicOutlineColor && outline }.group(groupPath, Group.Cosmetic)
+    override val startOutlineColor by c.setting("Start Outline Color", Color.RED.brighter(), "The color of the outline at the start of breaking") { vis() && renders && dynamicOutlineColor && outline }.group(groupPath, Group.Cosmetic)
+    override val endOutlineColor by c.setting("End Outline Color", Color.GREEN.brighter(), "The color of the outline at the end of breaking") { vis() && renders && dynamicOutlineColor && outline }.group(groupPath, Group.Cosmetic)
+}
diff --git a/src/main/kotlin/com/lambda/config/groups/BuildConfig.kt b/src/main/kotlin/com/lambda/config/groups/BuildConfig.kt
index 4bda0606d..44820c364 100644
--- a/src/main/kotlin/com/lambda/config/groups/BuildConfig.kt
+++ b/src/main/kotlin/com/lambda/config/groups/BuildConfig.kt
@@ -17,26 +17,33 @@
 
 package com.lambda.config.groups
 
-import net.minecraft.block.Block
+import com.lambda.util.Describable
+import com.lambda.util.NamedEnum
 
 interface BuildConfig {
     // General
     val pathing: Boolean
     val stayInRange: Boolean
     val collectDrops: Boolean
+    val interactionsPerTick: Int
     val maxPendingInteractions: Int
     val interactionTimeout: Int
 
     // Breaking
-    val rotateForBreak: Boolean
-    val breakConfirmation: Boolean
-    val breaksPerTick: Int
-    val breakWeakBlocks: Boolean
-    val forceSilkTouch: Boolean
-    val ignoredBlocks: Set
+    val breaking: BreakSettings
 
     // Placing
-    val rotateForPlace: Boolean
-    val placeConfirmation: Boolean
-    val placementsPerTick: Int
+    val placing: PlaceSettings
+
+    // Interacting
+    val interacting: InteractSettings
+
+    enum class SwingType(
+        override val displayName: String,
+        override val description: String
+    ) : NamedEnum, Describable {
+        Vanilla("Vanilla", "Play the hand swing locally and also notify the server (default, looks and works as expected)."),
+        Server("Server", "Only notify the server to swing; local animation may not play unless the server echoes it."),
+        Client("Client", "Only play the local swing animation; does not notify the server (purely visual).")
+    }
 }
diff --git a/src/main/kotlin/com/lambda/config/groups/BuildSettings.kt b/src/main/kotlin/com/lambda/config/groups/BuildSettings.kt
index de3133334..91bc45584 100644
--- a/src/main/kotlin/com/lambda/config/groups/BuildSettings.kt
+++ b/src/main/kotlin/com/lambda/config/groups/BuildSettings.kt
@@ -18,37 +18,41 @@
 package com.lambda.config.groups
 
 import com.lambda.config.Configurable
-import com.lambda.util.BlockUtils.allSigns
 import com.lambda.util.NamedEnum
+import com.lambda.interaction.request.breaking.BreakConfig.BreakConfirmationMode
+import com.lambda.interaction.request.placing.PlaceConfig
 
 class BuildSettings(
     c: Configurable,
-    baseGroup: NamedEnum,
-    vis: () -> Boolean = { true }
+    vararg groupPath: NamedEnum,
+    vis: () -> Boolean = { true },
 ) : BuildConfig {
     enum class Group(override val displayName: String) : NamedEnum {
         General("General"),
         Break("Break"),
-        Place("Place")
+        Place("Place"),
+        Interact("Interact")
     }
 
     // General
-    override val pathing by c.setting("Pathing", true, "Path to blocks", vis).group(baseGroup, Group.General)
-    override val stayInRange by c.setting("Stay In Range", true, "Stay in range of blocks", vis).group(baseGroup, Group.General)
-    override val collectDrops by c.setting("Collect All Drops", false, "Collect all drops when breaking blocks", vis).group(baseGroup, Group.General)
-    override val maxPendingInteractions by c.setting("Max Pending Interactions", 1, 1..10, 1, "Dont wait for this many interactions for the server response", visibility = vis).group(baseGroup, Group.General)
+    override val pathing by c.setting("Pathing", true, "Path to blocks", vis).group(*groupPath, Group.General)
+    override val stayInRange by c.setting("Stay In Range", true, "Stay in range of blocks", vis).group(*groupPath, Group.General)
+    override val collectDrops by c.setting("Collect All Drops", false, "Collect all drops when breaking blocks", vis).group(*groupPath, Group.General)
+    override val interactionsPerTick by c.setting("Interactions Per Tick", 5, 1..30, 1, "The amount of interactions that can happen per tick", visibility = vis).group(*groupPath, Group.General)
+    override val maxPendingInteractions by c.setting("Max Pending Interactions", 1, 1..10, 1, "Dont wait for this many interactions for the server response", visibility = vis).group(*groupPath, Group.General)
 
     // Breaking
-    override val rotateForBreak by c.setting("Rotate For Break", true, "Rotate towards block while breaking", vis).group(baseGroup, Group.Break)
-    override val breakConfirmation by c.setting("Break Confirmation", false, "Wait for block break confirmation", vis).group(baseGroup, Group.Break)
-    override val breaksPerTick by c.setting("Instant Breaks Per Tick", 5, 1..30, 1, "Maximum instant block breaks per tick", visibility = vis).group(baseGroup, Group.Break)
-    override val breakWeakBlocks by c.setting("Break Weak Blocks", false, "Break blocks that dont have structural integrity (e.g: grass)", vis).group(baseGroup, Group.Break)
-    override val forceSilkTouch by c.setting("Force Silk Touch", false, "Force silk touch when breaking blocks", vis).group(baseGroup, Group.Break)
-    override val ignoredBlocks by c.setting("Ignored Blocks", allSigns, allSigns, "Blocks that wont be broken", vis).group(baseGroup, Group.Break)
+    override val breaking = BreakSettings(c, groupPath.toList() + Group.Break, vis)
 
     // Placing
-    override val rotateForPlace by c.setting("Rotate For Place", true, "Rotate towards block while placing", vis).group(baseGroup, Group.Place)
-    override val placeConfirmation by c.setting("Place Confirmation", true, "Wait for block placement confirmation", vis).group(baseGroup, Group.Place)
-    override val placementsPerTick by c.setting("Instant Places Per Tick", 1, 1..30, 1, "Maximum instant block places per tick", visibility = vis).group(baseGroup, Group.Place)
-    override val interactionTimeout by c.setting("Interaction Timeout", 10, 1..30, 1, "Timeout for block breaks in ticks", unit = " ticks") { vis() && (placeConfirmation || breakConfirmation) }.group(baseGroup, Group.Break).group(baseGroup, Group.Place)
+    override val placing = PlaceSettings(c, groupPath.toList() + Group.Place, vis)
+
+    //Interacting
+    override val interacting = InteractSettings(c, groupPath.toList() + Group.Interact, vis)
+
+    override val interactionTimeout by c.setting("Interaction Timeout", 10, 1..30, 1, "Timeout for block breaks in ticks", unit = " ticks") {
+        vis() && (placing.placeConfirmationMode != PlaceConfig.PlaceConfirmationMode.None
+                || breaking.breakConfirmation != BreakConfirmationMode.None
+                || interacting.interactConfirmationMode != InteractionConfig.InteractConfirmationMode.None)
+    }.group(*groupPath, Group.Break, BreakSettings.Group.General).group(*groupPath, Group.Place).group(*groupPath, Group.Interact)
 }
diff --git a/src/main/kotlin/com/lambda/config/groups/HotbarSettings.kt b/src/main/kotlin/com/lambda/config/groups/HotbarSettings.kt
index 8ae23fc96..3deba31f3 100644
--- a/src/main/kotlin/com/lambda/config/groups/HotbarSettings.kt
+++ b/src/main/kotlin/com/lambda/config/groups/HotbarSettings.kt
@@ -18,16 +18,18 @@
 package com.lambda.config.groups
 
 import com.lambda.config.Configurable
-import com.lambda.interaction.request.Priority
+import com.lambda.event.events.TickEvent
 import com.lambda.interaction.request.hotbar.HotbarConfig
 import com.lambda.util.NamedEnum
 
 class HotbarSettings(
     c: Configurable,
     baseGroup: NamedEnum,
-    priority: Priority = 0,
     vis: () -> Boolean = { true }
-) : HotbarConfig(priority) {
-    override val keepTicks by c.setting("Keep Ticks", 3, 0..20, 1, "The number of ticks to keep the current hotbar selection active", " ticks", vis).group(baseGroup)
-    override var switchPause by c.setting("Switch Pause", 0, 0..20, 1, "The delay in ticks to pause actions after switching to the slot", " ticks", vis).group(baseGroup)
+) : HotbarConfig {
+    override val keepTicks by c.setting("Keep Ticks", 3, 0..20, 1, "The number of ticks to keep the current hotbar selection active", " ticks", visibility = vis).group(baseGroup)
+    override val swapDelay by c.setting("Swap Delay", 0, 0..3, 1, "The number of ticks delay before allowing another hotbar selection swap", " ticks", visibility = vis).group(baseGroup)
+    override val swapsPerTick by c.setting("Swaps Per Tick", 3, 1..10, 1, "The number of hotbar selection swaps that can take place each tick") { swapDelay <= 0 && vis() }.group(baseGroup)
+    override val swapPause by c.setting("Swap Pause", 0, 0..20, 1, "The delay in ticks to pause actions after switching to the slot", " ticks", visibility = vis).group(baseGroup)
+    override val sequenceStageMask by c.setting("Hotbar Stage Mask", setOf(TickEvent.Input.Post), description = "The sub-tick timing at which hotbar actions are performed", visibility = vis).group(baseGroup)
 }
\ No newline at end of file
diff --git a/src/main/kotlin/com/lambda/config/groups/InteractSettings.kt b/src/main/kotlin/com/lambda/config/groups/InteractSettings.kt
new file mode 100644
index 000000000..2cc98d016
--- /dev/null
+++ b/src/main/kotlin/com/lambda/config/groups/InteractSettings.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2025 Lambda
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see .
+ */
+
+package com.lambda.config.groups
+
+import com.lambda.config.Configurable
+import com.lambda.interaction.request.interacting.InteractConfig
+import com.lambda.util.NamedEnum
+
+class InteractSettings(
+    c: Configurable,
+    groupPath: List = emptyList(),
+    vis: () -> Boolean =  { true }
+) : InteractConfig {
+    override val rotate by c.setting("Rotate For Interact", true, "Rotates the player to look at the block when interacting", visibility = vis).group(groupPath)
+    override val swingHand by c.setting("Swing On Interact", true, "Swings the players hand after interacting", visibility = vis).group(groupPath)
+    override val interactSwingType by c.setting("Interact Swing Type", BuildConfig.SwingType.Vanilla, "The style of swing") { vis() && swingHand }.group(groupPath)
+    override val interactConfirmationMode by c.setting("Interact Confirmation Mode", InteractionConfig.InteractConfirmationMode.InteractThenAwait, "The style of confirmation for interactions", visibility = vis).group(groupPath)
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/lambda/config/groups/InteractionConfig.kt b/src/main/kotlin/com/lambda/config/groups/InteractionConfig.kt
index 65a8eefc0..e8bca2ca2 100644
--- a/src/main/kotlin/com/lambda/config/groups/InteractionConfig.kt
+++ b/src/main/kotlin/com/lambda/config/groups/InteractionConfig.kt
@@ -17,7 +17,9 @@
 
 package com.lambda.config.groups
 
-import com.lambda.interaction.request.rotation.visibilty.PointSelection
+import com.lambda.interaction.request.rotating.visibilty.PointSelection
+import com.lambda.util.Describable
+import com.lambda.util.NamedEnum
 
 interface InteractionConfig {
     /**
@@ -59,8 +61,12 @@ interface InteractionConfig {
      */
     val pointSelection: PointSelection
 
-    /**
-     * Whether to swing the hand when interacting.
-     */
-    val swingHand: Boolean
+    enum class InteractConfirmationMode(
+        override val displayName: String,
+        override val description: String
+    ): NamedEnum, Describable {
+        None("No confirmation", "Send the interaction and don’t wait for the server. Lowest latency, but effects may briefly appear if the server rejects it."),
+        InteractThenAwait("Interact now, confirm later", "Show interaction effects immediately, then wait for the server to confirm. Feels instant while still verifying the result."),
+        AwaitThenInteract("Confirm first, then interact", "Wait for the server response before showing any effects. Most accurate and safe, but adds a short delay.")
+    }
 }
diff --git a/src/main/kotlin/com/lambda/config/groups/InteractionSettings.kt b/src/main/kotlin/com/lambda/config/groups/InteractionSettings.kt
index 04531980b..70150f29c 100644
--- a/src/main/kotlin/com/lambda/config/groups/InteractionSettings.kt
+++ b/src/main/kotlin/com/lambda/config/groups/InteractionSettings.kt
@@ -18,7 +18,7 @@
 package com.lambda.config.groups
 
 import com.lambda.config.Configurable
-import com.lambda.interaction.request.rotation.visibilty.PointSelection
+import com.lambda.interaction.request.rotating.visibilty.PointSelection
 import com.lambda.util.NamedEnum
 import com.lambda.util.world.raycast.InteractionMask
 import kotlin.math.max
@@ -42,7 +42,7 @@ class InteractionSettings(
         return if (useDefaultReach) DEFAULT_ATTACK_REACH else attackReachSetting!!.value
     }
 
-    override val interactReach: Double get()  {
+    override val interactReach: Double get() {
         check(usage.block) {
             "Given interaction config has no place reach implementation"
         }
@@ -62,9 +62,6 @@ class InteractionSettings(
     override val resolution by c.setting("Resolution", 5, 1..20, 1, "The amount of grid divisions per surface of the hit box", "", vis).group(baseGroup)
     override val pointSelection by c.setting("Point Selection", PointSelection.Optimum, "The strategy to select the best hit point", vis).group(baseGroup)
 
-    // Swing
-    override val swingHand by c.setting("Swing Hand", true, "Whether to swing hand on interactions", vis).group(baseGroup)
-
     companion object {
         const val DEFAULT_ATTACK_REACH = 3.0
         const val DEFAULT_INTERACT_REACH = 4.5
diff --git a/src/main/kotlin/com/lambda/config/groups/InventorySettings.kt b/src/main/kotlin/com/lambda/config/groups/InventorySettings.kt
index ed43bd970..1b204a62b 100644
--- a/src/main/kotlin/com/lambda/config/groups/InventorySettings.kt
+++ b/src/main/kotlin/com/lambda/config/groups/InventorySettings.kt
@@ -19,16 +19,26 @@ package com.lambda.config.groups
 
 import com.lambda.config.Configurable
 import com.lambda.util.NamedEnum
+import com.lambda.interaction.request.inventory.InventoryConfig
 import com.lambda.util.item.ItemUtils
 
 class InventorySettings(
     c: Configurable,
     baseGroup: NamedEnum,
-    vis: () -> Boolean = { true },
+    vis: () -> Boolean = { true }
 ) : InventoryConfig {
-    override val disposables by c.setting("Disposables", ItemUtils.defaultDisposables, ItemUtils.defaultDisposables, "Items that will be ignored when checking for a free slot", vis).group(baseGroup)
-    override val accessEnderChest by c.setting("Access Ender Chest", false, "Allow access to the player's ender chest", vis).group(baseGroup)
-    override val swapWithDisposables by c.setting("Swap With Disposables", true, "Swap items with disposable ones", vis).group(baseGroup)
-    override val providerPriority by c.setting("Provider Priority", InventoryConfig.Priority.WithMinItems, "What container to prefer when retrieving the item from", vis).group(baseGroup)
-    override val storePriority by c.setting("Store Priority", InventoryConfig.Priority.WithMinItems, "What container to prefer when storing the item to", vis).group(baseGroup)
-}
+    enum class Group(override val displayName: String) : NamedEnum {
+        Container("Container"),
+        Access("Access")
+    }
+
+    override val disposables by c.setting("Disposables", ItemUtils.defaultDisposables, ItemUtils.defaultDisposables, "Items that will be ignored when checking for a free slot", vis).group(baseGroup, Group.Container)
+    override val swapWithDisposables by c.setting("Swap With Disposables", true, "Swap items with disposable ones", vis).group(baseGroup, Group.Container)
+    override val providerPriority by c.setting("Provider Priority", InventoryConfig.Priority.WithMinItems, "What container to prefer when retrieving the item from", vis).group(baseGroup, Group.Container)
+    override val storePriority by c.setting("Store Priority", InventoryConfig.Priority.WithMinItems, "What container to prefer when storing the item to", vis).group(baseGroup, Group.Container)
+
+    override val accessShulkerBoxes by c.setting("Access Shulker Boxes", true, "Allow access to the player's shulker boxes", vis).group(baseGroup, Group.Access)
+    override val accessEnderChest by c.setting("Access Ender Chest", false, "Allow access to the player's ender chest", vis).group(baseGroup, Group.Access)
+    override val accessChests by c.setting("Access Chests", false, "Allow access to the player's normal chests", vis).group(baseGroup, Group.Access)
+    override val accessStashes by c.setting("Access Stashes", false, "Allow access to the player's stashes", vis).group(baseGroup, Group.Access)
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/lambda/config/groups/PlaceSettings.kt b/src/main/kotlin/com/lambda/config/groups/PlaceSettings.kt
new file mode 100644
index 000000000..139186442
--- /dev/null
+++ b/src/main/kotlin/com/lambda/config/groups/PlaceSettings.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2025 Lambda
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see .
+ */
+
+package com.lambda.config.groups
+
+import com.lambda.config.Configurable
+import com.lambda.event.events.TickEvent
+import com.lambda.interaction.request.placing.PlaceConfig
+import com.lambda.interaction.request.placing.PlaceConfig.AirPlaceMode
+import com.lambda.interaction.request.placing.PlaceConfig.PlaceConfirmationMode
+import com.lambda.util.NamedEnum
+
+class PlaceSettings(
+    c: Configurable,
+    groupPath: List = emptyList(),
+    vis: () -> Boolean = { true }
+) : PlaceConfig {
+    override val rotateForPlace by c.setting("Rotate For Place", true, "Rotate towards block while placing", visibility = vis).group(groupPath)
+    override val airPlace by c.setting("Air Place", AirPlaceMode.None, "Allows for placing blocks without adjacent faces", visibility = vis).group(groupPath)
+    override val axisRotateSetting by c.setting("Axis Rotate", true, "Overrides the Rotate For Place setting and rotates the player on each axis to air place rotational blocks") { vis() && airPlace.isEnabled() }.group(groupPath)
+    override val placeStageMask by c.setting("Place Sequence Mode", setOf(TickEvent.Pre, TickEvent.Input.Pre, TickEvent.Player.Post), description = "The sub-tick timing at which break actions are performed", visibility = vis).group(groupPath)
+    override val placeConfirmationMode by c.setting("Place Confirmation", PlaceConfirmationMode.PlaceThenAwait, "Wait for block placement confirmation", visibility = vis).group(groupPath)
+    override val maxPendingPlacements by c.setting("Max Pending Placements", 5, 0..30, 1, "The maximum amount of pending placements", visibility = vis).group(groupPath)
+    override val placementsPerTick by c.setting("Places Per Tick", 1, 1..30, 1, "Maximum instant block places per tick", visibility = vis).group(groupPath)
+    override val swing by c.setting("Swing On Place", true, "Swings the players hand when placing", visibility = vis).group(groupPath)
+    override val swingType by c.setting("Place Swing Type", BuildConfig.SwingType.Vanilla, "The style of swing") { vis() && swing }.group(groupPath)
+    override val sounds by c.setting("Place Sounds", true, "Plays the placing sounds", visibility = vis).group(groupPath)
+}
diff --git a/src/main/kotlin/com/lambda/config/groups/RotationSettings.kt b/src/main/kotlin/com/lambda/config/groups/RotationSettings.kt
index 5e53eb762..1b8be36fb 100644
--- a/src/main/kotlin/com/lambda/config/groups/RotationSettings.kt
+++ b/src/main/kotlin/com/lambda/config/groups/RotationSettings.kt
@@ -18,9 +18,8 @@
 package com.lambda.config.groups
 
 import com.lambda.config.Configurable
-import com.lambda.interaction.request.Priority
-import com.lambda.interaction.request.rotation.RotationConfig
-import com.lambda.interaction.request.rotation.RotationMode
+import com.lambda.interaction.request.rotating.RotationConfig
+import com.lambda.interaction.request.rotating.RotationMode
 import com.lambda.util.NamedEnum
 import kotlin.math.PI
 import kotlin.math.abs
@@ -32,16 +31,15 @@ import kotlin.random.Random
 class RotationSettings(
     c: Configurable,
     baseGroup: NamedEnum,
-    priority: Priority = 0,
     vis: () -> Boolean = { true }
-) : RotationConfig(priority) {
-    override var rotationMode by c.setting("Mode", RotationMode.Sync, "SILENT - server-side rotation, SYNC - server-side rotation; client-side movement, LOCK - Lock camera, NONE - No rotation", vis).group(baseGroup)
+) : RotationConfig {
+    override var rotationMode by c.setting("Mode", RotationMode.Sync, "How the player is being rotated on interaction", vis).group(baseGroup)
 
     /** How many ticks to keep the rotation before resetting */
-    override val keepTicks by c.setting("Keep Rotation", 3, 0..10, 1, "Ticks to keep rotation", " ticks") { rotate && vis() }.group(baseGroup)
+    override val keepTicks by c.setting("Keep Rotation", 1, 1..10, 1, "Ticks to keep rotation", " ticks") { rotate && vis() }.group(baseGroup)
 
     /** How many ticks to wait before resetting the rotation */
-    override val decayTicks by c.setting("Reset Rotation", 3, 1..10, 1, "Ticks before rotation is reset", " ticks") { rotate && vis() }.group(baseGroup)
+    override val decayTicks by c.setting("Reset Rotation", 1, 1..10, 1, "Ticks before rotation is reset", " ticks") { rotate && vis() }.group(baseGroup)
 
     /** Whether the rotation is instant */
     var instant by c.setting("Instant Rotation", true, "Instantly rotate") { rotate && vis() }.group(baseGroup)
diff --git a/src/main/kotlin/com/lambda/config/groups/Targeting.kt b/src/main/kotlin/com/lambda/config/groups/Targeting.kt
index cb08f6370..c0981b97c 100644
--- a/src/main/kotlin/com/lambda/config/groups/Targeting.kt
+++ b/src/main/kotlin/com/lambda/config/groups/Targeting.kt
@@ -20,9 +20,9 @@ package com.lambda.config.groups
 import com.lambda.config.Configurable
 import com.lambda.context.SafeContext
 import com.lambda.friend.FriendManager.isFriend
-import com.lambda.interaction.request.rotation.Rotation.Companion.dist
-import com.lambda.interaction.request.rotation.Rotation.Companion.rotation
-import com.lambda.interaction.request.rotation.Rotation.Companion.rotationTo
+import com.lambda.interaction.request.rotating.Rotation.Companion.dist
+import com.lambda.interaction.request.rotating.Rotation.Companion.rotation
+import com.lambda.interaction.request.rotating.Rotation.Companion.rotationTo
 import com.lambda.threading.runSafe
 import com.lambda.util.NamedEnum
 import com.lambda.util.extension.fullHealth
diff --git a/src/main/kotlin/com/lambda/config/settings/comparable/BooleanSetting.kt b/src/main/kotlin/com/lambda/config/settings/comparable/BooleanSetting.kt
index 100fb8884..95d40ef2e 100644
--- a/src/main/kotlin/com/lambda/config/settings/comparable/BooleanSetting.kt
+++ b/src/main/kotlin/com/lambda/config/settings/comparable/BooleanSetting.kt
@@ -44,8 +44,9 @@ class BooleanSetting(
     override fun ImGuiBuilder.buildLayout() {
         checkbox(name, ::value)
 
-        sameLine()
-        helpMarker(description)
+        if (description.isNotEmpty()) {
+            lambdaTooltip(description)
+        }
     }
 
     override fun CommandBuilder.buildCommand(registry: CommandRegistryAccess) {
diff --git a/src/main/kotlin/com/lambda/config/settings/comparable/EnumSetting.kt b/src/main/kotlin/com/lambda/config/settings/comparable/EnumSetting.kt
index 8628d17d4..904cef6a9 100644
--- a/src/main/kotlin/com/lambda/config/settings/comparable/EnumSetting.kt
+++ b/src/main/kotlin/com/lambda/config/settings/comparable/EnumSetting.kt
@@ -26,6 +26,7 @@ import com.lambda.brigadier.executeWithResult
 import com.lambda.brigadier.required
 import com.lambda.config.AbstractSetting
 import com.lambda.gui.dsl.ImGuiBuilder
+import com.lambda.util.Describable
 import com.lambda.util.NamedEnum
 import com.lambda.util.StringUtils.capitalize
 import com.lambda.util.extension.CommandBuilder
@@ -57,14 +58,28 @@ class EnumSetting>(
     }
 
     override fun ImGuiBuilder.buildLayout() {
-        text(name)
+        val values = value.enumValues
+        val preview = value.displayValue
 
-        sameLine()
-        helpMarker(description)
+        combo("##$name", preview = preview) {
+            values.forEachIndexed { idx, v ->
+                val isSelected = idx == index
+
+                selectable(v.displayValue, isSelected) {
+                    if (!isSelected) index = idx
+                }
 
-        combo("##$name", ::index, value.enumValues.map { it.displayValue }.toTypedArray())
+                (v as? Describable)?.let { lambdaTooltip(it.description) }
+            }
+        }
+
+        (value as? Describable)?.let { lambdaTooltip(it.description) }
+        sameLine()
+        text(name)
+        if (description.isNotBlank()) lambdaTooltip(description)
     }
 
+
     override fun CommandBuilder.buildCommand(registry: CommandRegistryAccess) {
         required(word(name)) { parameter ->
             suggests { _, builder ->
diff --git a/src/main/kotlin/com/lambda/event/events/PlayerPacketEvent.kt b/src/main/kotlin/com/lambda/event/events/PlayerPacketEvent.kt
index 91f893072..9a98d011d 100644
--- a/src/main/kotlin/com/lambda/event/events/PlayerPacketEvent.kt
+++ b/src/main/kotlin/com/lambda/event/events/PlayerPacketEvent.kt
@@ -20,7 +20,7 @@ package com.lambda.event.events
 import com.lambda.event.Event
 import com.lambda.event.callback.Cancellable
 import com.lambda.event.callback.ICancellable
-import com.lambda.interaction.request.rotation.Rotation
+import com.lambda.interaction.request.rotating.Rotation
 import net.minecraft.network.packet.c2s.play.PlayerMoveC2SPacket
 import net.minecraft.util.math.Vec3d
 
diff --git a/src/main/kotlin/com/lambda/event/events/RotationEvent.kt b/src/main/kotlin/com/lambda/event/events/RotationEvent.kt
index e2cbc44fe..148b37fed 100644
--- a/src/main/kotlin/com/lambda/event/events/RotationEvent.kt
+++ b/src/main/kotlin/com/lambda/event/events/RotationEvent.kt
@@ -18,7 +18,7 @@
 package com.lambda.event.events
 
 import com.lambda.event.Event
-import com.lambda.interaction.request.rotation.RotationRequest
+import com.lambda.interaction.request.rotating.RotationRequest
 import net.minecraft.client.input.Input
 
 sealed class RotationEvent {
diff --git a/src/main/kotlin/com/lambda/event/events/TickEvent.kt b/src/main/kotlin/com/lambda/event/events/TickEvent.kt
index e5193ecc8..c77dabfd8 100644
--- a/src/main/kotlin/com/lambda/event/events/TickEvent.kt
+++ b/src/main/kotlin/com/lambda/event/events/TickEvent.kt
@@ -19,51 +19,97 @@ package com.lambda.event.events
 
 import com.lambda.event.Event
 
-sealed class TickEvent {
+/**
+ * Phases:
+ *
+ * 1. **Pre-Tick**: Increments uptime, steps world tick manager, decrement item use cooldown.
+ * 2. **GUI Update**: Processes delayed messages, updates HUD.
+ * 3. **Game Mode Update**: Updates targeted entity, ticks tutorial, and interaction managers.
+ * 4. **Texture Update**: Ticks texture manager.
+ * 5. **Screen Handling**: Manages screen logic, ticks current screen.
+ * 6. **Debug HUD Update**: Resets debug HUD chunk.
+ * 7. **Input Handling**: Handles input events, decrements attack cooldown.
+ * 8. **World Update**: Ticks game and world renderers, world entities.
+ * 9. **Music and Sound Update**: Ticks music tracker and sound manager.
+ * 10. **Tutorial and Social Interactions**: Handles tutorial and social interactions, ticks world.
+ * 11. **Pending Connection**: Ticks integrated server connection.
+ * 12. **Keyboard Handling**: Polls for debug crash key presses.
+ *
+ * @see net.minecraft.client.MinecraftClient.tick
+ */
+sealed class TickEvent : Event {
     /**
      * Triggered before each iteration of the game loop.
      *
      * Phases:
      *
-     * 1. **Pre-Tick**: Increments uptime, steps world tick manager, decrement item use cooldown.
-     * 2. **GUI Update**: Processes delayed messages, updates HUD.
-     * 3. **Game Mode Update**: Updates targeted entity, ticks tutorial, and interaction managers.
-     * 4. **Texture Update**: Ticks texture manager.
-     * 5. **Screen Handling**: Manages screen logic, ticks current screen.
-     * 6. **Debug HUD Update**: Resets debug HUD chunk.
-     * 7. **Input Handling**: Handles input events, decrements attack cooldown.
-     * 8. **World Update**: Ticks game and world renderers, world entities.
-     * 9. **Music and Sound Update**: Ticks music tracker and sound manager.
-     * 10. **Tutorial and Social Interactions**: Handles tutorial and social interactions, ticks world.
-     * 11. **Pending Connection**: Ticks integrated server connection.
-     * 12. **Keyboard Handling**: Polls for debug crash key presses.
-     *
-     * @see net.minecraft.client.MinecraftClient.tick
+     * 1. Increments uptime
+     * 2. Steps world tick manager
+     * 3. Decrements item use cooldown
      */
-    class Pre : Event
+    data object Pre : TickEvent()
 
     /**
      * Triggered after each iteration of the game loop.
-     * Targeted at 20 ticks per second.
+     */
+    data object Post : TickEvent()
+
+    /**
+     * Triggered during the network tick stage only
+     *
+     * Phases:
+     *
+     * 1. Synchronizes player inventory slot changes
+     * 2. Clears the outgoing packet queue
+     * 3. Ticks packet listeners
+     * 4. Flushes the connection channel
+     * 5. Updates network statistics
+     * 6. Updates the packet logger
+     *
+     * @see net.minecraft.client.network.ClientPlayerInteractionManager.tick
+     */
+    sealed class Network {
+        data object Pre : TickEvent()
+        data object Post : TickEvent()
+    }
+
+    /**
+     * Triggered during the input tick stage
      *
      * Phases:
      *
-     * 1. **Pre-Tick**: Increments uptime, steps world tick manager, decrement item use cooldown.
-     * 2. **GUI Update**: Processes delayed messages, updates HUD.
-     * 3. **Game Mode Update**: Updates targeted entity, ticks tutorial, and interaction managers.
-     * 4. **Texture Update**: Ticks texture manager.
-     * 5. **Screen Handling**: Manages screen logic, ticks current screen.
-     * 6. **Debug HUD Update**: Resets debug HUD chunk.
-     * 7. **Input Handling**: Handles input events, decrements attack cooldown.
-     * 8. **World Update**: Ticks game and world renderers, world entities (such as [TickEvent.Player]).
-     * 9. **Music and Sound Update**: Ticks music tracker and sound manager.
-     * 10. **Tutorial**: Handles tutorials, ticks world.
-     * 11. **Pending Connection**: Ticks integrated server connection.
-     * 12. **Keyboard Handling**: Polls for debug crash key presses.
-     *
-     * @see net.minecraft.client.MinecraftClient.tick
+     * 1. Handles various game specific keys
+     * 2. Handles block breaking
+     * 3. Adds block breaking particles
+     * 4. Swings the player arm
+     * 5. Decrements attack cooldown
+     *
+     * @see net.minecraft.client.MinecraftClient.handleInputEvents
+     */
+    sealed class Input {
+        data object Pre : TickEvent()
+        data object Post : TickEvent()
+    }
+
+    /**
+     * Triggered during the world render tick stage
+     *
+     * @see net.minecraft.client.render.WorldRenderer.tick
      */
-    class Post : Event
+    sealed class WorldRender {
+        data object Pre : TickEvent()
+        data object Post : TickEvent()
+    }
+
+    /**
+     * Triggered during the sound update tick stage
+     *
+     * @see net.minecraft.client.sound.SoundManager.tick
+     */
+    sealed class Sound {
+        data object Pre : TickEvent()
+        data object Post : TickEvent()
+    }
 
     /**
      * Triggered before ([Pre]) and after ([Post]) each render tick.
@@ -82,12 +128,12 @@ sealed class TickEvent {
         /**
          * Triggered before each render tick ([TickEvent.Render]) of the game loop.
          */
-        class Pre : Event
+        data object Pre : TickEvent()
 
         /**
          * Triggered after each render tick ([TickEvent.Render]) of the game loop.
          */
-        class Post : Event
+        data object Post : TickEvent()
     }
 
     /**
@@ -106,11 +152,11 @@ sealed class TickEvent {
         /**
          * Triggered before each player tick ([TickEvent.Player]).
          */
-        class Pre : Event
+        data object Pre : TickEvent()
 
         /**
          * Triggered after each player tick ([TickEvent.Player]).
          */
-        class Post : Event
+        data object Post : TickEvent()
     }
 }
diff --git a/src/main/kotlin/com/lambda/event/events/UpdateManagerEvent.kt b/src/main/kotlin/com/lambda/event/events/UpdateManagerEvent.kt
new file mode 100644
index 000000000..a16b8ffcf
--- /dev/null
+++ b/src/main/kotlin/com/lambda/event/events/UpdateManagerEvent.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2025 Lambda
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see .
+ */
+
+package com.lambda.event.events
+
+import com.lambda.event.Event
+
+sealed class UpdateManagerEvent {
+    data object Rotation : Event
+    data object Inventory: Event
+    data object Hotbar : Event
+    data object Break : Event
+    data object Place : Event
+    data object Interact: Event
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/lambda/event/listener/SafeListener.kt b/src/main/kotlin/com/lambda/event/listener/SafeListener.kt
index 564e10019..9b5c6f7af 100644
--- a/src/main/kotlin/com/lambda/event/listener/SafeListener.kt
+++ b/src/main/kotlin/com/lambda/event/listener/SafeListener.kt
@@ -120,7 +120,7 @@ class SafeListener(
         inline fun  Any.listen(
             priority: Int = 0,
             alwaysListen: Boolean = false,
-            noinline function: SafeContext.(T) -> Unit = {},
+            noinline function: SafeContext.(T) -> Unit = {}
         ): SafeListener {
             val listener = SafeListener(priority, this, alwaysListen) { event ->
                 runGameScheduled { function(event) }
diff --git a/src/main/kotlin/com/lambda/interaction/MovementConfiguration.kt b/src/main/kotlin/com/lambda/interaction/MovementConfiguration.kt
index 8da791eed..3d7442ec1 100644
--- a/src/main/kotlin/com/lambda/interaction/MovementConfiguration.kt
+++ b/src/main/kotlin/com/lambda/interaction/MovementConfiguration.kt
@@ -17,7 +17,7 @@
 
 package com.lambda.interaction
 
-import com.lambda.interaction.request.rotation.Rotation
+import com.lambda.interaction.request.rotating.Rotation
 import net.minecraft.util.math.Vec3d
 
 data class MovementConfiguration(
diff --git a/src/main/kotlin/com/lambda/interaction/PlayerPacketManager.kt b/src/main/kotlin/com/lambda/interaction/PlayerPacketManager.kt
index ec014023d..72d94f117 100644
--- a/src/main/kotlin/com/lambda/interaction/PlayerPacketManager.kt
+++ b/src/main/kotlin/com/lambda/interaction/PlayerPacketManager.kt
@@ -21,8 +21,8 @@ import com.lambda.context.SafeContext
 import com.lambda.event.EventFlow.post
 import com.lambda.event.EventFlow.postChecked
 import com.lambda.event.events.PlayerPacketEvent
-import com.lambda.interaction.request.rotation.Rotation
-import com.lambda.interaction.request.rotation.RotationManager
+import com.lambda.interaction.request.rotating.Rotation
+import com.lambda.interaction.request.rotating.RotationManager
 import com.lambda.threading.runSafe
 import com.lambda.util.collections.LimitedOrderedSet
 import com.lambda.util.math.approximate
@@ -54,7 +54,7 @@ object PlayerPacketManager {
         runSafe {
             PlayerPacketEvent.Pre(
                 player.pos,
-                RotationManager.currentRotation,
+                RotationManager.activeRotation,
                 player.isOnGround,
                 player.isSprinting,
                 player.isSneaking,
@@ -134,6 +134,9 @@ object PlayerPacketManager {
             }
         }
 
+        // Update the server rotation in RotationManager
+        RotationManager.onRotationSend()
+
         PlayerPacketEvent.Post().post()
     }
 
diff --git a/src/main/kotlin/com/lambda/interaction/blockplace/PlaceFinder.kt b/src/main/kotlin/com/lambda/interaction/blockplace/PlaceFinder.kt
index e36019c35..a45366261 100644
--- a/src/main/kotlin/com/lambda/interaction/blockplace/PlaceFinder.kt
+++ b/src/main/kotlin/com/lambda/interaction/blockplace/PlaceFinder.kt
@@ -20,7 +20,7 @@ package com.lambda.interaction.blockplace
 import com.lambda.context.SafeContext
 import com.lambda.interaction.blockplace.PlaceInteraction.canPlaceAt
 import com.lambda.interaction.blockplace.PlaceInteraction.isClickable
-import com.lambda.interaction.request.rotation.visibilty.VisibilityChecker.getVisibleSurfaces
+import com.lambda.interaction.request.rotating.visibilty.VisibilityChecker.getVisibleSurfaces
 import com.lambda.util.BlockUtils.blockState
 import com.lambda.util.math.distSq
 import com.lambda.util.math.getHitVec
diff --git a/src/main/kotlin/com/lambda/interaction/construction/blueprint/Blueprint.kt b/src/main/kotlin/com/lambda/interaction/construction/blueprint/Blueprint.kt
index b072d0023..4f96466c1 100644
--- a/src/main/kotlin/com/lambda/interaction/construction/blueprint/Blueprint.kt
+++ b/src/main/kotlin/com/lambda/interaction/construction/blueprint/Blueprint.kt
@@ -18,7 +18,6 @@
 package com.lambda.interaction.construction.blueprint
 
 import com.lambda.interaction.construction.verify.TargetState
-import com.lambda.util.BlockUtils.blockPos
 import com.lambda.util.collections.updatableLazy
 import com.lambda.util.extension.Structure
 import com.lambda.util.math.roundedBlockPos
@@ -53,29 +52,16 @@ abstract class Blueprint {
 
     fun isOutOfBounds(vec3d: Vec3d): Boolean = bounds.value?.contains(vec3d.roundedBlockPos) == false
 
-    val center get() = bounds.value?.center?.blockPos
+    val center get() = bounds.value?.center
 
     companion object {
         fun emptyStructure(): Structure = emptyMap()
 
-        fun Box.toStructure(targetState: TargetState): Structure =
-            BlockPos.stream(this)
-                .map { it.blockPos }
-                .toList()
-                .associateWith { targetState }
-
         fun BlockBox.toStructure(targetState: TargetState): Structure =
-            BlockPos.stream(this)
-                .map { it.blockPos }
-                .toList()
-                .associateWith { targetState }
+            BlockPos.stream(this).toList().associateWith { targetState }
 
         fun BlockPos.toStructure(targetState: TargetState): Structure =
-            setOf(this)
-                .associateWith { targetState }
-
-//        fun Schematic.fromSchematic() =
-//            this.blockMap.map { it.key to TargetState.BlockState(it.value) }.toMap()
+            setOf(this).associateWith { targetState }
 
         fun StructureTemplate.toStructure(): Structure =
             blockInfoLists
diff --git a/src/main/kotlin/com/lambda/interaction/construction/blueprint/PropagatingBlueprint.kt b/src/main/kotlin/com/lambda/interaction/construction/blueprint/PropagatingBlueprint.kt
index f5e39c58e..a7e06817d 100644
--- a/src/main/kotlin/com/lambda/interaction/construction/blueprint/PropagatingBlueprint.kt
+++ b/src/main/kotlin/com/lambda/interaction/construction/blueprint/PropagatingBlueprint.kt
@@ -23,13 +23,14 @@ import com.lambda.util.extension.Structure
 import net.minecraft.util.math.Vec3i
 
 data class PropagatingBlueprint(
-    val onFinish: SafeContext.(Structure) -> Structure = { it },
+    val onFinish: SafeContext.(Structure) -> Structure? = { it },
 ) : Blueprint() {
-    fun next() {
+    fun next() =
         runSafe {
-            structure = onFinish(structure)
+            onFinish(structure)?.also { new ->
+                structure = new
+            }
         }
-    }
 
     override var structure: Structure = emptyMap()
         private set(value) {
@@ -40,14 +41,14 @@ data class PropagatingBlueprint(
     override fun toString() = "Propagating Blueprint at ${center?.toShortString()}"
 
     companion object {
-        fun offset(offset: Vec3i): SafeContext.(Structure) -> Structure = {
+        fun offset(offset: Vec3i): SafeContext.(Structure) -> Structure? = {
             it.map { (pos, state) ->
                 pos.add(offset) to state
             }.toMap()
         }
 
         fun propagatingBlueprint(
-            onFinish: SafeContext.(Structure) -> Structure,
+            onFinish: SafeContext.(Structure) -> Structure?,
         ) = PropagatingBlueprint(onFinish)
     }
 }
diff --git a/src/main/kotlin/com/lambda/interaction/construction/blueprint/TickingBlueprint.kt b/src/main/kotlin/com/lambda/interaction/construction/blueprint/TickingBlueprint.kt
index f8e19d915..d7e7abcfe 100644
--- a/src/main/kotlin/com/lambda/interaction/construction/blueprint/TickingBlueprint.kt
+++ b/src/main/kotlin/com/lambda/interaction/construction/blueprint/TickingBlueprint.kt
@@ -23,13 +23,14 @@ import com.lambda.util.extension.Structure
 import net.minecraft.util.math.Vec3i
 
 data class TickingBlueprint(
-    val onTick: SafeContext.(Structure) -> Structure = { it },
+    val onTick: SafeContext.(Structure) -> Structure? = { it },
 ) : Blueprint() {
-    fun tick() {
+    fun tick() =
         runSafe {
-            structure = onTick(structure)
+            onTick(structure)?.also { new ->
+                structure = new
+            }
         }
-    }
 
     override var structure: Structure = emptyMap()
         private set(value) {
@@ -40,14 +41,14 @@ data class TickingBlueprint(
     override fun toString() = "Dynamic Blueprint at ${center?.toShortString()}"
 
     companion object {
-        fun offset(offset: Vec3i): SafeContext.(Structure) -> Structure = {
+        fun offset(offset: Vec3i): SafeContext.(Structure) -> Structure? = {
             it.map { (pos, state) ->
                 pos.add(offset) to state
             }.toMap()
         }
 
         fun tickingBlueprint(
-            onTick: SafeContext.(Structure) -> Structure,
+            onTick: SafeContext.(Structure) -> Structure?,
         ) = TickingBlueprint(onTick)
     }
 }
diff --git a/src/main/kotlin/com/lambda/interaction/construction/context/BreakContext.kt b/src/main/kotlin/com/lambda/interaction/construction/context/BreakContext.kt
index 9dcb27e36..04cef97b8 100644
--- a/src/main/kotlin/com/lambda/interaction/construction/context/BreakContext.kt
+++ b/src/main/kotlin/com/lambda/interaction/construction/context/BreakContext.kt
@@ -17,71 +17,70 @@
 
 package com.lambda.interaction.construction.context
 
-import com.lambda.config.groups.BuildConfig
 import com.lambda.context.SafeContext
 import com.lambda.graphics.renderer.esp.DirectionMask
 import com.lambda.graphics.renderer.esp.DirectionMask.exclude
-import com.lambda.interaction.construction.verify.TargetState
-import com.lambda.interaction.request.rotation.RotationRequest
-import com.lambda.threading.runSafe
-import com.lambda.util.world.raycast.RayCastUtils.distanceTo
+import com.lambda.interaction.material.StackSelection
+import com.lambda.interaction.request.breaking.BreakConfig
+import com.lambda.interaction.request.breaking.BreakRequest
+import com.lambda.interaction.request.hotbar.HotbarManager
+import com.lambda.interaction.request.hotbar.HotbarRequest
+import com.lambda.interaction.request.rotating.RotationRequest
+import com.lambda.util.BlockUtils.emptyState
 import net.minecraft.block.BlockState
-import net.minecraft.util.Hand
+import net.minecraft.block.FallingBlock
 import net.minecraft.util.hit.BlockHitResult
 import net.minecraft.util.math.BlockPos
-import net.minecraft.util.math.Direction
-import net.minecraft.util.math.Vec3d
 import java.awt.Color
+import kotlin.random.Random
 
 data class BreakContext(
-    override val pov: Vec3d,
     override val result: BlockHitResult,
     override val rotation: RotationRequest,
-    override val checkedState: BlockState,
-    override val targetState: TargetState,
-    override var hand: Hand,
-    val instantBreak: Boolean,
-) : BuildContext {
+    override var hotbarIndex: Int,
+    var itemSelection: StackSelection,
+    var instantBreak: Boolean,
+    override var cachedState: BlockState,
+    val sortMode: BreakConfig.SortMode
+) : BuildContext() {
     private val baseColor = Color(222, 0, 0, 25)
     private val sideColor = Color(222, 0, 0, 100)
 
-    override fun interact(swingHand: Boolean) {
-        runSafe {
-            if (interaction.updateBlockBreakingProgress(result.blockPos, result.side)) {
-                if (player.isCreative) interaction.blockBreakingCooldown = 0
-                if (swingHand) player.swingHand(hand)
-            }
-        }
-    }
+    override val blockPos: BlockPos = result.blockPos
+    override val expectedState = cachedState.emptyState
 
-    override val expectedPos: BlockPos
-        get() = result.blockPos
-
-    override val distance: Double by lazy {
-        result.distanceTo(pov)
-    }
-
-    fun exposedSides(ctx: SafeContext) =
-        Direction.entries.filter {
-            ctx.world.isAir(result.blockPos.offset(it))
-        }
-
-    override val expectedState: BlockState = checkedState.fluidState.blockState
+    val random = Random.nextDouble()
 
     override fun compareTo(other: BuildContext): Int {
         return when (other) {
-            is BreakContext -> compareBy {
-                it.rotation.target.angleDistance
+            is BreakContext -> compareByDescending {
+                if (it.cachedState.block is FallingBlock) it.blockPos.y else 0
+            }.thenBy {
+                it.instantBreak
+            }.thenBy {
+                when (sortMode) {
+                    BreakConfig.SortMode.Closest -> it.distance
+                    BreakConfig.SortMode.Farthest -> -it.distance
+                    BreakConfig.SortMode.Rotation -> it.rotation.target.angleDistance
+                    BreakConfig.SortMode.Random -> it.random
+                }
+            }.thenBy {
+                it.hotbarIndex == HotbarManager.serverSlot
             }.compare(this, other)
 
             else -> 1
         }
     }
 
-    override fun shouldRotate(config: BuildConfig) = config.rotateForBreak
-
     override fun SafeContext.buildRenderer() {
-        withState(checkedState, expectedPos, baseColor, DirectionMask.ALL.exclude(result.side))
-        withState(checkedState, expectedPos, sideColor, result.side)
+        withState(cachedState, blockPos, baseColor, DirectionMask.ALL.exclude(result.side))
+        withState(cachedState, blockPos, sideColor, result.side)
     }
-}
+
+    fun requestSwap(breakRequest: BreakRequest, minKeepTicks: Int = 0): Boolean =
+        HotbarRequest(
+            hotbarIndex,
+            breakRequest.hotbar,
+            breakRequest.hotbar.keepTicks.coerceAtLeast(minKeepTicks)
+        ).submit(false).done
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/lambda/interaction/construction/context/BuildContext.kt b/src/main/kotlin/com/lambda/interaction/construction/context/BuildContext.kt
index 3ce7cbe66..a0a9cd454 100644
--- a/src/main/kotlin/com/lambda/interaction/construction/context/BuildContext.kt
+++ b/src/main/kotlin/com/lambda/interaction/construction/context/BuildContext.kt
@@ -17,27 +17,22 @@
 
 package com.lambda.interaction.construction.context
 
-import com.lambda.config.groups.BuildConfig
 import com.lambda.interaction.construction.result.Drawable
-import com.lambda.interaction.construction.verify.TargetState
-import com.lambda.interaction.request.rotation.RotationRequest
+import com.lambda.interaction.request.rotating.RotationRequest
+import com.lambda.threading.runSafe
 import net.minecraft.block.BlockState
-import net.minecraft.util.Hand
 import net.minecraft.util.hit.BlockHitResult
 import net.minecraft.util.math.BlockPos
-import net.minecraft.util.math.Vec3d
 
-interface BuildContext : Comparable, Drawable {
-    val pov: Vec3d
-    val result: BlockHitResult
-    val distance: Double
-    val expectedState: BlockState
-    val targetState: TargetState
-    val expectedPos: BlockPos
-    val checkedState: BlockState
-    val hand: Hand
-    val rotation: RotationRequest
+abstract class BuildContext : Comparable, Drawable {
+    abstract val result: BlockHitResult
+    abstract val rotation: RotationRequest
+    abstract val hotbarIndex: Int
+    abstract val cachedState: BlockState
+    abstract val expectedState: BlockState
+    abstract val blockPos: BlockPos
 
-    fun interact(swingHand: Boolean)
-    fun shouldRotate(config: BuildConfig): Boolean
+    val distance by lazy {
+        runSafe { player.eyePos.distanceTo(result.pos) } ?: Double.MAX_VALUE
+    }
 }
diff --git a/src/main/kotlin/com/lambda/interaction/construction/context/InteractionContext.kt b/src/main/kotlin/com/lambda/interaction/construction/context/InteractionContext.kt
new file mode 100644
index 000000000..33bcb966a
--- /dev/null
+++ b/src/main/kotlin/com/lambda/interaction/construction/context/InteractionContext.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2025 Lambda
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see .
+ */
+
+package com.lambda.interaction.construction.context
+
+import com.lambda.context.SafeContext
+import com.lambda.graphics.renderer.esp.DirectionMask
+import com.lambda.graphics.renderer.esp.DirectionMask.exclude
+import com.lambda.interaction.request.Request.Companion.submit
+import com.lambda.interaction.request.hotbar.HotbarManager
+import com.lambda.interaction.request.hotbar.HotbarRequest
+import com.lambda.interaction.request.interacting.InteractRequest
+import com.lambda.interaction.request.rotating.RotationRequest
+import com.lambda.util.BlockUtils
+import com.lambda.util.BlockUtils.blockState
+import net.minecraft.block.BlockState
+import net.minecraft.util.hit.BlockHitResult
+import net.minecraft.util.math.BlockPos
+import java.awt.Color
+
+class InteractionContext(
+    override val result: BlockHitResult,
+    override val rotation: RotationRequest,
+    override var hotbarIndex: Int,
+    override var cachedState: BlockState,
+    override val expectedState: BlockState,
+) : BuildContext() {
+    private val baseColor = Color(35, 254, 79, 25)
+    private val sideColor = Color(35, 254, 79, 100)
+
+    override val blockPos: BlockPos = result.blockPos
+
+    override fun compareTo(other: BuildContext) =
+        when {
+            other is InteractionContext -> compareBy {
+                BlockUtils.fluids.indexOf(it.cachedState.fluidState.fluid)
+            }.thenByDescending {
+                it.cachedState.fluidState.level
+            }.thenBy {
+                it.rotation.target.angleDistance
+            }.thenBy {
+                it.hotbarIndex == HotbarManager.serverSlot
+            }.thenBy {
+                it.distance
+            }.compare(this, other)
+
+            else -> 1
+        }
+
+    override fun SafeContext.buildRenderer() {
+        withState(expectedState, blockPos, baseColor, DirectionMask.ALL.exclude(result.side.opposite))
+        withState(blockState(result.blockPos), result.blockPos, sideColor, result.side)
+    }
+
+    fun requestDependencies(request: InteractRequest): Boolean {
+        val hotbarRequest = submit(HotbarRequest(hotbarIndex, request.hotbar), false)
+        val validRotation = if (request.rotate) submit(rotation, false).done else true
+        return hotbarRequest.done && validRotation
+    }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/lambda/interaction/construction/context/PlaceContext.kt b/src/main/kotlin/com/lambda/interaction/construction/context/PlaceContext.kt
index 96edfd48f..f25101867 100644
--- a/src/main/kotlin/com/lambda/interaction/construction/context/PlaceContext.kt
+++ b/src/main/kotlin/com/lambda/interaction/construction/context/PlaceContext.kt
@@ -17,74 +17,50 @@
 
 package com.lambda.interaction.construction.context
 
-import com.lambda.config.groups.BuildConfig
+import com.lambda.Lambda.mc
 import com.lambda.context.SafeContext
 import com.lambda.graphics.renderer.esp.DirectionMask
 import com.lambda.graphics.renderer.esp.DirectionMask.exclude
-import com.lambda.interaction.construction.verify.TargetState
-import com.lambda.interaction.request.rotation.RotationRequest
-import com.lambda.threading.runSafe
+import com.lambda.interaction.request.Request.Companion.submit
+import com.lambda.interaction.request.hotbar.HotbarManager
+import com.lambda.interaction.request.hotbar.HotbarRequest
+import com.lambda.interaction.request.placing.PlaceRequest
+import com.lambda.interaction.request.rotating.RotationRequest
 import com.lambda.util.BlockUtils
 import com.lambda.util.BlockUtils.blockState
-import com.lambda.util.Communication.warn
 import net.minecraft.block.BlockState
-import net.minecraft.util.ActionResult
-import net.minecraft.util.Hand
 import net.minecraft.util.hit.BlockHitResult
 import net.minecraft.util.math.BlockPos
-import net.minecraft.util.math.Direction
-import net.minecraft.util.math.Vec3d
 import java.awt.Color
 
 data class PlaceContext(
-    override val pov: Vec3d,
     override val result: BlockHitResult,
     override val rotation: RotationRequest,
-    override val distance: Double,
+    override var hotbarIndex: Int,
+    override val blockPos: BlockPos,
+    override var cachedState: BlockState,
     override val expectedState: BlockState,
-    override val checkedState: BlockState,
-    override val hand: Hand,
-    override val expectedPos: BlockPos,
-    override val targetState: TargetState,
     val sneak: Boolean,
     val insideBlock: Boolean,
-    val primeDirection: Direction?,
-) : BuildContext {
+    val currentDirIsValid: Boolean = false
+) : BuildContext() {
     private val baseColor = Color(35, 188, 254, 25)
     private val sideColor = Color(35, 188, 254, 100)
 
-    override fun interact(swingHand: Boolean) {
-        runSafe {
-            val actionResult = interaction.interactBlock(
-                player, hand, result
-            )
-
-            if (actionResult is ActionResult.Success) {
-                if (actionResult.swingSource() == ActionResult.SwingSource.CLIENT && swingHand) {
-                    player.swingHand(hand)
-                }
-
-                if (!player.getStackInHand(hand).isEmpty && player.isCreative) {
-                    mc.gameRenderer.firstPersonRenderer.resetEquipProgress(hand)
-                }
-            } else {
-                warn("Internal interaction failed with $actionResult")
-            }
-        }
-    }
-
     override fun compareTo(other: BuildContext) =
         when (other) {
             is PlaceContext -> compareBy {
-                BlockUtils.fluids.indexOf(it.checkedState.fluidState.fluid)
+                BlockUtils.fluids.indexOf(it.cachedState.fluidState.fluid)
             }.thenByDescending {
-                it.checkedState.fluidState.level
-            }.thenBy {
-                it.hand
+                if (it.cachedState.fluidState.level != 0) it.blockPos.y else 0
+            }.thenByDescending {
+                it.cachedState.fluidState.level
             }.thenBy {
-                it.sneak
+                it.sneak == (mc.player?.isSneaking ?: false)
             }.thenBy {
                 it.rotation.target.angleDistance
+            }.thenBy {
+                it.hotbarIndex == HotbarManager.serverSlot
             }.thenBy {
                 it.distance
             }.thenBy {
@@ -95,9 +71,15 @@ data class PlaceContext(
         }
 
     override fun SafeContext.buildRenderer() {
-        withState(expectedState, expectedPos, baseColor, DirectionMask.ALL.exclude(result.side.opposite))
+        withState(expectedState, blockPos, baseColor, DirectionMask.ALL.exclude(result.side.opposite))
         withState(blockState(result.blockPos), result.blockPos, sideColor, result.side)
     }
 
-    override fun shouldRotate(config: BuildConfig) = config.rotateForPlace
+    fun requestDependencies(request: PlaceRequest): Boolean {
+        val hotbarRequest = submit(HotbarRequest(hotbarIndex, request.hotbar), false)
+        val validRotation = if (request.rotateForPlace) {
+            submit(rotation, false).done && currentDirIsValid
+        } else true
+        return hotbarRequest.done && validRotation
+    }
 }
diff --git a/src/main/kotlin/com/lambda/interaction/construction/processing/PlacementProcessor.kt b/src/main/kotlin/com/lambda/interaction/construction/processing/PlacementProcessor.kt
index 9f53c347b..d1880aedc 100644
--- a/src/main/kotlin/com/lambda/interaction/construction/processing/PlacementProcessor.kt
+++ b/src/main/kotlin/com/lambda/interaction/construction/processing/PlacementProcessor.kt
@@ -18,9 +18,9 @@
 package com.lambda.interaction.construction.processing
 
 import net.minecraft.block.BlockState
+import net.minecraft.util.math.BlockPos
 
 abstract class PlacementProcessor {
-    abstract fun acceptState(state: BlockState): Boolean
-    abstract fun preProcess(state: BlockState): PreprocessingStep
-}
-
+    abstract fun acceptsState(state: BlockState): Boolean
+    abstract fun preProcess(state: BlockState, pos: BlockPos, accumulator: PreProcessingInfoAccumulator)
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/lambda/interaction/construction/processing/PreprocessingStep.kt b/src/main/kotlin/com/lambda/interaction/construction/processing/PreProcessingInfo.kt
similarity index 65%
rename from src/main/kotlin/com/lambda/interaction/construction/processing/PreprocessingStep.kt
rename to src/main/kotlin/com/lambda/interaction/construction/processing/PreProcessingInfo.kt
index 37d4bac84..cb6b99424 100644
--- a/src/main/kotlin/com/lambda/interaction/construction/processing/PreprocessingStep.kt
+++ b/src/main/kotlin/com/lambda/interaction/construction/processing/PreProcessingInfo.kt
@@ -21,8 +21,18 @@ import com.lambda.interaction.construction.verify.SurfaceScan
 import net.minecraft.state.property.Property
 import net.minecraft.util.math.Direction
 
-data class PreprocessingStep(
-    val surfaceScan: SurfaceScan = SurfaceScan.DEFAULT,
-    val ignore: Set> = emptySet(),
-    val sides: Set = Direction.entries.toSet(),
-)
+interface PreProcessingInfo {
+    val surfaceScan: SurfaceScan
+    val ignore: Set>
+    val sides: Set
+    val shouldBeOmitted: Boolean
+
+    companion object {
+        val DEFAULT = object : PreProcessingInfo {
+            override val surfaceScan = SurfaceScan.DEFAULT
+            override val ignore = setOf>()
+            override val sides = Direction.entries.toSet()
+            override val shouldBeOmitted = false
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/lambda/interaction/construction/processing/PreProcessingInfoAccumulator.kt b/src/main/kotlin/com/lambda/interaction/construction/processing/PreProcessingInfoAccumulator.kt
new file mode 100644
index 000000000..def89c5e4
--- /dev/null
+++ b/src/main/kotlin/com/lambda/interaction/construction/processing/PreProcessingInfoAccumulator.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2025 Lambda
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see .
+ */
+
+package com.lambda.interaction.construction.processing
+
+import com.lambda.interaction.construction.verify.SurfaceScan
+import net.minecraft.state.property.Property
+import net.minecraft.util.math.Direction
+
+class PreProcessingInfoAccumulator(
+    override var surfaceScan: SurfaceScan = SurfaceScan.DEFAULT,
+    override val ignore: MutableSet> = ProcessorRegistry.postProcessedProperties.toMutableSet(),
+    override val sides: MutableSet = Direction.entries.toMutableSet(),
+    override var shouldBeOmitted: Boolean = false
+) : PreProcessingInfo {
+    @InfoAccumulator
+    fun offerSurfaceScan(scan: SurfaceScan) {
+        if (scan.mode.priority > surfaceScan.mode.priority) {
+            surfaceScan = scan
+        }
+    }
+
+    @InfoAccumulator
+    fun addIgnores(vararg properties: Property<*>) {
+        ignore.addAll(properties)
+    }
+
+    @InfoAccumulator
+    fun retainSides(predicate: (Direction) -> Boolean) {
+        sides.retainAll(predicate)
+    }
+
+    @InfoAccumulator
+    fun retainSides(vararg sides: Direction) {
+        this.sides.retainAll(sides.toSet())
+    }
+
+    @InfoAccumulator
+    fun omitPlacement() {
+        shouldBeOmitted = true
+    }
+
+    @InfoAccumulator
+    fun complete() = this as PreProcessingInfo
+
+    companion object {
+        @DslMarker
+        private annotation class InfoAccumulator
+    }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/lambda/interaction/construction/processing/ProcessorRegistry.kt b/src/main/kotlin/com/lambda/interaction/construction/processing/ProcessorRegistry.kt
index 26f66a4c6..8600518b9 100644
--- a/src/main/kotlin/com/lambda/interaction/construction/processing/ProcessorRegistry.kt
+++ b/src/main/kotlin/com/lambda/interaction/construction/processing/ProcessorRegistry.kt
@@ -21,23 +21,116 @@ import com.lambda.core.Loadable
 import com.lambda.interaction.construction.verify.TargetState
 import com.lambda.util.reflections.getInstances
 import net.minecraft.block.BlockState
-import net.minecraft.block.Blocks
+import net.minecraft.block.SlabBlock
+import net.minecraft.block.enums.SlabType
+import net.minecraft.state.property.Properties
+import net.minecraft.util.math.BlockPos
 
 object ProcessorRegistry : Loadable {
     private val processors = getInstances()
-    private val processorCache = mutableMapOf()
+    private val processorCache = mutableMapOf()
+
+    val postProcessedProperties = setOf(
+        Properties.EXTENDED,
+        Properties.EYE,
+        Properties.HAS_BOOK,
+        Properties.HAS_BOTTLE_0, Properties.HAS_BOTTLE_1, Properties.HAS_BOTTLE_2,
+        Properties.HAS_RECORD,
+        Properties.INVERTED,
+        Properties.LIT,
+        Properties.LOCKED,
+        Properties.OCCUPIED,
+        Properties.OPEN,
+        Properties.POWERED,
+        Properties.SIGNAL_FIRE,
+        Properties.SNOWY,
+        Properties.TRIGGERED,
+        Properties.UNSTABLE,
+        Properties.WATERLOGGED,
+        Properties.BERRIES,
+        Properties.BLOOM,
+        Properties.SHRIEKING,
+        Properties.CAN_SUMMON,
+        Properties.FLOWER_AMOUNT,
+        Properties.EAST_WALL_SHAPE, Properties.SOUTH_WALL_SHAPE, Properties.WEST_WALL_SHAPE, Properties.NORTH_WALL_SHAPE,
+        Properties.EAST_WIRE_CONNECTION, Properties.SOUTH_WIRE_CONNECTION, Properties.WEST_WIRE_CONNECTION, Properties.NORTH_WIRE_CONNECTION,
+        Properties.AGE_1,
+        Properties.AGE_2,
+        Properties.AGE_3,
+        Properties.AGE_4,
+        Properties.AGE_5,
+        Properties.AGE_7,
+        Properties.AGE_15,
+        Properties.AGE_25,
+        Properties.BITES,
+        Properties.CANDLES,
+        Properties.DELAY,
+        Properties.EGGS,
+        Properties.HATCH,
+        Properties.LAYERS,
+        Properties.LEVEL_3,
+        Properties.LEVEL_8,
+        Properties.LEVEL_1_8,
+        Properties.HONEY_LEVEL,
+        Properties.LEVEL_15,
+        Properties.MOISTURE,
+        Properties.NOTE,
+        Properties.PICKLES,
+        Properties.POWER,
+        Properties.STAGE,
+        Properties.CHARGES,
+        Properties.CHEST_TYPE,
+        Properties.COMPARATOR_MODE,
+        Properties.INSTRUMENT,
+        Properties.STAIR_SHAPE,
+        Properties.TILT,
+        Properties.THICKNESS,
+        Properties.SCULK_SENSOR_PHASE,
+        Properties.SLOT_0_OCCUPIED, Properties.SLOT_1_OCCUPIED, Properties.SLOT_2_OCCUPIED, Properties.SLOT_3_OCCUPIED, Properties.SLOT_4_OCCUPIED, Properties.SLOT_5_OCCUPIED,
+        Properties.DUSTED,
+        Properties.CRAFTING,
+        Properties.TRIAL_SPAWNER_STATE,
+        Properties.DISARMED,
+        Properties.ATTACHED,
+        Properties.DRAG,
+        Properties.ENABLED,
+        Properties.IN_WALL,
+        Properties.UP,
+        Properties.DOWN,
+        Properties.NORTH,
+        Properties.EAST,
+        Properties.SOUTH,
+        Properties.WEST,
+        Properties.PERSISTENT,
+        Properties.DISTANCE_1_7
+    )
 
     override fun load() = "Loaded ${processors.size} pre processors"
 
-    fun TargetState.findProcessorForState(): PreprocessingStep =
-        (this as? TargetState.State)?.let { state ->
-            processorCache.getOrPut(state.blockState) {
-                (processors.find { it.acceptState(state.blockState) } ?: DefaultProcessor).preProcess(state.blockState)
+    fun TargetState.getProcessingInfo(pos: BlockPos) =
+        if (this is TargetState.State) {
+            val get: () -> PreProcessingInfo? = get@ {
+                val infoAccumulator = PreProcessingInfoAccumulator()
+
+                processors.forEach { processor ->
+                    if (!processor.acceptsState(blockState)) return@forEach
+                    processor.preProcess(blockState, pos, infoAccumulator)
+                    if (infoAccumulator.shouldBeOmitted) {
+                        return@get null
+                    }
+                }
+
+                infoAccumulator.complete()
+            }
+            if (isExemptFromCache()) {
+                get()
+            } else {
+                processorCache.getOrPut(blockState, get)
             }
-        } ?: DefaultProcessor.preProcess(Blocks.AIR.defaultState)
+        } else {
+            PreProcessingInfo.DEFAULT
+        }
 
-    object DefaultProcessor : PlacementProcessor() {
-        override fun acceptState(state: BlockState) = true
-        override fun preProcess(state: BlockState) = PreprocessingStep()
-    }
+    private fun TargetState.State.isExemptFromCache() =
+        blockState.block is SlabBlock && blockState.get(Properties.SLAB_TYPE) == SlabType.DOUBLE
 }
diff --git a/src/main/kotlin/com/lambda/interaction/construction/processing/preprocessors/AttachmentPreProcessor.kt b/src/main/kotlin/com/lambda/interaction/construction/processing/preprocessors/AttachmentPreProcessor.kt
new file mode 100644
index 000000000..823f4b76a
--- /dev/null
+++ b/src/main/kotlin/com/lambda/interaction/construction/processing/preprocessors/AttachmentPreProcessor.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2025 Lambda
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see .
+ */
+
+package com.lambda.interaction.construction.processing.preprocessors
+
+import com.lambda.interaction.construction.processing.PlacementProcessor
+import com.lambda.interaction.construction.processing.PreProcessingInfoAccumulator
+import net.minecraft.block.BlockState
+import net.minecraft.block.enums.Attachment
+import net.minecraft.state.property.Properties
+import net.minecraft.util.math.BlockPos
+import net.minecraft.util.math.Direction
+
+// Collected using reflections and then accessed from a collection in ProcessorRegistry
+@Suppress("unused")
+object AttachmentPreProcessor : PlacementProcessor() {
+    override fun acceptsState(state: BlockState) =
+        state.properties.contains(Properties.ATTACHMENT)
+
+    override fun preProcess(state: BlockState, pos: BlockPos, accumulator: PreProcessingInfoAccumulator) {
+        val attachment = state.get(Properties.ATTACHMENT) ?: return
+        with (accumulator) {
+            when (attachment) {
+                Attachment.FLOOR -> retainSides(Direction.DOWN)
+                Attachment.CEILING -> retainSides(Direction.UP)
+                else -> retainSides { it in Direction.Type.HORIZONTAL }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/lambda/interaction/construction/processing/processors/AxisPreprocessor.kt b/src/main/kotlin/com/lambda/interaction/construction/processing/preprocessors/AxisPreProcessor.kt
similarity index 58%
rename from src/main/kotlin/com/lambda/interaction/construction/processing/processors/AxisPreprocessor.kt
rename to src/main/kotlin/com/lambda/interaction/construction/processing/preprocessors/AxisPreProcessor.kt
index bf134a14e..0142ae768 100644
--- a/src/main/kotlin/com/lambda/interaction/construction/processing/processors/AxisPreprocessor.kt
+++ b/src/main/kotlin/com/lambda/interaction/construction/processing/preprocessors/AxisPreProcessor.kt
@@ -15,22 +15,24 @@
  * along with this program.  If not, see .
  */
 
-package com.lambda.interaction.construction.processing.processors
+package com.lambda.interaction.construction.processing.preprocessors
 
 import com.lambda.interaction.construction.processing.PlacementProcessor
-import com.lambda.interaction.construction.processing.PreprocessingStep
+import com.lambda.interaction.construction.processing.PreProcessingInfoAccumulator
 import net.minecraft.block.BlockState
 import net.minecraft.state.property.Properties
-import net.minecraft.util.math.Direction
+import net.minecraft.util.math.BlockPos
 
-object AxisPreprocessor : PlacementProcessor() {
-    override fun acceptState(state: BlockState) =
+// Collected using reflections and then accessed from a collection in ProcessorRegistry
+@Suppress("unused")
+object AxisPreProcessor : PlacementProcessor() {
+    override fun acceptsState(state: BlockState) =
         state.getOrEmpty(Properties.AXIS).isPresent
 
-    override fun preProcess(state: BlockState): PreprocessingStep {
-        val axis = state.getOrEmpty(Properties.AXIS).get()
-        return PreprocessingStep(
-            sides = Direction.entries.filter { it.axis == axis }.toSet()
-        )
+    override fun preProcess(state: BlockState, pos: BlockPos, accumulator: PreProcessingInfoAccumulator) {
+        val axis = state.get(Properties.AXIS)
+        accumulator.retainSides { side ->
+            side.axis == axis
+        }
     }
 }
diff --git a/src/main/kotlin/com/lambda/interaction/construction/processing/processors/BlockFaceProcessor.kt b/src/main/kotlin/com/lambda/interaction/construction/processing/preprocessors/BlockFacePreProcessor.kt
similarity index 53%
rename from src/main/kotlin/com/lambda/interaction/construction/processing/processors/BlockFaceProcessor.kt
rename to src/main/kotlin/com/lambda/interaction/construction/processing/preprocessors/BlockFacePreProcessor.kt
index 3bdc5e11d..e0a0a02ee 100644
--- a/src/main/kotlin/com/lambda/interaction/construction/processing/processors/BlockFaceProcessor.kt
+++ b/src/main/kotlin/com/lambda/interaction/construction/processing/preprocessors/BlockFacePreProcessor.kt
@@ -15,27 +15,30 @@
  * along with this program.  If not, see .
  */
 
-package com.lambda.interaction.construction.processing.processors
+package com.lambda.interaction.construction.processing.preprocessors
 
 import com.lambda.interaction.construction.processing.PlacementProcessor
-import com.lambda.interaction.construction.processing.PreprocessingStep
+import com.lambda.interaction.construction.processing.PreProcessingInfoAccumulator
 import net.minecraft.block.BlockState
 import net.minecraft.block.enums.BlockFace
 import net.minecraft.state.property.Properties
+import net.minecraft.util.math.BlockPos
 import net.minecraft.util.math.Direction
-import kotlin.jvm.optionals.getOrNull
 
-object BlockFaceProcessor : PlacementProcessor() {
-    override fun acceptState(state: BlockState) =
+// Collected using reflections and then accessed from a collection in ProcessorRegistry
+@Suppress("unused")
+object BlockFacePreProcessor : PlacementProcessor() {
+    override fun acceptsState(state: BlockState) =
         state.getOrEmpty(Properties.BLOCK_FACE).isPresent
 
-    override fun preProcess(state: BlockState): PreprocessingStep {
-        val property = state.getOrEmpty(Properties.BLOCK_FACE).getOrNull() ?: return PreprocessingStep()
-        val sides = when (property) {
-            BlockFace.FLOOR -> setOf(Direction.DOWN)
-            BlockFace.CEILING -> setOf(Direction.UP)
-            BlockFace.WALL -> Direction.Type.HORIZONTAL.stream().toList().toSet()
+    override fun preProcess(state: BlockState, pos: BlockPos, accumulator: PreProcessingInfoAccumulator) {
+        val property = state.get(Properties.BLOCK_FACE) ?: return
+        with (accumulator) {
+            when (property) {
+                BlockFace.FLOOR -> retainSides(Direction.DOWN)
+                BlockFace.CEILING -> retainSides(Direction.UP)
+                BlockFace.WALL -> retainSides { it in Direction.Type.HORIZONTAL }
+            }
         }
-        return PreprocessingStep(sides = sides)
     }
 }
diff --git a/src/main/kotlin/com/lambda/interaction/construction/processing/processors/SlabProcessor.kt b/src/main/kotlin/com/lambda/interaction/construction/processing/preprocessors/BlockHalfPreProcessor.kt
similarity index 54%
rename from src/main/kotlin/com/lambda/interaction/construction/processing/processors/SlabProcessor.kt
rename to src/main/kotlin/com/lambda/interaction/construction/processing/preprocessors/BlockHalfPreProcessor.kt
index 6ffb7fd76..ef4ea9515 100644
--- a/src/main/kotlin/com/lambda/interaction/construction/processing/processors/SlabProcessor.kt
+++ b/src/main/kotlin/com/lambda/interaction/construction/processing/preprocessors/BlockHalfPreProcessor.kt
@@ -15,36 +15,32 @@
  * along with this program.  If not, see .
  */
 
-package com.lambda.interaction.construction.processing.processors
+package com.lambda.interaction.construction.processing.preprocessors
 
 import com.lambda.interaction.construction.processing.PlacementProcessor
-import com.lambda.interaction.construction.processing.PreprocessingStep
+import com.lambda.interaction.construction.processing.PreProcessingInfoAccumulator
 import com.lambda.interaction.construction.verify.ScanMode
 import com.lambda.interaction.construction.verify.SurfaceScan
 import net.minecraft.block.BlockState
-import net.minecraft.block.SlabBlock
-import net.minecraft.block.enums.SlabType
+import net.minecraft.block.enums.BlockHalf
 import net.minecraft.state.property.Properties
+import net.minecraft.util.math.BlockPos
 import net.minecraft.util.math.Direction
 
-object SlabProcessor : PlacementProcessor() {
-    override fun acceptState(state: BlockState) = state.block is SlabBlock
+// Collected using reflections and then accessed from a collection in ProcessorRegistry
+@Suppress("unused")
+object BlockHalfPreProcessor : PlacementProcessor() {
+    override fun acceptsState(state: BlockState) =
+        state.getOrEmpty(Properties.BLOCK_HALF).isPresent
 
-    override fun preProcess(state: BlockState): PreprocessingStep {
-        val slab = state.getOrEmpty(Properties.SLAB_TYPE).get()
+    override fun preProcess(state: BlockState, pos: BlockPos, accumulator: PreProcessingInfoAccumulator) {
+        val slab = state.get(Properties.BLOCK_HALF) ?: return
 
         val surfaceScan = when (slab) {
-             SlabType.BOTTOM -> SurfaceScan(
-                 ScanMode.LESSER_HALF, Direction.Axis.Y
-             )
-             SlabType.TOP -> SurfaceScan(
-                 ScanMode.GREATER_HALF, Direction.Axis.Y
-             )
-             SlabType.DOUBLE -> SurfaceScan(
-                 ScanMode.FULL, Direction.Axis.Y
-             )
+            BlockHalf.BOTTOM -> SurfaceScan(ScanMode.LESSER_BLOCK_HALF, Direction.Axis.Y)
+            BlockHalf.TOP -> SurfaceScan(ScanMode.GREATER_BLOCK_HALF, Direction.Axis.Y)
         }
 
-        return PreprocessingStep(surfaceScan)
+        accumulator.offerSurfaceScan(surfaceScan)
     }
 }
diff --git a/src/main/kotlin/com/lambda/interaction/construction/processing/preprocessors/DoorHingePreProcessor.kt b/src/main/kotlin/com/lambda/interaction/construction/processing/preprocessors/DoorHingePreProcessor.kt
new file mode 100644
index 000000000..07abad61d
--- /dev/null
+++ b/src/main/kotlin/com/lambda/interaction/construction/processing/preprocessors/DoorHingePreProcessor.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2025 Lambda
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see .
+ */
+
+package com.lambda.interaction.construction.processing.preprocessors
+
+import com.lambda.interaction.construction.processing.PlacementProcessor
+import com.lambda.interaction.construction.processing.PreProcessingInfoAccumulator
+import com.lambda.interaction.construction.verify.ScanMode
+import com.lambda.interaction.construction.verify.SurfaceScan
+import com.lambda.threading.runSafe
+import net.minecraft.block.BlockState
+import net.minecraft.block.enums.DoorHinge
+import net.minecraft.state.property.Properties
+import net.minecraft.util.math.BlockPos
+import net.minecraft.util.math.Direction
+
+// Collected using reflections and then accessed from a collection in ProcessorRegistry
+@Suppress("unused")
+object DoorHingePreProcessor : PlacementProcessor() {
+    override fun acceptsState(state: BlockState) =
+        state.properties.contains(Properties.DOOR_HINGE)
+
+    override fun preProcess(state: BlockState, pos: BlockPos, accumulator: PreProcessingInfoAccumulator) =
+        runSafe {
+            val side = state.get(Properties.DOOR_HINGE) ?: return@runSafe
+            val scanner = when (state.get(Properties.HORIZONTAL_FACING) ?: return@runSafe) {
+                Direction.NORTH ->
+                    if (side == DoorHinge.LEFT) SurfaceScan(ScanMode.LESSER_BLOCK_HALF, Direction.Axis.X)
+                    else SurfaceScan(ScanMode.GREATER_BLOCK_HALF, Direction.Axis.X)
+                Direction.EAST ->
+                    if (side == DoorHinge.LEFT) SurfaceScan(ScanMode.LESSER_BLOCK_HALF, Direction.Axis.Z)
+                    else SurfaceScan(ScanMode.GREATER_BLOCK_HALF, Direction.Axis.Z)
+                Direction.SOUTH ->
+                    if (side == DoorHinge.LEFT) SurfaceScan(ScanMode.GREATER_BLOCK_HALF, Direction.Axis.X)
+                    else SurfaceScan(ScanMode.LESSER_BLOCK_HALF, Direction.Axis.X)
+                Direction.DOWN,
+                Direction.UP,
+                Direction.WEST ->
+                    if (side == DoorHinge.LEFT) SurfaceScan(ScanMode.GREATER_BLOCK_HALF, Direction.Axis.Z)
+                    else SurfaceScan(ScanMode.LESSER_BLOCK_HALF, Direction.Axis.Z)
+            }
+            accumulator.offerSurfaceScan(scanner)
+        } ?: Unit
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/lambda/interaction/construction/processing/preprocessors/HopperFacingPreProcessor.kt b/src/main/kotlin/com/lambda/interaction/construction/processing/preprocessors/HopperFacingPreProcessor.kt
new file mode 100644
index 000000000..f37848f58
--- /dev/null
+++ b/src/main/kotlin/com/lambda/interaction/construction/processing/preprocessors/HopperFacingPreProcessor.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2025 Lambda
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see .
+ */
+
+package com.lambda.interaction.construction.processing.preprocessors
+
+import com.lambda.interaction.construction.processing.PlacementProcessor
+import com.lambda.interaction.construction.processing.PreProcessingInfoAccumulator
+import net.minecraft.block.BlockState
+import net.minecraft.state.property.Properties
+import net.minecraft.util.math.BlockPos
+import net.minecraft.util.math.Direction
+
+// Collected using reflections and then accessed from a collection in ProcessorRegistry
+@Suppress("unused")
+object HopperFacingPreProcessor : PlacementProcessor() {
+    override fun acceptsState(state: BlockState) =
+        state.properties.contains(Properties.HOPPER_FACING)
+
+    override fun preProcess(state: BlockState, pos: BlockPos, accumulator: PreProcessingInfoAccumulator) {
+        val facing = state.get(Properties.HOPPER_FACING) ?: return
+        when {
+            facing.axis == Direction.Axis.Y -> accumulator.retainSides { it.axis == Direction.Axis.Y }
+            else -> accumulator.retainSides(facing)
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/lambda/interaction/construction/processing/processors/FacingProcessor.kt b/src/main/kotlin/com/lambda/interaction/construction/processing/preprocessors/OmitPreProcessor.kt
similarity index 52%
rename from src/main/kotlin/com/lambda/interaction/construction/processing/processors/FacingProcessor.kt
rename to src/main/kotlin/com/lambda/interaction/construction/processing/preprocessors/OmitPreProcessor.kt
index a0331c71c..7e80c6915 100644
--- a/src/main/kotlin/com/lambda/interaction/construction/processing/processors/FacingProcessor.kt
+++ b/src/main/kotlin/com/lambda/interaction/construction/processing/preprocessors/OmitPreProcessor.kt
@@ -15,21 +15,25 @@
  * along with this program.  If not, see .
  */
 
-package com.lambda.interaction.construction.processing.processors
+package com.lambda.interaction.construction.processing.preprocessors
 
 import com.lambda.interaction.construction.processing.PlacementProcessor
-import com.lambda.interaction.construction.processing.PreprocessingStep
+import com.lambda.interaction.construction.processing.PreProcessingInfoAccumulator
 import net.minecraft.block.BlockState
+import net.minecraft.block.enums.DoubleBlockHalf
 import net.minecraft.state.property.Properties
+import net.minecraft.util.math.BlockPos
 
-object FacingProcessor : PlacementProcessor() {
-    override fun acceptState(state: BlockState) =
-        state.getOrEmpty(Properties.FACING).isPresent
+// Collected using reflections and then accessed from a collection in ProcessorRegistry
+@Suppress("unused")
+object OmitPreProcessor : PlacementProcessor() {
+    override fun acceptsState(state: BlockState) = true
 
-    override fun preProcess(state: BlockState): PreprocessingStep {
-        // Needs two sets of blocks: native and opposite!
-        return PreprocessingStep(
-            sides = setOf(state.getOrEmpty(Properties.FACING).get())
-        )
+    override fun preProcess(state: BlockState, pos: BlockPos, accumulator: PreProcessingInfoAccumulator) {
+        if (Properties.DOUBLE_BLOCK_HALF in state.properties) {
+            if (state.get(Properties.DOUBLE_BLOCK_HALF) == DoubleBlockHalf.UPPER) {
+                accumulator.omitPlacement()
+            }
+        }
     }
 }
diff --git a/src/main/kotlin/com/lambda/interaction/construction/processing/preprocessors/SlabPreProcessor.kt b/src/main/kotlin/com/lambda/interaction/construction/processing/preprocessors/SlabPreProcessor.kt
new file mode 100644
index 000000000..f46b986bc
--- /dev/null
+++ b/src/main/kotlin/com/lambda/interaction/construction/processing/preprocessors/SlabPreProcessor.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2025 Lambda
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see .
+ */
+
+package com.lambda.interaction.construction.processing.preprocessors
+
+import com.lambda.interaction.construction.processing.PlacementProcessor
+import com.lambda.interaction.construction.processing.PreProcessingInfoAccumulator
+import com.lambda.interaction.construction.verify.ScanMode
+import com.lambda.interaction.construction.verify.SurfaceScan
+import com.lambda.threading.runSafe
+import com.lambda.util.BlockUtils.blockState
+import net.minecraft.block.BlockState
+import net.minecraft.block.SlabBlock
+import net.minecraft.block.enums.SlabType
+import net.minecraft.state.property.Properties
+import net.minecraft.util.math.BlockPos
+import net.minecraft.util.math.Direction
+
+// Collected using reflections and then accessed from a collection in ProcessorRegistry
+@Suppress("unused")
+object SlabPreProcessor : PlacementProcessor() {
+    override fun acceptsState(state: BlockState) = state.block is SlabBlock
+
+    override fun preProcess(state: BlockState, pos: BlockPos, accumulator: PreProcessingInfoAccumulator) {
+        val slab = state.get(Properties.SLAB_TYPE) ?: return
+        val currentState = runSafe { blockState(pos) } ?: return
+
+        val surfaceScan = when (slab) {
+             SlabType.BOTTOM -> SurfaceScan(ScanMode.LESSER_BLOCK_HALF, Direction.Axis.Y)
+             SlabType.TOP -> SurfaceScan(ScanMode.GREATER_BLOCK_HALF, Direction.Axis.Y)
+             SlabType.DOUBLE -> {
+                 accumulator.addIgnores(Properties.SLAB_TYPE)
+                 if (currentState.block !is SlabBlock) SurfaceScan.DEFAULT
+                 else when (currentState.get(Properties.SLAB_TYPE)) {
+                     SlabType.BOTTOM -> SurfaceScan(ScanMode.GREATER_BLOCK_HALF, Direction.Axis.Y)
+                     else -> SurfaceScan(ScanMode.LESSER_BLOCK_HALF, Direction.Axis.Y)
+                 }
+             }
+        }
+
+        accumulator.offerSurfaceScan(surfaceScan)
+    }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/lambda/interaction/construction/result/BreakResult.kt b/src/main/kotlin/com/lambda/interaction/construction/result/BreakResult.kt
index 148de9aa8..b6f032a47 100644
--- a/src/main/kotlin/com/lambda/interaction/construction/result/BreakResult.kt
+++ b/src/main/kotlin/com/lambda/interaction/construction/result/BreakResult.kt
@@ -19,15 +19,13 @@ package com.lambda.interaction.construction.result
 
 import baritone.api.pathing.goals.GoalBlock
 import baritone.api.pathing.goals.GoalInverted
-import com.lambda.config.groups.InventoryConfig
 import com.lambda.context.SafeContext
 import com.lambda.interaction.construction.context.BreakContext
-import com.lambda.interaction.material.StackSelection.Companion.select
 import com.lambda.interaction.material.StackSelection.Companion.selectStack
-import com.lambda.interaction.material.container.ContainerManager.findBestAvailableTool
 import com.lambda.interaction.material.container.ContainerManager.transfer
 import com.lambda.interaction.material.container.MaterialContainer
 import com.lambda.interaction.material.container.containers.MainHandContainer
+import com.lambda.interaction.request.inventory.InventoryConfig
 import net.minecraft.block.BlockState
 import net.minecraft.item.Item
 import net.minecraft.util.math.BlockPos
@@ -99,13 +97,16 @@ sealed class BreakResult : BuildResult() {
         override val pausesParent get() = true
 
         override fun resolve() =
-            findBestAvailableTool(blockState, inventory = inventory)
-                ?.select()
-                ?.transfer(MainHandContainer, inventory)
-                ?: selectStack {
-                    isItem(badItem).not()
-                }.transfer(MainHandContainer, inventory)
-                ?: MaterialContainer.FailureTask("Couldn't find a tool for ${blockState.block.name.string} with $badItem in main hand.")
+            selectStack {
+                isItem(badItem).not()
+            }.let { selection ->
+                selection.transfer(MainHandContainer, inventory)
+                    ?: MaterialContainer.AwaitItemTask(
+                        "Couldn't find a tool for ${blockState.block.name.string} with $badItem in main hand.",
+                        selection,
+                        inventory
+                    )
+            }
 
         override fun SafeContext.buildRenderer() {
             withPos(blockPos, color)
@@ -139,11 +140,11 @@ sealed class BreakResult : BuildResult() {
     /**
      * The block is blocked by another liquid block that first has to be submerged.
      */
-    data class BlockedByLiquid(
+    data class BlockedByFluid(
         override val blockPos: BlockPos,
         val blockState: BlockState,
     ) : Drawable, BreakResult() {
-        override val rank = Rank.BREAK_IS_BLOCKED_BY_LIQUID
+        override val rank = Rank.BREAK_IS_BLOCKED_BY_FLUID
         private val color = Color(50, 12, 112, 100)
 
         override fun SafeContext.buildRenderer() {
diff --git a/src/main/kotlin/com/lambda/interaction/construction/result/BuildResult.kt b/src/main/kotlin/com/lambda/interaction/construction/result/BuildResult.kt
index bd9f4e02e..5e8d657f9 100644
--- a/src/main/kotlin/com/lambda/interaction/construction/result/BuildResult.kt
+++ b/src/main/kotlin/com/lambda/interaction/construction/result/BuildResult.kt
@@ -19,17 +19,17 @@ package com.lambda.interaction.construction.result
 
 import baritone.api.pathing.goals.GoalBlock
 import baritone.api.pathing.goals.GoalNear
-import com.lambda.config.groups.InventoryConfig
 import com.lambda.context.SafeContext
 import com.lambda.interaction.construction.context.BuildContext
+import com.lambda.interaction.material.StackSelection
 import com.lambda.interaction.material.StackSelection.Companion.select
 import com.lambda.interaction.material.container.ContainerManager.transfer
 import com.lambda.interaction.material.container.MaterialContainer
 import com.lambda.interaction.material.container.containers.MainHandContainer
+import com.lambda.interaction.request.inventory.InventoryConfig
 import com.lambda.util.BlockUtils.blockState
 import com.lambda.util.Nameable
 import net.minecraft.block.BlockState
-import net.minecraft.item.Item
 import net.minecraft.item.ItemStack
 import net.minecraft.util.math.BlockPos
 import net.minecraft.util.math.Box
@@ -190,23 +190,30 @@ abstract class BuildResult : ComparableResult, Nameable {
 
     /**
      * Player has an inefficient tool equipped.
-     * @param neededItem The best tool for the block state.
+     * @param neededSelection The best tool for the block state.
      */
-    data class WrongItem(
+    data class WrongItemSelection(
         override val blockPos: BlockPos,
         val context: BuildContext,
-        val neededItem: Item,
+        val neededSelection: StackSelection,
         val currentItem: ItemStack,
         val inventory: InventoryConfig
     ) : Drawable, Resolvable, BuildResult() {
-        override val name: String get() = "Wrong item ($currentItem) for ${blockPos.toShortString()} need ${neededItem.name.string}"
+        override val name: String get() = "Wrong item ($currentItem) for ${blockPos.toShortString()} need $neededSelection"
         override val rank = Rank.WRONG_ITEM
         private val color = Color(3, 252, 169, 25)
 
         override val pausesParent get() = true
 
-        override fun resolve() = neededItem.select()
-            .transfer(MainHandContainer, inventory) ?: MaterialContainer.FailureTask("Couldn't find ${neededItem.name.string} anywhere.")
+        override fun resolve() =
+            neededSelection.let { selection ->
+                selection.transfer(MainHandContainer, inventory)
+                    ?: MaterialContainer.AwaitItemTask(
+                        "Couldn't find $neededSelection anywhere.",
+                        selection,
+                        inventory
+                    )
+            }
 
         override fun SafeContext.buildRenderer() {
             if (blockState(blockPos).isAir) {
@@ -218,7 +225,7 @@ abstract class BuildResult : ComparableResult, Nameable {
 
         override fun compareTo(other: ComparableResult): Int {
             return when (other) {
-                is WrongItem -> context.compareTo(other.context)
+                is WrongItemSelection -> context.compareTo(other.context)
                 else -> super.compareTo(other)
             }
         }
@@ -242,8 +249,14 @@ abstract class BuildResult : ComparableResult, Nameable {
         override val pausesParent get() = true
 
         override fun resolve() =
-            neededStack.select()
-                .transfer(MainHandContainer, inventory) ?: MaterialContainer.FailureTask("Couldn't find ${neededStack.item.name.string} anywhere.")
+            neededStack.select().let { selection ->
+                selection.transfer(MainHandContainer, inventory)
+                    ?: MaterialContainer.AwaitItemTask(
+                        "Couldn't find ${neededStack.item.name.string} anywhere.",
+                        selection,
+                        inventory
+                    )
+            }
 
         override fun SafeContext.buildRenderer() {
             if (blockState(blockPos).isAir) {
@@ -255,7 +268,7 @@ abstract class BuildResult : ComparableResult, Nameable {
 
         override fun compareTo(other: ComparableResult): Int {
             return when (other) {
-                is WrongItem -> context.compareTo(other.context)
+                is WrongItemSelection -> context.compareTo(other.context)
                 else -> super.compareTo(other)
             }
         }
diff --git a/src/main/kotlin/com/lambda/interaction/construction/result/InteractResult.kt b/src/main/kotlin/com/lambda/interaction/construction/result/InteractResult.kt
new file mode 100644
index 000000000..bbfcd5137
--- /dev/null
+++ b/src/main/kotlin/com/lambda/interaction/construction/result/InteractResult.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2025 Lambda
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see .
+ */
+
+package com.lambda.interaction.construction.result
+
+import com.lambda.context.SafeContext
+import com.lambda.interaction.construction.context.InteractionContext
+import net.minecraft.util.math.BlockPos
+
+sealed class InteractResult : BuildResult() {
+    data class Interact(
+        override val blockPos: BlockPos,
+        override val context: InteractionContext
+    ) : Contextual, Drawable, InteractResult() {
+        override val rank = Rank.INTERACT_SUCCESS
+
+        override fun SafeContext.buildRenderer() {
+            with(context) { buildRenderer() }
+        }
+
+        override fun compareTo(other: ComparableResult) =
+            when (other) {
+                is Interact -> context.compareTo(other.context)
+                else -> super.compareTo(other)
+            }
+    }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/lambda/interaction/construction/result/PlaceResult.kt b/src/main/kotlin/com/lambda/interaction/construction/result/PlaceResult.kt
index b652c5f3d..6b2dc65a0 100644
--- a/src/main/kotlin/com/lambda/interaction/construction/result/PlaceResult.kt
+++ b/src/main/kotlin/com/lambda/interaction/construction/result/PlaceResult.kt
@@ -50,12 +50,11 @@ sealed class PlaceResult : BuildResult() {
             with(context) { buildRenderer() }
         }
 
-        override fun compareTo(other: ComparableResult): Int {
-            return when (other) {
+        override fun compareTo(other: ComparableResult) =
+            when (other) {
                 is Place -> context.compareTo(other.context)
                 else -> super.compareTo(other)
             }
-        }
     }
 
     /**
diff --git a/src/main/kotlin/com/lambda/interaction/construction/result/Rank.kt b/src/main/kotlin/com/lambda/interaction/construction/result/Rank.kt
index 511b5bb64..699ea70cf 100644
--- a/src/main/kotlin/com/lambda/interaction/construction/result/Rank.kt
+++ b/src/main/kotlin/com/lambda/interaction/construction/result/Rank.kt
@@ -20,6 +20,7 @@ package com.lambda.interaction.construction.result
 enum class Rank {
     // solvable
     BREAK_SUCCESS,
+    INTERACT_SUCCESS,
     PLACE_SUCCESS,
     WRONG_ITEM,
     BREAK_ITEM_CANT_MINE,
@@ -37,7 +38,7 @@ enum class Rank {
     BREAK_RESTRICTED,
     PLACE_NO_INTEGRITY,
     BREAK_SUBMERGE,
-    BREAK_IS_BLOCKED_BY_LIQUID,
+    BREAK_IS_BLOCKED_BY_FLUID,
     UNBREAKABLE,
     BREAK_NO_PERMISSION,
     PLACE_SCAFFOLD_EXCEEDED,
diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt
index c06a5ff13..35aff73d6 100644
--- a/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt
+++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt
@@ -19,72 +19,105 @@ package com.lambda.interaction.construction.simulation
 
 import com.lambda.config.groups.BuildConfig
 import com.lambda.config.groups.InteractionConfig
-import com.lambda.config.groups.InventoryConfig
 import com.lambda.context.SafeContext
 import com.lambda.interaction.construction.blueprint.Blueprint
 import com.lambda.interaction.construction.context.BreakContext
+import com.lambda.interaction.construction.context.InteractionContext
 import com.lambda.interaction.construction.context.PlaceContext
-import com.lambda.interaction.construction.processing.ProcessorRegistry.findProcessorForState
+import com.lambda.interaction.construction.processing.PreProcessingInfo
+import com.lambda.interaction.construction.processing.ProcessorRegistry.getProcessingInfo
 import com.lambda.interaction.construction.result.BreakResult
 import com.lambda.interaction.construction.result.BuildResult
+import com.lambda.interaction.construction.result.InteractResult
 import com.lambda.interaction.construction.result.PlaceResult
 import com.lambda.interaction.construction.verify.TargetState
-import com.lambda.interaction.material.container.ContainerManager.findBestAvailableTool
-import com.lambda.interaction.request.rotation.Rotation.Companion.rotation
-import com.lambda.interaction.request.rotation.Rotation.Companion.rotationTo
-import com.lambda.interaction.request.rotation.RotationConfig
-import com.lambda.interaction.request.rotation.RotationManager
-import com.lambda.interaction.request.rotation.RotationRequest
-import com.lambda.interaction.request.rotation.visibilty.VisibilityChecker.CheckedHit
-import com.lambda.interaction.request.rotation.visibilty.VisibilityChecker.getVisibleSurfaces
-import com.lambda.interaction.request.rotation.visibilty.VisibilityChecker.scanSurfaces
-import com.lambda.interaction.request.rotation.visibilty.lookAt
-import com.lambda.interaction.request.rotation.visibilty.lookAtBlock
+import com.lambda.interaction.material.ContainerSelection.Companion.selectContainer
+import com.lambda.interaction.material.StackSelection
+import com.lambda.interaction.material.StackSelection.Companion.select
+import com.lambda.interaction.material.StackSelection.Companion.selectStack
+import com.lambda.interaction.material.container.ContainerManager.containerWithMaterial
+import com.lambda.interaction.material.container.MaterialContainer
+import com.lambda.interaction.request.breaking.BreakConfig
+import com.lambda.interaction.request.breaking.BreakManager
+import com.lambda.interaction.request.inventory.InventoryConfig
+import com.lambda.interaction.request.placing.PlaceConfig
+import com.lambda.interaction.request.rotating.Rotation.Companion.rotation
+import com.lambda.interaction.request.rotating.Rotation.Companion.rotationTo
+import com.lambda.interaction.request.rotating.RotationConfig
+import com.lambda.interaction.request.rotating.RotationManager
+import com.lambda.interaction.request.rotating.RotationRequest
+import com.lambda.interaction.request.rotating.visibilty.PlaceDirection
+import com.lambda.interaction.request.rotating.visibilty.VisibilityChecker.CheckedHit
+import com.lambda.interaction.request.rotating.visibilty.VisibilityChecker.getVisibleSurfaces
+import com.lambda.interaction.request.rotating.visibilty.VisibilityChecker.scanSurfaces
+import com.lambda.interaction.request.rotating.visibilty.lookAt
+import com.lambda.interaction.request.rotating.visibilty.lookAtBlock
+import com.lambda.interaction.request.rotating.visibilty.lookInDirection
 import com.lambda.module.modules.client.TaskFlowModule
 import com.lambda.threading.runSafe
 import com.lambda.util.BlockUtils
 import com.lambda.util.BlockUtils.blockState
+import com.lambda.util.BlockUtils.calcItemBlockBreakingDelta
+import com.lambda.util.BlockUtils.hasFluid
 import com.lambda.util.BlockUtils.instantBreakable
-import com.lambda.util.BlockUtils.vecOf
+import com.lambda.util.BlockUtils.isEmpty
+import com.lambda.util.BlockUtils.isNotEmpty
 import com.lambda.util.Communication.warn
-import com.lambda.util.item.ItemStackUtils.equal
 import com.lambda.util.math.distSq
+import com.lambda.util.math.vec3d
+import com.lambda.util.player.SlotUtils.hotbar
 import com.lambda.util.player.copyPlayer
-import com.lambda.util.world.WorldUtils.hasFluid
 import com.lambda.util.world.WorldUtils.isLoaded
+import com.lambda.util.player.gamemode
 import com.lambda.util.world.raycast.RayCastUtils.blockResult
+import net.minecraft.block.BlockState
+import net.minecraft.block.FallingBlock
 import net.minecraft.block.OperatorBlock
+import net.minecraft.block.SlabBlock
+import net.minecraft.block.Waterloggable
+import net.minecraft.block.enums.SlabType
 import net.minecraft.block.pattern.CachedBlockPosition
+import net.minecraft.enchantment.Enchantments
+import net.minecraft.fluid.FlowableFluid
+import net.minecraft.fluid.LavaFluid
+import net.minecraft.fluid.WaterFluid
 import net.minecraft.item.BlockItem
+import net.minecraft.item.Item
 import net.minecraft.item.ItemPlacementContext
+import net.minecraft.item.ItemStack
 import net.minecraft.item.ItemUsageContext
 import net.minecraft.state.property.Properties
 import net.minecraft.util.Hand
 import net.minecraft.util.hit.BlockHitResult
 import net.minecraft.util.math.BlockPos
-import net.minecraft.util.math.Box
 import net.minecraft.util.math.Direction
 import net.minecraft.util.math.Vec3d
-import kotlin.jvm.optionals.getOrNull
+import net.minecraft.util.shape.VoxelShapes
 import kotlin.math.pow
 
 object BuildSimulator {
     fun Blueprint.simulate(
         eye: Vec3d,
-        interact: InteractionConfig = TaskFlowModule.interact,
+        interactionConfig: InteractionConfig = TaskFlowModule.interaction,
         rotation: RotationConfig = TaskFlowModule.rotation,
         inventory: InventoryConfig = TaskFlowModule.inventory,
         build: BuildConfig = TaskFlowModule.build,
     ) = runSafe {
         structure.entries.flatMap { (pos, target) ->
-            checkRequirements(pos, target, build)?.let {
-                return@flatMap setOf(it)
+            val preProcessing = target.getProcessingInfo(pos) ?: return@flatMap emptySet()
+            checkRequirements(pos, target, build).let {
+                if (it.isEmpty()) return@let
+                return@flatMap it
+            }
+            checkPostProcessResults(pos, eye, preProcessing, target, interactionConfig, build.placing, rotation, inventory).let {
+                if (it.isEmpty()) return@let
+                return@flatMap it
             }
-            checkPlaceResults(pos, target, eye, interact, rotation, inventory).let {
+            checkPlaceResults(pos, eye, preProcessing, target, build.placing, interactionConfig, rotation, inventory).let {
                 if (it.isEmpty()) return@let
                 return@flatMap it
             }
-            checkBreakResults(pos, eye, interact, rotation, inventory, build).let {
+            checkBreakResults(pos, eye, preProcessing, build.breaking, interactionConfig, rotation, inventory, build).let {
                 if (it.isEmpty()) return@let
                 return@flatMap it
             }
@@ -97,66 +130,241 @@ object BuildSimulator {
         pos: BlockPos,
         target: TargetState,
         build: BuildConfig
-    ): BuildResult? {/* the chunk is not loaded */
+    ): Set {
+        val acc = mutableSetOf()
+
+        /* the chunk is not loaded */
         if (!isLoaded(pos)) {
-            return BuildResult.ChunkNotLoaded(pos)
+            acc.add(BuildResult.ChunkNotLoaded(pos))
+            return acc
         }
 
         val state = blockState(pos)
 
         /* block is already in the correct state */
         if (target.matches(state, pos, world)) {
-            return BuildResult.Done(pos)
+            acc.add(BuildResult.Done(pos))
+            return acc
         }
 
         /* block should be ignored */
-        if (state.block in build.ignoredBlocks && target.type == TargetState.Type.AIR) {
-            return BuildResult.Ignored(pos)
+        if (state.block in build.breaking.ignoredBlocks && target.type == TargetState.Type.AIR) {
+            acc.add(BuildResult.Ignored(pos))
+            return acc
         }
 
         /* the player is in the wrong game mode to alter the block state */
-        if (player.isBlockBreakingRestricted(world, pos, interaction.currentGameMode)) {
-            return BuildResult.Restricted(pos)
+        if (player.isBlockBreakingRestricted(world, pos, gamemode)) {
+            acc.add(BuildResult.Restricted(pos))
+            return acc
         }
 
         /* the player has no permissions to alter the block state */
         if (state.block is OperatorBlock && !player.isCreativeLevelTwoOp) {
-            return BuildResult.NoPermission(pos, state)
+            acc.add(BuildResult.NoPermission(pos, state))
+            return acc
         }
 
         /* block is outside the world so it cant be altered */
         if (!world.worldBorder.contains(pos) || world.isOutOfHeightLimit(pos)) {
-            return BuildResult.OutOfWorld(pos)
+            acc.add(BuildResult.OutOfWorld(pos))
+            return acc
         }
 
         /* block is unbreakable, so it cant be broken or replaced */
-        if (state.getHardness(world, pos) < 0 && !player.isCreative) {
-            return BuildResult.Unbreakable(pos, state)
+        if (state.getHardness(world, pos) < 0 && !gamemode.isCreative) {
+            acc.add(BuildResult.Unbreakable(pos, state))
+            return acc
         }
 
-        return null
+        return acc
     }
 
-    private fun SafeContext.checkPlaceResults(
+    private fun SafeContext.checkPostProcessResults(
         pos: BlockPos,
-        target: TargetState,
         eye: Vec3d,
-        interact: InteractionConfig,
+        preProcessing: PreProcessingInfo,
+        targetState: TargetState,
+        interactionConfig: InteractionConfig,
+        place: PlaceConfig,
         rotation: RotationConfig,
         inventory: InventoryConfig
     ): Set {
+        if (targetState !is TargetState.State) return emptySet()
+
         val acc = mutableSetOf()
-        val targetPosState = blockState(pos)
 
-        if (target.isAir() || !targetPosState.isReplaceable) return acc
+        val state = blockState(pos)
+        if (!targetState.matches(state, pos, world, preProcessing.ignore))
+            return acc
+
+        val interactBlock: (BlockState, Set?, Item?, Boolean) -> Unit = interactBlock@ { expectedState, sides, item, placing ->
+            val boxes = state.getOutlineShape(world, pos).boundingBoxes.map { it.offset(pos) }
+            val validHits = mutableListOf()
+            val blockedHits = mutableSetOf()
+            val misses = mutableSetOf()
+            val airPlace = placing && place.airPlace.isEnabled()
+
+            boxes.forEach { box ->
+                val refinedSides = if (interactionConfig.checkSideVisibility) {
+                    box.getVisibleSurfaces(eye).let { visibleSides ->
+                        sides?.let { specific ->
+                            visibleSides.intersect(specific)
+                        } ?: visibleSides.toSet()
+                    }
+                } else sides ?: Direction.entries.toSet()
+
+                scanSurfaces(box, refinedSides, interactionConfig.resolution, preProcessing.surfaceScan) { hitSide, vec ->
+                    val distSquared = eye distSq vec
+                    if (distSquared > interactionConfig.interactReach.pow(2)) {
+                        misses.add(vec)
+                        return@scanSurfaces
+                    }
+
+                    val newRotation = eye.rotationTo(vec)
+
+                    val hit = if (interactionConfig.strictRayCast) {
+                        val rayCast = newRotation.rayCast(interactionConfig.interactReach, eye)
+                        when {
+                            rayCast != null && (!airPlace || eye distSq rayCast.pos <= distSquared) ->
+                                rayCast.blockResult
+
+                            airPlace -> {
+                                val hitVec = newRotation.castBox(box, interactionConfig.interactReach, eye)
+                                BlockHitResult(hitVec, hitSide, pos, false)
+                            }
+
+                            else -> null
+                        }
+                    } else {
+                        val hitVec = newRotation.castBox(box, interactionConfig.interactReach, eye)
+                        BlockHitResult(hitVec, hitSide, pos, false)
+                    } ?: return@scanSurfaces
+
+                    val checked = CheckedHit(hit, newRotation, interactionConfig.interactReach)
+                    if (hit.blockResult?.blockPos != pos) {
+                        blockedHits.add(vec)
+                        return@scanSurfaces
+                    }
+
+                    validHits.add(checked)
+                }
+            }
+
+            if (validHits.isEmpty()) {
+                if (misses.isNotEmpty()) {
+                    acc.add(BuildResult.OutOfReach(pos, eye, misses))
+                } else {
+                    //ToDo: Must clean up surface scan usage / renders. Added temporary direction until changes are made
+                    acc.add(BuildResult.NotVisible(pos, pos, Direction.UP, eye.distanceTo(pos.offset(Direction.UP).vec3d)))
+                }
+                return@interactBlock
+            }
+
+            interactionConfig.pointSelection.select(validHits)?.let { checkedHit ->
+                val checkedResult = checkedHit.hit
+                val rotationTarget = lookAt(checkedHit.targetRotation, 0.001)
+                val context = InteractionContext(
+                    checkedResult.blockResult ?: return@interactBlock,
+                    RotationRequest(rotationTarget, rotation),
+                    player.inventory.selectedSlot,
+                    state,
+                    expectedState
+                )
+
+                val stackSelection = (item ?: player.mainHandStack.item).select()
+                val hotbarCandidates = selectContainer {
+                    matches(stackSelection) and ofAnyType(MaterialContainer.Rank.HOTBAR)
+                }.let { predicate ->
+                    stackSelection.containerWithMaterial(inventory, predicate)
+                }
+
+                if (hotbarCandidates.isEmpty()) {
+                    acc.add(BuildResult.WrongItemSelection(pos, context, stackSelection, player.mainHandStack, inventory))
+                    return@interactBlock
+                } else {
+                    context.hotbarIndex = player.hotbar.indexOf(hotbarCandidates.first().matchingStacks(stackSelection).first())
+                }
+
+                acc.add(InteractResult.Interact(pos, context))
+            }
+        }
+
+        val mismatchedProperties = state.properties.filter { state.get(it) != targetState.blockState.get(it) }
+        mismatchedProperties.forEach { property ->
+            when (property) {
+                Properties.EYE -> {
+                    if (state.get(Properties.EYE)) return@forEach
+                    val expectedState = state.with(Properties.EYE, true)
+                    interactBlock(expectedState, null, null, false)
+                }
+                Properties.INVERTED -> {
+                    val expectedState = state.with(Properties.INVERTED, !state.get(Properties.INVERTED))
+                    interactBlock(expectedState, null, null, false)
+                }
+                Properties.DELAY -> {
+                    val expectedState = state.with(Properties.DELAY, state.cycle(Properties.DELAY).get(Properties.DELAY))
+                    interactBlock(expectedState, null, null, false)
+                }
+                Properties.COMPARATOR_MODE -> {
+                    val expectedState = state.with(Properties.COMPARATOR_MODE, state.cycle(Properties.COMPARATOR_MODE).get(Properties.COMPARATOR_MODE))
+                    interactBlock(expectedState, null, null, false)
+                }
+                Properties.OPEN -> {
+                    val expectedState = state.with(Properties.OPEN, !state.get(Properties.OPEN))
+                    interactBlock(expectedState, null, null, false)
+                }
+                Properties.SLAB_TYPE -> {
+                    if (targetState.blockState.get(Properties.SLAB_TYPE) != SlabType.DOUBLE) return@forEach
+                    checkPlaceResults(pos, eye, preProcessing, targetState, place, interactionConfig, rotation, inventory).let { placeResults ->
+                        acc.addAll(placeResults)
+                    }
+                }
+            }
+        }
+
+        if (acc.isEmpty()) {
+            acc.add(BuildResult.Done(pos))
+        }
 
-        val preprocessing = target.findProcessorForState()
+        return acc
+    }
 
-        preprocessing.sides.forEach { neighbor ->
-            val hitPos = if (targetPosState.isAir || targetPosState.hasFluid) pos.offset(neighbor) else pos
+    private fun SafeContext.checkPlaceResults(
+        pos: BlockPos,
+        eye: Vec3d,
+        preProcessing: PreProcessingInfo,
+        targetState: TargetState,
+        place: PlaceConfig,
+        interactionConfig: InteractionConfig,
+        rotation: RotationConfig,
+        inventory: InventoryConfig
+    ): Set {
+        val acc = mutableSetOf()
+        val currentState = blockState(pos)
+
+        val statePromoting = currentState.block is SlabBlock && targetState.matches(currentState, pos, world, preProcessing.ignore)
+        // If the target state is air then the only possible blocks it could place are to remove liquids so we use the Solid TargetState
+        val nextTargetState = if (targetState is TargetState.Air) {
+            if (currentState.hasFluid) TargetState.Solid
+            else return acc
+        } else if (targetState.isEmpty()) {
+            // Otherwise if the target state is empty, there's no situation where placement would be required so we can return
+            return acc
+        } else targetState
+        // For example, slabs count as state promoting because you are placing another block to promote the current state to the target state
+        if (!currentState.isReplaceable && !statePromoting) return acc
+
+        preProcessing.sides.forEach { neighbor ->
+            val hitPos = if (!place.airPlace.isEnabled() && (currentState.isEmpty || statePromoting))
+                pos.offset(neighbor)
+            else pos
             val hitSide = neighbor.opposite
 
-            val voxelShape = blockState(hitPos).getOutlineShape(world, hitPos)
+            val voxelShape = blockState(hitPos).getOutlineShape(world, hitPos).let { outlineShape ->
+                if (!outlineShape.isEmpty || !place.airPlace.isEnabled()) outlineShape
+                else VoxelShapes.fullCube()
+            }
             if (voxelShape.isEmpty) return@forEach
 
             val boxes = voxelShape.boundingBoxes.map { it.offset(hitPos) }
@@ -166,31 +374,41 @@ object BuildSimulator {
 
             val validHits = mutableListOf()
             val misses = mutableSetOf()
-            val reachSq = interact.interactReach.pow(2)
+            val reachSq = interactionConfig.interactReach.pow(2)
 
             boxes.forEach { box ->
-                val sides = if (interact.checkSideVisibility) {
+                val sides = if (interactionConfig.checkSideVisibility) {
                     box.getVisibleSurfaces(eye).intersect(setOf(hitSide))
-                } else {
-                    Direction.entries.toSet()
-                }
+                } else setOf(hitSide)
 
-                scanSurfaces(box, sides, interact.resolution, preprocessing.surfaceScan) { _, vec ->
-                    if (eye distSq vec > reachSq) {
+                scanSurfaces(box, sides, interactionConfig.resolution, preProcessing.surfaceScan) { _, vec ->
+                    val distSquared = eye distSq vec
+                    if (distSquared > reachSq) {
                         misses.add(vec)
                         return@scanSurfaces
                     }
 
                     val newRotation = eye.rotationTo(vec)
 
-                    val hit = if (interact.strictRayCast) {
-                        newRotation.rayCast(interact.interactReach, eye)?.blockResult
+                    val hit = if (interactionConfig.strictRayCast) {
+                        val rayCast = newRotation.rayCast(interactionConfig.interactReach, eye)
+                        when {
+                            rayCast != null && (!place.airPlace.isEnabled() || eye distSq rayCast.pos <= distSquared) ->
+                                rayCast.blockResult
+
+                            place.airPlace.isEnabled() -> {
+                                val hitVec = newRotation.castBox(box, interactionConfig.interactReach, eye)
+                                BlockHitResult(hitVec, hitSide, hitPos, false)
+                            }
+
+                            else -> null
+                        }
                     } else {
-                        val hitVec = newRotation.castBox(box, interact.interactReach, eye)
+                        val hitVec = newRotation.castBox(box, interactionConfig.interactReach, eye)
                         BlockHitResult(hitVec, hitSide, hitPos, false)
                     } ?: return@scanSurfaces
 
-                    val checked = CheckedHit(hit, newRotation, interact.interactReach)
+                    val checked = CheckedHit(hit, newRotation, interactionConfig.interactReach)
                     if (!checked.verify()) return@scanSurfaces
 
                     validHits.add(checked)
@@ -203,21 +421,21 @@ object BuildSimulator {
                     return@forEach
                 }
 
-                acc.add(BuildResult.NotVisible(pos, hitPos, hitSide, eye.distanceTo(hitPos.vecOf(hitSide))))
+                acc.add(BuildResult.NotVisible(pos, hitPos, hitSide, eye.distanceTo(hitPos.offset(hitSide).vec3d)))
                 return@forEach
             }
 
-            interact.pointSelection.select(validHits)?.let { checkedHit ->
-                val optimalStack = target.getStack(world, pos)
+            interactionConfig.pointSelection.select(validHits)?.let { checkedHit ->
+                val optimalStack = nextTargetState.getStack(world, pos, inventory)
 
                 // ToDo: For each hand and sneak or not?
                 val fakePlayer = copyPlayer(player).apply {
-                    setPos(eye.x, eye.y - standingEyeHeight, eye.z)
-                    this.rotation = checkedHit.targetRotation
+                    this.rotation = RotationManager.serverRotation
                 }
 
                 val checkedResult = checkedHit.hit
 
+                // ToDo: Override the stack used for this to account for blocks where replaceability is dependent on the held item
                 val usageContext = ItemUsageContext(
                     fakePlayer,
                     Hand.MAIN_HAND,
@@ -244,7 +462,7 @@ object BuildSimulator {
                     return@forEach
                 }
 
-                if (!context.canPlace()) {
+                if (!context.canPlace() && !statePromoting) {
                     acc.add(PlaceResult.CantReplace(pos, context))
                     return@forEach
                 }
@@ -254,61 +472,104 @@ object BuildSimulator {
                     return@forEach
                 }
 
-                val checked = blockItem.getPlacementContext(context)
-                if (checked == null) {
-                    acc.add(PlaceResult.ScaffoldExceeded(pos, context))
-                    return@forEach
-                } else {
-                    context = checked
-                }
+                context = blockItem.getPlacementContext(context)
+                    ?: run {
+                        acc.add(PlaceResult.ScaffoldExceeded(pos, context))
+                        return@forEach
+                    }
 
-                val resultState = blockItem.getPlacementState(context) ?: run {
-                    acc.add(PlaceResult.BlockedByEntity(pos))
-                    return@forEach
+                lateinit var resultState: BlockState
+                var rot = fakePlayer.rotation
+
+                val simulatePlaceState = placeState@ {
+                    resultState = blockItem.getPlacementState(context)
+                        ?: return@placeState PlaceResult.BlockedByEntity(pos)
+
+                    return@placeState if (!nextTargetState.matches(resultState, pos, world, preProcessing.ignore))
+                        PlaceResult.NoIntegrity(pos, resultState, context, (nextTargetState as? TargetState.State)?.blockState)
+                    else null
                 }
 
-                if (!target.matches(resultState, pos, world)) {
-                    acc.add(
-                        PlaceResult.NoIntegrity(
-                            pos, resultState, context, (target as? TargetState.State)?.blockState
-                        )
-                    )
-                    return@forEach
+                val currentDirIsValid = simulatePlaceState()?.let { basePlaceResult ->
+                    if (!place.rotateForPlace) {
+                        acc.add(basePlaceResult)
+                        return@forEach
+                    }
+                    false
+                } != false
+
+                run rotate@ {
+                    if (!place.axisRotate) {
+                        fakePlayer.rotation = checkedHit.targetRotation
+                        simulatePlaceState()?.let { rotatedPlaceResult ->
+                            acc.add(rotatedPlaceResult)
+                            return@forEach
+                        }
+                        rot = fakePlayer.rotation
+                        return@rotate
+                    }
+
+                    fakePlayer.rotation = player.rotation
+                    if (simulatePlaceState() == null) {
+                        rot = fakePlayer.rotation
+                        return@rotate
+                    }
+
+                    PlaceDirection.entries.asReversed().forEachIndexed direction@ { index, direction ->
+                        fakePlayer.rotation = direction.rotation
+                        when (val placeResult = simulatePlaceState()) {
+                            is PlaceResult.BlockedByEntity -> {
+                                acc.add(placeResult)
+                                return@forEach
+                            }
+
+                            is PlaceResult.NoIntegrity -> {
+                                if (index != PlaceDirection.entries.lastIndex) return@direction
+                                acc.add(placeResult)
+                                return@forEach
+                            }
+
+                            else -> {
+                                rot = fakePlayer.rotation
+                                return@rotate
+                            }
+                        }
+                    }
                 }
 
                 val blockHit = checkedResult.blockResult ?: return@forEach
                 val hitBlock = blockState(blockHit.blockPos).block
-                val shouldSneak = hitBlock::class in BlockUtils.interactionClasses
+                val shouldSneak = hitBlock::class in BlockUtils.interactionBlocks
 
-                val primeDirection =
-                    (target as? TargetState.State)?.blockState?.getOrEmpty(Properties.HORIZONTAL_FACING)?.getOrNull()
+                val rotationRequest = if (place.axisRotate) {
+                    lookInDirection(PlaceDirection.fromRotation(rot))
+                } else lookAt(rot, 0.001)
 
                 val placeContext = PlaceContext(
-                    eye,
                     blockHit,
-                    RotationRequest(lookAt(checkedHit.targetRotation, 0.001), rotation),
-                    eye.distanceTo(blockHit.pos),
-                    resultState,
-                    blockState(blockHit.blockPos),
-                    Hand.MAIN_HAND,
+                    RotationRequest(rotationRequest, rotation),
+                    player.inventory.selectedSlot,
                     context.blockPos,
-                    target,
+                    blockState(context.blockPos),
+                    resultState,
                     shouldSneak,
                     false,
-                    primeDirection
+                    currentDirIsValid
                 )
 
-                val currentHandStack = player.getStackInHand(Hand.MAIN_HAND)
-                if (target is TargetState.Stack && !target.itemStack.equal(currentHandStack)) {
-                    acc.add(BuildResult.WrongStack(pos, placeContext, target.itemStack, inventory))
-                    return@forEach
+                val selection = optimalStack.item.select()
+                val containerSelection = selectContainer { ofAnyType(MaterialContainer.Rank.HOTBAR) }
+                val container = selection.containerWithMaterial(inventory, containerSelection).firstOrNull() ?: run {
+                    acc.add(BuildResult.WrongItemSelection(pos, placeContext, optimalStack.item.select(), player.mainHandStack, inventory))
+                    return acc
                 }
-
-                if (optimalStack.item != currentHandStack.item) {
-                    acc.add(BuildResult.WrongItem(pos, placeContext, optimalStack.item, currentHandStack, inventory))
-                    return@forEach
+                val stack = selection.filterStacks(container.stacks).run {
+                    firstOrNull { player.inventory.getSlotWithStack(it) == player.inventory.selectedSlot }
+                        ?: first()
                 }
 
+                placeContext.hotbarIndex = player.inventory.getSlotWithStack(stack)
+
                 acc.add(PlaceResult.Place(pos, placeContext))
             }
         }
@@ -319,7 +580,9 @@ object BuildSimulator {
     private fun SafeContext.checkBreakResults(
         pos: BlockPos,
         eye: Vec3d,
-        interact: InteractionConfig,
+        preProcessing: PreProcessingInfo,
+        breaking: BreakConfig,
+        interactionConfig: InteractionConfig,
         rotation: RotationConfig,
         inventory: InventoryConfig,
         build: BuildConfig
@@ -328,59 +591,91 @@ object BuildSimulator {
         val state = blockState(pos)
 
         /* is a block that will be destroyed by breaking adjacent blocks */
-        if (build.breakWeakBlocks && state.block.hardness == 0f && !state.isAir) {
+        if (!breaking.breakWeakBlocks && state.block.hardness == 0f && state.isNotEmpty) {
             acc.add(BuildResult.Ignored(pos))
             return acc
         }
 
         /* player is standing on top of the block */
-        val pBox = player.boundingBox
-        val aabb = Box(pBox.minX, pBox.minY - 1.0E-6, pBox.minZ, pBox.maxX, pBox.minY, pBox.maxZ)
-        world.findSupportingBlockPos(player, aabb).orElse(null)?.let { support ->
+        if (breaking.avoidSupporting) player.supportingBlockPos.orElse(null)?.let { support ->
             if (support != pos) return@let
-            val belowSupport = blockState(support.down())
-            if (belowSupport.isSolidSurface(world, support, player, Direction.UP)) return@let
             acc.add(BreakResult.PlayerOnTop(pos, state))
             return acc
         }
 
         /* liquid needs to be submerged first to be broken */
         if (!state.fluidState.isEmpty && state.isReplaceable) {
-            val submerge = checkPlaceResults(pos, TargetState.Solid, eye, interact, rotation, inventory)
+            val submerge = checkPlaceResults(pos, eye, preProcessing, TargetState.Solid, build.placing, interactionConfig, rotation, inventory)
             acc.add(BreakResult.Submerge(pos, state, submerge))
             acc.addAll(submerge)
             return acc
         }
 
-        val adjacentLiquids = Direction.entries.filter {
-            it != Direction.DOWN && !blockState(pos.offset(it)).fluidState.isEmpty
-        }.map { pos.offset(it) }
+        if (breaking.avoidLiquids) {
+            val affectedBlocks = hashSetOf(pos)
+            val checkQueue = hashSetOf(pos)
 
-        /* block has liquids next to it that will leak when broken */
-        if (adjacentLiquids.isNotEmpty()) {
-            acc.add(BreakResult.BlockedByLiquid(pos, state))
-            adjacentLiquids.forEach { liquidPos ->
-                val submerge = if (blockState(liquidPos).isReplaceable) {
-                    checkPlaceResults(liquidPos, TargetState.Solid, eye, interact, rotation, inventory)
-                } else {
-                    checkBreakResults(liquidPos, eye, interact, rotation, inventory, build)
+            while (checkQueue.isNotEmpty()) {
+                val checkPos = checkQueue.first()
+                checkQueue.remove(checkPos)
+                for (offset in Direction.entries) {
+                    val adjacentPos = checkPos.offset(offset)
+
+                    if (blockState(adjacentPos).block !is FallingBlock) continue
+                    if (adjacentPos in affectedBlocks) continue
+
+                    if (offset == Direction.UP || FallingBlock.canFallThrough(blockState(adjacentPos.down()))) {
+                        checkQueue.add(adjacentPos)
+                        affectedBlocks.add(adjacentPos)
+                    }
                 }
-                acc.addAll(submerge)
             }
-            return acc
-        }
 
-        /* The current selected item cant mine the block */
-        Hand.entries.forEach {
-            val stack = player.getStackInHand(it)
-            if (stack.isEmpty) return@forEach
-            if (stack.item.canMine(stack, state, world, pos, player)) return@forEach
-            acc.add(BreakResult.ItemCantMine(pos, state, stack.item, inventory))
-            return acc
+            val affectedFluids = affectedBlocks.fold(hashMapOf()) { accumulator, affectedPos ->
+                Direction.entries.forEach { offset ->
+                    if (offset == Direction.DOWN) return@forEach
+
+                    val offsetPos = affectedPos.offset(offset)
+                    val offsetState = blockState(offsetPos)
+                    val fluidState = offsetState.fluidState
+                    val fluid = fluidState.fluid
+
+                    if (fluidState.isEmpty || fluid !is FlowableFluid) return@forEach
+
+                    if (offset == Direction.UP) {
+                        accumulator[offsetPos] = offsetState
+                        return@fold accumulator
+                    }
+
+                    if (offsetState.block is Waterloggable && !fluidState.isEmpty) {
+                        accumulator[offsetPos] = offsetState
+                        return@fold accumulator
+                    }
+
+                    val levelDecreasePerBlock =
+                        when (fluid) {
+                            is WaterFluid -> fluid.getLevelDecreasePerBlock(world)
+                            is LavaFluid -> fluid.getLevelDecreasePerBlock(world)
+                            else -> 0
+                        }
+
+                    if (fluidState.level - levelDecreasePerBlock > 0) {
+                        accumulator[offsetPos] = offsetState
+                        return@fold accumulator
+                    }
+                }
+
+                return@fold accumulator
+            }
+
+            if (affectedFluids.isNotEmpty()) {
+                acc.add(BreakResult.BlockedByFluid(pos, state))
+                return acc
+            }
         }
 
-        val currentRotation = RotationManager.currentRotation
-        val currentCast = currentRotation.rayCast(interact.interactReach, eye)
+        val currentRotation = RotationManager.activeRotation
+        val currentCast = currentRotation.rayCast(interactionConfig.interactReach, eye)
 
         val voxelShape = state.getOutlineShape(world, pos)
         voxelShape.getClosestPointTo(eye).ifPresent {
@@ -391,20 +686,22 @@ object BuildSimulator {
         val verify: CheckedHit.() -> Boolean = {
             hit.blockResult?.blockPos == pos
         }
-        val targetState = if (!state.fluidState.isEmpty) {
-            TargetState.State(state.fluidState.blockState)
-        } else {
-            TargetState.Air
-        }
 
+        // ToDo: Move this to a location where more of the context parameters can be properly set
         /* the player is buried inside the block */
         if (boxes.any { it.contains(eye) }) {
             currentCast?.blockResult?.let { blockHit ->
                 val rotationRequest = RotationRequest(
-                    lookAtBlock(pos, config = interact), rotation
+                    lookAtBlock(pos, config = interactionConfig), rotation
                 )
                 val breakContext = BreakContext(
-                    eye, blockHit, rotationRequest, state, targetState, player.activeHand, instantBreakable(state, pos)
+                    blockHit,
+                    rotationRequest,
+                    player.inventory.selectedSlot,
+                    StackSelection.EVERYTHING.select(),
+                    instantBreakable(state, pos, breaking.breakThreshold),
+                    state,
+                    breaking.sorter
                 )
                 acc.add(BreakResult.Break(pos, breakContext))
                 return acc
@@ -413,10 +710,11 @@ object BuildSimulator {
 
         val validHits = mutableListOf()
         val misses = mutableSetOf()
-        val reachSq = interact.interactReach.pow(2)
+        val reachSq = interactionConfig.interactReach.pow(2)
 
         boxes.forEach { box ->
-            scanSurfaces(box, Direction.entries.toSet(), interact.resolution) { side, vec ->
+            // ToDo: Rewrite Rotation request system to allow support for all sim features and use the rotation finder
+            scanSurfaces(box, Direction.entries.toSet(), interactionConfig.resolution) { side, vec ->
                 if (eye distSq vec > reachSq) {
                     misses.add(vec)
                     return@scanSurfaces
@@ -424,14 +722,14 @@ object BuildSimulator {
 
                 val newRotation = eye.rotationTo(vec)
 
-                val hit = if (interact.strictRayCast) {
-                    newRotation.rayCast(interact.interactReach, eye)?.blockResult
+                val hit = if (interactionConfig.strictRayCast) {
+                    newRotation.rayCast(interactionConfig.interactReach, eye)?.blockResult
                 } else {
-                    val hitVec = newRotation.castBox(box, interact.interactReach, eye)
+                    val hitVec = newRotation.castBox(box, interactionConfig.interactReach, eye)
                     BlockHitResult(hitVec, side, pos, false)
                 } ?: return@scanSurfaces
 
-                val checked = CheckedHit(hit, newRotation, interact.interactReach)
+                val checked = CheckedHit(hit, newRotation, interactionConfig.interactReach)
                 if (!checked.verify()) return@scanSurfaces
 
                 validHits.add(checked)
@@ -444,37 +742,79 @@ object BuildSimulator {
             return acc
         }
 
-        interact.pointSelection.select(validHits)?.let { checkedHit ->
-            val blockHit = checkedHit.hit.blockResult ?: return@let
-
-            val breakContext = BreakContext(
-                eye,
-                blockHit,
-                RotationRequest(lookAt(checkedHit.targetRotation, 0.001), rotation),
-                state,
-                targetState,
-                player.activeHand,
-                instantBreakable(state, pos)
-            )
-
-            /* player has a better tool for the job available */
-            if (!player.isCreative) findBestAvailableTool(state)
-                ?.let { bestTool ->
-                    Hand.entries.firstOrNull {
-                        val stack = player.getStackInHand(it)
-                        stack.item == bestTool
-                    }?.let { hand ->
-                        breakContext.hand = hand
-                        acc.add(BreakResult.Break(pos, breakContext))
-                        return acc
-                    } ?: run {
-                        acc.add(BuildResult.WrongItem(pos, breakContext, bestTool, player.activeItem, inventory))
-                        return acc
+        val bestHit = interactionConfig.pointSelection.select(validHits) ?: return acc
+        val blockHit = bestHit.hit.blockResult ?: return acc
+        val target = lookAt(bestHit.targetRotation, 0.001)
+        val rotationRequest = RotationRequest(target, rotation)
+        val instant = instantBreakable(state, pos, breaking.breakThreshold)
+
+        val breakContext = BreakContext(
+            blockHit,
+            rotationRequest,
+            player.inventory.selectedSlot,
+            StackSelection.EVERYTHING.select(),
+            instant,
+            state,
+            breaking.sorter
+        )
+
+        if (gamemode.isCreative) {
+            acc.add(BreakResult.Break(pos, breakContext))
+            return acc
+        }
+
+        val stackSelection = selectStack(
+            sorter = compareByDescending {
+                it.canBreak(CachedBlockPosition(world, pos, false))
+            }.thenByDescending {
+                state.calcItemBlockBreakingDelta(player, world, pos, it)
+            }
+        ) {
+            run {
+                if (breaking.suitableToolsOnly) isSuitableForBreaking(state)
+                else StackSelection.EVERYTHING
+            } and if (breaking.forceSilkTouch) {
+                hasEnchantment(Enchantments.AQUA_AFFINITY)
+            } else if (breaking.forceFortunePickaxe) {
+                hasEnchantment(Enchantments.FORTUNE, breaking.minFortuneLevel)
+            } else StackSelection.EVERYTHING
+        }
+
+        val silentSwapSelection = selectContainer {
+            ofAnyType(MaterialContainer.Rank.HOTBAR)
+        }
+
+        val swapCandidates = stackSelection.containerWithMaterial(inventory, silentSwapSelection)
+        if (swapCandidates.isEmpty()) {
+            acc.add(BuildResult.WrongItemSelection(pos, breakContext, stackSelection, player.mainHandStack, inventory))
+            return acc
+        }
+
+        val swapStack = swapCandidates.map { it.matchingStacks(stackSelection) }
+            .asSequence()
+            .flatten()
+            .filter { BreakManager.currentStackSelection.filterStack(it) }
+            .let { containerStacks ->
+                var bestStack = ItemStack.EMPTY
+                var bestBreakDelta = -1f
+                containerStacks.forEach { stack ->
+                    val breakDelta = state.calcItemBlockBreakingDelta(player, world, pos, stack)
+                    if (breakDelta > bestBreakDelta ||
+                        (stack == player.mainHandStack && breakDelta >= bestBreakDelta))
+                    {
+                        bestBreakDelta = breakDelta
+                        bestStack = stack
                     }
                 }
+                bestStack
+        }
 
-            acc.add(BreakResult.Break(pos, breakContext))
+        breakContext.apply {
+            hotbarIndex = player.hotbar.indexOf(swapStack)
+            itemSelection = stackSelection
+            instantBreak = instantBreakable(state, pos, if (breaking.swapMode.isEnabled()) swapStack else player.mainHandStack, breaking.breakThreshold)
         }
+	    acc.add(BreakResult.Break(pos, breakContext))
         return acc
     }
 }
diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/Simulation.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/Simulation.kt
index db869507b..768b4ab73 100644
--- a/src/main/kotlin/com/lambda/interaction/construction/simulation/Simulation.kt
+++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/Simulation.kt
@@ -19,13 +19,13 @@ package com.lambda.interaction.construction.simulation
 
 import com.lambda.config.groups.BuildConfig
 import com.lambda.config.groups.InteractionConfig
-import com.lambda.config.groups.InventoryConfig
 import com.lambda.context.SafeContext
 import com.lambda.interaction.construction.blueprint.Blueprint
 import com.lambda.interaction.construction.result.BuildResult
 import com.lambda.interaction.construction.result.Drawable
 import com.lambda.interaction.construction.simulation.BuildSimulator.simulate
-import com.lambda.interaction.request.rotation.RotationConfig
+import com.lambda.interaction.request.inventory.InventoryConfig
+import com.lambda.interaction.request.rotating.RotationConfig
 import com.lambda.module.modules.client.TaskFlowModule
 import com.lambda.threading.runSafe
 import com.lambda.util.BlockUtils.blockState
@@ -41,7 +41,7 @@ import java.awt.Color
 
 data class Simulation(
     val blueprint: Blueprint,
-    val interact: InteractionConfig = TaskFlowModule.interact,
+    val interactionConfig: InteractionConfig = TaskFlowModule.interaction,
     val rotation: RotationConfig = TaskFlowModule.rotation,
     val inventory: InventoryConfig = TaskFlowModule.inventory,
     val build: BuildConfig = TaskFlowModule.build,
@@ -63,12 +63,14 @@ data class Simulation(
             if (!playerFitsIn(blockPos)) return@getOrPut emptySet()
         }
 
-        blueprint.simulate(view, interact, rotation, inventory, build)
+        blueprint.simulate(view, interactionConfig, rotation, inventory, build)
     }
 
-    fun goodPositions() = cache.filter { it.value.any { it.rank.ordinal < 4 } }.map { PossiblePos(it.key.toBlockPos()) }
+    fun goodPositions() = cache
+        .filter { entry -> entry.value.any { it.rank.ordinal < 4 } }
+        .map { PossiblePos(it.key.toBlockPos(), it.value.count { it.rank.ordinal < 4 }) }
 
-    class PossiblePos(val pos: BlockPos): Drawable {
+    class PossiblePos(val pos: BlockPos, val interactions: Int): Drawable {
         override fun SafeContext.buildRenderer() {
             withBox(Vec3d.ofBottomCenter(pos).playerBox(), Color(0, 255, 0, 50))
         }
@@ -82,7 +84,7 @@ data class Simulation(
         fun Vec3d.playerBox(): Box = Box(x - 0.3, y, z - 0.3, x + 0.3, y + 1.8, z + 0.3).contract(1.0E-6)
 
         fun Blueprint.simulation(
-            interact: InteractionConfig = TaskFlowModule.interact,
+            interact: InteractionConfig = TaskFlowModule.interaction,
             rotation: RotationConfig = TaskFlowModule.rotation,
             inventory: InventoryConfig = TaskFlowModule.inventory,
             build: BuildConfig = TaskFlowModule.build,
diff --git a/src/main/kotlin/com/lambda/interaction/construction/verify/ScanMode.kt b/src/main/kotlin/com/lambda/interaction/construction/verify/ScanMode.kt
index 89a46f932..ba83a1d8d 100644
--- a/src/main/kotlin/com/lambda/interaction/construction/verify/ScanMode.kt
+++ b/src/main/kotlin/com/lambda/interaction/construction/verify/ScanMode.kt
@@ -17,6 +17,8 @@
 
 package com.lambda.interaction.construction.verify
 
-enum class ScanMode {
-    GREATER_HALF, LESSER_HALF, FULL
+enum class ScanMode(val priority: Int) {
+    GREATER_BLOCK_HALF(1),
+    LESSER_BLOCK_HALF(1),
+    FULL(0)
 }
diff --git a/src/main/kotlin/com/lambda/interaction/construction/verify/StateMatcher.kt b/src/main/kotlin/com/lambda/interaction/construction/verify/StateMatcher.kt
index 971406c1f..717d7ede8 100644
--- a/src/main/kotlin/com/lambda/interaction/construction/verify/StateMatcher.kt
+++ b/src/main/kotlin/com/lambda/interaction/construction/verify/StateMatcher.kt
@@ -17,13 +17,15 @@
 
 package com.lambda.interaction.construction.verify
 
+import com.lambda.interaction.request.inventory.InventoryConfig
 import net.minecraft.block.BlockState
 import net.minecraft.client.world.ClientWorld
 import net.minecraft.item.ItemStack
+import net.minecraft.state.property.Property
 import net.minecraft.util.math.BlockPos
 
 interface StateMatcher {
-    fun matches(state: BlockState, pos: BlockPos, world: ClientWorld): Boolean
-    fun getStack(world: ClientWorld, pos: BlockPos): ItemStack
-    fun isAir(): Boolean
+    fun matches(state: BlockState, pos: BlockPos, world: ClientWorld, ignoredProperties: Collection> = emptySet()): Boolean
+    fun getStack(world: ClientWorld, pos: BlockPos, inventory: InventoryConfig): ItemStack
+    fun isEmpty(): Boolean
 }
diff --git a/src/main/kotlin/com/lambda/interaction/construction/verify/SurfaceScan.kt b/src/main/kotlin/com/lambda/interaction/construction/verify/SurfaceScan.kt
index 7ab078d5a..c8d9d507e 100644
--- a/src/main/kotlin/com/lambda/interaction/construction/verify/SurfaceScan.kt
+++ b/src/main/kotlin/com/lambda/interaction/construction/verify/SurfaceScan.kt
@@ -26,5 +26,4 @@ data class SurfaceScan(
     companion object {
         val DEFAULT = SurfaceScan(ScanMode.FULL, Direction.Axis.Y)
     }
-
 }
diff --git a/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt b/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt
index 306699c8a..e10e99f12 100644
--- a/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt
+++ b/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt
@@ -18,87 +18,101 @@
 package com.lambda.interaction.construction.verify
 
 import com.lambda.interaction.material.container.ContainerManager.findDisposable
+import com.lambda.interaction.request.inventory.InventoryConfig
 import com.lambda.module.modules.client.TaskFlowModule
+import com.lambda.util.BlockUtils.isEmpty
+import com.lambda.util.BlockUtils.matches
 import com.lambda.util.StringUtils.capitalize
 import com.lambda.util.item.ItemUtils.block
 import net.minecraft.block.BlockState
 import net.minecraft.client.world.ClientWorld
 import net.minecraft.item.ItemStack
 import net.minecraft.item.Items
+import net.minecraft.state.property.Property
 import net.minecraft.util.math.BlockPos
 import net.minecraft.util.math.Direction
 
 sealed class TargetState(val type: Type) : StateMatcher {
 
     enum class Type {
-        AIR, SOLID, SUPPORT, STATE, BLOCK, STACK
+        EMPTY, AIR, SOLID, SUPPORT, STATE, BLOCK, STACK
+    }
+
+    data object Empty : TargetState(Type.EMPTY) {
+        override fun toString() = "Empty"
+
+        override fun matches(state: BlockState, pos: BlockPos, world: ClientWorld, ignoredProperties: Collection>) =
+            state.isEmpty
+
+        override fun getStack(world: ClientWorld, pos: BlockPos, inventory: InventoryConfig): ItemStack =
+            ItemStack.EMPTY
+
+        override fun isEmpty() = true
     }
 
     data object Air : TargetState(Type.AIR) {
         override fun toString() = "Air"
 
-        override fun matches(state: BlockState, pos: BlockPos, world: ClientWorld) =
+        override fun matches(state: BlockState, pos: BlockPos, world: ClientWorld, ignoredProperties: Collection>) =
             state.isAir
 
-        override fun getStack(world: ClientWorld, pos: BlockPos): ItemStack =
+        override fun getStack(world: ClientWorld, pos: BlockPos, inventory: InventoryConfig): ItemStack =
             ItemStack.EMPTY
 
-        override fun isAir() = true
+        override fun isEmpty() = true
     }
 
     data object Solid : TargetState(Type.SOLID) {
         override fun toString() = "Solid"
 
-        override fun matches(state: BlockState, pos: BlockPos, world: ClientWorld) =
+        override fun matches(state: BlockState, pos: BlockPos, world: ClientWorld, ignoredProperties: Collection>) =
             state.isSolidBlock(world, pos)
 
-        override fun getStack(world: ClientWorld, pos: BlockPos) =
-            findDisposable()?.stacks?.firstOrNull {
+        override fun getStack(world: ClientWorld, pos: BlockPos, inventory: InventoryConfig) =
+            findDisposable(inventory)?.stacks?.firstOrNull {
                 it.item.block in TaskFlowModule.inventory.disposables
             } ?: ItemStack(Items.NETHERRACK)
 
-        override fun isAir() = false
+        override fun isEmpty() = false
     }
 
     data class Support(val direction: Direction) : TargetState(Type.SUPPORT) {
         override fun toString() = "Support for ${direction.name}"
 
-        override fun matches(state: BlockState, pos: BlockPos, world: ClientWorld) =
+        override fun matches(state: BlockState, pos: BlockPos, world: ClientWorld, ignoredProperties: Collection>) =
             world.getBlockState(pos.offset(direction)).isSolidBlock(world, pos.offset(direction))
                     || state.isSolidBlock(world, pos)
 
-        override fun getStack(world: ClientWorld, pos: BlockPos) =
+        override fun getStack(world: ClientWorld, pos: BlockPos, inventory: InventoryConfig) =
             findDisposable()?.stacks?.firstOrNull {
                 it.item.block in TaskFlowModule.inventory.disposables
             } ?: ItemStack(Items.NETHERRACK)
 
-        override fun isAir() = false
+        override fun isEmpty() = false
     }
 
     data class State(val blockState: BlockState) : TargetState(Type.STATE) {
         override fun toString() = "State of $blockState"
 
-        override fun matches(state: BlockState, pos: BlockPos, world: ClientWorld) =
-            state.block == blockState.block && state.properties.all {
-                /*it in TaskFlowModule.defaultIgnoreTags ||*/ state[it] == blockState[it]
-            }
+        override fun matches(state: BlockState, pos: BlockPos, world: ClientWorld, ignoredProperties: Collection>) =
+            state.matches(blockState, ignoredProperties)
 
-        override fun getStack(world: ClientWorld, pos: BlockPos): ItemStack =
+        override fun getStack(world: ClientWorld, pos: BlockPos, inventory: InventoryConfig): ItemStack =
             blockState.block.getPickStack(world, pos, blockState, true)
 
-        override fun isAir() = blockState.isAir
+        override fun isEmpty() = blockState.isEmpty
     }
 
     data class Block(val block: net.minecraft.block.Block) : TargetState(Type.BLOCK) {
         override fun toString() = "Block of ${block.name.string.capitalize()}"
 
-        override fun matches(state: BlockState, pos: BlockPos, world: ClientWorld) =
+        override fun matches(state: BlockState, pos: BlockPos, world: ClientWorld, ignoredProperties: Collection>) =
             state.block == block
 
-        override fun getStack(world: ClientWorld, pos: BlockPos): ItemStack =
+        override fun getStack(world: ClientWorld, pos: BlockPos, inventory: InventoryConfig): ItemStack =
             block.getPickStack(world, pos, block.defaultState, true)
 
-        override fun isAir() = block.defaultState.isAir
+        override fun isEmpty() = block.defaultState.isEmpty
     }
 
     data class Stack(val itemStack: ItemStack) : TargetState(Type.STACK) {
@@ -107,12 +121,12 @@ sealed class TargetState(val type: Type) : StateMatcher {
 
         private val block = itemStack.item.block
 
-        override fun matches(state: BlockState, pos: BlockPos, world: ClientWorld) =
+        override fun matches(state: BlockState, pos: BlockPos, world: ClientWorld, ignoredProperties: Collection>) =
             state.block == block
 
-        override fun getStack(world: ClientWorld, pos: BlockPos): ItemStack =
+        override fun getStack(world: ClientWorld, pos: BlockPos, inventory: InventoryConfig): ItemStack =
             itemStack
 
-        override fun isAir() = false
+        override fun isEmpty() = false
     }
 }
diff --git a/src/main/kotlin/com/lambda/interaction/material/ContainerSelection.kt b/src/main/kotlin/com/lambda/interaction/material/ContainerSelection.kt
new file mode 100644
index 000000000..9d975bf9c
--- /dev/null
+++ b/src/main/kotlin/com/lambda/interaction/material/ContainerSelection.kt
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2025 Lambda
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see .
+ */
+
+package com.lambda.interaction.material
+
+import com.lambda.interaction.material.container.MaterialContainer
+
+/**
+ * ContainerSelection is a class that holds a predicate for matching MaterialContainers.
+ * It can be combined using "and", "or", etc.
+ */
+class ContainerSelection {
+	private var selector: (MaterialContainer) -> Boolean = { true }
+
+	/**
+	 * Tests whether the provided container matches this selection.
+	 */
+	@ContainerSelectionDsl
+	fun matches(container: MaterialContainer): Boolean = selector(container)
+
+	/**
+	 * Returns a function that matches containers having at least one stack
+	 * which matches the given StackSelection.
+	 */
+	@ContainerSelectionDsl
+	fun matches(stackSelection: StackSelection): (MaterialContainer) -> Boolean =
+		{ container -> container.matchingStacks(stackSelection).isNotEmpty() }
+
+	/**
+	 * Returns a function that checks whether a given MaterialContainer matches the criteria
+	 * defined in the provided ContainerSelection.
+	 */
+	@ContainerSelectionDsl
+	fun matches(containerSelection: ContainerSelection): (MaterialContainer) -> Boolean =
+		{ container -> containerSelection.matches(container) }
+
+	/**
+	 * Returns a function that matches containers whose rank is any of the types provided.
+	 */
+	@ContainerSelectionDsl
+	fun ofAnyType(vararg types: MaterialContainer.Rank): (MaterialContainer) -> Boolean =
+		{ container -> types.contains(container.rank) }
+
+	/**
+	 * Returns a function that matches containers whose rank is not any of the types provided.
+	 */
+	@ContainerSelectionDsl
+	fun noneOfType(vararg types: MaterialContainer.Rank): (MaterialContainer) -> Boolean =
+		{ container -> !types.contains(container.rank) }
+
+	/**
+	 * Returns a function that combines two container predicates using logical AND.
+	 */
+	@ContainerSelectionDsl
+	infix fun ((MaterialContainer) -> Boolean).and(other: (MaterialContainer) -> Boolean): (MaterialContainer) -> Boolean =
+		{ container -> this(container) && other(container) }
+
+	/**
+	 * Returns a function that combines two container predicates using logical OR.
+	 */
+	@ContainerSelectionDsl
+	infix fun ((MaterialContainer) -> Boolean).or(other: (MaterialContainer) -> Boolean): (MaterialContainer) -> Boolean =
+		{ container -> this(container) || other(container) }
+
+	/**
+	 * Returns a function that negates the current selection predicate.
+	 */
+	@ContainerSelectionDsl
+	fun ((MaterialContainer) -> Boolean).negate(): (MaterialContainer) -> Boolean =
+		{ container -> !this(container) }
+
+	companion object {
+		@DslMarker
+		annotation class ContainerSelectionDsl
+
+		@ContainerSelectionDsl
+		fun selectContainer(
+			block: ContainerSelection.() -> (MaterialContainer) -> Boolean
+		): ContainerSelection =
+			ContainerSelection().apply { selector = block() }
+	}
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/lambda/interaction/material/StackSelection.kt b/src/main/kotlin/com/lambda/interaction/material/StackSelection.kt
index 7f8aed985..0124a236c 100644
--- a/src/main/kotlin/com/lambda/interaction/material/StackSelection.kt
+++ b/src/main/kotlin/com/lambda/interaction/material/StackSelection.kt
@@ -17,12 +17,17 @@
 
 package com.lambda.interaction.material
 
-import com.lambda.util.BlockUtils.item
+import com.lambda.util.EnchantmentUtils.getEnchantment
 import com.lambda.util.item.ItemStackUtils.shulkerBoxContents
 import net.minecraft.block.Block
+import net.minecraft.block.BlockState
 import net.minecraft.enchantment.Enchantment
+import net.minecraft.enchantment.Enchantments
 import net.minecraft.item.Item
 import net.minecraft.item.ItemStack
+import net.minecraft.registry.RegistryKey
+import net.minecraft.registry.tag.ItemTags
+import net.minecraft.registry.tag.TagKey
 import net.minecraft.screen.slot.Slot
 import kotlin.reflect.KClass
 
@@ -31,6 +36,7 @@ import kotlin.reflect.KClass
  */
 class StackSelection {
     var selector: (ItemStack) -> Boolean = { true }
+    var comparator: Comparator? = null
     var count: Int = DEFAULT_AMOUNT
     var inShulkerBox: Boolean = false
 
@@ -44,28 +50,24 @@ class StackSelection {
     val optimalStack: ItemStack?
         get() = itemStack ?: item?.let { ItemStack(it, count) }
 
-    val filterStack: (ItemStack) -> Boolean
-        get() = { stack ->
-            if (inShulkerBox) {
-                stack.shulkerBoxContents.any { selector(it) }
-            } else {
-                selector(stack)
-            }
-        }
+    fun filterStack(stack: ItemStack) =
+        if (inShulkerBox) stack.shulkerBoxContents.any { selector(it) }
+        else selector(stack)
 
-    val filterSlot: (Slot) -> Boolean
-        get() = { slot ->
-            filterStack(slot.stack)
-        }
+    fun filterSlot(slot: Slot) = filterStack(slot.stack)
 
-    val filterStacks: (List) -> List
-        get() = {
-            it.filter(filterStack)
+    fun filterStacks(stacks: List): List =
+        stacks.filter(::filterStack).let { filteredStacks ->
+            comparator?.run {
+                filteredStacks.sortedWith(this)
+            } ?: filteredStacks
         }
 
-    val filterSlots: (List) -> List
-        get() = { slots ->
-            slots.filter { filterSlot(it) }
+    fun filterSlots(slots: List): List =
+        slots.filter(::filterSlot).let { filteredSlots ->
+            comparator?.run {
+                filteredSlots.sortedWith { slot, slot2 -> compare(slot.stack, slot2.stack) }
+            } ?: filteredSlots
         }
 
     /**
@@ -102,16 +104,44 @@ class StackSelection {
      * @param item The [Item] to be matched.
      * @return A predicate that matches the [Item].
      */
+    @StackSelectionDsl
     fun isItem(item: Item): (ItemStack) -> Boolean {
         this.item = item
         return { it.item == item }
     }
 
+    /**
+     * Returns a predicate that matches if the `ItemStack`'s item is one of the specified items in the collection.
+     *
+     * @param items The collection of `Item` instances to match against.
+     * @return A predicate that checks if the `ItemStack`'s item is contained in the provided collection.
+     */
+    @StackSelectionDsl
+    fun isOneOfItems(items: Collection): (ItemStack) -> Boolean = { it.item in items }
+
+    /**
+     * Returns a predicate that checks if a given `ItemStack` exists within the provided collection of `ItemStack`s.
+     *
+     * @param stacks A collection of `ItemStack` instances to be checked against.
+     * @return A predicate that evaluates to `true` if the given `ItemStack` is within the specified collection, otherwise `false`.
+     */
+    @StackSelectionDsl
+    fun isOneOfStacks(stacks: Collection): (ItemStack) -> Boolean = { it in stacks }
+
+    @StackSelectionDsl
+    fun isSuitableForBreaking(blockState: BlockState): (ItemStack) -> Boolean = { it.isSuitableFor(blockState) }
+
+    @StackSelectionDsl
+    fun isTag(tag: TagKey): (ItemStack) -> Boolean {
+        return { it.isIn(tag) }
+    }
+
     /**
      * [isItem] returns a predicate that matches a specific [Item] instance.
      * @param T The instance of [Item] to be matched.
      * @return A predicate that matches the [Item].
      */
+    @StackSelectionDsl
     inline fun  isItem(): (ItemStack) -> Boolean {
         itemClass = T::class
         return { it.item is T }
@@ -122,9 +152,10 @@ class StackSelection {
      * @param block The [Block] to be matched.
      * @return A predicate that matches the [Block].
      */
+    @StackSelectionDsl
     fun isBlock(block: Block): (ItemStack) -> Boolean {
-        item = block.item
-        return { it.item == block.item }
+        item = block.asItem()
+        return { it.item == block.asItem() }
     }
 
     /**
@@ -132,6 +163,7 @@ class StackSelection {
      * @param stack The [ItemStack] to be matched.
      * @return A predicate that matches the [ItemStack].
      */
+    @StackSelectionDsl
     fun isItemStack(stack: ItemStack): (ItemStack) -> Boolean {
         this.itemStack = stack
         return { ItemStack.areEqual(it, stack) }
@@ -142,6 +174,7 @@ class StackSelection {
      * @param damage The damage value to be matched.
      * @return A predicate that matches the damage value.
      */
+    @StackSelectionDsl
     fun hasDamage(damage: Int): (ItemStack) -> Boolean {
         this.damage = damage
         return { it.damage == damage }
@@ -153,21 +186,20 @@ class StackSelection {
      * @param level The level to be matched (if -1 will look for any level above 0).
      * @return A predicate that matches the [Enchantment] and `level`.
      */
-    fun hasEnchantment(enchantment: Enchantment, level: Int = -1): (ItemStack) -> Boolean = {
-        true
-
-        // TODO: Figure out what the fuck the new registry system is lmao
-        /*if (level < 0) {
-            EnchantmentHelper.getLevel(enchantment, it) > 0
+    @StackSelectionDsl
+    fun hasEnchantment(enchantment: RegistryKey, level: Int = -1): (ItemStack) -> Boolean = {
+        if (level < 0) {
+            it.getEnchantment(enchantment) > 0
         } else {
-            EnchantmentHelper.getLevel(enchantment, it) == level
-        }*/
+            it.getEnchantment(enchantment) == level
+        }
     }
 
     /**
      * Returns the negation of the original predicate.
      * @return A new predicate that matches if the original predicate does not match.
      */
+    @StackSelectionDsl
     fun ((ItemStack) -> Boolean).not(): (ItemStack) -> Boolean {
         return { !this(it) }
     }
@@ -177,6 +209,7 @@ class StackSelection {
      * @param otherPredicate The second predicate.
      * @return A new predicate that matches if both inputs predicate match.
      */
+    @StackSelectionDsl
     infix fun ((ItemStack) -> Boolean).and(otherPredicate: (ItemStack) -> Boolean): (ItemStack) -> Boolean {
         return { this(it) && otherPredicate(it) }
     }
@@ -186,6 +219,7 @@ class StackSelection {
      * @param otherPredicate The second predicate.
      * @return A new predicate that matches if either input predicate matches.
      */
+    @StackSelectionDsl
     infix fun ((ItemStack) -> Boolean).or(otherPredicate: (ItemStack) -> Boolean): (ItemStack) -> Boolean {
         return { this(it) || otherPredicate(it) }
     }
@@ -199,6 +233,9 @@ class StackSelection {
     }
 
     companion object {
+        @DslMarker
+        annotation class StackSelectionDsl
+
         const val DEFAULT_AMOUNT = 1
         val FULL_SHULKERS: (ItemStack) -> Boolean = { stack ->
             stack.shulkerBoxContents.none { it.isEmpty }
@@ -207,9 +244,20 @@ class StackSelection {
             stack.shulkerBoxContents.all { it.isEmpty }
         }
         val EVERYTHING: (ItemStack) -> Boolean = { true }
+        val NOTHING: (ItemStack) -> Boolean = { false }
 
-        fun Item.select(): StackSelection = selectStack { isItem(this@select) }
-        fun ItemStack.select(): StackSelection = selectStack { isItemStack(this@select) }
+        @StackSelectionDsl
+        fun Item.select() = selectStack { isItem(this@select) }
+        @StackSelectionDsl
+        fun ItemStack.select() = selectStack { isItemStack(this@select) }
+        @StackSelectionDsl
+        @JvmName("selectStacks")
+        fun Collection.select() = selectStack { isOneOfStacks(this@select) }
+        @StackSelectionDsl
+        @JvmName("selectItems")
+        fun Collection.select() = selectStack { isOneOfItems(this@select) }
+
+        @StackSelectionDsl
         fun ((ItemStack) -> Boolean).select() = selectStack { this@select }
 
         /**
@@ -218,12 +266,26 @@ class StackSelection {
          * @param block The predicate to be used to select the items.
          * @return A [StackSelection] with the given parameters.
          */
+        @StackSelectionDsl
+        fun selectStack(
+            count: Int = DEFAULT_AMOUNT,
+            inShulkerBox: Boolean = false,
+            block: StackSelection.() -> (ItemStack) -> Boolean,
+        ) = StackSelection().apply {
+            selector = block()
+            this.count = count
+            this.inShulkerBox = inShulkerBox
+        }
+
+        @StackSelectionDsl
         fun selectStack(
             count: Int = DEFAULT_AMOUNT,
             inShulkerBox: Boolean = false,
+            sorter: Comparator? = null,
             block: StackSelection.() -> (ItemStack) -> Boolean,
         ) = StackSelection().apply {
             selector = block()
+            comparator = sorter
             this.count = count
             this.inShulkerBox = inShulkerBox
         }
diff --git a/src/main/kotlin/com/lambda/interaction/material/container/ContainerManager.kt b/src/main/kotlin/com/lambda/interaction/material/container/ContainerManager.kt
index 813a0c6cb..ccf6d179f 100644
--- a/src/main/kotlin/com/lambda/interaction/material/container/ContainerManager.kt
+++ b/src/main/kotlin/com/lambda/interaction/material/container/ContainerManager.kt
@@ -17,27 +17,24 @@
 
 package com.lambda.interaction.material.container
 
-import com.lambda.config.groups.InventoryConfig
 import com.lambda.core.Loadable
 import com.lambda.event.events.InventoryEvent
 import com.lambda.event.events.PlayerEvent
 import com.lambda.event.listener.SafeListener.Companion.listen
+import com.lambda.interaction.material.ContainerSelection
 import com.lambda.interaction.material.StackSelection
 import com.lambda.interaction.material.StackSelection.Companion.select
 import com.lambda.interaction.material.container.containers.ChestContainer
 import com.lambda.interaction.material.container.containers.EnderChestContainer
+import com.lambda.interaction.request.inventory.InventoryConfig
 import com.lambda.module.modules.client.TaskFlowModule
 import com.lambda.util.BlockUtils.blockEntity
-import com.lambda.util.BlockUtils.item
 import com.lambda.util.Communication.info
 import com.lambda.util.extension.containerStacks
-import com.lambda.util.item.ItemUtils
 import com.lambda.util.reflections.getInstances
-import net.minecraft.block.BlockState
 import net.minecraft.block.entity.BlockEntity
 import net.minecraft.block.entity.ChestBlockEntity
 import net.minecraft.block.entity.EnderChestBlockEntity
-import net.minecraft.item.Item
 import net.minecraft.screen.GenericContainerScreenHandler
 import net.minecraft.screen.ScreenHandlerType
 
@@ -92,30 +89,30 @@ object ContainerManager : Loadable {
     fun container() = container.flatMap { setOf(it) + it.shulkerContainer }.sorted()
 
     fun StackSelection.transfer(destination: MaterialContainer, inventory: InventoryConfig = TaskFlowModule.inventory) =
-        findContainerWithMaterial(this, inventory)?.transfer(this, destination)
+        findContainerWithMaterial(inventory)?.transfer(this, destination)
 
     fun findContainer(
         block: (MaterialContainer) -> Boolean,
     ): MaterialContainer? = container().find(block)
 
-    fun findContainerWithMaterial(
-        selection: StackSelection,
+    fun StackSelection.findContainerWithMaterial(
         inventory: InventoryConfig
     ): MaterialContainer? =
-        containerWithMaterial(selection, inventory).firstOrNull()
+        containerWithMaterial(inventory).firstOrNull()
 
     fun findContainerWithSpace(
         selection: StackSelection,
     ): MaterialContainer? =
         containerWithSpace(selection).firstOrNull()
 
-    fun containerWithMaterial(
-        selection: StackSelection,
+    fun StackSelection.containerWithMaterial(
         inventory: InventoryConfig = TaskFlowModule.inventory,
+        containerSelection: ContainerSelection = inventory.containerSelection,
     ): List =
         container()
-            .sortedWith(inventory.providerPriority.materialComparator(selection))
-            .filter { it.materialAvailable(selection) >= selection.count }
+            .sortedWith(inventory.providerPriority.materialComparator(this))
+            .filter { it.materialAvailable(this) >= count }
+            .filter { containerSelection.matches(it) }
 
     fun containerWithSpace(
         selection: StackSelection,
@@ -124,23 +121,10 @@ object ContainerManager : Loadable {
         container()
             .sortedWith(inventory.providerPriority.spaceComparator(selection))
             .filter { it.spaceAvailable(selection) >= selection.count }
-
-    fun findBestAvailableTool(
-        blockState: BlockState,
-        availableTools: Set = ItemUtils.tools,
-        inventory: InventoryConfig = TaskFlowModule.inventory,
-    ) = availableTools.map {
-        it to it.getMiningSpeed(it.defaultStack, blockState)
-    }.filter { (item, speed) ->
-        speed > 1.0
-                && item.defaultStack.isSuitableFor(blockState)
-                && containerWithMaterial(item.select(), inventory).isNotEmpty()
-    }.maxByOrNull {
-        it.second
-    }?.first
+            .filter { inventory.containerSelection.matches(it) }
 
     fun findDisposable(inventory: InventoryConfig = TaskFlowModule.inventory) = container().find { container ->
-        inventory.disposables.any { container.materialAvailable(it.item.select()) >= 0 }
+        inventory.disposables.any { container.materialAvailable(it.asItem().select()) >= 0 }
     }
 
     class NoContainerFound(selection: StackSelection) : Exception("No container found matching $selection")
diff --git a/src/main/kotlin/com/lambda/interaction/material/container/MaterialContainer.kt b/src/main/kotlin/com/lambda/interaction/material/container/MaterialContainer.kt
index 26b6d6dea..3fdd3a135 100644
--- a/src/main/kotlin/com/lambda/interaction/material/container/MaterialContainer.kt
+++ b/src/main/kotlin/com/lambda/interaction/material/container/MaterialContainer.kt
@@ -18,10 +18,15 @@
 package com.lambda.interaction.material.container
 
 import com.lambda.context.SafeContext
+import com.lambda.event.events.TickEvent
+import com.lambda.event.listener.SafeListener.Companion.listen
 import com.lambda.interaction.material.StackSelection
+import com.lambda.interaction.material.container.ContainerManager.findContainerWithMaterial
 import com.lambda.interaction.material.container.containers.ShulkerBoxContainer
 import com.lambda.interaction.material.transfer.TransferResult
+import com.lambda.interaction.request.inventory.InventoryConfig
 import com.lambda.task.Task
+import com.lambda.util.Communication.logError
 import com.lambda.util.Nameable
 import com.lambda.util.item.ItemStackUtils.count
 import com.lambda.util.item.ItemStackUtils.empty
@@ -34,6 +39,8 @@ import com.lambda.util.text.buildText
 import com.lambda.util.text.highlighted
 import com.lambda.util.text.literal
 import com.lambda.util.text.text
+import com.lambda.util.item.ItemUtils.toItemCount
+import com.lambda.util.text.*
 import net.minecraft.item.ItemStack
 import net.minecraft.text.Text
 
@@ -49,13 +56,13 @@ abstract class MaterialContainer(
         literal("\n")
         literal("Contains ")
         val available = materialAvailable(selection)
-        highlighted(if (available == Int.MAX_VALUE) "∞" else available.toString())
+        highlighted(if (available == Int.MAX_VALUE) "∞" else available.toItemCount())
         literal(" of ")
         highlighted("${selection.optimalStack?.name?.string}")
         literal("\n")
         literal("Could store ")
         val left = spaceAvailable(selection)
-        highlighted(if (left == Int.MAX_VALUE) "∞" else left.toString())
+        highlighted(if (left == Int.MAX_VALUE) "∞" else left.toItemCount())
         literal(" of ")
         highlighted("${selection.optimalStack?.name?.string}")
     }
@@ -91,6 +98,20 @@ abstract class MaterialContainer(
         }
     }
 
+    class AwaitItemTask(override val name: String, val selection: StackSelection, inventory: InventoryConfig) : Task() {
+        init {
+            listen {
+                if (selection.findContainerWithMaterial(inventory) != null) {
+                    success()
+                }
+            }
+        }
+
+        override fun SafeContext.onStart() {
+            logError(name)
+        }
+    }
+
     /**
      * Withdraws items from the container to the player's inventory.
      */
diff --git a/src/main/kotlin/com/lambda/interaction/material/container/containers/CreativeContainer.kt b/src/main/kotlin/com/lambda/interaction/material/container/containers/CreativeContainer.kt
index 6f4902b16..c145f80af 100644
--- a/src/main/kotlin/com/lambda/interaction/material/container/containers/CreativeContainer.kt
+++ b/src/main/kotlin/com/lambda/interaction/material/container/containers/CreativeContainer.kt
@@ -24,6 +24,7 @@ import com.lambda.interaction.material.container.MaterialContainer
 import com.lambda.interaction.material.transfer.TransactionExecutor
 import com.lambda.task.Task
 import com.lambda.util.item.ItemStackUtils.equal
+import com.lambda.util.player.gamemode
 import com.lambda.util.text.buildText
 import com.lambda.util.text.literal
 import net.minecraft.item.ItemStack
@@ -43,7 +44,7 @@ data object CreativeContainer : MaterialContainer(Rank.CREATIVE) {
         override val name: String get() = "Removing $selection from creative inventory"
 
         override fun SafeContext.onStart() {
-            if (!player.isCreative) {
+            if (!gamemode.isCreative) {
                 // ToDo: Maybe switch gamemode?
                 throw NotInCreativeModeException()
             }
@@ -69,7 +70,7 @@ data object CreativeContainer : MaterialContainer(Rank.CREATIVE) {
             selection.optimalStack?.let { optimalStack ->
                 if (player.mainHandStack.equal(optimalStack)) return
 
-                if (!player.isCreative) {
+                if (!gamemode.isCreative) {
                     // ToDo: Maybe switch gamemode?
                     throw NotInCreativeModeException()
                 }
diff --git a/src/main/kotlin/com/lambda/interaction/material/container/containers/MainHandContainer.kt b/src/main/kotlin/com/lambda/interaction/material/container/containers/MainHandContainer.kt
index 619bff600..2dd92fff6 100644
--- a/src/main/kotlin/com/lambda/interaction/material/container/containers/MainHandContainer.kt
+++ b/src/main/kotlin/com/lambda/interaction/material/container/containers/MainHandContainer.kt
@@ -63,7 +63,8 @@ object MainHandContainer : MaterialContainer(Rank.MAIN_HAND) {
 
                 when (moveStack) {
                     in player.hotbar -> swapToHotbarSlot(player.hotbar.indexOf(moveStack))
-                    in player.storage -> pickFromInventory(player.combined.indexOf(moveStack))
+                    // ToDo: Use pickFromInventory
+                    in player.storage -> swap(player.combined.indexOf(moveStack), 0)
                 }
 
                 if (hand == Hand.OFF_HAND) swapHands()
diff --git a/src/main/kotlin/com/lambda/interaction/material/transfer/SlotTransfer.kt b/src/main/kotlin/com/lambda/interaction/material/transfer/SlotTransfer.kt
index 6ae86f3d4..c2b816af5 100644
--- a/src/main/kotlin/com/lambda/interaction/material/transfer/SlotTransfer.kt
+++ b/src/main/kotlin/com/lambda/interaction/material/transfer/SlotTransfer.kt
@@ -17,12 +17,12 @@
 
 package com.lambda.interaction.material.transfer
 
-import com.lambda.config.groups.InventoryConfig
 import com.lambda.context.SafeContext
 import com.lambda.event.events.TickEvent
 import com.lambda.event.listener.SafeListener.Companion.listen
 import com.lambda.interaction.material.StackSelection
 import com.lambda.interaction.material.transfer.TransactionExecutor.Companion.transfer
+import com.lambda.interaction.request.inventory.InventoryConfig
 import com.lambda.module.modules.client.TaskFlowModule
 import com.lambda.task.Task
 import com.lambda.util.extension.containerSlots
diff --git a/src/main/kotlin/com/lambda/interaction/material/transfer/transaction/SwapHandsTransaction.kt b/src/main/kotlin/com/lambda/interaction/material/transfer/transaction/SwapHandsTransaction.kt
index 2bae71140..77c77b145 100644
--- a/src/main/kotlin/com/lambda/interaction/material/transfer/transaction/SwapHandsTransaction.kt
+++ b/src/main/kotlin/com/lambda/interaction/material/transfer/transaction/SwapHandsTransaction.kt
@@ -17,17 +17,16 @@
 
 package com.lambda.interaction.material.transfer.transaction
 
-import com.lambda.event.events.InventoryEvent
 import com.lambda.event.events.TickEvent
 import com.lambda.event.listener.SafeListener.Companion.listen
 import com.lambda.interaction.material.transfer.InventoryTransaction
 import net.minecraft.network.packet.c2s.play.PlayerActionC2SPacket
+import net.minecraft.util.Hand
 import net.minecraft.util.math.BlockPos
 import net.minecraft.util.math.Direction
 
 class SwapHandsTransaction @Ta5kBuilder constructor() : InventoryTransaction() {
     override val name: String get() = "Swap Hand Stacks"
-    private var confirming = false
 
     init {
         listen {
@@ -35,6 +34,9 @@ class SwapHandsTransaction @Ta5kBuilder constructor() : InventoryTransaction() {
                 failure("Spectators cannot swap hands")
                 return@listen
             }
+            val offhandStack = player.getStackInHand(Hand.OFF_HAND)
+            player.setStackInHand(Hand.OFF_HAND, player.getStackInHand(Hand.MAIN_HAND))
+            player.setStackInHand(Hand.MAIN_HAND, offhandStack)
             connection.sendPacket(
                 PlayerActionC2SPacket(
                     PlayerActionC2SPacket.Action.SWAP_ITEM_WITH_OFFHAND,
@@ -42,11 +44,6 @@ class SwapHandsTransaction @Ta5kBuilder constructor() : InventoryTransaction() {
                     Direction.DOWN
                 )
             )
-            confirming = true
-        }
-
-        listen {
-            if (it.slot != player.inventory.selectedSlot) return@listen
             finish()
         }
     }
diff --git a/src/main/kotlin/com/lambda/interaction/request/ActionInfo.kt b/src/main/kotlin/com/lambda/interaction/request/ActionInfo.kt
new file mode 100644
index 000000000..4c64ae909
--- /dev/null
+++ b/src/main/kotlin/com/lambda/interaction/request/ActionInfo.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2025 Lambda
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see .
+ */
+
+package com.lambda.interaction.request
+
+import com.lambda.interaction.construction.context.BuildContext
+
+interface ActionInfo {
+    val context: BuildContext
+    val pendingInteractionsList: MutableCollection
+}
diff --git a/src/main/kotlin/com/lambda/interaction/request/ManagerUtils.kt b/src/main/kotlin/com/lambda/interaction/request/ManagerUtils.kt
new file mode 100644
index 000000000..805f18e91
--- /dev/null
+++ b/src/main/kotlin/com/lambda/interaction/request/ManagerUtils.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2025 Lambda
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see .
+ */
+
+package com.lambda.interaction.request
+
+import com.lambda.util.reflections.getInstances
+import net.minecraft.util.math.BlockPos
+
+object ManagerUtils {
+    val managers = getInstances>()
+    val accumulatedManagerPriority = managers.map { it.stagePriority }.reduce { acc, priority -> acc + priority }
+    val positionBlockingManagers = getInstances()
+
+    fun isPosBlocked(pos: BlockPos) =
+        positionBlockingManagers.any { manager -> manager.blockedPositions.any { blocked -> blocked == pos } }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/lambda/interaction/request/Priority.kt b/src/main/kotlin/com/lambda/interaction/request/PositionBlocking.kt
similarity index 86%
rename from src/main/kotlin/com/lambda/interaction/request/Priority.kt
rename to src/main/kotlin/com/lambda/interaction/request/PositionBlocking.kt
index 26d6f21e7..ef3cd6a77 100644
--- a/src/main/kotlin/com/lambda/interaction/request/Priority.kt
+++ b/src/main/kotlin/com/lambda/interaction/request/PositionBlocking.kt
@@ -17,4 +17,8 @@
 
 package com.lambda.interaction.request
 
-typealias Priority = Int
\ No newline at end of file
+import net.minecraft.util.math.BlockPos
+
+interface PositionBlocking {
+    val blockedPositions: List
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/lambda/interaction/request/PostActionHandler.kt b/src/main/kotlin/com/lambda/interaction/request/PostActionHandler.kt
new file mode 100644
index 000000000..154474219
--- /dev/null
+++ b/src/main/kotlin/com/lambda/interaction/request/PostActionHandler.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2025 Lambda
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see .
+ */
+
+package com.lambda.interaction.request
+
+import com.lambda.config.groups.BuildConfig
+import com.lambda.event.events.ConnectionEvent
+import com.lambda.event.events.TickEvent
+import com.lambda.event.listener.SafeListener.Companion.listen
+import com.lambda.event.listener.UnsafeListener.Companion.listenUnsafe
+import com.lambda.interaction.request.breaking.BrokenBlockHandler
+import com.lambda.util.collections.LimitedDecayQueue
+
+abstract class PostActionHandler {
+    abstract val pendingActions: LimitedDecayQueue
+
+    init {
+        listen(priority = Int.MAX_VALUE) {
+            pendingActions.cleanUp()
+        }
+
+        listenUnsafe(priority = Int.MIN_VALUE) {
+            pendingActions.clear()
+        }
+    }
+
+    fun T.startPending() {
+        pendingActions.add(this)
+        pendingInteractionsList.add(context)
+    }
+
+    fun T.stopPending() {
+        pendingActions.remove(this)
+        pendingInteractionsList.remove(context)
+    }
+
+    fun setPendingConfigs(build: BuildConfig) {
+        BrokenBlockHandler.pendingActions.setSizeLimit(build.breaking.maxPendingBreaks)
+        BrokenBlockHandler.pendingActions.setDecayTime(build.interactionTimeout * 50L)
+    }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/lambda/interaction/request/Request.kt b/src/main/kotlin/com/lambda/interaction/request/Request.kt
index 49e253ad2..d2c64d42e 100644
--- a/src/main/kotlin/com/lambda/interaction/request/Request.kt
+++ b/src/main/kotlin/com/lambda/interaction/request/Request.kt
@@ -17,8 +17,16 @@
 
 package com.lambda.interaction.request
 
-abstract class Request (
-    val priority: Priority
-) {
+abstract class Request {
+    abstract val config: RequestConfig
+    var fresh = true
+
     abstract val done: Boolean
-}
\ No newline at end of file
+
+    abstract fun submit(queueIfClosed: Boolean = true): Request
+
+    companion object {
+        fun submit(request: Request, queueIfClosed: Boolean = true) =
+            request.submit(queueIfClosed)
+    }
+}
diff --git a/src/main/kotlin/com/lambda/interaction/request/RequestConfig.kt b/src/main/kotlin/com/lambda/interaction/request/RequestConfig.kt
index ee906c748..e756864d8 100644
--- a/src/main/kotlin/com/lambda/interaction/request/RequestConfig.kt
+++ b/src/main/kotlin/com/lambda/interaction/request/RequestConfig.kt
@@ -17,11 +17,4 @@
 
 package com.lambda.interaction.request
 
-abstract class RequestConfig (
-    val priority: Priority
-) {
-    protected abstract fun requestInternal(request: R)
-
-    fun request(request: R): R =
-        request.apply(::requestInternal)
-}
\ No newline at end of file
+interface RequestConfig
\ No newline at end of file
diff --git a/src/main/kotlin/com/lambda/interaction/request/RequestHandler.kt b/src/main/kotlin/com/lambda/interaction/request/RequestHandler.kt
index d09e6213b..f23c77bba 100644
--- a/src/main/kotlin/com/lambda/interaction/request/RequestHandler.kt
+++ b/src/main/kotlin/com/lambda/interaction/request/RequestHandler.kt
@@ -17,53 +17,106 @@
 
 package com.lambda.interaction.request
 
-import java.util.concurrent.ConcurrentHashMap
+import com.lambda.context.SafeContext
+import com.lambda.core.Loadable
+import com.lambda.event.Event
+import com.lambda.event.events.TickEvent
+import com.lambda.event.listener.SafeListener.Companion.listen
+import com.lambda.interaction.request.ManagerUtils.accumulatedManagerPriority
+import com.lambda.threading.runSafe
+import kotlin.reflect.KClass
 
 /**
- * This class manages a collection of requests, each associated with a `RequestConfig`.
- * It provides a mechanism to register requests and select the highest priority request
- * for processing.
+ * This class handles requests, offering specific opening times, and an option to queue a request for the
+ * next opening if closed
  */
-abstract class RequestHandler {
+abstract class RequestHandler(
+    val stagePriority: Int,
+    private vararg val openStages: Event,
+    private val onOpen: (SafeContext.() -> Unit)? = null,
+    private val onClose: (SafeContext.() -> Unit)? = null
+) : Loadable {
+    /**
+     * Represents if the handler is accepting requests at any given time
+     */
+    private var acceptingRequests = false
 
-    private val requestMap = ConcurrentHashMap, R>()
+    /**
+     * Represents the sequence stage the current tick is at
+     */
+    var tickStage: Event? = null; private set
 
     /**
-     * The currently active request.
+     * If a request is made while the handler isn't accepting requests, it is placed into [queuedRequest] and run
+     * at the start of the next open request timeframe
      */
-    var currentRequest: R? = null; protected set
+    var queuedRequest: R? = null; protected set
+
+    /**
+     * Represents if the handler performed any external actions within this tick
+     */
+    var activeThisTick = false; protected set
+
+    override fun load(): String {
+        openStages.forEach { openRequestsFor(it::class, it) }
+
+        listen(Int.MIN_VALUE) {
+            activeThisTick = false
+            queuedRequest = null
+        }
+
+        return super.load()
+    }
 
     /**
-     * Registers a new request with the given configuration.
+     * opens the handler for requests for the duration of the given event
+     */
+    private inline fun  openRequestsFor(instance: KClass, stage: T) {
+        listen(instance, priority = (Int.MAX_VALUE - 1) - (accumulatedManagerPriority - stagePriority)) {
+            tickStage = stage
+            queuedRequest?.let { request ->
+                handleRequest(request)
+                request.fresh = false
+                queuedRequest = null
+            }
+            acceptingRequests = true
+            onOpen?.invoke(this)
+            preEvent()
+        }
+
+        listen(instance, priority = (Int.MIN_VALUE + 1) + stagePriority) {
+            onClose?.invoke(this)
+            acceptingRequests = false
+        }
+    }
+
+    /**
+     * Registers a new request
      *
-     * @param config The configuration for the request.
      * @param request The request to register.
+     * @param queueIfClosed queues the request for the next time the handlers accepting requests
      * @return The registered request.
      */
-    fun registerRequest(config: RequestConfig, request: R): R {
-        requestMap[config] = request
+    fun request(request: R, queueIfClosed: Boolean = true): R {
+        if (!acceptingRequests) {
+            val canOverrideQueued = queuedRequest?.run { config === request.config } != false
+            if (queueIfClosed && canOverrideQueued) {
+                queuedRequest = request
+            }
+            return request
+        }
+
+        runSafe {
+            handleRequest(request)
+            request.fresh = false
+        }
         return request
     }
 
     /**
-     * Updates the current request to the highest priority registered request.
-     * Clears the internal request map after updating.
-     *
-     * @return True, if the request was updated.
+     * Handles a request
      */
-    protected fun updateRequest(
-        keepIfNull: Boolean = false,
-        filter: (Map.Entry, R>) -> Boolean = { true }
-    ): Boolean {
-        val prev = currentRequest
-
-        currentRequest = requestMap.entries
-                .filter(filter)
-                .maxByOrNull { it.key.priority }?.value
+    abstract fun SafeContext.handleRequest(request: R)
 
-        if (keepIfNull && currentRequest == null) currentRequest = prev
-
-        requestMap.clear()
-        return prev != currentRequest
-    }
-}
\ No newline at end of file
+    protected abstract fun preEvent(): Event
+}
diff --git a/src/main/kotlin/com/lambda/interaction/request/breaking/BreakConfig.kt b/src/main/kotlin/com/lambda/interaction/request/breaking/BreakConfig.kt
new file mode 100644
index 000000000..81dec0294
--- /dev/null
+++ b/src/main/kotlin/com/lambda/interaction/request/breaking/BreakConfig.kt
@@ -0,0 +1,149 @@
+/*
+ * Copyright 2025 Lambda
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see .
+ */
+
+package com.lambda.interaction.request.breaking
+
+import com.lambda.config.groups.BuildConfig
+import com.lambda.event.Event
+import com.lambda.interaction.request.RequestConfig
+import com.lambda.util.Describable
+import com.lambda.util.NamedEnum
+import net.minecraft.block.Block
+import java.awt.Color
+
+interface BreakConfig : RequestConfig {
+    val breakMode: BreakMode
+    val sorter: SortMode
+    val breakThreshold: Float
+    val rebreak: Boolean
+
+    val doubleBreak: Boolean
+    val unsafeCancels: Boolean
+
+    val fudgeFactor: Int
+    //ToDo: Needs a more advanced player simulation implementation to predict the next ticks onGround / submerged status
+//    abstract val desyncFix: Boolean
+    val breakDelay: Int
+
+    val breakStageMask: Set
+
+    val swapMode: SwapMode
+
+    val swing: SwingMode
+    val swingType: BuildConfig.SwingType
+
+    val rotateForBreak: Boolean
+
+    val breakConfirmation: BreakConfirmationMode
+    val breaksPerTick: Int
+    val maxPendingBreaks: Int
+
+    val avoidLiquids: Boolean
+    val avoidSupporting: Boolean
+    val breakWeakBlocks: Boolean
+    val ignoredBlocks: Set
+
+    val suitableToolsOnly: Boolean
+    val forceSilkTouch: Boolean
+    val forceFortunePickaxe: Boolean
+    val minFortuneLevel: Int
+
+    val sounds: Boolean
+    val particles: Boolean
+    val breakingTexture: Boolean
+
+    val renders: Boolean
+    val fill: Boolean
+    val outline: Boolean
+    val outlineWidth: Int
+    val animation: AnimationMode
+
+    val dynamicFillColor: Boolean
+    val staticFillColor: Color
+    val startFillColor: Color
+    val endFillColor: Color
+
+    val dynamicOutlineColor: Boolean
+    val staticOutlineColor: Color
+    val startOutlineColor: Color
+    val endOutlineColor: Color
+
+    enum class BreakMode(
+        override val displayName: String,
+        override val description: String
+    ): NamedEnum, Describable {
+        Vanilla("Vanilla", "Uses vanilla breaking"),
+        Packet("Packet", "Breaks blocks using only using packets")
+    }
+
+    enum class SortMode(
+        override val displayName: String,
+        override val description: String
+    ): NamedEnum, Describable {
+        Closest("Closest", "Breaks blocks closest to the player eye position"),
+        Farthest("Farthest", "Breaks blocks farthest from the player eye position"),
+        Rotation("Rotation", "Breaks blocks that are closest to the player rotation"),
+        Random("Random", "Breaks blocks in a random order")
+    }
+
+    enum class SwapMode(
+        override val displayName: String,
+        override val description: String
+    ) : NamedEnum, Describable {
+        None("None", "Never auto-swap tools. Keeps whatever you’re holding"),
+        Start("Start", "Auto-swap to the best tool right when the break starts. No further swaps during the same break"),
+        End("End", "Stay on your current tool at first, then auto-swap to the best tool right before the block finishes breaking to speed up the final stretch"),
+        StartAndEnd("Start and End", "Auto-swap to the best tool at the start, and again right before the block finishes breaking if it would be faster"),
+        Constant("Constant", "Always keep the best tool selected for the entire break. Swaps as needed to maintain optimal speed");
+
+        fun isEnabled() = this != None
+    }
+
+
+    enum class SwingMode(
+        override val displayName: String,
+        override val description: String
+    ) : NamedEnum, Describable {
+        Constant("Constant", "Swings the hand every tick"),
+        StartAndEnd("Start and End", "Swings the hand at the start and end of breaking"),
+        Start("Start", "Swings the hand at the start of breaking"),
+        End("End", "Swings the hand at the end of breaking"),
+        None("None", "Does not swing the hand at all");
+
+        fun isEnabled() = this != None
+    }
+
+    enum class BreakConfirmationMode(
+        override val displayName: String,
+        override val description: String
+    ) : NamedEnum, Describable {
+        None("No confirmation", "Breaks immediately without waiting for the server. Lowest latency, but can briefly show break effects even if the server later disagrees."),
+        BreakThenAwait("Break now, confirm later", "Shows the break effects right away (particles/sounds) and then waits for the server to confirm. Feels instant while keeping results consistent."),
+        AwaitThenBreak("Confirm first, then break", "Waits for the server response before showing break effects. Most accurate and safest, but adds a short delay.");
+    }
+
+    enum class AnimationMode(
+        override val displayName: String,
+        override val description: String
+    ) : NamedEnum, Describable {
+        None("None", "Does not render any breaking animation"),
+        Out("Out", "Renders a growing animation"),
+        In("In", "Renders a shrinking animation"),
+        OutIn("Out In", "Renders a growing and shrinking animation"),
+        InOut("In Out", "Renders a shrinking and growing animation")
+    }
+}
diff --git a/src/main/kotlin/com/lambda/interaction/request/breaking/BreakInfo.kt b/src/main/kotlin/com/lambda/interaction/request/breaking/BreakInfo.kt
new file mode 100644
index 000000000..b65308f10
--- /dev/null
+++ b/src/main/kotlin/com/lambda/interaction/request/breaking/BreakInfo.kt
@@ -0,0 +1,168 @@
+/*
+ * Copyright 2025 Lambda
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see .
+ */
+
+package com.lambda.interaction.request.breaking
+
+import com.lambda.interaction.construction.context.BreakContext
+import com.lambda.interaction.request.ActionInfo
+import com.lambda.util.BlockUtils.calcItemBlockBreakingDelta
+import com.lambda.util.Describable
+import com.lambda.util.NamedEnum
+import com.lambda.util.OneSetPerTick
+import net.minecraft.client.network.ClientPlayerEntity
+import net.minecraft.client.network.ClientPlayerInteractionManager
+import net.minecraft.client.world.ClientWorld
+import net.minecraft.entity.ItemEntity
+import net.minecraft.entity.player.PlayerEntity
+import net.minecraft.item.ItemStack
+import net.minecraft.network.packet.c2s.play.PlayerActionC2SPacket
+import net.minecraft.network.packet.c2s.play.PlayerActionC2SPacket.Action
+import net.minecraft.world.BlockView
+
+data class BreakInfo(
+    override var context: BreakContext,
+    var type: BreakType,
+    var request: BreakRequest
+) : ActionInfo {
+    // Delegates
+    val breakConfig get() = request.build.breaking
+    override val pendingInteractionsList get() = request.pendingInteractions
+
+    // Pre Processing
+    var shouldProgress = false
+    var couldReBreak by OneSetPerTick(value = false, throwOnLimitBreach = true)
+    var shouldSwap by OneSetPerTick(value = false, throwOnLimitBreach = true)
+    var swapStack: ItemStack by OneSetPerTick(ItemStack.EMPTY, true)
+    var minSwapTicks by OneSetPerTick(0, true)
+    var serverBreakTicks = 0
+
+    // BreakInfo Specific
+    var updatedThisTick by OneSetPerTick(false, resetAfterTick = true).apply { set(true) }
+    var updatedPreProcessingThisTick by OneSetPerTick(value = false, throwOnLimitBreach = true, resetAfterTick = true)
+    var progressedThisTick by OneSetPerTick(value = false, throwOnLimitBreach = true, resetAfterTick = true)
+
+    // Processing
+    var breaking = false
+    var abandoned = false
+    var breakingTicks by OneSetPerTick(0, true)
+    var soundsCooldown by OneSetPerTick(0f, true)
+    var vanillaInstantBreakable = false
+    val rebreakable get() = !vanillaInstantBreakable && type == BreakType.Primary
+
+    enum class BreakType(
+        override val displayName: String,
+        override val description: String
+    ) : NamedEnum, Describable {
+        Primary("Primary", "The main block you’re breaking right now."),
+        Secondary("Secondary", "A second block broken at the same time (when double‑break is enabled)."),
+        RedundantSecondary("Redundant Secondary", "A previously started secondary break that’s now ignored/monitored only (no new actions)."),
+        Rebreak("Rebreak", "Re-attempts a recent break to finish callbacks or correct state after updates.");
+
+        fun getBreakThreshold(breakConfig: BreakConfig) =
+            when (this) {
+                Primary -> breakConfig.breakThreshold
+                else -> 1.0f
+            }
+    }
+
+    // Post Processing
+    @Volatile
+    var broken = false; private set
+    private var item: ItemEntity? = null
+    val callbacksCompleted
+        @Synchronized get() = broken && (request.onItemDrop == null || item != null)
+
+    @Synchronized
+    fun internalOnBreak() {
+        if (type != BreakType.Rebreak) broken = true
+        item?.let { item ->
+            request.onItemDrop?.invoke(item)
+        }
+    }
+
+    @Synchronized
+    fun internalOnItemDrop(item: ItemEntity) {
+        if (type != BreakType.Rebreak) this.item = item
+        if (broken || type == BreakType.Rebreak) {
+            request.onItemDrop?.invoke(item)
+        }
+    }
+
+    fun updateInfo(context: BreakContext, request: BreakRequest? = null) {
+        updatedThisTick = true
+        this.context = context
+        request?.let { this.request = it }
+        if (type == BreakType.RedundantSecondary) type = BreakType.Secondary
+    }
+
+    fun resetCallbacks() {
+        broken = false
+        item = null
+    }
+
+    fun shouldSwap(player: ClientPlayerEntity, world: BlockView): Boolean {
+        val item = player.inventory.getStack(context.hotbarIndex)
+        val breakDelta = context.cachedState.calcItemBlockBreakingDelta(player, world, context.blockPos, item)
+        val breakProgress = breakDelta * (breakingTicks + 1)
+        return if (couldReBreak)
+            breakConfig.swapMode.isEnabled()
+        else when (breakConfig.swapMode) {
+            BreakConfig.SwapMode.None -> false
+            BreakConfig.SwapMode.Start -> !breaking
+            BreakConfig.SwapMode.End -> breakProgress >= getBreakThreshold()
+            BreakConfig.SwapMode.StartAndEnd -> !breaking || breakProgress >= getBreakThreshold()
+            BreakConfig.SwapMode.Constant -> true
+        }
+    }
+
+    fun setBreakingTextureStage(
+        player: ClientPlayerEntity,
+        world: ClientWorld,
+        stage: Int = getBreakTextureProgress(player, world)
+    ) {
+        world.setBlockBreakingInfo(player.id, context.blockPos, stage)
+    }
+
+    private fun getBreakTextureProgress(player: PlayerEntity, world: ClientWorld): Int {
+        val swapMode = breakConfig.swapMode
+        val item = if (swapMode.isEnabled() && swapMode != BreakConfig.SwapMode.Start) player.inventory.getStack(context.hotbarIndex) else player.mainHandStack
+        val breakDelta = context.cachedState.calcItemBlockBreakingDelta(player, world, context.blockPos, item)
+        val progress = (breakDelta * breakingTicks) / (getBreakThreshold() + (breakDelta * breakConfig.fudgeFactor))
+        return if (progress > 0.0f) (progress * 10.0f).toInt().coerceAtMost(9) else -1
+    }
+
+    fun getBreakThreshold() = type.getBreakThreshold(breakConfig)
+
+    fun startBreakPacket(world: ClientWorld, interaction: ClientPlayerInteractionManager) =
+        breakPacket(Action.START_DESTROY_BLOCK, world, interaction)
+
+    fun stopBreakPacket(world: ClientWorld, interaction: ClientPlayerInteractionManager) =
+        breakPacket(Action.STOP_DESTROY_BLOCK, world, interaction)
+
+    fun abortBreakPacket(world: ClientWorld, interaction: ClientPlayerInteractionManager) =
+        breakPacket(Action.ABORT_DESTROY_BLOCK, world, interaction)
+
+    private fun breakPacket(action: Action, world: ClientWorld, interaction: ClientPlayerInteractionManager) =
+        interaction.sendSequencedPacket(world) { sequence: Int ->
+            PlayerActionC2SPacket(
+                action,
+                context.blockPos,
+                context.result.side,
+                sequence
+            )
+        }
+}
diff --git a/src/main/kotlin/com/lambda/interaction/request/breaking/BreakManager.kt b/src/main/kotlin/com/lambda/interaction/request/breaking/BreakManager.kt
new file mode 100644
index 000000000..6c549c98e
--- /dev/null
+++ b/src/main/kotlin/com/lambda/interaction/request/breaking/BreakManager.kt
@@ -0,0 +1,904 @@
+/*
+ * Copyright 2025 Lambda
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see .
+ */
+
+package com.lambda.interaction.request.breaking
+
+import com.lambda.context.SafeContext
+import com.lambda.event.Event
+import com.lambda.event.EventFlow.post
+import com.lambda.event.events.ConnectionEvent
+import com.lambda.event.events.EntityEvent
+import com.lambda.event.events.RenderEvent
+import com.lambda.event.events.TickEvent
+import com.lambda.event.events.UpdateManagerEvent
+import com.lambda.event.events.WorldEvent
+import com.lambda.event.listener.SafeListener.Companion.listen
+import com.lambda.event.listener.UnsafeListener.Companion.listenUnsafe
+import com.lambda.graphics.renderer.esp.builders.buildFilled
+import com.lambda.graphics.renderer.esp.builders.buildOutline
+import com.lambda.interaction.construction.blueprint.Blueprint.Companion.toStructure
+import com.lambda.interaction.construction.blueprint.StaticBlueprint.Companion.toBlueprint
+import com.lambda.interaction.construction.context.BreakContext
+import com.lambda.interaction.construction.result.BreakResult
+import com.lambda.interaction.construction.simulation.BuildSimulator.simulate
+import com.lambda.interaction.construction.verify.TargetState
+import com.lambda.interaction.material.StackSelection
+import com.lambda.interaction.material.StackSelection.Companion.select
+import com.lambda.interaction.request.ManagerUtils.isPosBlocked
+import com.lambda.interaction.request.PositionBlocking
+import com.lambda.interaction.request.RequestHandler
+import com.lambda.interaction.request.breaking.BreakConfig.BreakConfirmationMode
+import com.lambda.interaction.request.breaking.BreakConfig.BreakMode
+import com.lambda.interaction.request.breaking.BreakInfo.BreakType
+import com.lambda.interaction.request.breaking.BreakInfo.BreakType.*
+import com.lambda.interaction.request.breaking.BreakManager.activeRequest
+import com.lambda.interaction.request.breaking.BreakManager.breakInfos
+import com.lambda.interaction.request.breaking.BreakManager.breaks
+import com.lambda.interaction.request.breaking.BreakManager.canAccept
+import com.lambda.interaction.request.breaking.BreakManager.checkForCancels
+import com.lambda.interaction.request.breaking.BreakManager.initNewBreak
+import com.lambda.interaction.request.breaking.BreakManager.instantBreaks
+import com.lambda.interaction.request.breaking.BreakManager.maxBreaksThisTick
+import com.lambda.interaction.request.breaking.BreakManager.performInstantBreaks
+import com.lambda.interaction.request.breaking.BreakManager.processNewBreaks
+import com.lambda.interaction.request.breaking.BreakManager.processRequest
+import com.lambda.interaction.request.breaking.BreakManager.simulateAbandoned
+import com.lambda.interaction.request.breaking.BreakManager.updateBreakProgress
+import com.lambda.interaction.request.breaking.BrokenBlockHandler.destroyBlock
+import com.lambda.interaction.request.breaking.BrokenBlockHandler.pendingActions
+import com.lambda.interaction.request.breaking.BrokenBlockHandler.setPendingConfigs
+import com.lambda.interaction.request.breaking.BrokenBlockHandler.startPending
+import com.lambda.interaction.request.interacting.InteractionManager
+import com.lambda.interaction.request.placing.PlaceManager
+import com.lambda.interaction.request.rotating.RotationRequest
+import com.lambda.threading.runSafe
+import com.lambda.util.BlockUtils.blockState
+import com.lambda.util.BlockUtils.calcItemBlockBreakingDelta
+import com.lambda.util.BlockUtils.isEmpty
+import com.lambda.util.BlockUtils.isNotBroken
+import com.lambda.util.BlockUtils.isNotEmpty
+import com.lambda.util.extension.partialTicks
+import com.lambda.util.item.ItemUtils.block
+import com.lambda.util.math.lerp
+import com.lambda.util.player.gamemode
+import com.lambda.util.player.swingHand
+import net.minecraft.block.BlockState
+import net.minecraft.client.network.ClientPlayerEntity
+import net.minecraft.client.sound.PositionedSoundInstance
+import net.minecraft.client.sound.SoundInstance
+import net.minecraft.entity.ItemEntity
+import net.minecraft.item.ItemStack
+import net.minecraft.sound.SoundCategory
+import net.minecraft.util.Hand
+import net.minecraft.util.math.BlockPos
+import net.minecraft.util.math.Box
+import net.minecraft.world.BlockView
+import kotlin.collections.firstOrNull
+import kotlin.collections.forEach
+import kotlin.collections.lastOrNull
+import kotlin.math.max
+
+object BreakManager : RequestHandler(
+    0,
+    TickEvent.Pre,
+    TickEvent.Input.Pre,
+    TickEvent.Input.Post,
+    TickEvent.Player.Post,
+    onOpen = { processRequest(activeRequest); simulateAbandoned() },
+    onClose = { checkForCancels() }
+), PositionBlocking {
+    private var primaryBreak: BreakInfo?
+        get() = breakInfos[0]
+        set(value) { breakInfos[0] = value }
+    private var secondaryBreak: BreakInfo?
+        get() = breakInfos[1]
+        set(value) { breakInfos[1] = value }
+    private val breakInfos = arrayOfNulls(2)
+    val currentStackSelection
+        get() = breakInfos
+            .lastOrNull {
+                it != null && it.type != RedundantSecondary && (it.breakConfig.doubleBreak || it.type == Secondary)
+            }?.context?.itemSelection
+            ?: StackSelection.EVERYTHING.select()
+
+    private val pendingBreakCount get() = breakInfos.count { it != null } + pendingActions.size
+    override val blockedPositions
+        get() = breakInfos.mapNotNull { it?.context?.blockPos } + pendingActions.map { it.context.blockPos }
+
+    private var activeRequest: BreakRequest? = null
+
+    private var rotationRequest: RotationRequest? = null
+    private val rotated get() = rotationRequest?.done != false
+
+    private var swapped = false
+        set(value) {
+            field = value
+            if (!value)
+                breakInfos.forEach { it?.serverBreakTicks = 0 }
+        }
+    var swappedStack: ItemStack = ItemStack.EMPTY
+        set(value) {
+            if (value != field)
+                breakInfos.forEach { it?.serverBreakTicks = 0 }
+            field = value
+        }
+    private var breakCooldown = 0
+    var breaksThisTick = 0
+    private var maxBreaksThisTick = 0
+
+    private var breaks = mutableListOf()
+    private var instantBreaks = mutableListOf()
+
+    var lastPosStarted: BlockPos? = null
+        set(value) {
+            if (value != field) RebreakManager.clearRebreak()
+            field = value
+        }
+
+    fun Any.onBreak(
+        alwaysListen: Boolean = false,
+        priority: Int = 0,
+        block: SafeContext.() -> Unit
+    ) = this.listen(priority, alwaysListen) {
+        block()
+    }
+
+    override fun load(): String {
+        super.load()
+
+        listen(priority = Int.MIN_VALUE) {
+            if (breakCooldown > 0) {
+                breakCooldown--
+            }
+            activeRequest = null
+            breaks = mutableListOf()
+            instantBreaks = mutableListOf()
+            breaksThisTick = 0
+        }
+
+        listen(priority = Int.MIN_VALUE) { event ->
+            if (event.pos == RebreakManager.rebreak?.context?.blockPos) return@listen
+
+            breakInfos
+                .filterNotNull()
+                .firstOrNull { it.context.blockPos == event.pos }
+                ?.let { info ->
+                    val currentState = info.context.cachedState
+                    // if not broken
+                    if (isNotBroken(currentState, event.newState)) {
+                        // update the cached state
+                        info.context.cachedState = event.newState
+                        return@listen
+                    }
+                    destroyBlock(info)
+                    if (info.type == RedundantSecondary) {
+                        info.nullify()
+                        return@listen
+                    }
+                    info.request.onStop?.invoke(info.context.blockPos)
+                    info.internalOnBreak()
+                    if (info.callbacksCompleted)
+                        RebreakManager.offerRebreak(info)
+                    else info.startPending()
+                    info.nullify()
+                }
+        }
+
+        // ToDo: Dependent on the tracked data order. When set stack is called after position it wont work
+        listen(priority = Int.MIN_VALUE) {
+            if (it.entity !is ItemEntity) return@listen
+
+            // ToDo: Proper item drop prediction system
+            RebreakManager.rebreak?.let { reBreak ->
+                if (matchesBlockItem(reBreak, it.entity)) return@listen
+            }
+
+            breakInfos
+                .filterNotNull()
+                .firstOrNull { info -> matchesBlockItem(info, it.entity) }
+                ?.internalOnItemDrop(it.entity)
+        }
+
+        listen { event ->
+            val activeStack = breakInfos
+                .filterNotNull()
+                .firstOrNull()?.let { info ->
+                    player.inventory.getStack(info.context.hotbarIndex)
+                } ?: return@listen
+
+            breakInfos
+                .filterNotNull()
+                .forEach { info ->
+                    val config = info.breakConfig
+                    if (!config.renders) return@listen
+                    val swapMode = info.breakConfig.swapMode
+                    val breakDelta = info.context.cachedState.calcBreakDelta(
+                        player,
+                        world,
+                        info.context.blockPos,
+                        info.breakConfig,
+                        if (info.type != RedundantSecondary && swapMode.isEnabled() && swapMode != BreakConfig.SwapMode.Start) activeStack else null
+                    ).toDouble()
+                    val currentDelta = info.breakingTicks * breakDelta
+
+                    val threshold = if (info.type == Primary) info.breakConfig.breakThreshold else 1f
+                    val adjustedThreshold = threshold + (breakDelta * config.fudgeFactor)
+
+                    val currentProgress = currentDelta / adjustedThreshold
+                    val nextTicksProgress = (currentDelta + breakDelta) / adjustedThreshold
+                    val interpolatedProgress = lerp(mc.partialTicks, currentProgress, nextTicksProgress)
+
+                    val fillColor = if (config.dynamicFillColor) lerp(interpolatedProgress, config.startFillColor, config.endFillColor)
+                    else config.staticFillColor
+                    val outlineColor = if (config.dynamicOutlineColor) lerp(interpolatedProgress, config.startOutlineColor, config.endOutlineColor)
+                    else config.staticOutlineColor
+
+                    info.context.cachedState.getOutlineShape(world, info.context.blockPos).boundingBoxes.map {
+                        it.offset(info.context.blockPos)
+                    }.forEach boxes@ { box ->
+                        val interpolated = interpolateBox(box, interpolatedProgress, info.breakConfig)
+                        if (config.fill) event.renderer.buildFilled(interpolated, fillColor)
+                        if (config.outline) event.renderer.buildOutline(interpolated, outlineColor)
+                    }
+                }
+        }
+
+        listenUnsafe(priority = Int.MIN_VALUE) {
+            breakInfos.forEach { it?.nullify() }
+            breakCooldown = 0
+        }
+
+        return "Loaded Break Manager"
+    }
+
+    /**
+     * Attempts to accept and process the request, if there is not already an [activeRequest].
+     * If the request is processed and all breaks completed, the [activeRequest] is cleared.
+     *
+     * @see processRequest
+     */
+    override fun SafeContext.handleRequest(request: BreakRequest) {
+        if (activeRequest != null || PlaceManager.activeThisTick || InteractionManager.activeThisTick || request.contexts.isEmpty()) return
+
+        activeRequest = request
+        processRequest(request)
+    }
+
+    /**
+     * If the request is fresh, local variables are populated through the [processRequest] method.
+     * It then attempts to perform as many breaks within this tick as possible from the [instantBreaks] collection.
+     * The [breakInfos] are then updated if the dependencies are present, E.G. if the user has rotations enabled,
+     * or the player needs to swap to a different hotbar slot.
+     *
+     * @see performInstantBreaks
+     * @see processNewBreaks
+     * @see updateBreakProgress
+     */
+    private fun SafeContext.processRequest(breakRequest: BreakRequest?) {
+        repeat(2) {
+            breakRequest?.let { request ->
+                if (request.fresh) populateFrom(request)
+                if (performInstantBreaks(request)) {
+                    processNewBreaks(request)
+                }
+            }
+
+            // Reversed so that the breaking order feels natural to the user as the primary break is always the
+            // last break to be started
+            run {
+                if (!handlePreProcessing()) return@run
+                breakInfos
+                    .filterNotNull()
+                    .filter { it.type != RedundantSecondary && it.updatedThisTick }
+                    .asReversed()
+                    .forEach { info ->
+                        if (info.shouldProgress)
+                            updateBreakProgress(info)
+                    }
+            }
+        }
+
+        if (instantBreaks.isEmpty() && breaks.isEmpty()) {
+            activeRequest = null
+        }
+        if (breaksThisTick > 0 || breakInfos.any { it != null && it.type != RedundantSecondary }) {
+            activeThisTick = true
+        }
+    }
+
+    /**
+     * Filters the requests [BreakContext]s, and iterates over the [breakInfos] collection looking for matches
+     * in positions. If a match is found, the [BreakInfo] is updated with the new context. Otherwise, the break is cancelled.
+     * The [instantBreaks] and [breaks] collections are then populated with the new appropriate contexts, and the [maxBreaksThisTick]
+     * value is set.
+     *
+     * @see canAccept
+     */
+    private fun SafeContext.populateFrom(request: BreakRequest) {
+        // Sanitize the new breaks
+        val newBreaks = request.contexts
+            .distinctBy { it.blockPos }
+            .toMutableList()
+
+        // Update the current break infos
+        breakInfos
+            .filterNotNull()
+            .forEach { info ->
+                newBreaks.find { ctx -> ctx.blockPos == info.context.blockPos && canAccept(ctx) }?.let { ctx ->
+                    if (!info.updatedThisTick || info.abandoned) {
+                        info.updateInfo(ctx, request)
+                        if (info.type == RedundantSecondary)
+                            info.request.onStart?.invoke(info.context.blockPos)
+                        else if (info.abandoned) {
+                            info.abandoned = false
+                            info.request.onStart?.invoke(info.context.blockPos)
+                        } else
+                            info.request.onUpdate?.invoke(info.context.blockPos)
+                    }
+                    newBreaks.remove(ctx)
+                    return@forEach
+                }
+            }
+
+        instantBreaks = newBreaks
+            .filter { it.instantBreak }
+            .toMutableList()
+
+        breaks = newBreaks
+            .filter { !it.instantBreak }
+            .toMutableList()
+
+        val breakConfig = request.config
+        val pendingLimit = (breakConfig.maxPendingBreaks - pendingBreakCount).coerceAtLeast(0)
+        maxBreaksThisTick = breakConfig.breaksPerTick.coerceAtMost(pendingLimit)
+    }
+
+    /**
+     * @return if the break context can be accepted.
+     */
+    private fun SafeContext.canAccept(newCtx: BreakContext): Boolean {
+        if (breakInfos.none { it?.context?.blockPos == newCtx.blockPos } && isPosBlocked(newCtx.blockPos)) return false
+
+        if (!currentStackSelection.filterStack(player.inventory.getStack(newCtx.hotbarIndex)))
+            return false
+
+        val blockState = blockState(newCtx.blockPos)
+        val hardness = newCtx.cachedState.getHardness(world, newCtx.blockPos)
+
+        return blockState.isNotEmpty && hardness != 600f && hardness != -1f
+    }
+
+    private fun SafeContext.handlePreProcessing(): Boolean {
+        breakInfos
+            .filterNotNull()
+            .filter { it.type != RedundantSecondary && it.updatedThisTick }
+            .let { infos ->
+                rotationRequest = infos.firstOrNull { info -> info.breakConfig.rotateForBreak }
+                    ?.let { info ->
+                        val rotation = info.context.rotation
+                        rotation.submit(false)
+                    }
+
+                if (breakInfos.none { it != null && it.type != RedundantSecondary }) {
+                    swapped = false
+                    swappedStack = player.mainHandStack
+                    return true
+                }
+
+                infos.forEach {
+                    it.updatePreProcessing(player, world)
+                }
+                infos.firstOrNull()?.let { info ->
+                    infos.firstOrNull { it.shouldSwap && it.shouldProgress }?.let { last ->
+                        if (!info.context.requestSwap(info.request, max(info.minSwapTicks, last.minSwapTicks))) {
+                            swapped = false
+                            return false
+                        }
+                        swappedStack = info.swapStack
+                        swapped = true
+                        info.serverBreakTicks++
+                        return true
+                    }
+                }
+            }
+
+        return true
+    }
+
+    /**
+     * Attempts to break as many [BreakContext]'s as possible from the [instantBreaks] collection within this tick.
+     *
+     * @return false if a break could not be performed.
+     */
+    private fun SafeContext.performInstantBreaks(request: BreakRequest): Boolean {
+        val iterator = instantBreaks.iterator()
+        while (iterator.hasNext()) {
+            if (breaksThisTick + 1 > maxBreaksThisTick) return false
+
+            val ctx = iterator.next()
+
+            if (!canAccept(ctx)) continue
+
+            rotationRequest = if (request.config.rotateForBreak) ctx.rotation.submit(false) else null
+            if (!rotated || tickStage !in request.config.breakStageMask) return false
+
+            val breakInfo = initNewBreak(ctx, request) ?: return false
+            if (!handlePreProcessing()) return false
+
+            updateBreakProgress(breakInfo)
+            iterator.remove()
+        }
+        return true
+    }
+
+    /**
+     * Attempts to start breaking as many [BreakContext]'s from the [breaks] collection as possible.
+     *
+     * @return false if a context cannot be started or the maximum active breaks has been reached.
+     *
+     * @see initNewBreak
+     */
+    private fun SafeContext.processNewBreaks(request: BreakRequest): Boolean {
+        val iterator = breaks.iterator()
+        while (iterator.hasNext()) {
+            val ctx = iterator.next()
+
+            if (!canAccept(ctx)) continue
+
+            initNewBreak(ctx, request) ?: return false
+            iterator.remove()
+        }
+        return true
+    }
+
+    /**
+     * Attempts to accept the [requestCtx] into the [breakInfos].
+     *
+     * @return the [BreakInfo] or null if the break context wasn't accepted.
+     */
+    private fun SafeContext.initNewBreak(
+        requestCtx: BreakContext,
+        request: BreakRequest
+    ): BreakInfo? {
+        if (breakCooldown > 0) return null
+
+        val breakInfo = BreakInfo(requestCtx, Primary, request)
+        primaryBreak?.let { primaryInfo ->
+            if (!primaryInfo.breakConfig.doubleBreak || secondaryBreak != null) {
+                if (!primaryInfo.updatedThisTick && tickStage in primaryInfo.breakConfig.breakStageMask) {
+                    primaryInfo.cancelBreak()
+                    return@let
+                } else return null
+            }
+
+            if (!primaryInfo.breaking) {
+                secondaryBreak = breakInfo.apply { type = Secondary }
+                return secondaryBreak
+            }
+
+            primaryInfo.stopBreakPacket(world, interaction)
+            primaryInfo.makeSecondary()
+            return@let
+        }
+
+        primaryBreak = breakInfo
+        setPendingConfigs(request.build)
+        return primaryBreak
+    }
+
+    private fun SafeContext.simulateAbandoned() {
+        // Cancelled but double breaking so requires break manager to continue the simulation
+        breakInfos
+            .asSequence()
+            .filterNotNull()
+            .filter { it.abandoned && it.type != RedundantSecondary }
+            .forEach { info ->
+                with (info.request) {
+                    info.context.blockPos
+                        .toStructure(TargetState.Empty)
+                        .toBlueprint()
+                        .simulate(player.eyePos, interact, rotation, inventory, build)
+                        .asSequence()
+                        .filterIsInstance()
+                        .filter { canAccept(it.context) }
+                        .sorted()
+                        .let { sim ->
+                            info.updateInfo(sim.firstOrNull()?.context ?: return@forEach)
+                        }
+                }
+            }
+    }
+
+    private fun checkForCancels() {
+        breakInfos
+            .filterNotNull()
+            .asSequence()
+            .filter { !it.updatedThisTick && tickStage in it.breakConfig.breakStageMask }
+            .forEach { info ->
+                if (info.type == RedundantSecondary && !info.progressedThisTick) {
+                    val cachedState = info.context.cachedState
+                    if (cachedState.isEmpty || cachedState.isAir) {
+                        info.nullify()
+                        return@forEach
+                    }
+                    info.progressedThisTick = true
+                    info.breakingTicks++
+                }
+                else info.cancelBreak()
+            }
+    }
+
+    /**
+     * Begins the post-break logic sequence for the given [info].
+     *
+     * [BreakConfirmationMode.None] Will assume the block has been broken server side, and will only persist
+     * the [info] if the requester has any untriggered callbacks. E.g., if the block has broken, but the item hasn't dropped
+     * and the requester has specified an itemDrop callback.
+     *
+     * [BreakConfirmationMode.BreakThenAwait] Will perform all post-block break actions, such as spawning break particles,
+     * playing sounds, etc. However, it will store the [info] in the pending interaction collections before triggering the
+     * [BreakInfo.internalOnBreak] callback, in case the server rejects the break.
+     *
+     * [BreakConfirmationMode.AwaitThenBreak] Will immediately place the [info] into the pending interaction collections.
+     * Once the server responds, confirming the break, the post-break actions will take place, and the [BreakInfo.internalOnBreak]
+     * callback will be triggered.
+     *
+     * @see destroyBlock
+     * @see startPending
+     */
+    private fun SafeContext.onBlockBreak(info: BreakInfo) {
+        info.request.onStop?.invoke(info.context.blockPos)
+        when (info.breakConfig.breakConfirmation) {
+            BreakConfirmationMode.None -> {
+                destroyBlock(info)
+                info.internalOnBreak()
+                if (!info.callbacksCompleted) {
+                    info.startPending()
+                } else {
+                    RebreakManager.offerRebreak(info)
+                }
+            }
+            BreakConfirmationMode.BreakThenAwait -> {
+                destroyBlock(info)
+                info.startPending()
+            }
+            BreakConfirmationMode.AwaitThenBreak -> {
+                info.startPending()
+            }
+        }
+        breaksThisTick++
+        info.nullify()
+    }
+
+    private fun BreakInfo.updatePreProcessing(player: ClientPlayerEntity, world: BlockView) {
+        shouldProgress = !progressedThisTick
+                && tickStage in breakConfig.breakStageMask
+                && (rotated || type != Primary)
+
+        if (updatedPreProcessingThisTick) return
+        updatedPreProcessingThisTick = true
+
+        couldReBreak = RebreakManager.couldRebreak(this, player, world)
+        shouldSwap = shouldSwap(player, world)
+
+        val cachedState = context.cachedState
+        swapStack = player.inventory.getStack(context.hotbarIndex)
+
+        val breakAmount = cachedState.calcBreakDelta(player, world, context.blockPos, breakConfig, swapStack) * (breakingTicks + 1)
+        val breakAmountNoEfficiency = cachedState.calcBreakDelta(player, world, context.blockPos, breakConfig, swapStack, ignoreEfficiency = true) * (breakingTicks + 1)
+
+        minSwapTicks = if (breakAmount >= getBreakThreshold() || couldReBreak) {
+            val min = if (breakAmountNoEfficiency >= getBreakThreshold()) 0 else 1
+            serverBreakTicks++
+            min
+        } else 0
+    }
+
+    /**
+     * Makes the [BreakInfo] a secondary if not already.
+     */
+    private fun BreakInfo.makeSecondary() {
+        if (secondaryBreak === this) return
+        secondaryBreak = this.apply {
+            type = Secondary
+        }
+        primaryBreak = null
+    }
+
+    /**
+     * Attempts to cancel the break.
+     *
+     * Secondary blocks are monitored by the server, and keep breaking regardless of the clients actions.
+     * This means that the break cannot be completely stopped, instead, it must be monitored as we can't start
+     * more secondary break infos until the previous has broken or its state has turned to air.
+     *
+     * If the user has [BreakConfig.unsafeCancels] enabled, the info is made redundant, and mostly ignored.
+     * If not, the break continues.
+     */
+    private fun BreakInfo.cancelBreak() =
+        runSafe {
+            if (type == RedundantSecondary || abandoned) return@runSafe
+            when (type) {
+                Primary -> {
+                    nullify()
+                    setBreakingTextureStage(player, world, -1)
+                    abortBreakPacket(world, interaction)
+                    request.onCancel?.invoke(context.blockPos)
+                }
+                Secondary -> {
+                    if (breakConfig.unsafeCancels) {
+                        type = RedundantSecondary
+                        setBreakingTextureStage(player, world, -1)
+                        request.onCancel?.invoke(context.blockPos)
+                    } else {
+                        abandoned = true
+                    }
+                }
+                else -> {}
+            }
+        }
+
+    /**
+     * Nullifies the break. If the block is not broken, the [BreakInfo.internalOnCancel] callback gets triggered
+     */
+    private fun BreakInfo.nullify() = type.nullify()
+
+    /**
+     * Nullifies the [BreakInfo] reference in the [breakInfos] array based on the [BreakType]
+     */
+    private fun BreakType.nullify() =
+        when (this) {
+            Primary, Rebreak -> primaryBreak = null
+            else -> secondaryBreak = null
+        }
+
+    /**
+     * A modified version of the vanilla updateBlockBreakingProgress method.
+     *
+     * @return if the update was successful.
+     *
+     * @see net.minecraft.client.network.ClientPlayerInteractionManager.updateBlockBreakingProgress
+     */
+    private fun SafeContext.updateBreakProgress(info: BreakInfo): Boolean {
+        val config = info.breakConfig
+        val ctx = info.context
+
+        info.progressedThisTick = true
+
+        if (!info.breaking) {
+            if (!startBreaking(info)) {
+                info.nullify()
+                info.request.onCancel?.invoke(ctx.blockPos)
+                return false
+            }
+            val swing = config.swing
+            if (swing.isEnabled() && (swing != BreakConfig.SwingMode.End || info.type == Rebreak)) {
+                swingHand(config.swingType, Hand.MAIN_HAND)
+            }
+            return true
+        }
+
+        val hitResult = ctx.result
+
+        if (gamemode.isCreative && world.worldBorder.contains(ctx.blockPos)) {
+            breakCooldown = config.breakDelay
+            lastPosStarted = ctx.blockPos
+            onBlockBreak(info)
+            info.startBreakPacket(world, interaction)
+            if (config.swing.isEnabled()) {
+                swingHand(config.swingType, Hand.MAIN_HAND)
+            }
+            return true
+        }
+
+        val blockState = blockState(ctx.blockPos)
+        if (blockState.isEmpty || blockState.isAir) {
+            info.nullify()
+            info.request.onCancel?.invoke(ctx.blockPos)
+            return false
+        }
+
+        info.breakingTicks++
+        val progress = blockState.calcBreakDelta(
+            player,
+            world,
+            ctx.blockPos,
+            config
+        ) * (info.breakingTicks - config.fudgeFactor)
+
+        val overBreakThreshold = progress >= info.getBreakThreshold()
+
+        if (config.sounds) {
+            if (info.soundsCooldown % 4.0f == 0.0f) {
+                val blockSoundGroup = blockState.soundGroup
+                mc.soundManager.play(
+                    PositionedSoundInstance(
+                        blockSoundGroup.hitSound,
+                        SoundCategory.BLOCKS,
+                        (blockSoundGroup.getVolume() + 1.0f) / 8.0f,
+                        blockSoundGroup.getPitch() * 0.5f,
+                        SoundInstance.createRandom(),
+                        ctx.blockPos
+                    )
+                )
+            }
+            info.soundsCooldown++
+        }
+
+        if (config.particles) {
+            mc.particleManager.addBlockBreakingParticles(ctx.blockPos, hitResult.side)
+        }
+
+        if (config.breakingTexture) {
+            info.setBreakingTextureStage(player, world)
+        }
+
+        val swing = config.swing
+        if (overBreakThreshold && (!swapped || info.serverBreakTicks >= info.breakConfig.fudgeFactor)) {
+            if (info.type == Primary) {
+                onBlockBreak(info)
+                info.stopBreakPacket(world, interaction)
+            } else {
+                onBlockBreak(info)
+            }
+            if (swing.isEnabled() && swing != BreakConfig.SwingMode.Start) swingHand(config.swingType, Hand.MAIN_HAND)
+            breakCooldown = config.breakDelay
+        } else {
+            if (swing == BreakConfig.SwingMode.Constant) swingHand(config.swingType, Hand.MAIN_HAND)
+        }
+
+        return true
+    }
+
+    /**
+     * A modified version of the minecraft attackBlock method.
+     *
+     * @return if the block started breaking successfully.
+     *
+     * @see net.minecraft.client.network.ClientPlayerInteractionManager.attackBlock
+     */
+    private fun SafeContext.startBreaking(info: BreakInfo): Boolean {
+        val ctx = info.context
+
+        if (info.couldReBreak) {
+            when (val rebreakResult = RebreakManager.handleUpdate(info.context, info.request)) {
+                is RebreakResult.StillBreaking -> {
+                    primaryBreak = rebreakResult.breakInfo.apply {
+                        type = Primary
+                        RebreakManager.clearRebreak()
+                        request.onStart?.invoke(ctx.blockPos)
+                    }
+
+                    primaryBreak?.let { primary ->
+                        updateBreakProgress(primary)
+                    }
+                    return true
+                }
+                is RebreakResult.Rebroke -> {
+                    info.type = Rebreak
+                    info.nullify()
+                    info.request.onReBreak?.invoke(ctx.blockPos)
+                    return true
+                }
+                else -> {}
+            }
+        }
+
+        if (player.isBlockBreakingRestricted(world, ctx.blockPos, gamemode)) return false
+        if (!world.worldBorder.contains(ctx.blockPos)) return false
+
+        if (gamemode.isCreative) {
+            lastPosStarted = ctx.blockPos
+            info.request.onStart?.invoke(ctx.blockPos)
+            onBlockBreak(info)
+            info.startBreakPacket(world, interaction)
+            breakCooldown = info.breakConfig.breakDelay
+            return true
+        }
+        if (info.breaking) return false
+        info.request.onStart?.invoke(ctx.blockPos)
+
+        lastPosStarted = ctx.blockPos
+
+        val blockState = blockState(ctx.blockPos)
+        val notEmpty = blockState.isNotEmpty
+        if (notEmpty && info.breakingTicks == 0) {
+            blockState.onBlockBreakStart(world, ctx.blockPos, player)
+        }
+
+        val breakDelta = blockState.calcBreakDelta(player, world, ctx.blockPos, info.breakConfig)
+        info.vanillaInstantBreakable = breakDelta >= 1
+        val serverSwapped = !swapped || info.serverBreakTicks >= info.breakConfig.fudgeFactor
+        if (notEmpty && (breakDelta >= info.getBreakThreshold() && serverSwapped)) {
+            onBlockBreak(info)
+            if (!info.vanillaInstantBreakable) breakCooldown = info.breakConfig.breakDelay
+        } else {
+            info.apply {
+                breaking = true
+                breakingTicks = 1
+                soundsCooldown = 0.0f
+                if (breakConfig.breakingTexture) {
+                    setBreakingTextureStage(player, world)
+                }
+            }
+        }
+
+        if (info.breakConfig.breakMode == BreakMode.Packet) {
+            info.stopBreakPacket(world, interaction)
+        }
+
+        info.startBreakPacket(world, interaction)
+
+        if (info.type == Secondary || (!info.vanillaInstantBreakable && breakDelta >= info.breakConfig.breakThreshold)) {
+            info.stopBreakPacket(world, interaction)
+        }
+
+        return true
+    }
+
+    fun BlockState.calcBreakDelta(
+        player: ClientPlayerEntity,
+        world: BlockView,
+        pos: BlockPos,
+        config: BreakConfig,
+        item: ItemStack? = null,
+        ignoreEfficiency: Boolean = false
+    ) = runSafe {
+        val delta = calcItemBlockBreakingDelta(player, world, pos, item ?: player.mainHandStack, ignoreEfficiency)
+        //ToDo: This setting requires some fixes / improvements in the player movement prediction to work properly. Currently, it's broken
+//        if (config.desyncFix) {
+//            val nextTickPrediction = buildPlayerPrediction().next()
+//            if (player.isOnGround && !nextTickPrediction.onGround) {
+//                delta /= 5.0f
+//            }
+//
+//            val affectedThisTick = player.isSubmergedIn(FluidTags.WATER) && !EnchantmentHelper.hasAquaAffinity(player)
+//            val simulatedPlayer = nextTickPrediction.predictionEntity.player
+//            val affectedNextTick = simulatedPlayer.isSubmergedIn(FluidTags.WATER) && !EnchantmentHelper.hasAquaAffinity(simulatedPlayer)
+//            if (!affectedThisTick && affectedNextTick) {
+//                delta /= 5.0f
+//            }
+//        }
+        delta
+    } ?: 0f
+
+    /**
+     * @return if the [ItemEntity] matches the [BreakInfo]'s expected item drop.
+     */
+    fun matchesBlockItem(info: BreakInfo, entity: ItemEntity): Boolean {
+        val inRange = info.context.blockPos.toCenterPos().isInRange(entity.pos, 0.5)
+        val correctMaterial = info.context.cachedState.block == entity.stack.item.block
+        return inRange && correctMaterial
+    }
+
+    private fun interpolateBox(box: Box, progress: Double, config: BreakConfig): Box {
+        val boxCenter = Box(box.center, box.center)
+        return when (config.animation) {
+            BreakConfig.AnimationMode.Out -> lerp(progress, boxCenter, box)
+            BreakConfig.AnimationMode.In -> lerp(progress, box, boxCenter)
+            BreakConfig.AnimationMode.InOut ->
+                if (progress >= 0.5f) lerp((progress - 0.5) * 2, boxCenter, box)
+                else lerp(progress * 2, box, boxCenter)
+            BreakConfig.AnimationMode.OutIn ->
+                if (progress >= 0.5f) lerp((progress - 0.5) * 2, box, boxCenter)
+                else lerp(progress * 2, boxCenter, box)
+            else -> box
+        }
+    }
+
+    override fun preEvent(): Event = UpdateManagerEvent.Break.post()
+}
diff --git a/src/main/kotlin/com/lambda/interaction/request/breaking/BreakRequest.kt b/src/main/kotlin/com/lambda/interaction/request/breaking/BreakRequest.kt
new file mode 100644
index 000000000..3fe4abfe9
--- /dev/null
+++ b/src/main/kotlin/com/lambda/interaction/request/breaking/BreakRequest.kt
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2025 Lambda
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see .
+ */
+
+package com.lambda.interaction.request.breaking
+
+import com.lambda.config.groups.BuildConfig
+import com.lambda.config.groups.InteractionConfig
+import com.lambda.interaction.construction.context.BreakContext
+import com.lambda.interaction.construction.context.BuildContext
+import com.lambda.interaction.request.Request
+import com.lambda.interaction.request.hotbar.HotbarConfig
+import com.lambda.interaction.request.inventory.InventoryConfig
+import com.lambda.interaction.request.rotating.RotationConfig
+import com.lambda.module.modules.client.TaskFlowModule
+import com.lambda.threading.runSafe
+import com.lambda.util.BlockUtils.blockState
+import com.lambda.util.BlockUtils.isEmpty
+import net.minecraft.entity.ItemEntity
+import net.minecraft.util.math.BlockPos
+
+data class BreakRequest(
+    val contexts: Collection,
+    val pendingInteractions: MutableCollection,
+    val build: BuildConfig = TaskFlowModule.build,
+    val hotbar: HotbarConfig = TaskFlowModule.hotbar,
+    val rotation: RotationConfig = TaskFlowModule.rotation,
+    val inventory: InventoryConfig = TaskFlowModule.inventory,
+    val interact: InteractionConfig = TaskFlowModule.interaction
+) : Request() {
+    override val config = build.breaking
+    var onStart: ((BlockPos) -> Unit)? = null
+    var onUpdate: ((BlockPos) -> Unit)? = null
+    var onStop: ((BlockPos) -> Unit)? = null
+    var onCancel: ((BlockPos) -> Unit)? = null
+    var onItemDrop: ((ItemEntity) -> Unit)? = null
+    var onReBreakStart: ((BlockPos) -> Unit)? = null
+    var onReBreak: ((BlockPos) -> Unit)? = null
+
+    override val done: Boolean
+        get() = runSafe { contexts.all { blockState(it.blockPos).isEmpty } } == true
+
+    override fun submit(queueIfClosed: Boolean) =
+        BreakManager.request(this, queueIfClosed)
+
+    @DslMarker
+    annotation class BreakRequestBuilder
+
+    @BreakRequestBuilder
+    class RequestBuilder(
+        contexts: Collection,
+        pendingInteractions: MutableCollection,
+        rotation: RotationConfig,
+        hotbar: HotbarConfig,
+        interact: InteractionConfig,
+        inventory: InventoryConfig,
+        build: BuildConfig
+    ) {
+        val request = BreakRequest(contexts, pendingInteractions, build, hotbar, rotation, inventory, interact)
+
+        @BreakRequestBuilder
+        fun onStart(callback: (BlockPos) -> Unit) {
+            request.onStart = callback
+        }
+
+        @BreakRequestBuilder
+        fun onUpdate(callback: (BlockPos) -> Unit) {
+            request.onUpdate = callback
+        }
+
+        @BreakRequestBuilder
+        fun onStop(callback: (BlockPos) -> Unit) {
+            request.onStop = callback
+        }
+
+        @BreakRequestBuilder
+        fun onCancel(callback: (BlockPos) -> Unit) {
+            request.onCancel = callback
+        }
+
+        @BreakRequestBuilder
+        fun onItemDrop(callback: (ItemEntity) -> Unit) {
+            request.onItemDrop = callback
+        }
+
+        @BreakRequestBuilder
+        fun onReBreakStart(callback: (BlockPos) -> Unit) {
+            request.onReBreakStart = callback
+        }
+
+        @BreakRequestBuilder
+        fun onReBreak(callback: (BlockPos) -> Unit) {
+            request.onReBreak = callback
+        }
+
+        @BreakRequestBuilder
+        fun build(): BreakRequest = request
+    }
+
+    companion object {
+        @BreakRequestBuilder
+        fun breakRequest(
+            contexts: Collection,
+            pendingInteractions: MutableCollection,
+            rotation: RotationConfig = TaskFlowModule.rotation,
+            hotbar: HotbarConfig = TaskFlowModule.hotbar,
+            interact: InteractionConfig = TaskFlowModule.interaction,
+            inventory: InventoryConfig = TaskFlowModule.inventory,
+            build: BuildConfig = TaskFlowModule.build,
+            builder: RequestBuilder.() -> Unit
+        ) = RequestBuilder(contexts, pendingInteractions, rotation, hotbar, interact, inventory, build).apply(builder).build()
+    }
+}
diff --git a/src/main/kotlin/com/lambda/interaction/request/breaking/BrokenBlockHandler.kt b/src/main/kotlin/com/lambda/interaction/request/breaking/BrokenBlockHandler.kt
new file mode 100644
index 000000000..ac672e111
--- /dev/null
+++ b/src/main/kotlin/com/lambda/interaction/request/breaking/BrokenBlockHandler.kt
@@ -0,0 +1,164 @@
+/*
+ * Copyright 2025 Lambda
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see .
+ */
+
+package com.lambda.interaction.request.breaking
+
+import com.lambda.context.SafeContext
+import com.lambda.event.events.EntityEvent
+import com.lambda.event.events.WorldEvent
+import com.lambda.event.listener.SafeListener.Companion.listen
+import com.lambda.interaction.construction.processing.ProcessorRegistry
+import com.lambda.interaction.request.PostActionHandler
+import com.lambda.interaction.request.breaking.BreakConfig.BreakConfirmationMode
+import com.lambda.interaction.request.breaking.BreakManager.lastPosStarted
+import com.lambda.interaction.request.breaking.BreakManager.matchesBlockItem
+import com.lambda.interaction.request.breaking.RebreakManager.rebreak
+import com.lambda.module.modules.client.TaskFlowModule
+import com.lambda.threading.runSafe
+import com.lambda.util.BlockUtils.emptyState
+import com.lambda.util.BlockUtils.fluidState
+import com.lambda.util.BlockUtils.isEmpty
+import com.lambda.util.BlockUtils.isNotBroken
+import com.lambda.util.BlockUtils.matches
+import com.lambda.util.Communication.warn
+import com.lambda.util.collections.LimitedDecayQueue
+import com.lambda.util.player.gamemode
+import net.minecraft.block.OperatorBlock
+import net.minecraft.entity.ItemEntity
+import net.minecraft.util.math.ChunkSectionPos
+
+/**
+ * This object is designed to handle blocks that have been broken client side, yet are awaiting
+ * confirmation from the server, and / or an item drop.
+ *
+ * @see BreakManager
+ */
+object BrokenBlockHandler : PostActionHandler() {
+    override val pendingActions = LimitedDecayQueue(
+        TaskFlowModule.build.maxPendingInteractions, TaskFlowModule.build.interactionTimeout * 50L
+    ) { info ->
+        runSafe {
+            val pos = info.context.blockPos
+            val loaded = world.isChunkLoaded(ChunkSectionPos.getSectionCoord(pos.x), ChunkSectionPos.getSectionCoord(pos.z))
+            if (!loaded) return@runSafe
+
+            if (!info.broken) {
+                warn("${info.type} ${info::class.simpleName} at ${info.context.blockPos.toShortString()} timed out with cached state ${info.context.cachedState}")
+            } else if (!TaskFlowModule.ignoreItemDropWarnings) {
+                warn("${info.type} ${info::class.simpleName}'s item drop at ${info.context.blockPos.toShortString()} timed out")
+            }
+
+            if (!info.broken && info.breakConfig.breakConfirmation != BreakConfirmationMode.AwaitThenBreak) {
+                world.setBlockState(info.context.blockPos, info.context.cachedState)
+            }
+        }
+        info.request.onCancel?.invoke(info.context.blockPos)
+        info.pendingInteractionsList.remove(info.context)
+    }
+
+    init {
+        listen(priority = Int.MIN_VALUE) { event ->
+            run {
+                pendingActions.firstOrNull { it.context.blockPos == event.pos }
+                    ?: if (rebreak?.context?.blockPos == event.pos) rebreak
+                    else null
+            }?.let { pending ->
+                val currentState = pending.context.cachedState
+                // return if the block's not broken
+                if (isNotBroken(currentState, event.newState)) {
+                    // return if the state hasn't changed
+                    if (event.newState.matches(currentState, ProcessorRegistry.postProcessedProperties)) {
+                        pending.context.cachedState = event.newState
+                        return@listen
+                    }
+
+                    if (pending.type == BreakInfo.BreakType.Rebreak) {
+                        pending.context.cachedState = event.newState
+                    } else {
+                        this@BrokenBlockHandler.warn("Broken block at ${event.pos.toShortString()} was rejected with ${event.newState} instead of ${pending.context.cachedState.emptyState}")
+                        pending.stopPending()
+                    }
+                    return@listen
+                }
+
+                if (pending.breakConfig.breakConfirmation == BreakConfirmationMode.AwaitThenBreak
+                    || (pending.type == BreakInfo.BreakType.Rebreak && !pending.breakConfig.rebreak)
+                    ) {
+                    destroyBlock(pending)
+                }
+                pending.internalOnBreak()
+                if (pending.callbacksCompleted) {
+                    pending.stopPending()
+                    if (lastPosStarted == pending.context.blockPos) {
+                        RebreakManager.offerRebreak(pending)
+                    }
+                }
+                return@listen
+            }
+        }
+
+        listen(priority = Int.MIN_VALUE) {
+            if (it.entity !is ItemEntity) return@listen
+            run {
+                pendingActions.firstOrNull { info -> matchesBlockItem(info, it.entity) }
+                    ?: rebreak?.let { info ->
+                        return@run if (matchesBlockItem(info, it.entity)) info
+                        else null
+                    }
+            }?.let { pending ->
+                pending.internalOnItemDrop(it.entity)
+                if (pending.callbacksCompleted) {
+                    pending.stopPending()
+                    if (lastPosStarted == pending.context.blockPos) {
+                        RebreakManager.offerRebreak(pending)
+                    }
+                }
+                return@listen
+            }
+        }
+    }
+
+    /**
+     * A modified version of the minecraft breakBlock method.
+     *
+     * Performs the actions required to display break particles, sounds, texture overlay, etc.
+     * based on the users settings.
+     *
+     * @return if the blocks state was set or not.
+     *
+     * @see net.minecraft.client.world.ClientWorld.breakBlock
+     */
+    fun SafeContext.destroyBlock(info: BreakInfo): Boolean {
+        val ctx = info.context
+
+        if (player.isBlockBreakingRestricted(world, ctx.blockPos, gamemode)) return false
+        if (!player.mainHandStack.canMine(ctx.cachedState, world, ctx.blockPos, player)) return false
+
+        val block = ctx.cachedState.block
+        if (block is OperatorBlock && !player.isCreativeLevelTwoOp) return false
+        if (ctx.cachedState.isEmpty) return false
+
+        block.onBreak(world, ctx.blockPos, ctx.cachedState, player)
+        val fluidState = fluidState(ctx.blockPos)
+        val setState = world.setBlockState(ctx.blockPos, fluidState.blockState, 11)
+        if (setState) block.onBroken(world, ctx.blockPos, ctx.cachedState)
+
+        if (info.breakConfig.breakingTexture) info.setBreakingTextureStage(player, world, -1)
+
+        return setState
+    }
+}
diff --git a/src/main/kotlin/com/lambda/interaction/request/breaking/RebreakManager.kt b/src/main/kotlin/com/lambda/interaction/request/breaking/RebreakManager.kt
new file mode 100644
index 000000000..bd32b6104
--- /dev/null
+++ b/src/main/kotlin/com/lambda/interaction/request/breaking/RebreakManager.kt
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2025 Lambda
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see .
+ */
+
+package com.lambda.interaction.request.breaking
+
+import com.lambda.event.events.ConnectionEvent
+import com.lambda.event.events.TickEvent
+import com.lambda.event.listener.SafeListener.Companion.listen
+import com.lambda.event.listener.UnsafeListener.Companion.listenUnsafe
+import com.lambda.interaction.construction.context.BreakContext
+import com.lambda.interaction.request.breaking.BreakManager.calcBreakDelta
+import com.lambda.interaction.request.breaking.BrokenBlockHandler.destroyBlock
+import com.lambda.threading.runSafe
+import com.lambda.util.BlockUtils.calcItemBlockBreakingDelta
+import com.lambda.util.player.swingHand
+import net.minecraft.client.network.ClientPlayerEntity
+import net.minecraft.util.Hand
+import net.minecraft.world.BlockView
+
+object RebreakManager {
+    var rebreak: BreakInfo? = null
+
+    init {
+        listen(priority = Int.MIN_VALUE) {
+            rebreak?.run {
+                if (!progressedThisTick) {
+                    breakingTicks++
+                    progressedThisTick = true
+                }
+            }
+        }
+
+        listenUnsafe(priority = Int.MIN_VALUE) {
+            rebreak = null
+        }
+    }
+
+    fun offerRebreak(info: BreakInfo) {
+        if (!info.rebreakable) return
+
+        rebreak = info.apply {
+            type = BreakInfo.BreakType.Rebreak
+            breaking = true
+            resetCallbacks()
+        }
+        info.request.onReBreakStart?.invoke(info.context.blockPos)
+    }
+
+    fun clearRebreak() {
+        rebreak = null
+    }
+
+    fun couldRebreak(info: BreakInfo, player: ClientPlayerEntity, world: BlockView) =
+        rebreak?.let { reBreak ->
+            val stack = if (info.breakConfig.swapMode.isEnabled())
+                player.inventory.getStack(info.context.hotbarIndex)
+            else player.mainHandStack
+            val breakDelta = info.context.cachedState.calcItemBlockBreakingDelta(player, world, info.context.blockPos, stack)
+            reBreak.breakConfig.rebreak &&
+                    info.context.blockPos == reBreak.context.blockPos &&
+                    !reBreak.updatedThisTick &&
+                    ((reBreak.breakingTicks - info.breakConfig.fudgeFactor) * breakDelta >= info.breakConfig.breakThreshold)
+        } == true
+
+    fun handleUpdate(ctx: BreakContext, breakRequest: BreakRequest) =
+        runSafe {
+            val reBreak = this@RebreakManager.rebreak ?: return@runSafe RebreakResult.Ignored
+
+            reBreak.updateInfo(ctx, breakRequest)
+
+            val context = reBreak.context
+            val breakDelta = context.cachedState.calcBreakDelta(player, world, context.blockPos, reBreak.breakConfig)
+            return@runSafe if ((reBreak.breakingTicks - reBreak.breakConfig.fudgeFactor) * breakDelta >= reBreak.breakConfig.breakThreshold) {
+                if (reBreak.breakConfig.breakConfirmation != BreakConfig.BreakConfirmationMode.AwaitThenBreak) {
+                    destroyBlock(reBreak)
+                }
+                reBreak.stopBreakPacket(world, interaction)
+                if (reBreak.breakConfig.swing.isEnabled()) {
+                    swingHand(reBreak.breakConfig.swingType, Hand.MAIN_HAND)
+                }
+                BreakManager.breaksThisTick++
+                RebreakResult.Rebroke
+            } else {
+                RebreakResult.StillBreaking(reBreak)
+            }
+        }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/lambda/interaction/request/breaking/RebreakResult.kt b/src/main/kotlin/com/lambda/interaction/request/breaking/RebreakResult.kt
new file mode 100644
index 000000000..7e1a00522
--- /dev/null
+++ b/src/main/kotlin/com/lambda/interaction/request/breaking/RebreakResult.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2025 Lambda
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see .
+ */
+
+package com.lambda.interaction.request.breaking
+
+sealed class RebreakResult {
+    data object Ignored : RebreakResult()
+
+    data object Rebroke : RebreakResult()
+
+    class StillBreaking(
+        val breakInfo: BreakInfo
+    ) : RebreakResult()
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/lambda/interaction/request/hotbar/HotbarConfig.kt b/src/main/kotlin/com/lambda/interaction/request/hotbar/HotbarConfig.kt
index 140ec6d0f..43a2c806d 100644
--- a/src/main/kotlin/com/lambda/interaction/request/hotbar/HotbarConfig.kt
+++ b/src/main/kotlin/com/lambda/interaction/request/hotbar/HotbarConfig.kt
@@ -17,36 +17,42 @@
 
 package com.lambda.interaction.request.hotbar
 
-import com.lambda.interaction.request.Priority
+import com.lambda.event.Event
 import com.lambda.interaction.request.RequestConfig
 
-/*
+/**
  * Abstract base class for configuring hotbar slot switch behavior.
  *
  * @param priority The priority of this configuration.
  */
-abstract class HotbarConfig(
-    priority: Priority
-) : RequestConfig(priority) {
+interface HotbarConfig : RequestConfig {
 
     /**
      * The number of ticks to keep the current hotbar selection active.
      */
-    abstract val keepTicks: Int
+    val keepTicks: Int
+
+    /**
+     * The delay, in ticks, between swapping hotbar selections
+     */
+    val swapDelay: Int
+
+    /**
+     * The amount of hotbar selection swaps that can happen per tick
+     *
+     * Only makes a difference if swapDelay is set to 0
+     */
+    val swapsPerTick: Int
 
     /**
      * The delay in ticks to pause actions after switching to the slot.
      *
      * Affects the validity state of the request
      */
-    abstract var switchPause: Int
+    val swapPause: Int
 
     /**
-     * Registers a hotbar request with the HotbarManager.
-     *
-     * @param request The hotbar request to register.
+     * The sub-tick timings at which hotbar actions can be performed
      */
-    override fun requestInternal(request: HotbarRequest) {
-        HotbarManager.registerRequest(this, request)
-    }
+    val sequenceStageMask: Set
 }
\ No newline at end of file
diff --git a/src/main/kotlin/com/lambda/interaction/request/hotbar/HotbarManager.kt b/src/main/kotlin/com/lambda/interaction/request/hotbar/HotbarManager.kt
index ed8419418..4264d1d5a 100644
--- a/src/main/kotlin/com/lambda/interaction/request/hotbar/HotbarManager.kt
+++ b/src/main/kotlin/com/lambda/interaction/request/hotbar/HotbarManager.kt
@@ -17,38 +17,95 @@
 
 package com.lambda.interaction.request.hotbar
 
-import com.lambda.core.Loadable
-import com.lambda.event.events.InventoryEvent
+import com.lambda.context.SafeContext
+import com.lambda.event.Event
+import com.lambda.event.EventFlow.post
 import com.lambda.event.events.TickEvent
+import com.lambda.event.events.UpdateManagerEvent
 import com.lambda.event.listener.SafeListener.Companion.listen
 import com.lambda.interaction.request.RequestHandler
+import com.lambda.interaction.request.hotbar.HotbarManager.checkResetSwap
 import com.lambda.threading.runSafe
 
-object HotbarManager : RequestHandler(), Loadable {
+object HotbarManager : RequestHandler(
+    1,
+    TickEvent.Pre,
+    TickEvent.Input.Pre,
+    TickEvent.Input.Post,
+    TickEvent.Player.Post,
+    onClose = { checkResetSwap() }
+) {
     val serverSlot get() = runSafe {
         interaction.lastSelectedSlot
-    } ?: -1
+    } ?: 0
 
-    override fun load() = "Loaded Hotbar Manager"
+    private var swapsThisTick = 0
+    private var maxSwapsThisTick = 0
+    private var swapDelay = 0
 
-    init {
-        listen {
-            it.slot = currentRequest?.slot ?: return@listen
-        }
+    var activeRequest: HotbarRequest? = null
+
+    override fun load(): String {
+        super.load()
 
-        listen {
-            updateRequest()
+        listen(priority = Int.MIN_VALUE) {
+            swapsThisTick = 0
+            if (swapDelay > 0) swapDelay--
+            val activeInfo = activeRequest ?: return@listen
+
+            activeInfo.swapPauseAge++
+            activeInfo.activeRequestAge++
+            activeInfo.keepTicks--
         }
 
-        listen {
-            val request = currentRequest ?: return@listen
+        return "Loaded Hotbar Manager"
+    }
+
+    override fun SafeContext.handleRequest(request: HotbarRequest) {
+        maxSwapsThisTick = request.swapsPerTick
+        swapDelay = swapDelay.coerceAtMost(request.swapDelay)
+
+        if (tickStage !in request.sequenceStageMask) return
+
+        val sameButLonger = activeRequest?.let { active ->
+            request.slot == active.slot && request.keepTicks >= active.keepTicks
+        } == true
+
+        if (sameButLonger) activeRequest?.let { current ->
+            request.swapPauseAge = current.swapPauseAge
+        } else run swap@ {
+            if (request.slot != activeRequest?.slot) {
+                if (swapsThisTick + 1 > maxSwapsThisTick || swapDelay > 0) return
 
-            request.keepTicks--
-            request.switchPause--
+                activeRequest?.let { current ->
+                    if (current.swappedThisTick && current.keeping) return
+                }
 
-            if (request.keepTicks <= 0) {
-                currentRequest = null
+                swapsThisTick++
+                swapDelay = request.swapDelay
+                return@swap
+            }
+
+            activeRequest?.let { current ->
+                request.swapPauseAge = current.swapPauseAge
+                if (current.swappedThisTick && current.keeping) return
+            }
+        }
+
+        activeRequest = request
+        interaction.syncSelectedSlot()
+        return
+    }
+
+    private fun SafeContext.checkResetSwap() {
+        activeRequest?.let { active ->
+            val canStopSwap = swapsThisTick < maxSwapsThisTick
+            if (active.keepTicks <= 0 && tickStage in active.sequenceStageMask && canStopSwap) {
+                activeRequest = null
+                interaction.syncSelectedSlot()
             }
         }
     }
-}
\ No newline at end of file
+
+    override fun preEvent(): Event = UpdateManagerEvent.Hotbar.post()
+}
diff --git a/src/main/kotlin/com/lambda/interaction/request/hotbar/HotbarRequest.kt b/src/main/kotlin/com/lambda/interaction/request/hotbar/HotbarRequest.kt
index dc297aeee..6ef88613a 100644
--- a/src/main/kotlin/com/lambda/interaction/request/hotbar/HotbarRequest.kt
+++ b/src/main/kotlin/com/lambda/interaction/request/hotbar/HotbarRequest.kt
@@ -17,17 +17,24 @@
 
 package com.lambda.interaction.request.hotbar
 
-import com.lambda.interaction.request.Priority
 import com.lambda.interaction.request.Request
 
 class HotbarRequest(
     val slot: Int,
-    priority: Priority,
-    var keepTicks: Int = 3,
-    var switchPause: Int = 0,
-) : Request(priority) {
-    override val done: Boolean get() =
-        // The request has to be valid at least for 1 tick
-        // (if for some dumb reason the switch pause is bigger than the decay time)
-        HotbarManager.serverSlot == slot && (switchPause <= 0 || keepTicks <= 0)
-}
\ No newline at end of file
+    override val config: HotbarConfig,
+    override var keepTicks: Int = config.keepTicks,
+    override var swapPause: Int = config.swapPause
+) : Request(), HotbarConfig by config {
+    var activeRequestAge = 0
+    var swapPauseAge = 0
+
+    val swapPaused get() = swapPauseAge < swapPause
+    val swappedThisTick get() = activeRequestAge <= 0
+    val keeping get() = keepTicks > 0
+
+    override val done: Boolean
+        get() = slot == HotbarManager.serverSlot && !swapPaused
+
+    override fun submit(queueIfClosed: Boolean) =
+        HotbarManager.request(this, queueIfClosed)
+}
diff --git a/src/main/kotlin/com/lambda/interaction/request/interacting/InteractConfig.kt b/src/main/kotlin/com/lambda/interaction/request/interacting/InteractConfig.kt
new file mode 100644
index 000000000..249733bb0
--- /dev/null
+++ b/src/main/kotlin/com/lambda/interaction/request/interacting/InteractConfig.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2025 Lambda
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see .
+ */
+
+package com.lambda.interaction.request.interacting
+
+import com.lambda.config.groups.BuildConfig
+import com.lambda.config.groups.InteractionConfig
+import com.lambda.interaction.request.RequestConfig
+
+interface InteractConfig : RequestConfig {
+    val rotate: Boolean
+    val swingHand: Boolean
+    val interactSwingType: BuildConfig.SwingType
+    val interactConfirmationMode: InteractionConfig.InteractConfirmationMode
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/lambda/interaction/request/interacting/InteractRequest.kt b/src/main/kotlin/com/lambda/interaction/request/interacting/InteractRequest.kt
new file mode 100644
index 000000000..d544c1aa9
--- /dev/null
+++ b/src/main/kotlin/com/lambda/interaction/request/interacting/InteractRequest.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2025 Lambda
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see .
+ */
+
+package com.lambda.interaction.request.interacting
+
+import com.lambda.Lambda.mc
+import com.lambda.config.groups.BuildConfig
+import com.lambda.interaction.construction.context.BuildContext
+import com.lambda.interaction.construction.context.InteractionContext
+import com.lambda.interaction.request.Request
+import com.lambda.interaction.request.hotbar.HotbarConfig
+import com.lambda.interaction.request.rotating.RotationConfig
+import com.lambda.util.BlockUtils.matches
+import net.minecraft.util.math.BlockPos
+
+data class InteractRequest(
+    val contexts: Collection,
+    val onInteract: ((BlockPos) -> Unit)?,
+    val pendingInteractionsList: MutableCollection,
+    override val config: InteractConfig,
+    val build: BuildConfig,
+    val hotbar: HotbarConfig,
+    val rotation: RotationConfig
+) : Request(), InteractConfig by config {
+    override val done: Boolean
+        get() = contexts.all { mc.world?.getBlockState(it.blockPos)?.matches(it.expectedState) == true }
+
+    override fun submit(queueIfClosed: Boolean) =
+        InteractionManager.request(this, queueIfClosed)
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/lambda/interaction/request/interacting/InteractedBlockHandler.kt b/src/main/kotlin/com/lambda/interaction/request/interacting/InteractedBlockHandler.kt
new file mode 100644
index 000000000..9733db974
--- /dev/null
+++ b/src/main/kotlin/com/lambda/interaction/request/interacting/InteractedBlockHandler.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2025 Lambda
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see .
+ */
+
+package com.lambda.interaction.request.interacting
+
+import com.lambda.Lambda.mc
+import com.lambda.config.groups.InteractionConfig
+import com.lambda.event.events.WorldEvent
+import com.lambda.event.listener.SafeListener.Companion.listen
+import com.lambda.interaction.construction.processing.ProcessorRegistry
+import com.lambda.interaction.request.PostActionHandler
+import com.lambda.module.modules.client.TaskFlowModule
+import com.lambda.util.BlockUtils.matches
+import com.lambda.util.Communication.info
+import com.lambda.util.Communication.warn
+import com.lambda.util.collections.LimitedDecayQueue
+
+object InteractedBlockHandler : PostActionHandler() {
+    override val pendingActions = LimitedDecayQueue(
+        TaskFlowModule.build.maxPendingInteractions, TaskFlowModule.build.interactionTimeout * 50L
+    ) {
+        info("${it::class.simpleName} at ${it.context.blockPos.toShortString()} timed out")
+        if (it.interactConfirmationMode != InteractionConfig.InteractConfirmationMode.AwaitThenInteract) {
+            mc.world?.setBlockState(it.context.blockPos, it.context.cachedState)
+        }
+        it.pendingInteractionsList.remove(it.context)
+    }
+
+    init {
+        listen(priority = Int.MIN_VALUE) { event ->
+            pendingActions
+                .firstOrNull { it.context.blockPos == event.pos }
+                ?.let { pending ->
+                    if (!pending.context.expectedState.matches(event.newState)) {
+                        if (pending.context.cachedState.matches(event.newState, ProcessorRegistry.postProcessedProperties)) {
+                            pending.context.cachedState = event.newState
+                            return@listen
+                        }
+
+                        pending.stopPending()
+
+                        this@InteractedBlockHandler.warn("Interacted block at ${event.pos.toShortString()} was rejected with ${event.newState} instead of ${pending.context.expectedState}")
+                        return@listen
+                    }
+
+                    pending.stopPending()
+
+                    //ToDo: reliable way to recreate the sounds played when interacting with any given block
+//                    if (pending.interactConfirmationMode == InteractionConfig.InteractConfirmationMode.AwaitThenInteract)
+//                        with (pending.context) {
+//                            cachedState.onUse(world, player, Hand.MAIN_HAND, result)
+//                        }
+                }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/lambda/interaction/request/interacting/InteractionInfo.kt b/src/main/kotlin/com/lambda/interaction/request/interacting/InteractionInfo.kt
new file mode 100644
index 000000000..da14884a7
--- /dev/null
+++ b/src/main/kotlin/com/lambda/interaction/request/interacting/InteractionInfo.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2025 Lambda
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see .
+ */
+
+package com.lambda.interaction.request.interacting
+
+import com.lambda.interaction.construction.context.BuildContext
+import com.lambda.interaction.construction.context.InteractionContext
+import com.lambda.interaction.request.ActionInfo
+
+data class InteractionInfo(
+    override val context: InteractionContext,
+    override val pendingInteractionsList: MutableCollection,
+    private val config: InteractConfig
+) : ActionInfo, InteractConfig by config
\ No newline at end of file
diff --git a/src/main/kotlin/com/lambda/interaction/request/interacting/InteractionManager.kt b/src/main/kotlin/com/lambda/interaction/request/interacting/InteractionManager.kt
new file mode 100644
index 000000000..0b5a418c3
--- /dev/null
+++ b/src/main/kotlin/com/lambda/interaction/request/interacting/InteractionManager.kt
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2025 Lambda
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see .
+ */
+
+package com.lambda.interaction.request.interacting
+
+import com.lambda.config.groups.InteractionConfig
+import com.lambda.context.SafeContext
+import com.lambda.event.EventFlow.post
+import com.lambda.event.events.MovementEvent
+import com.lambda.event.events.TickEvent
+import com.lambda.event.events.UpdateManagerEvent
+import com.lambda.event.listener.SafeListener.Companion.listen
+import com.lambda.interaction.construction.context.InteractionContext
+import com.lambda.interaction.request.ManagerUtils.isPosBlocked
+import com.lambda.interaction.request.PositionBlocking
+import com.lambda.interaction.request.RequestHandler
+import com.lambda.interaction.request.breaking.BreakManager
+import com.lambda.interaction.request.interacting.InteractedBlockHandler.pendingActions
+import com.lambda.interaction.request.interacting.InteractedBlockHandler.setPendingConfigs
+import com.lambda.interaction.request.interacting.InteractedBlockHandler.startPending
+import com.lambda.interaction.request.interacting.InteractionManager.activeRequest
+import com.lambda.interaction.request.interacting.InteractionManager.processRequest
+import com.lambda.interaction.request.placing.PlaceManager
+import com.lambda.util.player.MovementUtils.sneaking
+import com.lambda.util.player.swingHand
+import net.minecraft.network.packet.c2s.play.PlayerInteractBlockC2SPacket
+import net.minecraft.util.Hand
+
+object InteractionManager : RequestHandler(
+    0,
+    TickEvent.Pre,
+    TickEvent.Input.Pre,
+    TickEvent.Input.Post,
+    TickEvent.Player.Post,
+    onOpen = { activeRequest?.let { processRequest(it) } }
+), PositionBlocking {
+    private var activeRequest: InteractRequest? = null
+    private var potentialInteractions = mutableListOf()
+
+    private var interactionsThisTick = 0
+    private var maxInteractionsThisTick = 0
+
+    override val blockedPositions
+        get() = pendingActions.map { it.context.blockPos }
+
+    override fun load(): String {
+        super.load()
+
+        listen(priority = Int.MIN_VALUE) {
+            activeRequest = null
+            interactionsThisTick = 0
+            potentialInteractions.clear()
+        }
+
+        listen(priority = Int.MIN_VALUE) {
+            if (potentialInteractions.isNotEmpty()) {
+                it.input.sneaking = false
+            }
+        }
+
+        return "Loaded Interaction Manager"
+    }
+
+    override fun SafeContext.handleRequest(request: InteractRequest) {
+        if (activeRequest != null || BreakManager.activeThisTick || PlaceManager.activeThisTick) return
+
+        activeRequest = request
+        processRequest(request)
+        if (interactionsThisTick > 0) activeThisTick = true
+    }
+
+    fun SafeContext.processRequest(request: InteractRequest) {
+        if (request.fresh) populateFrom(request)
+
+        if (player.isSneaking) return
+
+        val iterator = potentialInteractions.iterator()
+        while (iterator.hasNext()) {
+            if (interactionsThisTick + 1 > maxInteractionsThisTick) break
+            val ctx = iterator.next()
+
+            if (!ctx.requestDependencies(request)) return
+
+            if (request.interactConfirmationMode != InteractionConfig.InteractConfirmationMode.None) {
+                InteractionInfo(ctx, request.pendingInteractionsList, request).startPending()
+            }
+            if (request.interactConfirmationMode != InteractionConfig.InteractConfirmationMode.AwaitThenInteract) {
+                interaction.interactBlock(player, Hand.MAIN_HAND, ctx.result)
+            } else {
+                interaction.sendSequencedPacket(world) { sequence ->
+                    PlayerInteractBlockC2SPacket(Hand.MAIN_HAND, ctx.result, sequence)
+                }
+            }
+            if (request.swingHand) {
+                swingHand(request.interactSwingType, Hand.MAIN_HAND)
+            }
+            request.onInteract?.invoke(ctx.blockPos)
+            interactionsThisTick++
+            iterator.remove()
+        }
+    }
+
+    private fun populateFrom(request: InteractRequest) {
+        setPendingConfigs(request.build)
+        potentialInteractions = request.contexts
+            .filter { !isPosBlocked(it.blockPos) }
+            .toMutableList()
+
+        val pendingLimit =  (request.build.maxPendingInteractions - pendingActions.size).coerceAtLeast(0)
+        maxInteractionsThisTick = (request.build.interactionsPerTick.coerceAtMost(pendingLimit))
+    }
+
+    override fun preEvent() = UpdateManagerEvent.Interact.post()
+}
diff --git a/src/main/kotlin/com/lambda/config/groups/InventoryConfig.kt b/src/main/kotlin/com/lambda/interaction/request/inventory/InventoryConfig.kt
similarity index 57%
rename from src/main/kotlin/com/lambda/config/groups/InventoryConfig.kt
rename to src/main/kotlin/com/lambda/interaction/request/inventory/InventoryConfig.kt
index b2c7070ba..ede85e3d5 100644
--- a/src/main/kotlin/com/lambda/config/groups/InventoryConfig.kt
+++ b/src/main/kotlin/com/lambda/interaction/request/inventory/InventoryConfig.kt
@@ -15,24 +15,45 @@
  * along with this program.  If not, see .
  */
 
-package com.lambda.config.groups
+package com.lambda.interaction.request.inventory
 
+import com.lambda.interaction.material.ContainerSelection
 import com.lambda.interaction.material.StackSelection
 import com.lambda.interaction.material.container.MaterialContainer
+import com.lambda.interaction.request.RequestConfig
+import com.lambda.util.Describable
+import com.lambda.util.NamedEnum
 import net.minecraft.block.Block
 
-interface InventoryConfig {
+interface InventoryConfig : RequestConfig {
     val disposables: Set
-    val accessEnderChest: Boolean
-
     val swapWithDisposables: Boolean
-
     val providerPriority: Priority
     val storePriority: Priority
 
-    enum class Priority {
-        WithMinItems,
-        WithMaxItems;
+    val accessShulkerBoxes: Boolean
+    val accessEnderChest: Boolean
+    val accessChests: Boolean
+    val accessStashes: Boolean
+
+    val containerSelection: ContainerSelection
+        get() = ContainerSelection.selectContainer {
+            val allowedContainers = mutableSetOf().apply {
+                addAll(MaterialContainer.Rank.entries)
+                if (!accessShulkerBoxes) remove(MaterialContainer.Rank.SHULKER_BOX)
+                if (!accessEnderChest) remove(MaterialContainer.Rank.ENDER_CHEST)
+                if (!accessChests) remove(MaterialContainer.Rank.CHEST)
+                if (!accessStashes) remove(MaterialContainer.Rank.STASH)
+            }
+            ofAnyType(*allowedContainers.toTypedArray())
+        }
+
+    enum class Priority(
+        override val displayName: String,
+        override val description: String
+    ) : NamedEnum, Describable {
+        WithMinItems("With Min Items", "Pick containers with the fewest matching items (or least space) first; useful for topping off or clearing leftovers."),
+        WithMaxItems("With Max Items", "Pick containers with the most matching items (or most space) first; ideal for bulk moves with fewer transfers.");
 
         fun materialComparator(selection: StackSelection) =
             when (this) {
diff --git a/src/main/kotlin/com/lambda/interaction/request/inventory/InventoryManager.kt b/src/main/kotlin/com/lambda/interaction/request/inventory/InventoryManager.kt
new file mode 100644
index 000000000..4c86403e9
--- /dev/null
+++ b/src/main/kotlin/com/lambda/interaction/request/inventory/InventoryManager.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2025 Lambda
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see .
+ */
+
+package com.lambda.interaction.request.inventory
+
+import com.lambda.context.SafeContext
+import com.lambda.event.EventFlow.post
+import com.lambda.event.events.TickEvent
+import com.lambda.event.events.UpdateManagerEvent
+
+import com.lambda.interaction.request.RequestHandler
+import com.lambda.interaction.request.inventory.InventoryManager.activeRequest
+import com.lambda.interaction.request.inventory.InventoryManager.processRequest
+
+object InventoryManager : RequestHandler(
+    1,
+    TickEvent.Pre,
+    TickEvent.Input.Pre,
+    TickEvent.Input.Post,
+    TickEvent.Player.Post,
+    onOpen = { activeRequest?.let { processRequest(it) } }
+) {
+    var activeRequest: InventoryRequest? = null
+
+    override fun load(): String {
+        super.load()
+
+        return "Loaded Inventory Manager"
+    }
+
+    override fun SafeContext.handleRequest(request: InventoryRequest) {
+        TODO("Not yet implemented")
+    }
+
+    fun SafeContext.processRequest(request: InventoryRequest) {
+
+    }
+
+    override fun preEvent() = UpdateManagerEvent.Inventory.post()
+}
diff --git a/src/main/kotlin/com/lambda/interaction/request/inventory/InventoryRequest.kt b/src/main/kotlin/com/lambda/interaction/request/inventory/InventoryRequest.kt
new file mode 100644
index 000000000..9b073b726
--- /dev/null
+++ b/src/main/kotlin/com/lambda/interaction/request/inventory/InventoryRequest.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2025 Lambda
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see .
+ */
+
+package com.lambda.interaction.request.inventory
+
+import com.lambda.interaction.request.Request
+
+class InventoryRequest(
+    override val config: InventoryConfig
+) : Request(), InventoryConfig by config {
+    override val done: Boolean
+        get() = TODO("Not yet implemented")
+
+    override fun submit(queueIfClosed: Boolean) =
+        InventoryManager.request(this, queueIfClosed)
+}
diff --git a/src/main/kotlin/com/lambda/interaction/request/placing/PlaceConfig.kt b/src/main/kotlin/com/lambda/interaction/request/placing/PlaceConfig.kt
new file mode 100644
index 000000000..5240c2335
--- /dev/null
+++ b/src/main/kotlin/com/lambda/interaction/request/placing/PlaceConfig.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2025 Lambda
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see .
+ */
+
+package com.lambda.interaction.request.placing
+
+import com.lambda.config.groups.BuildConfig
+import com.lambda.event.Event
+import com.lambda.interaction.request.RequestConfig
+import com.lambda.util.Describable
+import com.lambda.util.NamedEnum
+
+interface PlaceConfig : RequestConfig {
+    val rotateForPlace: Boolean
+    val airPlace: AirPlaceMode
+    val axisRotateSetting: Boolean
+    val axisRotate
+        get() = rotateForPlace && airPlace.isEnabled() && axisRotateSetting
+    val placeStageMask: Set
+    val placeConfirmationMode: PlaceConfirmationMode
+    val maxPendingPlacements: Int
+    val placementsPerTick: Int
+    val swing: Boolean
+    val swingType: BuildConfig.SwingType
+    val sounds: Boolean
+
+    enum class AirPlaceMode(
+        override val displayName: String,
+        override val description: String
+    ) : NamedEnum, Describable {
+        None("None", "Do not attempt air placements; only place against valid supports."),
+        Standard("Standard", "Try common air-place techniques for convenience; moderate compatibility."),
+        Grim("Grim", "Use grim specific air placing.")
+        ;
+
+        fun isEnabled() = this != None
+    }
+
+    enum class PlaceConfirmationMode(
+        override val displayName: String,
+        override val description: String
+    ) : NamedEnum, Describable {
+        None("No confirmation", "Place immediately without waiting for the server; lowest latency, possible brief desync."),
+        PlaceThenAwait("Place now, confirm later", "Show placement right away, then wait for server confirmation to verify."),
+        AwaitThenPlace("Confirm first, then place", "Wait for server response before showing placement; most accurate, adds a short delay.")
+    }
+
+}
diff --git a/src/main/kotlin/com/lambda/interaction/request/placing/PlaceInfo.kt b/src/main/kotlin/com/lambda/interaction/request/placing/PlaceInfo.kt
new file mode 100644
index 000000000..dbb51800e
--- /dev/null
+++ b/src/main/kotlin/com/lambda/interaction/request/placing/PlaceInfo.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2025 Lambda
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see .
+ */
+
+package com.lambda.interaction.request.placing
+
+import com.lambda.interaction.construction.context.BuildContext
+import com.lambda.interaction.construction.context.PlaceContext
+import com.lambda.interaction.request.ActionInfo
+import net.minecraft.util.math.BlockPos
+
+data class PlaceInfo(
+    override val context: PlaceContext,
+    override val pendingInteractionsList: MutableCollection,
+    val onPlace: ((BlockPos) -> Unit)?,
+    val placeConfig: PlaceConfig
+) : ActionInfo
\ No newline at end of file
diff --git a/src/main/kotlin/com/lambda/interaction/request/placing/PlaceManager.kt b/src/main/kotlin/com/lambda/interaction/request/placing/PlaceManager.kt
new file mode 100644
index 000000000..aac1c4dec
--- /dev/null
+++ b/src/main/kotlin/com/lambda/interaction/request/placing/PlaceManager.kt
@@ -0,0 +1,361 @@
+/*
+ * Copyright 2025 Lambda
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see .
+ */
+
+package com.lambda.interaction.request.placing
+
+import com.lambda.context.SafeContext
+import com.lambda.event.Event
+import com.lambda.event.EventFlow.post
+import com.lambda.event.events.MovementEvent
+import com.lambda.event.events.TickEvent
+import com.lambda.event.events.UpdateManagerEvent
+import com.lambda.event.listener.SafeListener.Companion.listen
+import com.lambda.interaction.construction.context.PlaceContext
+import com.lambda.interaction.request.ManagerUtils.isPosBlocked
+import com.lambda.interaction.request.PositionBlocking
+import com.lambda.interaction.request.RequestHandler
+import com.lambda.interaction.request.breaking.BreakManager
+import com.lambda.interaction.request.interacting.InteractionManager
+import com.lambda.interaction.request.placing.PlaceManager.activeRequest
+import com.lambda.interaction.request.placing.PlaceManager.maxPlacementsThisTick
+import com.lambda.interaction.request.placing.PlaceManager.placeBlock
+import com.lambda.interaction.request.placing.PlaceManager.populateFrom
+import com.lambda.interaction.request.placing.PlaceManager.potentialPlacements
+import com.lambda.interaction.request.placing.PlaceManager.processRequest
+import com.lambda.interaction.request.placing.PlacedBlockHandler.pendingActions
+import com.lambda.interaction.request.placing.PlacedBlockHandler.setPendingConfigs
+import com.lambda.interaction.request.placing.PlacedBlockHandler.startPending
+import com.lambda.util.BlockUtils.blockState
+import com.lambda.util.Communication.warn
+import com.lambda.util.player.MovementUtils.sneaking
+import com.lambda.util.player.gamemode
+import com.lambda.util.player.isItemOnCooldown
+import com.lambda.util.player.swingHand
+import net.minecraft.block.BlockState
+import net.minecraft.block.pattern.CachedBlockPosition
+import net.minecraft.client.network.ClientPlayerEntity
+import net.minecraft.item.BlockItem
+import net.minecraft.item.ItemPlacementContext
+import net.minecraft.item.ItemStack
+import net.minecraft.item.ItemUsageContext
+import net.minecraft.network.packet.c2s.play.PlayerActionC2SPacket
+import net.minecraft.network.packet.c2s.play.PlayerInteractBlockC2SPacket
+import net.minecraft.sound.SoundCategory
+import net.minecraft.util.ActionResult
+import net.minecraft.util.Hand
+import net.minecraft.util.hit.BlockHitResult
+import net.minecraft.util.math.BlockPos
+import net.minecraft.util.math.Direction
+import net.minecraft.world.GameMode
+
+object PlaceManager : RequestHandler(
+    0,
+    TickEvent.Pre,
+    TickEvent.Input.Pre,
+    TickEvent.Input.Post,
+    TickEvent.Player.Post,
+    onOpen = { activeRequest?.let { processRequest(it) } }
+), PositionBlocking {
+    private var activeRequest: PlaceRequest? = null
+    private var potentialPlacements = mutableListOf()
+
+    private var placementsThisTick = 0
+    private var maxPlacementsThisTick = 0
+
+    private var shouldSneak = false
+    private val validSneak: (player: ClientPlayerEntity) -> Boolean =
+        { player -> shouldSneak == player.isSneaking }
+
+    override val blockedPositions
+        get() = pendingActions.map { it.context.blockPos }
+
+    fun Any.onPlace(
+        alwaysListen: Boolean = false,
+        priority: Int = 0,
+        block: SafeContext.() -> Unit
+    ) = this.listen(priority, alwaysListen) {
+        block()
+    }
+
+    override fun load(): String {
+        super.load()
+
+        listen(priority = Int.MIN_VALUE) {
+            activeRequest = null
+            placementsThisTick = 0
+            potentialPlacements.clear()
+        }
+
+        listen(priority = Int.MIN_VALUE) {
+            if (shouldSneak) {
+                shouldSneak = false
+                it.input.sneaking = true
+            }
+        }
+
+        return "Loaded Place Manager"
+    }
+
+    /**
+     * accepts, and processes the request, as long as the current [activeRequest] is null, and the [BreakManager] has not
+     * been active this tick.
+     *
+     * @see processRequest
+     */
+    override fun SafeContext.handleRequest(request: PlaceRequest) {
+        if (activeRequest != null || BreakManager.activeThisTick || InteractionManager.activeThisTick) return
+
+        activeRequest = request
+        processRequest(request)
+        if (placementsThisTick > 0) activeThisTick = true
+    }
+
+    /**
+     * If the request is fresh, local variables are populated through the [processRequest] method.
+     * It then attempts to perform as many placements within this tick as possible from the [potentialPlacements] collection.
+     *
+     * If all the [maxPlacementsThisTick] limit is reached and the user has rotations enabled, it will start rotating to
+     * the next predicted placement in the list for optimal speed.
+     *
+     * @see populateFrom
+     * @see placeBlock
+     */
+    fun SafeContext.processRequest(request: PlaceRequest) {
+        if (request.fresh) populateFrom(request)
+
+        val iterator = potentialPlacements.iterator()
+        while (iterator.hasNext()) {
+            if (placementsThisTick + 1 > maxPlacementsThisTick) break
+            val ctx = iterator.next()
+
+            if (ctx.sneak) shouldSneak = true
+            if (!ctx.requestDependencies(request) || !validSneak(player)) return
+//            if (tickStage !in request.build.placing.placeStageMask) return
+
+            val actionResult = placeBlock(ctx, request, Hand.MAIN_HAND)
+            if (!actionResult.isAccepted) warn("Placement interaction failed with $actionResult")
+            placementsThisTick++
+            iterator.remove()
+        }
+        if (potentialPlacements.isEmpty()) activeRequest = null
+    }
+
+    /**
+     * Filters the [request]'s [PlaceContext]s, placing them into the [potentialPlacements] collection, and
+     * setting the maxPlacementsThisTick value.
+     *
+     * @see isPosBlocked
+     */
+    private fun populateFrom(request: PlaceRequest) {
+        setPendingConfigs(request.build)
+        potentialPlacements = request.contexts
+            .filter { !isPosBlocked(it.blockPos) }
+            .toMutableList()
+
+        val pendingLimit =  (request.maxPendingPlacements - pendingActions.size).coerceAtLeast(0)
+        maxPlacementsThisTick = (request.placementsPerTick.coerceAtMost(pendingLimit))
+    }
+
+    /**
+     * A modified version of the minecraft interactBlock method, renamed to better suit its usage.
+     *
+     * @see net.minecraft.client.network.ClientPlayerInteractionManager.interactBlock
+     */
+    private fun SafeContext.placeBlock(placeContext: PlaceContext, request: PlaceRequest, hand: Hand): ActionResult {
+        interaction.syncSelectedSlot()
+        val hitResult = placeContext.result
+        if (!world.worldBorder.contains(hitResult.blockPos)) return ActionResult.FAIL
+        if (gamemode == GameMode.SPECTATOR) return ActionResult.PASS
+        return interactBlockInternal(placeContext, request, request.build.placing, hand, hitResult)
+    }
+
+    /**
+     * A modified version of the minecraft interactBlockInternal method.
+     *
+     * @see net.minecraft.client.network.ClientPlayerInteractionManager.interactBlockInternal
+     */
+    private fun SafeContext.interactBlockInternal(
+        placeContext: PlaceContext,
+        request: PlaceRequest,
+        placeConfig: PlaceConfig,
+        hand: Hand,
+        hitResult: BlockHitResult
+    ): ActionResult {
+        val handNotEmpty = player.getStackInHand(hand).isEmpty.not()
+        val cantInteract = player.shouldCancelInteraction() && handNotEmpty
+        if (!cantInteract) {
+            val blockState = blockState(hitResult.blockPos)
+            if (!connection.hasFeature(blockState.block.requiredFeatures)) {
+                return ActionResult.FAIL
+            }
+
+            val actionResult = blockState.onUse(world, player, hitResult)
+            if (actionResult.isAccepted) {
+                return actionResult
+            }
+        }
+
+        val stack = player.mainHandStack
+
+        if (!stack.isEmpty && !isItemOnCooldown(stack)) {
+            val itemUsageContext = ItemUsageContext(player, hand, hitResult)
+            return if (gamemode.isCreative) {
+                val i = stack.count
+                useOnBlock(placeContext, request, hand, hitResult, placeConfig, stack, itemUsageContext)
+                    .also {
+                        stack.count = i
+                    }
+            } else
+                useOnBlock(placeContext, request, hand, hitResult, placeConfig, stack, itemUsageContext)
+        }
+        return ActionResult.PASS
+    }
+
+    /**
+     * A modified version of the minecraft useOnBlock method.
+     *
+     * @see net.minecraft.item.Item.useOnBlock
+     */
+    private fun SafeContext.useOnBlock(
+        placeContext: PlaceContext,
+        request: PlaceRequest,
+        hand: Hand,
+        hitResult: BlockHitResult,
+        placeConfig: PlaceConfig,
+        itemStack: ItemStack,
+        context: ItemUsageContext
+    ): ActionResult {
+        val cachedBlockPosition = CachedBlockPosition(world, context.blockPos, false)
+
+        val cantModifyWorld = !player.abilities.allowModifyWorld
+        val cantPlaceOn = !itemStack.canPlaceOn(cachedBlockPosition)
+        if (cantModifyWorld && cantPlaceOn) return ActionResult.PASS
+
+        val item = (itemStack.item as? BlockItem) ?: return ActionResult.PASS
+
+        return place(placeContext, request, hand, hitResult, placeConfig, item, ItemPlacementContext(context))
+    }
+
+    /**
+     * A modified version of the minecraft place method.
+     *
+     * @see net.minecraft.item.BlockItem.place
+     */
+    private fun SafeContext.place(
+        placeContext: PlaceContext,
+        request: PlaceRequest,
+        hand: Hand,
+        hitResult: BlockHitResult,
+        placeConfig: PlaceConfig,
+        item: BlockItem,
+        context: ItemPlacementContext
+    ): ActionResult {
+        if (!item.block.isEnabled(world.enabledFeatures)) return ActionResult.FAIL
+        if (!context.canPlace()) return ActionResult.FAIL
+
+        val itemPlacementContext = item.getPlacementContext(context) ?: return ActionResult.FAIL
+        val blockState = item.getPlacementState(itemPlacementContext) ?: return ActionResult.FAIL
+
+        val stackInHand = player.getStackInHand(hand)
+        val stackCountPre = stackInHand.count
+        if (placeConfig.placeConfirmationMode != PlaceConfig.PlaceConfirmationMode.None) {
+            PlaceInfo(placeContext, request.pendingInteractions, request.onPlace, placeConfig).startPending()
+        }
+
+        if (placeConfig.airPlace == PlaceConfig.AirPlaceMode.Grim) {
+            val placeHand = if (hand == Hand.MAIN_HAND) Hand.OFF_HAND else Hand.MAIN_HAND
+            airPlaceOffhandSwap()
+            sendPlacePacket(placeHand, hitResult)
+            airPlaceOffhandSwap()
+        } else {
+            sendPlacePacket(hand, hitResult)
+        }
+
+        if (placeConfig.swing) {
+            swingHand(placeConfig.swingType, hand)
+
+            if (!stackInHand.isEmpty && (stackInHand.count != stackCountPre || player.isInCreativeMode)) {
+                mc.gameRenderer.firstPersonRenderer.resetEquipProgress(hand)
+            }
+        }
+
+        val itemStack = itemPlacementContext.stack
+        if (!player.abilities.creativeMode) itemStack.decrement(1)
+
+        if (placeConfig.placeConfirmationMode == PlaceConfig.PlaceConfirmationMode.AwaitThenPlace)
+            return ActionResult.SUCCESS
+
+        // TODO: Implement restriction checks (e.g., world height) to prevent unnecessary server requests when the
+        //  "AwaitThenPlace" confirmation setting is enabled, as the block state setting methods that validate these
+        //  rules are not called.
+        if (!item.place(itemPlacementContext, blockState)) return ActionResult.FAIL
+
+        val blockPos = itemPlacementContext.blockPos
+        var state = world.getBlockState(blockPos)
+        if (state.isOf(blockState.block)) {
+            state = item.placeFromNbt(blockPos, world, itemStack, state)
+            item.postPlacement(blockPos, world, player, itemStack, state)
+            state.block.onPlaced(world, blockPos, state, player, itemStack)
+        }
+
+        if (placeConfig.sounds) placeSound(state, blockPos)
+
+        if (placeConfig.placeConfirmationMode == PlaceConfig.PlaceConfirmationMode.None) {
+            request.onPlace?.invoke(placeContext.blockPos)
+        }
+
+        return ActionResult.SUCCESS
+    }
+
+    /**
+     * sends the block placement packet using the given [hand] and [hitResult].
+     */
+    private fun SafeContext.sendPlacePacket(hand: Hand, hitResult: BlockHitResult) =
+        interaction.sendSequencedPacket(world) { sequence: Int ->
+            PlayerInteractBlockC2SPacket(hand, hitResult, sequence)
+        }
+
+    /**
+     * Plays the block placement sound at a given position.
+     */
+    fun SafeContext.placeSound(state: BlockState, pos: BlockPos) {
+        val blockSoundGroup = state.soundGroup
+        world.playSound(
+            player,
+            pos,
+            state.soundGroup.placeSound,
+            SoundCategory.BLOCKS,
+            (blockSoundGroup.getVolume() + 1.0f) / 2.0f,
+            blockSoundGroup.getPitch() * 0.8f
+        )
+    }
+
+    /**
+     * Must be called before and after placing a block to bypass grim's air place checks.
+     */
+    private fun SafeContext.airPlaceOffhandSwap() {
+        connection.sendPacket(
+            PlayerActionC2SPacket(
+                PlayerActionC2SPacket.Action.SWAP_ITEM_WITH_OFFHAND,
+                BlockPos.ORIGIN,
+                Direction.DOWN
+            )
+        )
+    }
+
+    override fun preEvent(): Event = UpdateManagerEvent.Place.post()
+}
diff --git a/src/main/kotlin/com/lambda/interaction/request/placing/PlaceRequest.kt b/src/main/kotlin/com/lambda/interaction/request/placing/PlaceRequest.kt
new file mode 100644
index 000000000..65e1efccd
--- /dev/null
+++ b/src/main/kotlin/com/lambda/interaction/request/placing/PlaceRequest.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2025 Lambda
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see .
+ */
+
+package com.lambda.interaction.request.placing
+
+import com.lambda.config.groups.BuildConfig
+import com.lambda.interaction.construction.context.BuildContext
+import com.lambda.interaction.construction.context.PlaceContext
+import com.lambda.interaction.request.Request
+import com.lambda.interaction.request.hotbar.HotbarConfig
+import com.lambda.interaction.request.rotating.RotationConfig
+import com.lambda.threading.runSafe
+import com.lambda.util.BlockUtils.blockState
+import com.lambda.util.BlockUtils.matches
+import net.minecraft.util.math.BlockPos
+
+data class PlaceRequest(
+    val contexts: Collection,
+    val build: BuildConfig,
+    val rotation: RotationConfig,
+    val hotbar: HotbarConfig,
+    val pendingInteractions: MutableCollection,
+    val onPlace: ((BlockPos) -> Unit)? = null
+) : Request(), PlaceConfig by build.placing {
+    override val config = build.placing
+    override val done: Boolean
+        get() = runSafe {
+            contexts.all { it.expectedState.matches(blockState(it.blockPos)) }
+        } == true
+
+    override fun submit(queueIfClosed: Boolean) =
+        PlaceManager.request(this, queueIfClosed)
+}
diff --git a/src/main/kotlin/com/lambda/interaction/request/placing/PlacedBlockHandler.kt b/src/main/kotlin/com/lambda/interaction/request/placing/PlacedBlockHandler.kt
new file mode 100644
index 000000000..f239bbe0b
--- /dev/null
+++ b/src/main/kotlin/com/lambda/interaction/request/placing/PlacedBlockHandler.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2025 Lambda
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see .
+ */
+
+package com.lambda.interaction.request.placing
+
+import com.lambda.event.events.WorldEvent
+import com.lambda.event.listener.SafeListener.Companion.listen
+import com.lambda.interaction.construction.processing.ProcessorRegistry
+import com.lambda.interaction.request.PostActionHandler
+import com.lambda.interaction.request.placing.PlaceManager.placeSound
+import com.lambda.module.modules.client.TaskFlowModule
+import com.lambda.threading.runSafe
+import com.lambda.util.BlockUtils.matches
+import com.lambda.util.Communication.info
+import com.lambda.util.Communication.warn
+import com.lambda.util.collections.LimitedDecayQueue
+
+object PlacedBlockHandler : PostActionHandler() {
+    override val pendingActions = LimitedDecayQueue(
+        TaskFlowModule.build.maxPendingInteractions, TaskFlowModule.build.interactionTimeout * 50L
+    ) {
+        info("${it::class.simpleName} at ${it.context.blockPos.toShortString()} timed out")
+        if (it.placeConfig.placeConfirmationMode != PlaceConfig.PlaceConfirmationMode.AwaitThenPlace) {
+            runSafe {
+                world.setBlockState(it.context.blockPos, it.context.cachedState)
+            }
+        }
+        it.pendingInteractionsList.remove(it.context)
+    }
+
+    init {
+        listen(priority = Int.MIN_VALUE) { event ->
+            pendingActions
+                .firstOrNull { it.context.blockPos == event.pos }
+                ?.let { pending ->
+                    if (!pending.context.expectedState.matches(event.newState)) {
+                        if (pending.context.cachedState.matches(event.newState, ProcessorRegistry.postProcessedProperties)) {
+                            pending.context.cachedState = event.newState
+                            return@listen
+                        }
+
+                        pending.stopPending()
+
+                        this@PlacedBlockHandler.warn("Placed block at ${event.pos.toShortString()} was rejected with ${event.newState} instead of ${pending.context.expectedState}")
+                        return@listen
+                    }
+
+                    pending.stopPending()
+
+                    if (pending.placeConfig.placeConfirmationMode == PlaceConfig.PlaceConfirmationMode.AwaitThenPlace)
+                        with(pending.context) { placeSound(expectedState, blockPos) }
+                    pending.onPlace?.invoke(pending.context.blockPos)
+                }
+        }
+    }
+}
diff --git a/src/main/kotlin/com/lambda/interaction/request/rotation/Rotation.kt b/src/main/kotlin/com/lambda/interaction/request/rotating/Rotation.kt
similarity index 86%
rename from src/main/kotlin/com/lambda/interaction/request/rotation/Rotation.kt
rename to src/main/kotlin/com/lambda/interaction/request/rotating/Rotation.kt
index 9d550505f..bd141cccd 100644
--- a/src/main/kotlin/com/lambda/interaction/request/rotation/Rotation.kt
+++ b/src/main/kotlin/com/lambda/interaction/request/rotating/Rotation.kt
@@ -15,7 +15,7 @@
  * along with this program.  If not, see .
  */
 
-package com.lambda.interaction.request.rotation
+package com.lambda.interaction.request.rotating
 
 import com.lambda.Lambda.mc
 import com.lambda.threading.runSafe
@@ -29,7 +29,7 @@ import com.lambda.util.world.raycast.RayCastUtils.rayCast
 import net.minecraft.entity.Entity
 import net.minecraft.util.math.Box
 import net.minecraft.util.math.Direction
-import net.minecraft.util.math.MathHelper
+import net.minecraft.util.math.MathHelper.wrapDegrees
 import net.minecraft.util.math.Vec3d
 import kotlin.math.abs
 import kotlin.math.atan2
@@ -101,11 +101,19 @@ data class Rotation(val yaw: Double, val pitch: Double) {
                 pitch = value.pitchF
             }
 
-        fun wrap(deg: Double) = MathHelper.wrapDegrees(deg)
+        fun wrap(deg: Double) = wrapDegrees(deg)
+        fun wrap(deg: Float) = wrapDegrees(deg)
+        fun Rotation.wrap() = Rotation(wrap(yaw), pitch)
 
         fun Rotation.lerp(other: Rotation, delta: Double): Rotation {
-            val yaw = this.yaw + delta * (other.yaw - this.yaw)
-            val pitch = this.pitch + delta * (other.pitch - this.pitch)
+            // Calculate the wrapped difference to ensure we take the shortest path
+            val yawDiff = wrap(other.yaw - this.yaw)
+            val pitchDiff = wrap(other.pitch - this.pitch)
+
+            // Apply the delta to the wrapped difference
+            val yaw = wrap(this.yaw + delta * yawDiff)
+            val pitch = wrap(this.pitch + delta * pitchDiff)
+
             return Rotation(yaw, pitch)
         }
 
@@ -118,8 +126,8 @@ data class Rotation(val yaw: Double, val pitch: Double) {
             val yawSpeed = abs(yawDiff / diff) * speed
             val pitchSpeed = abs(pitchDiff / diff) * speed
 
-            val yaw = yaw + yawDiff.coerceIn(-yawSpeed, yawSpeed)
-            val pitch = pitch + pitchDiff.coerceIn(-pitchSpeed, pitchSpeed)
+            val yaw = wrap(yaw + yawDiff.coerceIn(-yawSpeed, yawSpeed))
+            val pitch = wrap(pitch + pitchDiff.coerceIn(-pitchSpeed, pitchSpeed))
 
             return Rotation(yaw, pitch)
         }
diff --git a/src/main/kotlin/com/lambda/interaction/request/rotation/RotationConfig.kt b/src/main/kotlin/com/lambda/interaction/request/rotating/RotationConfig.kt
similarity index 62%
rename from src/main/kotlin/com/lambda/interaction/request/rotation/RotationConfig.kt
rename to src/main/kotlin/com/lambda/interaction/request/rotating/RotationConfig.kt
index 67d2bd924..cb223b421 100644
--- a/src/main/kotlin/com/lambda/interaction/request/rotation/RotationConfig.kt
+++ b/src/main/kotlin/com/lambda/interaction/request/rotating/RotationConfig.kt
@@ -15,50 +15,40 @@
  * along with this program.  If not, see .
  */
 
-package com.lambda.interaction.request.rotation
+package com.lambda.interaction.request.rotating
 
-import com.lambda.interaction.request.Priority
 import com.lambda.interaction.request.RequestConfig
 
-/*
- * Abstract base class for configuring rotation behavior.
- *
- * @param priority The priority of this configuration.
- */
-abstract class RotationConfig(priority: Priority) : RequestConfig(priority) {
+interface RotationConfig : RequestConfig {
     /**
      * - [RotationMode.Silent] Spoofing server-side rotation.
      * - [RotationMode.Sync] Spoofing server-side rotation and adjusting client-side movement based on reported rotation (for Grim).
      * - [RotationMode.Lock] Locks the camera client-side.
      * - [RotationMode.None] No rotation.
      */
-    abstract val rotationMode: RotationMode
+    val rotationMode: RotationMode
 
     /**
      * The rotation speed (in degrees).
      */
-    abstract val turnSpeed: Double
+    val turnSpeed: Double
 
     /**
      * Ticks the rotation should not be changed.
      */
-    abstract val keepTicks: Int
+    val keepTicks: Int
 
     /**
      * Ticks to rotate back to the actual rotation.
      */
-    abstract val decayTicks: Int
+    val decayTicks: Int
 
     val rotate: Boolean get() = rotationMode != RotationMode.None
 
-    override fun requestInternal(request: RotationRequest) {
-        RotationManager.registerRequest(this, request)
-    }
-
-    open class Instant(mode: RotationMode, priority: Priority = 0) : RotationConfig(priority) {
-        override val turnSpeed get() = 360.0
-        override val keepTicks get() = 1
-        override val decayTicks get() = 1
+    open class Instant(mode: RotationMode) : RotationConfig {
         override val rotationMode = mode
+        override val keepTicks = 1
+        override val decayTicks = 1
+        override val turnSpeed = 360.0
     }
-}
\ No newline at end of file
+}
diff --git a/src/main/kotlin/com/lambda/interaction/request/rotation/RotationManager.kt b/src/main/kotlin/com/lambda/interaction/request/rotating/RotationManager.kt
similarity index 51%
rename from src/main/kotlin/com/lambda/interaction/request/rotation/RotationManager.kt
rename to src/main/kotlin/com/lambda/interaction/request/rotating/RotationManager.kt
index b5553cb6f..cb25dd411 100644
--- a/src/main/kotlin/com/lambda/interaction/request/rotation/RotationManager.kt
+++ b/src/main/kotlin/com/lambda/interaction/request/rotating/RotationManager.kt
@@ -15,22 +15,19 @@
  * along with this program.  If not, see .
  */
 
-package com.lambda.interaction.request.rotation
+package com.lambda.interaction.request.rotating
 
 import com.lambda.Lambda.mc
 import com.lambda.context.SafeContext
 import com.lambda.core.Loadable
 import com.lambda.event.EventFlow.post
-import com.lambda.event.events.ConnectionEvent
-import com.lambda.event.events.PacketEvent
-import com.lambda.event.events.PlayerPacketEvent
-import com.lambda.event.events.RotationEvent
-import com.lambda.event.events.TickEvent
+import com.lambda.event.events.*
 import com.lambda.event.listener.SafeListener.Companion.listen
 import com.lambda.event.listener.UnsafeListener.Companion.listenUnsafe
 import com.lambda.interaction.request.RequestHandler
-import com.lambda.interaction.request.rotation.Rotation.Companion.slerp
-import com.lambda.interaction.request.rotation.visibilty.lookAt
+import com.lambda.interaction.request.rotating.Rotation.Companion.slerp
+import com.lambda.interaction.request.rotating.Rotation.Companion.wrap
+import com.lambda.interaction.request.rotating.visibilty.lookAt
 import com.lambda.module.modules.client.Baritone
 import com.lambda.threading.runGameScheduled
 import com.lambda.threading.runSafe
@@ -47,146 +44,161 @@ import kotlin.math.round
 import kotlin.math.sign
 import kotlin.math.sin
 
-object RotationManager : RequestHandler(), Loadable {
-    var currentRotation = Rotation.ZERO
-    private var prevRotation = Rotation.ZERO
-
-    override fun load() = "Loaded Rotation Manager"
-
-    /**
-     * Registers a listener called immediately before [RotationManager] handles its context and applies rotation updates.
-     *
-     * This is useful if you need to synchronize with the latest player state (including inputs and movement),
-     * or if you must place your rotation requests as the tick is completed and just before the new rotation is processed.
-     *
-     * @param alwaysListen Whether to keep this listener active at all times. Defaults to false.
-     * @param block A callback providing a [SafeContext] where you can place rotation-related operations or other logic.
-     */
+object RotationManager : RequestHandler(
+    1,
+    TickEvent.Pre,
+    TickEvent.Input.Pre,
+    TickEvent.Input.Post,
+    TickEvent.Player.Post,
+) {
+    var activeRotation = Rotation.ZERO
+    var serverRotation = Rotation.ZERO
+    var prevServerRotation = Rotation.ZERO
+
+    var activeRequest: RotationRequest? = null
+    private var changedThisTick = false
+
     fun Any.onRotate(
         alwaysListen: Boolean = false,
-        block: SafeContext.() -> Unit,
-    ) = this.listen(0, alwaysListen) {
+        priority: Int = 0,
+        block: SafeContext.() -> Unit
+    ) = this.listen(priority, alwaysListen) {
         block()
     }
 
-    init {
-        // For some reason we have to update AFTER sending player packets
-        // instead of updating on TickEvent.Pre (am I doing something wrong?)
-        listen(Int.MIN_VALUE) {
-            // Update the request
-            val changed = updateRequest(true) { entry ->
-                // skip requests that have failed to build the rotation
-                // to free the request place for others
-                entry.value.target.targetRotation.value != null
-            }
-
-            if (!changed) { // rebuild the rotation if the same context gets used again
-                currentRequest?.target?.targetRotation?.update()
-            }
-
-            // Calculate the target rotation
-            val targetRotation = currentRequest?.let { request ->
-                val rotationTo = if (request.keepTicks >= 0)
-                    request.target.targetRotation.value
-                        ?: currentRotation // same context gets used again && the rotation is null this tick
-                else player.rotation
-
-                val speedMultiplier = if (request.keepTicks < 0) 1.0 else request.speedMultiplier
-                val turnSpeed = request.turnSpeed() * speedMultiplier
-
-                currentRotation.slerp(rotationTo, turnSpeed)
-            } ?: player.rotation
+    override fun load(): String {
+        super.load()
 
-            // Update the current rotation
-            prevRotation = currentRotation
-            currentRotation = targetRotation/*.fixSensitivity(prevRotation)*/
-
-            // Handle LOCK mode
-            if (currentRequest?.mode == RotationMode.Lock) {
-                player.yaw = currentRotation.yawF
-                player.pitch = currentRotation.pitchF
+        listen(priority = Int.MAX_VALUE) {
+            activeRequest?.let {
+                if (it.keepTicks <= 0 && it.decayTicks <= 0) {
+                    activeRequest = null
+                }
             }
+        }
 
-            // Tick and reset the context
-            currentRequest?.let {
-                if (--it.keepTicks > 0) return@let
-                if (--it.decayTicks >= 0) return@let
-                currentRequest = null
+        listen(priority = Int.MIN_VALUE) {
+            activeRequest?.let { request ->
+                request.age++
             }
+            changedThisTick = false
         }
 
-        listen { event ->
+        listen(priority = Int.MIN_VALUE) { event ->
             val packet = event.packet
             if (packet !is PlayerPositionLookS2CPacket) return@listen
 
             runGameScheduled {
-                // TODO: wtf happened here ?
-                // reset(Rotation(packet.yaw, packet.pitch))
+                reset(Rotation(packet.change.yaw, packet.change.pitch))
             }
         }
 
-        listenUnsafe {
+        listenUnsafe(priority = Int.MIN_VALUE) {
             reset(Rotation.ZERO)
         }
+
+        return "Loaded Rotation Manager"
+    }
+
+    override fun SafeContext.handleRequest(request: RotationRequest) {
+        activeRequest?.let { if (it.age <= 0) return }
+        if (request.target.targetRotation.value != null) {
+            activeRequest = request
+            updateActiveRotation()
+            changedThisTick = true
+        }
+    }
+
+    @JvmStatic
+    fun processRotations() = runSafe {
+        if (activeRequest != null) activeThisTick = true
+
+        if (!changedThisTick) { // rebuild the rotation if the same context gets used again
+            activeRequest?.target?.targetRotation?.update()
+            updateActiveRotation()
+        }
+
+        // Tick and reset the context
+        activeRequest?.let {
+            if (it.keepTicks-- > 0) return@let
+            it.decayTicks--
+        }
+    }
+
+    fun onRotationSend() {
+        prevServerRotation = serverRotation
+        serverRotation = activeRotation/*.fixSensitivity(prevServerRotation)*/
+
+        // Handle LOCK mode
+        if (activeRequest?.rotationMode == RotationMode.Lock) {
+            mc.player?.yaw = serverRotation.yawF
+            mc.player?.pitch = serverRotation.pitchF
+        }
+    }
+
+    private fun SafeContext.updateActiveRotation() {
+        activeRotation = activeRequest?.let { request ->
+            val rotationTo = if (request.keepTicks >= 0)
+                request.target.targetRotation.value
+                    ?: activeRotation // same context gets used again && the rotation is null this tick
+            else player.rotation
+
+            val speedMultiplier = if (request.keepTicks < 0) 1.0 else request.speedMultiplier
+            val turnSpeed = request.turnSpeed * speedMultiplier
+
+            serverRotation.slerp(rotationTo, turnSpeed).wrap()
+        } ?: player.rotation
     }
 
     private fun reset(rotation: Rotation) {
-        prevRotation = rotation
-        currentRotation = rotation
-        currentRequest = null
+        prevServerRotation = rotation
+        serverRotation = rotation
+        activeRotation = rotation
+        activeRequest = null
     }
 
     private val smoothRotation
-        get() =
-            lerp(mc.partialTicks, prevRotation, currentRotation)
+        get() = lerp(mc.partialTicks, serverRotation, activeRotation)
 
     @JvmStatic
     val lockRotation
-        get() =
-            if (currentRequest?.mode == RotationMode.Lock) smoothRotation else null
+        get() = if (activeRequest?.rotationMode == RotationMode.Lock) smoothRotation else null
 
     @JvmStatic
-    val renderYaw
-        get() =
-            if (currentRequest == null) null else smoothRotation.yaw.toFloat()
+    val headYaw
+        get() = if (activeRequest == null) null else activeRotation.yawF
 
     @JvmStatic
-    val renderPitch
-        get() =
-            if (currentRequest == null) null else smoothRotation.pitch.toFloat()
+    val headPitch
+        get() = if (activeRequest == null) null else activeRotation.pitchF
 
     @JvmStatic
     val handYaw
-        get() =
-            if (currentRequest?.mode == RotationMode.Lock) currentRotation.yaw.toFloat() else null
+        get() = if (activeRequest?.rotationMode == RotationMode.Lock) activeRotation.yawF else null
 
     @JvmStatic
     val handPitch
-        get() =
-            if (currentRequest?.mode == RotationMode.Lock) currentRotation.pitch.toFloat() else null
+        get() = if (activeRequest?.rotationMode == RotationMode.Lock) activeRotation.pitchF else null
 
     @JvmStatic
     val movementYaw: Float?
         get() {
-            if (currentRequest?.mode == RotationMode.Silent) return null
-            return currentRotation.yaw.toFloat()
+            if (activeRequest == null || activeRequest?.rotationMode == RotationMode.Silent) return null
+            return activeRotation.yaw.toFloat()
         }
 
     @JvmStatic
     val movementPitch: Float?
         get() {
-            if (currentRequest?.mode == RotationMode.Silent) return null
-            return currentRotation.pitch.toFloat()
+            if (activeRequest == null || activeRequest?.rotationMode == RotationMode.Silent) return null
+            return activeRotation.pitch.toFloat()
         }
 
-    @JvmStatic
-    var updatingRenderState = false
-
     @JvmStatic
     fun getRotationForVector(deltaTime: Double): Vec2d? {
-        if (currentRequest?.mode == RotationMode.Silent) return null
+        if (activeRequest == null || activeRequest?.rotationMode == RotationMode.Silent) return null
 
-        val rot = lerp(deltaTime, prevRotation, currentRotation)
+        val rot = lerp(deltaTime, serverRotation, activeRotation)
         return Vec2d(rot.yaw, rot.pitch)
     }
 
@@ -231,9 +243,9 @@ object RotationManager : RequestHandler(), Loadable {
             if (signForward <= 1.0E-5f && signStrafe <= 1.0E-5F) return@runSafe
 
             // Actual yaw used by the physics engine
-            var actualYaw = currentRotation.yaw
+            var actualYaw = activeRotation.yaw
 
-            if (currentRequest?.mode == RotationMode.Silent) {
+            if (activeRequest?.rotationMode == RotationMode.Silent) {
                 actualYaw = player.yaw.toDouble()
             }
 
@@ -258,10 +270,12 @@ object RotationManager : RequestHandler(), Loadable {
             // Makes baritone movement safe
             // when yaw difference is too big to compensate it by modifying keyboard input
             val minYawDist = movementYawList
-                .map { currentRotation.yaw + it } // all possible movement directions (including diagonals)
+                .map { activeRotation.yaw + it } // all possible movement directions (including diagonals)
                 .minOf { Rotation.angleDifference(it, baritoneYaw) }
 
             if (minYawDist > 5.0) input.movementVector = Vec2f.ZERO
         }
     }
+
+    override fun preEvent() = UpdateManagerEvent.Rotation.post()
 }
diff --git a/src/main/kotlin/com/lambda/interaction/request/rotation/RotationMode.kt b/src/main/kotlin/com/lambda/interaction/request/rotating/RotationMode.kt
similarity index 61%
rename from src/main/kotlin/com/lambda/interaction/request/rotation/RotationMode.kt
rename to src/main/kotlin/com/lambda/interaction/request/rotating/RotationMode.kt
index aa7baaa2d..59313d50d 100644
--- a/src/main/kotlin/com/lambda/interaction/request/rotation/RotationMode.kt
+++ b/src/main/kotlin/com/lambda/interaction/request/rotating/RotationMode.kt
@@ -15,24 +15,23 @@
  * along with this program.  If not, see .
  */
 
-package com.lambda.interaction.request.rotation
+package com.lambda.interaction.request.rotating
 
-import com.lambda.interaction.request.rotation.RotationMode.Lock
-import com.lambda.interaction.request.rotation.RotationMode.None
-import com.lambda.interaction.request.rotation.RotationMode.Silent
-import com.lambda.interaction.request.rotation.RotationMode.Sync
+import com.lambda.util.Describable
 import com.lambda.util.NamedEnum
 
-
 /**
  * @property Silent Spoofing server-side rotation.
  * @property Sync Spoofing server-side rotation and adjusting client-side movement based on reported rotation (for Grim).
  * @property Lock Locks the camera client-side.
  * @property None No rotation.
  */
-enum class RotationMode {
-    Silent,
-    Sync,
-    Lock,
-    None
+enum class RotationMode(
+    override val displayName: String,
+    override val description: String
+) : NamedEnum, Describable {
+    Silent("Silent", "Rotate for interactions without moving your camera (server-only rotation spoof)."),
+    Sync("Sync", "Rotate both server and client view so your camera turns to face the target."),
+    Lock("Lock", "Keep rotation fixed on the target until the action finishes; ignores other rotation changes."),
+    None("None", "Do not auto-rotate; use your current view direction.")
 }
diff --git a/src/main/kotlin/com/lambda/interaction/request/rotation/RotationRequest.kt b/src/main/kotlin/com/lambda/interaction/request/rotating/RotationRequest.kt
similarity index 57%
rename from src/main/kotlin/com/lambda/interaction/request/rotation/RotationRequest.kt
rename to src/main/kotlin/com/lambda/interaction/request/rotating/RotationRequest.kt
index 2b839a6f8..9e9579843 100644
--- a/src/main/kotlin/com/lambda/interaction/request/rotation/RotationRequest.kt
+++ b/src/main/kotlin/com/lambda/interaction/request/rotating/RotationRequest.kt
@@ -15,31 +15,26 @@
  * along with this program.  If not, see .
  */
 
-package com.lambda.interaction.request.rotation
+package com.lambda.interaction.request.rotating
 
-import com.lambda.interaction.request.Priority
 import com.lambda.interaction.request.Request
-import com.lambda.interaction.request.rotation.visibilty.RotationTarget
+import com.lambda.interaction.request.rotating.visibilty.RotationTarget
 import com.lambda.threading.runSafe
 
 data class RotationRequest(
     val target: RotationTarget,
-    val prio: Priority,
-    val mode: RotationMode,
-    var keepTicks: Int = 3,
-    var decayTicks: Int = 0,
-    val turnSpeed: () -> Double = { 180.0 },
+    override val config: RotationConfig,
+    override val rotationMode: RotationMode = config.rotationMode,
+    override val turnSpeed: Double = config.turnSpeed,
+    override var keepTicks: Int = config.keepTicks,
+    override var decayTicks: Int = config.decayTicks,
     val speedMultiplier: Double = 1.0
-) : Request(prio) {
-
-    constructor(
-        target: RotationTarget,
-        config: RotationConfig,
-        speedMultiplier: Double = 1.0
-    ) : this(target, config.priority, config.rotationMode, config.keepTicks, config.decayTicks, config::turnSpeed, speedMultiplier)
+) : Request(), RotationConfig by config {
+    var age = 0
 
     override val done: Boolean get() =
-        mode == RotationMode.None || runSafe {
-            target.verify(target)
-        } == true
-}
+        rotationMode == RotationMode.None || runSafe { target.verify() } == true
+
+    override fun submit(queueIfClosed: Boolean): RotationRequest =
+        RotationManager.request(this, queueIfClosed)
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/lambda/interaction/request/rotating/visibilty/PlaceDirection.kt b/src/main/kotlin/com/lambda/interaction/request/rotating/visibilty/PlaceDirection.kt
new file mode 100644
index 000000000..6b3e70210
--- /dev/null
+++ b/src/main/kotlin/com/lambda/interaction/request/rotating/visibilty/PlaceDirection.kt
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2025 Lambda
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see .
+ */
+
+package com.lambda.interaction.request.rotating.visibilty
+
+import com.lambda.interaction.request.rotating.Rotation
+import net.minecraft.entity.Entity
+import net.minecraft.util.math.Direction
+import net.minecraft.util.math.MathHelper
+import net.minecraft.util.math.Vec3i
+
+enum class PlaceDirection(
+    val rotation: Rotation,
+    val vector: Vec3i,
+) {
+    UpNorth  (-180.0, -90.0,  0,  1, -1),
+    UpSouth  (   0.0, -90.0,  0,  1,  1),
+    UpWest   (  90.0, -90.0,  1,  1,  0),
+    UpEast   ( -90.0, -90.0, -1,  1,  0),
+
+    DownNorth(-180.0,  90.0,  0, -1, -1),
+    DownSouth(   0.0,  90.0,  0, -1,  1),
+    DownWest (  90.0,  90.0,  1, -1,  0),
+    DownEast ( -90.0,  90.0, -1, -1,  0),
+
+    North    (-180.0,   0.0,  0,  0, -1),
+    South    (   0.0,   0.0,  0,  0,  1),
+    West     (  90.0,   0.0,  1,  0,  0),
+    East     ( -90.0,   0.0, -1,  0,  0);
+
+    constructor(yaw: Double, pitch: Double, x: Int, y: Int, z: Int)
+            : this(Rotation(yaw, pitch), Vec3i(x, y, z))
+
+    //ToDo: snap to the area not the cardinal to avoid excess rotation distance
+    fun snapToArea(rot: Rotation): Rotation = rotation
+
+    fun isInArea(rot: Rotation) = fromRotation(rot) == this
+
+    companion object {
+        /**
+         * A modified version of the minecraft getEntityFacingOrder method. This version takes a
+         * [Rotation] instead of an [Entity]
+         *
+         * @see Direction.getEntityFacingOrder
+         */
+        fun fromRotation(rotation: Rotation): PlaceDirection {
+            val pitchRad = rotation.pitchF * (Math.PI.toFloat() / 180f)
+            val yawRad = -rotation.yawF * (Math.PI.toFloat() / 180f)
+
+            val sinPitch = MathHelper.sin(pitchRad)
+            val cosPitch = MathHelper.cos(pitchRad)
+            val sinYaw = MathHelper.sin(yawRad)
+            val cosYaw = MathHelper.cos(yawRad)
+
+            val isFacingEast = sinYaw > 0.0f
+            val isFacingUp = sinPitch < 0.0f
+            val isFacingSouth = cosYaw > 0.0f
+
+            val eastWestStrength = if (isFacingEast) sinYaw else -sinYaw
+            val upDownStrength = if (isFacingUp) -sinPitch else sinPitch
+            val northSouthStrength = if (isFacingSouth) cosYaw else -cosYaw
+
+            val adjustedEastWestStrength = eastWestStrength * cosPitch
+            val adjustedNorthSouthStrength = northSouthStrength * cosPitch
+
+            return when {
+                eastWestStrength > northSouthStrength -> when {
+                    upDownStrength > adjustedEastWestStrength -> when {
+                        isFacingUp && isFacingEast -> UpEast
+                        isFacingUp -> UpWest
+                        isFacingEast -> DownEast
+                        else -> DownWest
+                    }
+                    else -> if (isFacingEast) East else West
+                }
+                upDownStrength > adjustedNorthSouthStrength -> when {
+                    isFacingUp && isFacingSouth -> UpSouth
+                    isFacingUp -> UpNorth
+                    isFacingSouth -> DownSouth
+                    else -> DownNorth
+                }
+                else -> if (isFacingSouth) South else North
+            }
+        }
+    }
+}
diff --git a/src/main/kotlin/com/lambda/interaction/request/rotating/visibilty/PointSelection.kt b/src/main/kotlin/com/lambda/interaction/request/rotating/visibilty/PointSelection.kt
new file mode 100644
index 000000000..6395d12ad
--- /dev/null
+++ b/src/main/kotlin/com/lambda/interaction/request/rotating/visibilty/PointSelection.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2025 Lambda
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see .
+ */
+
+package com.lambda.interaction.request.rotating.visibilty
+
+import com.lambda.interaction.request.rotating.Rotation.Companion.dist
+import com.lambda.interaction.request.rotating.RotationManager
+import com.lambda.util.Describable
+import com.lambda.util.NamedEnum
+import com.lambda.util.math.distSq
+import com.lambda.util.math.times
+
+enum class PointSelection(
+    override val displayName: String,
+    override val description: String,
+    val select: (Collection) -> VisibilityChecker.CheckedHit?
+) : NamedEnum, Describable {
+    ByRotation(
+        "By Rotation",
+        "Choose the point that needs the least rotation from your current view (minimal camera turn).",
+        select = { hits ->
+            hits.minByOrNull { RotationManager.activeRotation dist it.targetRotation }
+        }
+    ),
+    Optimum(
+        "Optimum",
+        "Choose the point closest to the average of all candidates (balanced and stable aim).",
+        select = { hits ->
+            val optimum = hits
+                .mapNotNull { it.hit.pos }
+                .reduceOrNull { acc, pos -> acc.add(pos) }
+                ?.times(1 / hits.size.toDouble())
+
+            optimum?.let { center ->
+                hits.minByOrNull { it.hit.pos?.distSq(center) ?: 0.0 }
+            }
+        }
+    )
+}
diff --git a/src/main/kotlin/com/lambda/interaction/request/rotation/visibilty/RequestedHit.kt b/src/main/kotlin/com/lambda/interaction/request/rotating/visibilty/RequestedHit.kt
similarity index 90%
rename from src/main/kotlin/com/lambda/interaction/request/rotation/visibilty/RequestedHit.kt
rename to src/main/kotlin/com/lambda/interaction/request/rotating/visibilty/RequestedHit.kt
index a0158d23b..0a8b5910e 100644
--- a/src/main/kotlin/com/lambda/interaction/request/rotation/visibilty/RequestedHit.kt
+++ b/src/main/kotlin/com/lambda/interaction/request/rotating/visibilty/RequestedHit.kt
@@ -15,11 +15,11 @@
  * along with this program.  If not, see .
  */
 
-package com.lambda.interaction.request.rotation.visibilty
+package com.lambda.interaction.request.rotating.visibilty
 
-import com.lambda.interaction.request.rotation.Rotation
-import com.lambda.interaction.request.rotation.RotationManager
-import com.lambda.interaction.request.rotation.visibilty.VisibilityChecker.ALL_SIDES
+import com.lambda.interaction.request.rotating.Rotation
+import com.lambda.interaction.request.rotating.RotationManager
+import com.lambda.interaction.request.rotating.visibilty.VisibilityChecker.ALL_SIDES
 import com.lambda.threading.runSafe
 import com.lambda.util.BlockUtils.blockState
 import com.lambda.util.world.raycast.RayCastUtils.blockResult
@@ -60,7 +60,7 @@ abstract class RequestedHit {
      *
      * @return True if the hit is valid, false otherwise.
      */
-    fun verifyRotation(rotation: Rotation = RotationManager.currentRotation) =
+    fun verifyRotation(rotation: Rotation = RotationManager.activeRotation) =
         rotation.rayCast(reach)?.let { verifyHit(it) } ?: false
 
     /**
@@ -69,7 +69,7 @@ abstract class RequestedHit {
      * @return [HitResult] if passed, null otherwise.
      */
     fun hitIfValid() =
-        RotationManager.currentRotation.rayCast(reach)?.let {
+        RotationManager.activeRotation.rayCast(reach)?.let {
             if (!verifyHit(it)) null else it
         }
 
diff --git a/src/main/kotlin/com/lambda/interaction/request/rotation/visibilty/RequestedHitDsl.kt b/src/main/kotlin/com/lambda/interaction/request/rotating/visibilty/RequestedHitDsl.kt
similarity index 95%
rename from src/main/kotlin/com/lambda/interaction/request/rotation/visibilty/RequestedHitDsl.kt
rename to src/main/kotlin/com/lambda/interaction/request/rotating/visibilty/RequestedHitDsl.kt
index 2638b787b..0c6230b81 100644
--- a/src/main/kotlin/com/lambda/interaction/request/rotation/visibilty/RequestedHitDsl.kt
+++ b/src/main/kotlin/com/lambda/interaction/request/rotating/visibilty/RequestedHitDsl.kt
@@ -15,7 +15,7 @@
  * along with this program.  If not, see .
  */
 
-package com.lambda.interaction.request.rotation.visibilty
+package com.lambda.interaction.request.rotating.visibilty
 
 import net.minecraft.entity.LivingEntity
 import net.minecraft.util.math.BlockPos
diff --git a/src/main/kotlin/com/lambda/interaction/request/rotation/visibilty/RotationTarget.kt b/src/main/kotlin/com/lambda/interaction/request/rotating/visibilty/RotationTarget.kt
similarity index 70%
rename from src/main/kotlin/com/lambda/interaction/request/rotation/visibilty/RotationTarget.kt
rename to src/main/kotlin/com/lambda/interaction/request/rotating/visibilty/RotationTarget.kt
index f77f2915a..74ff1c42a 100644
--- a/src/main/kotlin/com/lambda/interaction/request/rotation/visibilty/RotationTarget.kt
+++ b/src/main/kotlin/com/lambda/interaction/request/rotating/visibilty/RotationTarget.kt
@@ -15,14 +15,14 @@
  * along with this program.  If not, see .
  */
 
-package com.lambda.interaction.request.rotation.visibilty
+package com.lambda.interaction.request.rotating.visibilty
 
 import com.lambda.context.SafeContext
-import com.lambda.interaction.request.rotation.Rotation
-import com.lambda.interaction.request.rotation.Rotation.Companion.dist
-import com.lambda.interaction.request.rotation.RotationConfig
-import com.lambda.interaction.request.rotation.RotationManager
-import com.lambda.interaction.request.rotation.RotationRequest
+import com.lambda.interaction.request.rotating.Rotation
+import com.lambda.interaction.request.rotating.Rotation.Companion.dist
+import com.lambda.interaction.request.rotating.RotationConfig
+import com.lambda.interaction.request.rotating.RotationManager
+import com.lambda.interaction.request.rotating.RotationRequest
 import com.lambda.threading.runSafe
 import com.lambda.util.collections.updatableLazy
 
@@ -35,7 +35,7 @@ import com.lambda.util.collections.updatableLazy
  */
 data class RotationTarget(
     val hit: RequestedHit? = null,
-    val verify: RotationTarget.() -> Boolean = { hit?.verifyRotation() ?: true },
+    val verify: () -> Boolean = { hit?.verifyRotation() ?: true },
     private val buildRotation: SafeContext.() -> Rotation?,
 ) {
     val targetRotation = updatableLazy {
@@ -43,7 +43,7 @@ data class RotationTarget(
     }
 
     val angleDistance get() = runSafe {
-        targetRotation.value?.dist(RotationManager.currentRotation)
+        targetRotation.value?.dist(RotationManager.serverRotation)
     } ?: 1000.0
 
     /**
@@ -52,6 +52,6 @@ data class RotationTarget(
      * @param config The rotation configuration.
      * @return [RotationRequest] containing this [RotationTarget].
      */
-    fun requestBy(config: RotationConfig) =
-        config.request(RotationRequest(this, config))
+    fun requestBy(config: RotationConfig, queueIfClosed: Boolean = true) =
+        RotationRequest(this, config).submit(queueIfClosed)
 }
diff --git a/src/main/kotlin/com/lambda/interaction/request/rotation/visibilty/RotationTargets.kt b/src/main/kotlin/com/lambda/interaction/request/rotating/visibilty/RotationTargets.kt
similarity index 81%
rename from src/main/kotlin/com/lambda/interaction/request/rotation/visibilty/RotationTargets.kt
rename to src/main/kotlin/com/lambda/interaction/request/rotating/visibilty/RotationTargets.kt
index 01c7eaaba..62c934a5e 100644
--- a/src/main/kotlin/com/lambda/interaction/request/rotation/visibilty/RotationTargets.kt
+++ b/src/main/kotlin/com/lambda/interaction/request/rotating/visibilty/RotationTargets.kt
@@ -15,17 +15,18 @@
  * along with this program.  If not, see .
  */
 
-package com.lambda.interaction.request.rotation.visibilty
+package com.lambda.interaction.request.rotating.visibilty
 
 import com.lambda.config.groups.InteractionConfig
 import com.lambda.context.SafeContext
 import com.lambda.interaction.construction.verify.SurfaceScan
-import com.lambda.interaction.request.rotation.Rotation
-import com.lambda.interaction.request.rotation.Rotation.Companion.dist
-import com.lambda.interaction.request.rotation.RotationManager
-import com.lambda.interaction.request.rotation.visibilty.VisibilityChecker.ALL_SIDES
-import com.lambda.interaction.request.rotation.visibilty.VisibilityChecker.findRotation
+import com.lambda.interaction.request.rotating.Rotation
+import com.lambda.interaction.request.rotating.Rotation.Companion.dist
+import com.lambda.interaction.request.rotating.RotationManager
+import com.lambda.interaction.request.rotating.visibilty.VisibilityChecker.ALL_SIDES
+import com.lambda.interaction.request.rotating.visibilty.VisibilityChecker.findRotation
 import com.lambda.module.modules.client.TaskFlowModule
+import com.lambda.util.extension.rotation
 import com.lambda.util.world.raycast.InteractionMask
 import net.minecraft.entity.LivingEntity
 import net.minecraft.util.hit.BlockHitResult
@@ -47,9 +48,18 @@ annotation class RotationDsl
 @RotationDsl
 fun lookAt(angle: Rotation, maxAngleDistance: Double = 10.0) =
     RotationTarget(null, {
-        RotationManager.currentRotation dist angle < maxAngleDistance
+        RotationManager.activeRotation dist angle < maxAngleDistance
     }) { angle }
 
+@RotationDsl
+fun lookInDirection(direction: PlaceDirection) =
+    RotationTarget(null, {
+        PlaceDirection.fromRotation(RotationManager.activeRotation) == direction
+    }) {
+        if (!direction.isInArea(player.rotation)) direction.snapToArea(RotationManager.activeRotation)
+        else player.rotation
+    }
+
 /**
  * Creates a [RotationTarget] based on a requested hit, but doesn't build the rotation.
  *
@@ -73,7 +83,7 @@ fun lookAtHit(hit: RequestedHit, rotation: SafeContext.() -> Rotation?) =
 @RotationDsl
 fun lookAtHit(
     hit: HitResult,
-    config: InteractionConfig = TaskFlowModule.interact,
+    config: InteractionConfig = TaskFlowModule.interaction,
 ): RotationTarget? {
     return when (hit) {
         is BlockHitResult -> lookAtBlock(hit.blockPos, setOf(hit.side), SurfaceScan.DEFAULT, config)
@@ -92,7 +102,7 @@ fun lookAtHit(
 @RotationDsl
 fun lookAtEntity(
     entity: LivingEntity,
-    config: InteractionConfig = TaskFlowModule.interact
+    config: InteractionConfig = TaskFlowModule.interaction
 ): RotationTarget {
     val requestedHit = entityHit(entity, config.attackReach)
 
@@ -122,7 +132,7 @@ fun lookAtBlock(
     pos: BlockPos,
     sides: Set = ALL_SIDES,
     surfaceScan: SurfaceScan = SurfaceScan.DEFAULT,
-    config: InteractionConfig = TaskFlowModule.interact,
+    config: InteractionConfig = TaskFlowModule.interaction,
 ): RotationTarget {
     val requestedHit = blockHit(pos, sides, config.interactReach)
 
diff --git a/src/main/kotlin/com/lambda/interaction/request/rotation/visibilty/VisibilityChecker.kt b/src/main/kotlin/com/lambda/interaction/request/rotating/visibilty/VisibilityChecker.kt
similarity index 81%
rename from src/main/kotlin/com/lambda/interaction/request/rotation/visibilty/VisibilityChecker.kt
rename to src/main/kotlin/com/lambda/interaction/request/rotating/visibilty/VisibilityChecker.kt
index c8a21f013..b50b46e66 100644
--- a/src/main/kotlin/com/lambda/interaction/request/rotation/visibilty/VisibilityChecker.kt
+++ b/src/main/kotlin/com/lambda/interaction/request/rotating/visibilty/VisibilityChecker.kt
@@ -15,15 +15,15 @@
  * along with this program.  If not, see .
  */
 
-package com.lambda.interaction.request.rotation.visibilty
+package com.lambda.interaction.request.rotating.visibilty
 
 import com.lambda.config.groups.InteractionConfig
 import com.lambda.context.SafeContext
 import com.lambda.interaction.construction.verify.ScanMode
 import com.lambda.interaction.construction.verify.SurfaceScan
-import com.lambda.interaction.request.rotation.Rotation
-import com.lambda.interaction.request.rotation.Rotation.Companion.rotationTo
-import com.lambda.interaction.request.rotation.RotationManager
+import com.lambda.interaction.request.rotating.Rotation
+import com.lambda.interaction.request.rotating.Rotation.Companion.rotationTo
+import com.lambda.interaction.request.rotating.RotationManager
 import com.lambda.module.modules.client.TaskFlowModule
 import com.lambda.util.extension.component6
 import com.lambda.util.math.distSq
@@ -34,6 +34,7 @@ import net.minecraft.util.math.Box
 import net.minecraft.util.math.Direction
 import net.minecraft.util.math.Vec3d
 import java.util.*
+import kotlin.math.floor
 import kotlin.math.pow
 
 /**
@@ -64,7 +65,7 @@ object VisibilityChecker {
         interaction: InteractionConfig,
         verify: CheckedHit.() -> Boolean
     ): CheckedHit? {
-        val currentRotation = RotationManager.currentRotation
+        val currentRotation = RotationManager.activeRotation
 
         if (boxes.any { it.contains(eye) }) {
             currentRotation.rayCast(reach, eye)?.let { hit ->
@@ -165,54 +166,66 @@ object VisibilityChecker {
      */
     fun scanSurfaces(
         box: Box,
-        excludedSides: Set = emptySet(),
+        sides: Set = emptySet(),
         resolution: Int = 5,
         scan: SurfaceScan = SurfaceScan.DEFAULT,
         check: (Direction, Vec3d) -> Unit,
     ) {
-        excludedSides.forEach { side ->
-            if (excludedSides.isNotEmpty() && side !in excludedSides) return@forEach
+        sides.forEach { side ->
             val (minX, minY, minZ, maxX, maxY, maxZ) = box.contract(TaskFlowModule.shrinkFactor).bounds(side)
-            val stepX = (maxX - minX) / resolution
-            val stepY = (maxY - minY) / resolution
-            val stepZ = (maxZ - minZ) / resolution
 
-            // Determine the bounds to scan based on the axis and mode
-            val (startX, endX) = if (scan.axis == Direction.Axis.X && stepX != 0.0) {
-                val centerX = (minX + maxX) / 2
+            // Determine the bounds to scan based on the axis and mode. Skip if no part of the face is in the desired bounds
+            val (startX, endX) = if (scan.axis == Direction.Axis.X && maxX != minX) {
                 when (scan.mode) {
-                    ScanMode.GREATER_HALF -> centerX + 0.01 to maxX
-                    ScanMode.LESSER_HALF -> minX to centerX - 0.01
+                    ScanMode.GREATER_BLOCK_HALF -> (floor(minX) + 0.501).let { center ->
+                        if (maxX < center) return@forEach
+                        minX.coerceAtLeast(center) to maxX
+                    }
+                    ScanMode.LESSER_BLOCK_HALF -> (floor(maxX) + 0.499).let { center ->
+                        if (minX > center) return@forEach
+                        minX to maxX.coerceAtMost(center)
+                    }
                     ScanMode.FULL -> minX to maxX
                 }
             } else minX to maxX
 
-            val (startY, endY) = if (scan.axis == Direction.Axis.Y && stepY != 0.0) {
-                val centerY = (minY + maxY) / 2
+            val (startY, endY) = if (scan.axis == Direction.Axis.Y && maxY != minY) {
                 when (scan.mode) {
-                    ScanMode.GREATER_HALF -> centerY + 0.01 to maxY
-                    ScanMode.LESSER_HALF -> minY to centerY - 0.01
+                    ScanMode.GREATER_BLOCK_HALF -> (floor(minY) + 0.501).let { center ->
+                        if (maxY < center) return@forEach
+                        minY.coerceAtLeast(center) to maxY
+                    }
+                    ScanMode.LESSER_BLOCK_HALF -> (floor(maxY) + 0.499).let { center ->
+                        if (minY > center) return@forEach
+                        minY to maxY.coerceAtMost(center)
+                    }
                     ScanMode.FULL -> minY to maxY
                 }
             } else minY to maxY
 
-            val (startZ, endZ) = if (scan.axis == Direction.Axis.Z && stepZ != 0.0) {
-                val centerZ = (minZ + maxZ) / 2
+            val (startZ, endZ) = if (scan.axis == Direction.Axis.Z && maxZ != minZ) {
                 when (scan.mode) {
-                    ScanMode.GREATER_HALF -> centerZ + 0.01 to maxZ
-                    ScanMode.LESSER_HALF -> minZ to centerZ - 0.01
+                    ScanMode.GREATER_BLOCK_HALF -> (floor(minZ) + 0.501).let { center ->
+                        if (maxZ < center) return@forEach
+                        minZ.coerceAtLeast(center) to maxZ
+                    }
+                    ScanMode.LESSER_BLOCK_HALF -> (floor(maxZ) + 0.499).let { center ->
+                        if (minZ > center) return@forEach
+                        minZ to maxZ.coerceAtMost(center)
+                    }
                     ScanMode.FULL -> minZ to maxZ
                 }
             } else minZ to maxZ
 
-            (0..resolution).forEach outer@{ i ->
-                val x = if (stepX != 0.0) startX + stepX * i else startX
-                if (x > endX) return@outer
-                (0..resolution).forEach inner@{ j ->
-                    val y = if (stepY != 0.0) startY + stepY * j else startY
-                    if (y > endY) return@inner
+            val stepX = (endX - startX) / resolution
+            val stepY = (endY - startY) / resolution
+            val stepZ = (endZ - startZ) / resolution
+
+            (0..resolution).forEach outer@ { i ->
+                val x = if (stepX != 0.0) startX + (stepX * i) else startX
+                (0..resolution).forEach inner@ { j ->
+                    val y = if (stepY != 0.0) startY + (stepY * j) else startY
                     val z = if (stepZ != 0.0) startZ + stepZ * ((if (stepX != 0.0) j else i)) else startZ
-                    if (z > endZ) return@inner
                     check(side, Vec3d(x, y, z))
                 }
             }
diff --git a/src/main/kotlin/com/lambda/interaction/request/rotation/visibilty/PointSelection.kt b/src/main/kotlin/com/lambda/interaction/request/rotation/visibilty/PointSelection.kt
deleted file mode 100644
index 39caf5c0c..000000000
--- a/src/main/kotlin/com/lambda/interaction/request/rotation/visibilty/PointSelection.kt
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright 2025 Lambda
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see .
- */
-
-package com.lambda.interaction.request.rotation.visibilty
-
-import com.lambda.interaction.request.rotation.Rotation.Companion.dist
-import com.lambda.interaction.request.rotation.RotationManager
-import com.lambda.util.math.distSq
-
-enum class PointSelection(val select: (MutableList) -> VisibilityChecker.CheckedHit?) {
-    ByRotation({ hits ->
-        hits.minByOrNull {
-            RotationManager.currentRotation dist it.targetRotation
-        }
-    }),
-    Optimum( optimum@ { hits ->
-        val optimum = hits.map { it.hit.pos }.reduceOrNull { acc, vec3d ->
-            acc.add(vec3d)
-        }?.multiply(1.0 / hits.size.toDouble()) ?: return@optimum null
-
-        hits.minByOrNull {
-            it.hit.pos distSq optimum
-        }
-    })
-}
\ No newline at end of file
diff --git a/src/main/kotlin/com/lambda/module/modules/client/Baritone.kt b/src/main/kotlin/com/lambda/module/modules/client/Baritone.kt
index f57f0159c..ba73b1609 100644
--- a/src/main/kotlin/com/lambda/module/modules/client/Baritone.kt
+++ b/src/main/kotlin/com/lambda/module/modules/client/Baritone.kt
@@ -32,5 +32,5 @@ object Baritone : Module(
         ROTATION("Rotation")
     }
 
-    val rotation = RotationSettings(this, Group.ROTATION, Int.MAX_VALUE)
+    val rotation = RotationSettings(this, Group.ROTATION)
 }
diff --git a/src/main/kotlin/com/lambda/module/modules/client/TaskFlowModule.kt b/src/main/kotlin/com/lambda/module/modules/client/TaskFlowModule.kt
index 4a865b616..870d91674 100644
--- a/src/main/kotlin/com/lambda/module/modules/client/TaskFlowModule.kt
+++ b/src/main/kotlin/com/lambda/module/modules/client/TaskFlowModule.kt
@@ -18,6 +18,8 @@
 package com.lambda.module.modules.client
 
 import com.lambda.config.groups.BuildSettings
+import com.lambda.config.groups.HotbarSettings
+import com.lambda.config.groups.InteractSettings
 import com.lambda.config.groups.InteractionSettings
 import com.lambda.config.groups.InventorySettings
 import com.lambda.config.groups.RotationSettings
@@ -39,16 +41,19 @@ object TaskFlowModule : Module(
         Rotation("Rotation"),
         Interaction("Interaction"),
         Inventory("Inventory"),
+        Hotbar("Hotbar"),
         Debug("Debug")
     }
 
     val build = BuildSettings(this, Group.Build)
     val rotation = RotationSettings(this, Group.Rotation)
-    val interact = InteractionSettings(this, Group.Interaction, InteractionMask.Both)
+    val interaction = InteractionSettings(this, Group.Interaction, InteractionMask.Both)
     val inventory = InventorySettings(this, Group.Inventory)
+    val hotbar = HotbarSettings(this, Group.Hotbar)
 
     val showAllEntries by setting("Show All Entries", false, "Show all entries in the task tree").group(Group.Debug)
     val shrinkFactor by setting("Shrink Factor", 0.001, 0.0..1.0, 0.001).group(Group.Debug)
+    val ignoreItemDropWarnings by setting("Ignore Drop Warnings", false, "Hides the item drop warnings from the break manager").group(Group.Debug)
 
     @Volatile
     var drawables = listOf()
diff --git a/src/main/kotlin/com/lambda/module/modules/combat/Criticals.kt b/src/main/kotlin/com/lambda/module/modules/combat/Criticals.kt
index 82ccd963f..7df4e35e1 100644
--- a/src/main/kotlin/com/lambda/module/modules/combat/Criticals.kt
+++ b/src/main/kotlin/com/lambda/module/modules/combat/Criticals.kt
@@ -20,8 +20,8 @@ package com.lambda.module.modules.combat
 import com.lambda.context.SafeContext
 import com.lambda.event.events.PlayerEvent
 import com.lambda.event.listener.SafeListener.Companion.listen
-import com.lambda.interaction.request.rotation.Rotation
-import com.lambda.interaction.request.rotation.Rotation.Companion.rotationTo
+import com.lambda.interaction.request.rotating.Rotation
+import com.lambda.interaction.request.rotating.Rotation.Companion.rotationTo
 import com.lambda.module.Module
 import com.lambda.module.tag.ModuleTag
 import com.lambda.util.extension.rotation
diff --git a/src/main/kotlin/com/lambda/module/modules/combat/CrystalAura.kt b/src/main/kotlin/com/lambda/module/modules/combat/CrystalAura.kt
index b9aba1911..8b08bef13 100644
--- a/src/main/kotlin/com/lambda/module/modules/combat/CrystalAura.kt
+++ b/src/main/kotlin/com/lambda/module/modules/combat/CrystalAura.kt
@@ -30,10 +30,10 @@ import com.lambda.graphics.gl.Matrices.buildWorldProjection
 import com.lambda.graphics.gl.Matrices.withVertexTransform
 import com.lambda.graphics.renderer.gui.FontRenderer
 import com.lambda.graphics.renderer.gui.FontRenderer.drawString
-import com.lambda.interaction.request.rotation.Rotation.Companion.rotationTo
-import com.lambda.interaction.request.rotation.RotationManager
-import com.lambda.interaction.request.rotation.visibilty.VisibilityChecker.getVisibleSurfaces
-import com.lambda.interaction.request.rotation.visibilty.lookAt
+import com.lambda.interaction.request.rotating.Rotation.Companion.rotationTo
+import com.lambda.interaction.request.rotating.RotationManager
+import com.lambda.interaction.request.rotating.visibilty.VisibilityChecker.getVisibleSurfaces
+import com.lambda.interaction.request.rotating.visibilty.lookAt
 import com.lambda.module.Module
 import com.lambda.module.tag.ModuleTag
 import com.lambda.threading.runSafe
@@ -461,7 +461,7 @@ object CrystalAura : Module(
                 if (side.axis != Direction.Axis.Y) vec += Vec3d(0.0, 0.45, 0.0)
 
                 player.eyePos.rotationTo(vec)
-            } ?: RotationManager.currentRotation
+            } ?: RotationManager.activeRotation
         }
 
         /**
diff --git a/src/main/kotlin/com/lambda/module/modules/combat/KillAura.kt b/src/main/kotlin/com/lambda/module/modules/combat/KillAura.kt
index bd782287f..f7043ba24 100644
--- a/src/main/kotlin/com/lambda/module/modules/combat/KillAura.kt
+++ b/src/main/kotlin/com/lambda/module/modules/combat/KillAura.kt
@@ -17,6 +17,7 @@
 
 package com.lambda.module.modules.combat
 
+import com.lambda.config.groups.InteractSettings
 import com.lambda.config.groups.InteractionSettings
 import com.lambda.config.groups.RotationSettings
 import com.lambda.config.groups.Targeting
@@ -24,22 +25,22 @@ import com.lambda.context.SafeContext
 import com.lambda.event.events.PlayerPacketEvent
 import com.lambda.event.events.TickEvent
 import com.lambda.event.listener.SafeListener.Companion.listen
-import com.lambda.interaction.material.StackSelection.Companion.select
+import com.lambda.interaction.material.StackSelection.Companion.selectStack
 import com.lambda.interaction.material.container.ContainerManager.transfer
 import com.lambda.interaction.material.container.containers.MainHandContainer
-import com.lambda.interaction.request.rotation.RotationManager
-import com.lambda.interaction.request.rotation.visibilty.lookAtEntity
+import com.lambda.interaction.request.rotating.RotationManager
+import com.lambda.interaction.request.rotating.visibilty.lookAtEntity
 import com.lambda.module.Module
 import com.lambda.module.tag.ModuleTag
 import com.lambda.task.RootTask.run
 import com.lambda.util.NamedEnum
 import com.lambda.util.item.ItemStackUtils.attackDamage
-import com.lambda.util.item.ItemStackUtils.itemAttackSpeed
+import com.lambda.util.item.ItemStackUtils.attackSpeed
 import com.lambda.util.math.random
-import com.lambda.util.player.SlotUtils.combined
 import com.lambda.util.world.raycast.InteractionMask
 import com.lambda.util.world.raycast.RayCastUtils.entityResult
 import net.minecraft.entity.LivingEntity
+import net.minecraft.registry.tag.ItemTags
 import net.minecraft.util.Hand
 import net.minecraft.util.math.Vec3d
 
@@ -49,7 +50,8 @@ object KillAura : Module(
     tag = ModuleTag.COMBAT,
 ) {
     // Interact
-    private val interactionSettings = InteractionSettings(this, Group.Interact, InteractionMask.Entity)
+    private val interactionSettings = InteractionSettings(this, Group.Interaction, InteractionMask.Entity)
+    private val interactSettings = InteractSettings(this, listOf(Group.Interact))
     private val swap by setting("Swap", true, "Swap to the item with the highest damage")
     private val attackMode by setting("Attack Mode", AttackMode.Cooldown).group(Group.Interact)
     private val cooldownOffset by setting("Cooldown Offset", 0, -5..5, 1) { attackMode == AttackMode.Cooldown }.group(Group.Interact)
@@ -78,6 +80,7 @@ object KillAura : Module(
     private var onGroundTicks = 0
 
     enum class Group(override val displayName: String): NamedEnum {
+        Interaction("Interaction"),
         Interact("Interact"),
         Targeting("Targeting"),
         Aiming("Aiming")
@@ -98,9 +101,10 @@ object KillAura : Module(
         listen {
             target?.let { entity ->
                 if (swap) {
-                    val selection = player.combined
-                        .maxBy { stack -> stack.attackDamage } // ToDo: Write our own enchantment utils
-                        .select()
+                    val selection = selectStack(
+                        sorter = compareByDescending { attackDamage(stack = it) }
+                    ) { isTag(ItemTags.SWORDS) }
+
 
                     if (!selection.selector(player.mainHandStack)) {
                         selection.transfer(MainHandContainer)
@@ -126,7 +130,7 @@ object KillAura : Module(
         // Cooldown check
         when (attackMode) {
             AttackMode.Cooldown -> {
-                if (player.lastAttackedTicks < 20/player.itemAttackSpeed + cooldownOffset) return
+                if (player.lastAttackedTicks < 20/player.attackSpeed() + cooldownOffset) return
             }
 
             AttackMode.Delay -> {
@@ -137,7 +141,7 @@ object KillAura : Module(
         // Rotation check
         run {
             if (!rotate) return@run
-            val angle = RotationManager.currentRotation
+            val angle = RotationManager.activeRotation
 
             if (interactionSettings.strictRayCast) {
                 val cast = angle.rayCast(interactionSettings.attackReach)
@@ -150,7 +154,7 @@ object KillAura : Module(
 
         // Attack
         interaction.attackEntity(player, target)
-        if (interactionSettings.swingHand) player.swingHand(Hand.MAIN_HAND)
+        if (interactSettings.swingHand) player.swingHand(Hand.MAIN_HAND)
 
         lastAttackTime = System.currentTimeMillis()
         hitDelay = (hitDelay1..hitDelay2).random() * 50
diff --git a/src/main/kotlin/com/lambda/module/modules/debug/PropertyPrinter.kt b/src/main/kotlin/com/lambda/module/modules/debug/PropertyPrinter.kt
new file mode 100644
index 000000000..3f74a3765
--- /dev/null
+++ b/src/main/kotlin/com/lambda/module/modules/debug/PropertyPrinter.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2025 Lambda
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see .
+ */
+
+package com.lambda.module.modules.debug
+
+import com.lambda.module.Module
+import com.lambda.module.tag.ModuleTag
+import com.lambda.util.FolderRegister
+import com.lambda.util.extension.resolveFile
+import net.minecraft.block.Block
+import net.minecraft.block.Blocks
+
+object PropertyPrinter : Module(
+    name = "PropertyPrinter",
+    description = "Prints all properties coupled with all the states that use them into a text file",
+    tag = ModuleTag.DEBUG,
+) {
+    init {
+        onEnable {
+            val file = FolderRegister.lambda.resolve("property-print").resolveFile("property-print.txt")
+            file.parentFile.mkdirs()
+            file.writeText("")
+            StateInfo.propertyFields.forEach properties@ { property ->
+                file.appendText("${property.value.name}\n")
+                Blocks::class.java.declaredFields.forEach blocks@ { field ->
+                    field.isAccessible = true
+                    val block = field.get(null)
+                    if (!Block::class.java.isAssignableFrom(block::class.java)) return@blocks
+                    if (property.key in (block as Block).defaultState.properties) {
+                        file.appendText("    $block\n")
+                    }
+                }
+                file.appendText("\n\n\n\n\n")
+            }
+            disable()
+        }
+    }
+}
diff --git a/src/main/kotlin/com/lambda/module/modules/debug/SilentSwap.kt b/src/main/kotlin/com/lambda/module/modules/debug/SilentSwap.kt
new file mode 100644
index 000000000..80215d153
--- /dev/null
+++ b/src/main/kotlin/com/lambda/module/modules/debug/SilentSwap.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2025 Lambda
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see .
+ */
+
+package com.lambda.module.modules.debug
+
+import com.lambda.config.groups.HotbarSettings
+import com.lambda.event.events.PlayerEvent
+import com.lambda.event.listener.SafeListener.Companion.listen
+import com.lambda.interaction.request.Request.Companion.submit
+import com.lambda.interaction.request.hotbar.HotbarRequest
+import com.lambda.module.Module
+import com.lambda.module.tag.ModuleTag
+import com.lambda.util.Communication.info
+import com.lambda.util.NamedEnum
+
+object SilentSwap : Module(
+    name = "SilentSwap",
+    description = "SilentSwap",
+    tag = ModuleTag.DEBUG,
+) {
+    private enum class Group(override val displayName: String) : NamedEnum {
+        Hotbar("Hotbar")
+    }
+
+    private val hotbar = HotbarSettings(this, Group.Hotbar)
+
+    init {
+        listen {
+            if (!submit(HotbarRequest(0, hotbar)).done) {
+                it.cancel()
+                return@listen
+            }
+            info("${interaction.lastSelectedSlot} ${player.mainHandStack}")
+        }
+    }
+}
diff --git a/src/main/kotlin/com/lambda/module/modules/debug/StateInfo.kt b/src/main/kotlin/com/lambda/module/modules/debug/StateInfo.kt
new file mode 100644
index 000000000..95a6f3ff6
--- /dev/null
+++ b/src/main/kotlin/com/lambda/module/modules/debug/StateInfo.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2025 Lambda
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see .
+ */
+
+package com.lambda.module.modules.debug
+
+import com.lambda.event.events.KeyboardEvent
+import com.lambda.event.listener.SafeListener.Companion.listen
+import com.lambda.module.Module
+import com.lambda.module.tag.ModuleTag
+import com.lambda.util.BlockUtils.blockState
+import com.lambda.util.Communication.info
+import com.lambda.util.KeyCode
+import net.minecraft.block.BlockState
+import net.minecraft.state.property.Properties
+import net.minecraft.state.property.Property
+import net.minecraft.util.hit.BlockHitResult
+
+object StateInfo : Module(
+    name = "StateInfo",
+    description = "Prints the target block's state into chat",
+    tag = ModuleTag.DEBUG,
+) {
+    private val printBind by setting("Print", KeyCode.UNBOUND, "The bind used to print the info to chat")
+
+    val propertyFields = Properties::class.java.declaredFields
+        .filter { Property::class.java.isAssignableFrom(it.type) }
+        .associateBy { it.get(null) as Property<*> }
+
+    init {
+        listen { event ->
+            if (!event.isPressed) return@listen
+            if (event.keyCode != printBind.keyCode) return@listen
+            val crosshair = mc.crosshairTarget ?: return@listen
+            if (crosshair !is BlockHitResult) return@listen
+            info(blockState(crosshair.blockPos).betterToString())
+        }
+    }
+
+    private fun BlockState.betterToString(): String {
+        val stringBuilder = StringBuilder()
+        stringBuilder.append(this.owner.toString() + "\n")
+
+        if (entries.isNotEmpty()) {
+            stringBuilder.append("      [\n")
+
+            stringBuilder.append(
+                entries.entries.joinToString("\n") { (property, value) ->
+                    val fieldName = propertyFields[property]?.name ?: property.toString()
+                    "          $fieldName = ${nameValue(property, value)}"
+                }
+            )
+
+            stringBuilder.append("\n      ]")
+        }
+
+        return stringBuilder.toString()
+    }
+
+    @Suppress("UNCHECKED_CAST")
+    private fun ?> nameValue(property: Property, value: Comparable<*>): String {
+        return property.name(value as T)
+    }
+}
diff --git a/src/main/kotlin/com/lambda/module/modules/movement/Speed.kt b/src/main/kotlin/com/lambda/module/modules/movement/Speed.kt
index 3124beee6..5a28a8c7f 100644
--- a/src/main/kotlin/com/lambda/module/modules/movement/Speed.kt
+++ b/src/main/kotlin/com/lambda/module/modules/movement/Speed.kt
@@ -21,11 +21,11 @@ import com.lambda.context.SafeContext
 import com.lambda.event.events.ClientEvent
 import com.lambda.event.events.MovementEvent
 import com.lambda.event.listener.SafeListener.Companion.listen
-import com.lambda.interaction.request.rotation.Rotation
-import com.lambda.interaction.request.rotation.RotationConfig
-import com.lambda.interaction.request.rotation.RotationManager.onRotate
-import com.lambda.interaction.request.rotation.RotationMode
-import com.lambda.interaction.request.rotation.visibilty.lookAt
+import com.lambda.interaction.request.rotating.Rotation
+import com.lambda.interaction.request.rotating.RotationConfig
+import com.lambda.interaction.request.rotating.RotationManager.onRotate
+import com.lambda.interaction.request.rotating.RotationMode
+import com.lambda.interaction.request.rotating.visibilty.lookAt
 import com.lambda.module.Module
 import com.lambda.module.tag.ModuleTag
 import com.lambda.util.NamedEnum
@@ -69,7 +69,7 @@ object Speed : Module(
     private val ncpTimerBoost by setting("Timer Boost", 1.08, 1.0..1.1, 0.01) { mode == Mode.NCP_STRAFE }
 
     // Grim
-    private val rotationConfig = RotationConfig.Instant(RotationMode.Sync, Int.MIN_VALUE + 1)
+    private val rotationConfig = RotationConfig.Instant(RotationMode.Sync)
 
     private var prevTickJumping = false
 
diff --git a/src/main/kotlin/com/lambda/module/modules/movement/TargetStrafe.kt b/src/main/kotlin/com/lambda/module/modules/movement/TargetStrafe.kt
index 99374db48..b6e4e6260 100644
--- a/src/main/kotlin/com/lambda/module/modules/movement/TargetStrafe.kt
+++ b/src/main/kotlin/com/lambda/module/modules/movement/TargetStrafe.kt
@@ -20,7 +20,7 @@ package com.lambda.module.modules.movement
 import com.lambda.event.events.RotationEvent
 import com.lambda.event.events.TickEvent
 import com.lambda.event.listener.SafeListener.Companion.listen
-import com.lambda.interaction.request.rotation.Rotation.Companion.rotationTo
+import com.lambda.interaction.request.rotating.Rotation.Companion.rotationTo
 import com.lambda.module.Module
 import com.lambda.module.modules.combat.KillAura
 import com.lambda.module.tag.ModuleTag
diff --git a/src/main/kotlin/com/lambda/module/modules/player/AntiAim.kt b/src/main/kotlin/com/lambda/module/modules/player/AntiAim.kt
new file mode 100644
index 000000000..d7dce379d
--- /dev/null
+++ b/src/main/kotlin/com/lambda/module/modules/player/AntiAim.kt
@@ -0,0 +1,222 @@
+/*
+ * Copyright 2025 Lambda
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see .
+ */
+
+package com.lambda.module.modules.player
+
+import com.lambda.config.groups.RotationSettings
+import com.lambda.context.SafeContext
+import com.lambda.event.events.TickEvent
+import com.lambda.event.events.UpdateManagerEvent
+import com.lambda.event.listener.SafeListener.Companion.listen
+import com.lambda.interaction.request.Request.Companion.submit
+import com.lambda.interaction.request.rotating.Rotation
+import com.lambda.interaction.request.rotating.Rotation.Companion.rotationTo
+import com.lambda.interaction.request.rotating.Rotation.Companion.wrap
+import com.lambda.interaction.request.rotating.RotationRequest
+import com.lambda.interaction.request.rotating.visibilty.lookAt
+import com.lambda.module.Module
+import com.lambda.module.tag.ModuleTag
+import com.lambda.util.NamedEnum
+import com.lambda.util.math.distSq
+import net.minecraft.entity.Entity
+import net.minecraft.entity.player.PlayerEntity
+import net.minecraft.util.math.MathHelper.wrapDegrees
+import kotlin.random.Random
+
+object AntiAim : Module(
+    name = "AntiAim",
+    description = "Rotates the player using the given configs",
+    tag = ModuleTag.MOVEMENT,
+) {
+    private enum class Group(override val displayName: String) : NamedEnum {
+        General("General"),
+        Rotation("Rotation")
+    }
+
+    private val yaw by setting("Yaw Mode", YawMode.Spin, "The mode used when setting the players yaw").group(Group.General)
+        .onValueChange { _, to ->
+            if (to == YawMode.Custom) {
+                // To bypass recursion issue
+                setConfigCustomYaw(player.yaw)
+            }
+        }
+    private val spinMode by setting("Spin Mode", LeftRight.Right) { yaw == YawMode.Spin }.group(Group.General)
+    private val sideMode by setting("Side Mode", LeftRight.Left) { yaw == YawMode.Sideways }.group(Group.General)
+    private var customYaw by setting("Custom Yaw", 0f, -179f..180f, 1f) { yaw == YawMode.Custom }.group(Group.General)
+    private val yawPlayerMode by setting("Yaw Player mode", PlayerMode.Closest) { yaw == YawMode.Player }.group(Group.General)
+
+    private val pitch by setting("Pitch Mode", PitchMode.UpAndDown, "The mode used when setting the players pitch").group(Group.General)
+        .onValueChange { _, to ->
+            if (to == PitchMode.Custom) {
+                // To bypass recursion issue
+                setConfigCustomPitch(player.pitch)
+            }
+        }
+    private val verticalMode by setting("Vertical Mode", VerticalMode.Up) { pitch == PitchMode.Vertical }.group(Group.General)
+    private var customPitch by setting("Custom Pitch", 0f, -90f..90f, 1f) { pitch == PitchMode.Custom }.group(Group.General)
+    private val pitchPlayerMode by setting("Pitch Player Mode", PlayerMode.Closest) { pitch == PitchMode.Player }.group(Group.General)
+
+    private val yawSpeed by setting("Yaw Speed", 30, 1..90, 1, "Yaw rotation degrees per tick", "°") { yaw != YawMode.None }.group(Group.General)
+    private val pitchSpeed by setting("Pitch Speed", 30, 1..90, 1, "Pitch rotation degrees per tick", "°") { pitch != PitchMode.None }.group(Group.General)
+
+    private val rotation = RotationSettings(this, Group.Rotation)
+
+    private var currentYaw = 0.0f
+    private var currentPitch = 0.0f
+
+    private var jitterRight = true
+    private var jitterUp = true
+    private var pitchingUp = true
+
+    init {
+        onEnable {
+            currentYaw = player.yaw
+            currentPitch = player.pitch
+        }
+
+        listen(priority = Int.MAX_VALUE) {
+            currentYaw = wrapDegrees(when (yaw) {
+                YawMode.Spin -> when (spinMode) {
+                    LeftRight.Left -> currentYaw - yawSpeed
+                    LeftRight.Right -> currentYaw + yawSpeed
+                }
+                YawMode.Jitter -> {
+                    val delta = Random.nextFloat() * (yawSpeed)
+                    if (jitterRight) {
+                        jitterRight = false
+                        currentYaw + delta
+                    } else {
+                        jitterRight = true
+                        currentYaw - delta
+                    }
+                }
+                YawMode.Sideways -> when (sideMode) {
+                    LeftRight.Left -> player.yaw - 90
+                    LeftRight.Right -> player.yaw + 90
+                }
+                YawMode.Backwards -> player.yaw - 180
+                YawMode.Custom -> customYaw
+                YawMode.Player -> {
+                    val target = getLookAtPlayer(yawPlayerMode)
+                    target?.let {
+                        player.eyePos.rotationTo(target.eyePos).yawF
+                    } ?: player.yaw
+                }
+                YawMode.None -> player.yaw
+            })
+
+            currentPitch = when (pitch) {
+                PitchMode.UpAndDown -> {
+                    if (pitchingUp) {
+                        (currentPitch - pitchSpeed).also {
+                            if (currentPitch <= -90) {
+                                pitchingUp = false
+                            }
+                        }
+                    } else {
+                        (currentPitch + pitchSpeed).also {
+                            if (currentPitch >= 90) {
+                                pitchingUp = true
+                            }
+                        }
+                    }
+                }
+                PitchMode.Jitter -> {
+                    val delta = Random.nextFloat() * (pitchSpeed)
+                    if (jitterUp) {
+                        jitterUp = false
+                        currentPitch - delta
+                    } else {
+                        jitterUp = true
+                        currentPitch + delta
+                    }
+                }
+                PitchMode.Vertical -> {
+                    when (verticalMode) {
+                        VerticalMode.Up -> -90f
+                        VerticalMode.Down -> 90f
+                    }
+                }
+                PitchMode.Custom -> customPitch
+                PitchMode.Player -> {
+                    val target = getLookAtPlayer(pitchPlayerMode)
+                    target?.let {
+                        player.eyePos.rotationTo(target.eyePos).pitchF
+                    } ?: player.pitch
+                }
+                PitchMode.None -> player.pitch
+            }.coerceIn(-90f..90f)
+        }
+
+        listen(priority = Int.MIN_VALUE) {
+            if (currentYaw == wrap(player.yaw) && currentPitch == player.pitch) return@listen
+            submit(RotationRequest(lookAt(Rotation(currentYaw, currentPitch)), rotation), false)
+        }
+    }
+
+    private fun SafeContext.getLookAtPlayer(playerMode: PlayerMode): Entity? {
+        val players = world.entities.filter { it is PlayerEntity && it != player }
+        return when (playerMode) {
+            PlayerMode.Closest -> players.minByOrNull { it.eyePos distSq player.eyePos }
+            PlayerMode.Farthest -> players.maxByOrNull { it.eyePos distSq player.eyePos }
+            PlayerMode.Random -> players.randomOrNull()
+        }
+    }
+
+    private fun setConfigCustomYaw(newYaw: Float) {
+        customYaw = newYaw
+    }
+
+    private fun setConfigCustomPitch(newPitch: Float) {
+        customPitch = newPitch
+    }
+
+    private enum class YawMode {
+        None,
+        Spin,
+        Jitter,
+        Sideways,
+        Backwards,
+        Custom,
+        Player
+    }
+
+    private enum class LeftRight {
+        Left,
+        Right
+    }
+
+    private enum class PlayerMode {
+        Closest,
+        Farthest,
+        Random
+    }
+
+    private enum class PitchMode {
+        None,
+        UpAndDown,
+        Jitter,
+        Vertical,
+        Custom,
+        Player
+    }
+
+    private enum class VerticalMode {
+        Up,
+        Down
+    }
+}
diff --git a/src/main/kotlin/com/lambda/module/modules/player/FastBreak.kt b/src/main/kotlin/com/lambda/module/modules/player/FastBreak.kt
index b32df887e..2c7cf84fc 100644
--- a/src/main/kotlin/com/lambda/module/modules/player/FastBreak.kt
+++ b/src/main/kotlin/com/lambda/module/modules/player/FastBreak.kt
@@ -17,184 +17,59 @@
 
 package com.lambda.module.modules.player
 
-import com.lambda.context.SafeContext
-import com.lambda.event.events.PacketEvent
+import com.lambda.config.groups.BuildSettings
 import com.lambda.event.events.PlayerEvent
-import com.lambda.event.events.RenderEvent
-import com.lambda.event.events.TickEvent
 import com.lambda.event.listener.SafeListener.Companion.listen
-import com.lambda.graphics.renderer.esp.DynamicAABB
-import com.lambda.graphics.renderer.esp.builders.buildFilled
-import com.lambda.graphics.renderer.esp.builders.buildOutline
-import com.lambda.graphics.renderer.esp.global.DynamicESP
+import com.lambda.interaction.construction.context.BreakContext
+import com.lambda.interaction.construction.context.BuildContext
+import com.lambda.interaction.material.StackSelection.Companion.select
+import com.lambda.interaction.request.breaking.BreakRequest
+import com.lambda.interaction.request.rotating.Rotation.Companion.rotation
+import com.lambda.interaction.request.rotating.RotationRequest
+import com.lambda.interaction.request.rotating.visibilty.lookAt
 import com.lambda.module.Module
-import com.lambda.module.modules.player.FastBreak.Group
+import com.lambda.module.modules.client.TaskFlowModule
 import com.lambda.module.tag.ModuleTag
+import com.lambda.util.BlockUtils.blockState
 import com.lambda.util.NamedEnum
-import com.lambda.util.extension.tickDelta
-import com.lambda.util.math.lerp
-import com.lambda.util.math.transform
-import net.minecraft.network.packet.c2s.play.PlayerActionC2SPacket
-import net.minecraft.network.packet.c2s.play.PlayerActionC2SPacket.Action
-import net.minecraft.util.math.Box
-import java.awt.Color
+import net.minecraft.util.Hand
+import net.minecraft.util.hit.BlockHitResult
+import java.util.concurrent.ConcurrentLinkedQueue
 
 object FastBreak : Module(
     name = "FastBreak",
     description = "Break blocks faster.",
     tag = ModuleTag.PLAYER,
 ) {
-    private val breakDelay by setting("Break Delay", 5, 0..5, 1, unit = "ticks", description = "The tick delay between breaking blocks").group(Group.Mining)
-    private val breakThreshold by setting("Break Threshold", 0.7f, 0.2f..1.0f, 0.1f, description = "The progress at which the block will break.").group(Group.Mining)
-
-    private val renderMode by setting("Render Mode", RenderMode.Out, "The animation style of the renders").group(Group.Mining)
-    private val renderSetting by setting("Render Setting", RenderSetting.Both, "The different ways to draw the renders") { renderMode.isEnabled() }.group(Group.Mining)
-
-    private val fillColourMode by setting("Fill Mode", ColourMode.Dynamic) { renderSetting != RenderSetting.Outline }.group(Group.Render)
-    private val staticFillColour by setting("Static Fill Colour", Color(1f, 0f, 0f, 0.3f), "The colour used to render the static fill of the box") { renderMode.isEnabled() && renderSetting != RenderSetting.Outline && fillColourMode == ColourMode.Static }.group(Group.Render)
-    private val startFillColour by setting("Start Fill Colour", Color(1f, 0f, 0f, 0.3f), "The colour used to render the start fill of the box") { renderMode.isEnabled() && renderSetting != RenderSetting.Outline && fillColourMode == ColourMode.Dynamic }.group(Group.Render)
-    private val endFillColour by setting("End Fill Colour", Color(0f, 1f, 0f, 0.3f), "The colour used to render the end fill of the box") { renderMode.isEnabled() && renderSetting != RenderSetting.Outline && fillColourMode == ColourMode.Dynamic }.group(Group.Render)
-
-    private val outlineColourMode by setting("Outline Mode", ColourMode.Dynamic) { renderSetting != RenderSetting.Fill }.group(Group.Render)
-    private val staticOutlineColour by setting("Static Outline Colour", Color(1f, 0f, 0f, 0.3f), "The colour used to render the static outline of the box") { renderMode.isEnabled() && renderSetting != RenderSetting.Fill && outlineColourMode == ColourMode.Static }.group(Group.Render)
-    private val startOutlineColour by setting("Start Outline Colour", Color(1f, 0f, 0f, 0.3f), "The colour used to render the start outline of the box") { renderMode.isEnabled() && renderSetting != RenderSetting.Fill && outlineColourMode == ColourMode.Dynamic }.group(Group.Render)
-    private val endOutlineColour by setting("End Outline Colour", Color(0f, 1f, 0f, 0.3f), "The colour used to render the end outline of the box") { renderMode.isEnabled() && renderSetting != RenderSetting.Fill && outlineColourMode == ColourMode.Dynamic }.group(Group.Render)
-    private val outlineWidth by setting("Outline Width", 1f, 0f..3f, 0.1f, "the thickness of the outline") { renderMode.isEnabled() && renderSetting != RenderSetting.Fill }
-
-    private val renderer = DynamicESP
-    private var boxSet = emptySet()
-
-    private enum class Group(override val displayName: String): NamedEnum {
-        Mining("Mining"), Render("Render")
+    private enum class Group(override val displayName: String) : NamedEnum {
+        Build("Build")
     }
 
-    private enum class RenderMode {
-        Out, In, InOut, OutIn, Static, None;
-
-        fun isEnabled(): Boolean =
-            this != None
-    }
-
-    private enum class ColourMode {
-        Static, Dynamic
-    }
+    private val buildConfig = BuildSettings(this, Group.Build)
 
-    private enum class RenderSetting {
-        Both, Fill, Outline
-    }
+    private val pendingInteractions = ConcurrentLinkedQueue()
 
     init {
-        listen {
-            if (it.packet !is PlayerActionC2SPacket
-                || it.packet.action != Action.STOP_DESTROY_BLOCK
-            ) return@listen
-
-            connection.sendPacket(
-                PlayerActionC2SPacket(
-                    Action.ABORT_DESTROY_BLOCK,
-                    // For the exploit to work, the position must be outside the player range, so any
-                    // position farther than 6 blocks will work.
-                    // This is only required for grim 2 and potentially grim 3 in the future if they update it
-                    it.packet.pos.up(2024 - 4 - 18),
-                    it.packet.direction
-                )
+        listen { it.cancel() }
+        listen { event ->
+            event.cancel()
+            player.swingHand(Hand.MAIN_HAND)
+
+            val hitResult = mc.crosshairTarget as? BlockHitResult ?: return@listen
+            val pos = event.pos
+            val state = blockState(pos)
+
+            val breakContext = BreakContext(
+                hitResult,
+                RotationRequest(lookAt(player.rotation), TaskFlowModule.rotation),
+                player.inventory.selectedSlot,
+                player.mainHandStack.select(),
+                state.calcBlockBreakingDelta(player, world, pos) >= buildConfig.breaking.breakThreshold,
+                state,
+                buildConfig.breaking.sorter
             )
-        }
-
-        listen {
-            interaction.blockBreakingCooldown = interaction.blockBreakingCooldown.coerceAtMost(breakDelay)
-        }
 
-        listen {
-            it.progress += world.getBlockState(it.pos)
-                .calcBlockBreakingDelta(player, world, it.pos) * (1 - breakThreshold)
-        }
-
-        listen {
-            if (!renderMode.isEnabled()) return@listen
-
-            val pos = interaction.currentBreakingPos
-            boxSet = world.getBlockState(pos).getOutlineShape(world, pos).boundingBoxes.toSet()
-        }
-
-        listen {
-            if (!interaction.isBreakingBlock || !renderMode.isEnabled()) return@listen
-
-            val pos = interaction.currentBreakingPos
-            val breakDelta = world.getBlockState(pos).calcBlockBreakingDelta(player, world, pos)
-
-            renderer.clear()
-            boxSet.forEach { box ->
-                val previousFactor = interaction.currentBreakingProgress - breakDelta
-                val nextFactor = interaction.currentBreakingProgress
-                val currentFactor = lerp(mc.tickDelta.toFloat(), previousFactor, nextFactor)
-
-                val fillColour = if (fillColourMode == ColourMode.Dynamic) {
-                    lerp(currentFactor.toDouble(), startFillColour, endFillColour)
-                } else {
-                    staticFillColour
-                }
-
-                val outlineColour = if (outlineColourMode == ColourMode.Dynamic) {
-                    lerp(currentFactor.toDouble(), startOutlineColour, endOutlineColour)
-                } else {
-                    staticOutlineColour
-                }
-
-                val renderBox = if (renderMode != RenderMode.Static) {
-                    getLerpBox(box, currentFactor).offset(pos)
-                } else {
-                    box.offset(pos)
-                }
-
-                val dynamicAABB = DynamicAABB()
-                dynamicAABB.update(renderBox)
-
-                if (renderSetting != RenderSetting.Outline) {
-                    renderer.buildFilled(dynamicAABB, fillColour)
-                }
-
-                if (renderSetting != RenderSetting.Fill) {
-                    renderer.buildOutline(dynamicAABB, outlineColour)
-                }
-            }
-            renderer.upload()
+            BreakRequest(setOf(breakContext), pendingInteractions, buildConfig).submit()
         }
     }
-
-    private fun getLerpBox(box: Box, factor: Float): Box {
-        val boxCenter = Box(box.center, box.center)
-        when (renderMode) {
-            RenderMode.Out -> {
-                return lerp(factor.toDouble(), boxCenter, box)
-            }
-
-            RenderMode.In -> {
-                return lerp(factor.toDouble(), box, boxCenter)
-            }
-
-            RenderMode.InOut -> {
-                return if (factor >= 0.5f) {
-                    lerp((factor.toDouble() - 0.5) * 2, boxCenter, box)
-                } else {
-                    lerp(factor.toDouble() * 2, box, boxCenter)
-                }
-            }
-
-            RenderMode.OutIn -> {
-                return if (factor >= 0.5f) {
-                    lerp((factor.toDouble() - 0.5) * 2, box, boxCenter)
-                } else {
-                    lerp(factor.toDouble() * 2, boxCenter, box)
-                }
-            }
-
-            else -> {
-                return box
-            }
-        }
-    }
-
-    fun SafeContext.interpolateProgress(min: Double = 0.0, max: Double = 1.0) =
-        transform(interaction.currentBreakingProgress.toDouble(), 0.0, 1.0, min, max)
 }
diff --git a/src/main/kotlin/com/lambda/module/modules/player/Freecam.kt b/src/main/kotlin/com/lambda/module/modules/player/Freecam.kt
index 6f37fcba2..4eb6b3b76 100644
--- a/src/main/kotlin/com/lambda/module/modules/player/Freecam.kt
+++ b/src/main/kotlin/com/lambda/module/modules/player/Freecam.kt
@@ -23,11 +23,11 @@ import com.lambda.event.events.MovementEvent
 import com.lambda.event.events.PlayerEvent
 import com.lambda.event.events.RenderEvent
 import com.lambda.event.listener.SafeListener.Companion.listen
-import com.lambda.interaction.request.rotation.Rotation
-import com.lambda.interaction.request.rotation.RotationConfig
-import com.lambda.interaction.request.rotation.RotationManager.onRotate
-import com.lambda.interaction.request.rotation.RotationMode
-import com.lambda.interaction.request.rotation.visibilty.lookAtHit
+import com.lambda.interaction.request.rotating.Rotation
+import com.lambda.interaction.request.rotating.RotationConfig
+import com.lambda.interaction.request.rotating.RotationManager.onRotate
+import com.lambda.interaction.request.rotating.RotationMode
+import com.lambda.interaction.request.rotating.visibilty.lookAtHit
 import com.lambda.module.Module
 import com.lambda.module.tag.ModuleTag
 import com.lambda.util.extension.partialTicks
diff --git a/src/main/kotlin/com/lambda/module/modules/player/HighwayTools.kt b/src/main/kotlin/com/lambda/module/modules/player/HighwayTools.kt
index b91f9ba80..0d25446c7 100644
--- a/src/main/kotlin/com/lambda/module/modules/player/HighwayTools.kt
+++ b/src/main/kotlin/com/lambda/module/modules/player/HighwayTools.kt
@@ -109,7 +109,7 @@ object HighwayTools : Module(
 
     private fun buildHighway() {
         runningTask = propagatingBlueprint {
-            if (distanceMoved < distance || distance < 0) {
+            if (distance !in 0..distanceMoved) {
                 var structure = emptyStructure()
                 val slice = generateSlice()
                 repeat(sliceSize) {
@@ -125,6 +125,7 @@ object HighwayTools : Module(
                 emptyStructure()
             }
         }.build(
+            collectDrops = build.collectDrops,
             build = build,
             rotation = rotation,
             interact = interact,
@@ -173,7 +174,7 @@ object HighwayTools : Module(
                 1,
             ).associateWith { target(pavement, pavementMaterial) }
 
-            if (cornerBlock == Corner.None) {
+            if (cornerBlock == Corner.None && rimHeight > 0) {
                 // Support for the left rim
                 structure += generateDirectionalTube(
                     orthogonal,
diff --git a/src/main/kotlin/com/lambda/module/modules/player/InventoryMove.kt b/src/main/kotlin/com/lambda/module/modules/player/InventoryMove.kt
index 460cd52fd..3496cf9d3 100644
--- a/src/main/kotlin/com/lambda/module/modules/player/InventoryMove.kt
+++ b/src/main/kotlin/com/lambda/module/modules/player/InventoryMove.kt
@@ -20,11 +20,11 @@ package com.lambda.module.modules.player
 import com.lambda.event.events.MovementEvent
 import com.lambda.event.listener.SafeListener.Companion.listen
 import com.lambda.gui.LambdaScreen
-import com.lambda.interaction.request.rotation.Rotation
-import com.lambda.interaction.request.rotation.RotationConfig
-import com.lambda.interaction.request.rotation.RotationManager.onRotate
-import com.lambda.interaction.request.rotation.RotationMode
-import com.lambda.interaction.request.rotation.visibilty.lookAt
+import com.lambda.interaction.request.rotating.Rotation
+import com.lambda.interaction.request.rotating.RotationConfig
+import com.lambda.interaction.request.rotating.RotationManager.onRotate
+import com.lambda.interaction.request.rotating.RotationMode
+import com.lambda.interaction.request.rotating.visibilty.lookAt
 import com.lambda.module.Module
 import com.lambda.module.tag.ModuleTag
 import com.lambda.util.KeyboardUtils.isKeyPressed
diff --git a/src/main/kotlin/com/lambda/module/modules/player/Nuker.kt b/src/main/kotlin/com/lambda/module/modules/player/Nuker.kt
index 96f0e922a..0f5c8457e 100644
--- a/src/main/kotlin/com/lambda/module/modules/player/Nuker.kt
+++ b/src/main/kotlin/com/lambda/module/modules/player/Nuker.kt
@@ -20,10 +20,12 @@ package com.lambda.module.modules.player
 import com.lambda.interaction.construction.blueprint.TickingBlueprint.Companion.tickingBlueprint
 import com.lambda.interaction.construction.verify.TargetState
 import com.lambda.module.Module
+import com.lambda.module.modules.client.TaskFlowModule
 import com.lambda.module.tag.ModuleTag
 import com.lambda.task.RootTask.run
 import com.lambda.task.Task
 import com.lambda.task.tasks.BuildTask.Companion.build
+import com.lambda.util.BaritoneUtils
 import com.lambda.util.BlockUtils.blockPos
 import com.lambda.util.BlockUtils.blockState
 import net.minecraft.util.math.BlockPos
@@ -36,47 +38,50 @@ object Nuker : Module(
     private val height by setting("Height", 4, 1..8, 1)
     private val width by setting("Width", 4, 1..8, 1)
     private val flatten by setting("Flatten", true)
-    private val onlyBreakInstant by setting("Only Break Instant", true)
+    private val fillFluids by setting("Fill Fluids", false, "Removes liquids by filling them in before breaking")
+    private val instantOnly by setting("Instant Only", false)
     private val fillFloor by setting("Fill Floor", false)
+    private val baritoneSelection by setting("Baritone Selection", false, "Restricts nuker to your baritone selection")
 
     private var task: Task<*>? = null
 
     init {
         onEnable {
             task = tickingBlueprint {
-                    val selection = BlockPos.iterateOutwards(player.blockPos, width, height, width)
-                        .asSequence()
-                        .map { it.blockPos }
-                        .filter { !world.isAir(it) }
-                        .filter { !flatten || it.y >= player.blockPos.y }
-                        .filter { !onlyBreakInstant || blockState(it).getHardness(world, it) <= 1 }
-                        .filter { blockState(it).getHardness(world, it) >= 0 }
-                        .associateWith { TargetState.Air }
-
-                    if (fillFloor) {
-                        val floor = BlockPos.iterateOutwards(player.blockPos.down(), width, 0, width)
-                            .map { it.blockPos }
-                            .associateWith { TargetState.Solid }
-                        return@tickingBlueprint selection + floor
+                val selection = BlockPos.iterateOutwards(player.blockPos, width, height, width)
+                    .asSequence()
+                    .map { it.blockPos }
+                    .filter { !world.isAir(it) }
+                    .filter { !flatten || it.y >= player.blockPos.y }
+                    .filter { !instantOnly || blockState(it).getHardness(world, it) <= TaskFlowModule.build.breaking.breakThreshold }
+                    .filter { pos ->
+                        if (!baritoneSelection) true
+                        else BaritoneUtils.primary.selectionManager.selections.any {
+                            val min = it.min()
+                            val max = it.max()
+                            pos.x >= min.x && pos.x <= max.x
+                                    && pos.y >= min.y && pos.y <= max.y
+                                    && pos.z >= min.z && pos.z <= max.z
+                        }
                     }
+                    .associateWith { if (fillFluids) TargetState.Air else TargetState.Empty }
 
-                    selection
+                if (fillFloor) {
+                    val floor = BlockPos.iterateOutwards(player.blockPos.down(), width, 0, width)
+                        .map { it.blockPos }
+                        .associateWith { TargetState.Solid }
+                    return@tickingBlueprint selection + floor
                 }
-                // ToDo: Add build setting delegates
-                .build()
+
+                selection
+            }.build(finishOnDone = false)
+            // ToDo: Add build setting delegates
+
             task?.run()
         }
 
         onDisable {
             task?.cancel()
         }
-
-//        listener {
-//            task?.let {
-//                if (!it.isRunning) return@listener
-//
-//                info(it.info)
-//            }
-//        }
     }
 }
diff --git a/src/main/kotlin/com/lambda/module/modules/player/PacketMine.kt b/src/main/kotlin/com/lambda/module/modules/player/PacketMine.kt
new file mode 100644
index 000000000..387418393
--- /dev/null
+++ b/src/main/kotlin/com/lambda/module/modules/player/PacketMine.kt
@@ -0,0 +1,294 @@
+/*
+ * Copyright 2025 Lambda
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see .
+ */
+
+package com.lambda.module.modules.player
+
+import com.lambda.config.groups.BuildSettings
+import com.lambda.config.groups.HotbarSettings
+import com.lambda.config.groups.InteractionSettings
+import com.lambda.config.groups.InventorySettings
+import com.lambda.config.groups.RotationSettings
+import com.lambda.context.SafeContext
+import com.lambda.event.events.PlayerEvent
+import com.lambda.event.events.RenderEvent
+import com.lambda.event.events.TickEvent
+import com.lambda.event.listener.SafeListener.Companion.listen
+import com.lambda.graphics.renderer.esp.builders.buildFilled
+import com.lambda.graphics.renderer.esp.builders.buildOutline
+import com.lambda.interaction.construction.blueprint.StaticBlueprint.Companion.toBlueprint
+import com.lambda.interaction.construction.context.BreakContext
+import com.lambda.interaction.construction.context.BuildContext
+import com.lambda.interaction.construction.result.BreakResult
+import com.lambda.interaction.construction.simulation.BuildSimulator.simulate
+import com.lambda.interaction.construction.verify.TargetState
+import com.lambda.interaction.request.breaking.BreakRequest.Companion.breakRequest
+import com.lambda.module.Module
+import com.lambda.module.tag.ModuleTag
+import com.lambda.util.BlockUtils.blockState
+import com.lambda.util.Describable
+import com.lambda.util.NamedEnum
+import com.lambda.util.math.distSq
+import com.lambda.util.math.lerp
+import com.lambda.util.math.setAlpha
+import com.lambda.util.world.raycast.InteractionMask
+import net.minecraft.util.math.BlockPos
+import net.minecraft.util.math.Box
+import java.awt.Color
+import java.util.concurrent.ConcurrentLinkedQueue
+
+object PacketMine : Module(
+    name = "PacketMine",
+    description = "automatically breaks blocks, and does it faster",
+    tag = ModuleTag.PLAYER
+) {
+    private enum class Group(override val displayName: String) : NamedEnum {
+        General("General"),
+        Build("Build"),
+        Rotation("Rotation"),
+        Interaction("Interaction"),
+        Inventory("Inventory"),
+        Hotbar("Hotbar"),
+        Render("Render")
+    }
+
+    private val rebreakMode by setting("Rebreak Mode", RebreakMode.Manual, "The method used to re-break blocks after they've been broken once") { breakConfig.rebreak }.group(Group.General)
+    private val breakRadius by setting("Break Radius", 0, 0..5, 1, "Selects and breaks all blocks within the break radius of the selected block").group(Group.General)
+    private val flatten by setting("Flatten", true, "Wont allow breaking extra blocks under your players position") { breakRadius > 0 }.group(Group.General)
+    private val queue by setting("Queue", false, "Queues blocks to break so you can select multiple at once").group(Group.General)
+        .onValueChange { _, to -> if (!to) queuePositions.clear() }
+    private val queueOrder by setting("Queue Order", QueueOrder.Standard, "Which end of the queue to break blocks from") { queue }.group(Group.General)
+
+    private val build = BuildSettings(this, Group.Build)
+    private val breakConfig = build.breaking
+    private val rotation = RotationSettings(this, Group.Rotation)
+    private val interact = InteractionSettings(this, Group.Interaction, InteractionMask.Block)
+    private val inventory = InventorySettings(this, Group.Inventory)
+    private val hotbar = HotbarSettings(this, Group.Hotbar)
+
+    private val renderQueue by setting("Render Queue", true, "Adds renders to signify what block positions are queued").group(Group.Render)
+    private val renderSize by setting("Render Size", 0.3f, 0.01f..1f, 0.01f, "The scale of the queue renders") { renderQueue }.group(Group.Render)
+    private val renderMode by setting("Render Mode", RenderMode.State, "The style of the queue renders") { renderQueue }.group(Group.Render)
+    private val dynamicColor by setting("Dynamic Color", true, "Interpolates the color between start and end") { renderQueue }.group(Group.Render)
+    private val staticColor by setting("Color", Color(255, 0, 0, 60).brighter()) { renderQueue && !dynamicColor }.group(Group.Render)
+    private val startColor by setting("Start Color", Color(255, 255, 0, 60).brighter(), "The color of the start (closest to breaking) of the queue") { renderQueue && dynamicColor }.group(Group.Render)
+    private val endColor by setting("End Color", Color(255, 0, 0, 60).brighter(), "The color of the end (farthest from breaking) of the queue") { renderQueue && dynamicColor }.group(Group.Render)
+
+    private val pendingInteractions = ConcurrentLinkedQueue()
+
+    private var breaks = 0
+    private var itemDrops = 0
+
+    private val breakPositions = arrayOfNulls(2)
+    private val queuePositions = ArrayList>()
+    private val SafeContext.queueSorted
+        get() = when (queueOrder) {
+            QueueOrder.Standard -> queuePositions
+            QueueOrder.Reversed -> queuePositions.asReversed()
+            QueueOrder.Closest -> queuePositions.sortedBy {
+                it.firstOrNull()
+                    ?.toCenterPos()
+                    ?.let { center ->
+                        center distSq player.pos
+                    } ?: Double.MAX_VALUE
+            }
+        }
+
+    private var reBreakPos: BlockPos? = null
+    private var attackedThisTick = false
+
+    init {
+        listen {
+            attackedThisTick = false
+        }
+
+        listen {
+            it.cancel()
+        }
+
+        listen { event ->
+            event.cancel()
+            val pos = event.pos
+            val positions = mutableListOf().apply {
+                if (breakRadius <= 0) {
+                    add(pos)
+                    return@apply
+                }
+                BlockPos.iterateOutwards(pos, breakRadius, breakRadius, breakRadius).forEach { blockPos ->
+                    if (blockPos distSq pos <= (breakRadius * breakRadius) && (!flatten || (blockPos.y >= player.blockPos.y || blockPos == pos))) {
+                        add(blockPos.toImmutable())
+                    }
+                }
+            }
+            positions.removeIf { breakPos ->
+                (queue && queuePositions.any { it == breakPos }) || breakPos == breakPositions[1]
+            }
+            if (positions.isEmpty()) return@listen
+            val activeBreaking = if (queue) {
+                queuePositions.add(positions)
+                breakPositions.toList() + queueSorted.flatten()
+            } else {
+                queuePositions.clear()
+                queuePositions.add(positions)
+                queuePositions.flatten() + if (breakConfig.doubleBreak) {
+                    breakPositions[1] ?: breakPositions[0]
+                } else null
+            }
+            requestBreakManager(activeBreaking)
+            attackedThisTick = true
+            queuePositions.trimToSize()
+        }
+
+        listen {
+            if (!attackedThisTick) {
+                requestBreakManager((breakPositions + queueSorted.flatten()).toList())
+                if (!breakConfig.rebreak || (rebreakMode != RebreakMode.Auto && rebreakMode != RebreakMode.AutoConstant)) return@listen
+                val reBreak = reBreakPos ?: return@listen
+                requestBreakManager(listOf(reBreak), true)
+            }
+        }
+
+        listen { event ->
+            if (!renderQueue) return@listen
+            queueSorted.forEachIndexed { index, positions ->
+                positions.forEach { pos ->
+                    val color = if (dynamicColor) lerp(index / queuePositions.size.toDouble(), startColor, endColor)
+                    else staticColor
+                    val boxes = when (renderMode) {
+                        RenderMode.State -> blockState(pos).getOutlineShape(world, pos).boundingBoxes
+                        RenderMode.Box -> listOf(Box(0.0, 0.0, 0.0, 1.0, 1.0, 1.0))
+                    }.map { lerp(renderSize.toDouble(), Box(it.center, it.center), it).offset(pos) }
+
+                    boxes.forEach { box ->
+                        event.renderer.buildFilled(box, color)
+                        event.renderer.buildOutline(box, color.setAlpha(1.0))
+                    }
+                }
+            }
+        }
+
+        onDisable {
+            breakPositions[0] = null
+            breakPositions[1] = null
+            queuePositions.clear()
+            reBreakPos = null
+            attackedThisTick = false
+        }
+    }
+
+    private fun SafeContext.requestBreakManager(requestPositions: Collection, reBreaking: Boolean = false) {
+        if (requestPositions.count { it != null } <= 0) return
+        val breakContexts = breakContexts(requestPositions)
+        if (!reBreaking) {
+            queuePositions.retainAllPositions(breakContexts)
+        }
+        breakRequest(
+            breakContexts, pendingInteractions, rotation, hotbar, interact, inventory, build,
+        ) {
+            onStart { queuePositions.removePos(it); addBreak(it) }
+            onUpdate { queuePositions.removePos(it) }
+            onStop { removeBreak(it); breaks++ }
+            onCancel { removeBreak(it, true) }
+            onReBreakStart { reBreakPos = it }
+            onReBreak { reBreakPos = it }
+        }.submit()
+    }
+
+    private fun SafeContext.breakContexts(positions: Collection) =
+        positions
+            .asSequence()
+            .filterNotNull()
+            .associateWith { TargetState.State(blockState(it).fluidState.blockState) }
+            .toBlueprint()
+            .simulate(player.eyePos, interact, rotation, inventory, build)
+            .asSequence()
+            .filterIsInstance()
+            .map { it.context }
+            .toCollection(mutableListOf())
+
+    private fun addBreak(pos: BlockPos) {
+        if (breakConfig.doubleBreak && breakPositions[0] != null) {
+            breakPositions[1] = breakPositions[0]
+        }
+        breakPositions[0] = pos
+        reBreakPos = null
+    }
+
+    private fun removeBreak(pos: BlockPos, includeReBreak: Boolean = false) {
+        breakPositions.forEachIndexed { index, breakPos ->
+            if (breakPos == pos) {
+                breakPositions[index] = null
+            }
+        }
+        if (includeReBreak && pos == reBreakPos) {
+            reBreakPos = null
+        }
+    }
+
+    private fun ArrayList>.removePos(element: BlockPos): Boolean {
+        var anyRemoved = false
+        removeIf {
+            val removed = it.remove(element)
+            anyRemoved = anyRemoved or removed
+            return@removeIf removed && it.isEmpty()
+        }
+        return anyRemoved
+    }
+
+    private fun ArrayList>.retainAllPositions(positions: Collection): Boolean {
+        var modified = false
+        forEach {
+            modified = modified or it.retainAll { pos ->
+                positions.any { retain ->
+                    retain.blockPos == pos
+                }
+            }
+        }
+        return modified
+    }
+
+    private fun ArrayList>.any(predicate: (BlockPos) -> Boolean): Boolean {
+        if (isEmpty()) return false
+        forEach { if (it.any(predicate)) return true }
+        return false
+    }
+
+    enum class RebreakMode(
+        override val displayName: String,
+        override val description: String
+    ) : NamedEnum, Describable {
+        Manual("Manual", "Re-break only when you trigger it explicitly."),
+        Auto("Auto", "Automatically re-break when it’s beneficial or required."),
+        AutoConstant("Auto (Constant)", "Continuously re-break as soon as conditions allow; most aggressive.")
+    }
+
+    enum class QueueOrder(
+        override val displayName: String,
+        override val description: String
+    ) : NamedEnum, Describable {
+        Standard("Standard", "Process in planned order (first in, first out)."),
+        Reversed("Reversed", "Process in reverse planned order (last in, first out)."),
+        Closest("Closest", "Process the closest targets first.")
+    }
+
+    private enum class RenderMode(
+        override val displayName: String,
+        override val description: String
+    ) : NamedEnum, Describable {
+        State("State", "Render the actual block state for preview."),
+        Box("Box", "Render a simple box to show position and size.")
+    }
+}
diff --git a/src/main/kotlin/com/lambda/module/modules/player/Replay.kt b/src/main/kotlin/com/lambda/module/modules/player/Replay.kt
index c9cb5b3b3..04ceaba03 100644
--- a/src/main/kotlin/com/lambda/module/modules/player/Replay.kt
+++ b/src/main/kotlin/com/lambda/module/modules/player/Replay.kt
@@ -32,11 +32,11 @@ import com.lambda.event.EventFlow.lambdaScope
 import com.lambda.event.events.KeyboardEvent
 import com.lambda.event.events.MovementEvent
 import com.lambda.event.listener.SafeListener.Companion.listen
-import com.lambda.interaction.request.rotation.Rotation
-import com.lambda.interaction.request.rotation.RotationConfig
-import com.lambda.interaction.request.rotation.RotationManager.onRotate
-import com.lambda.interaction.request.rotation.RotationMode
-import com.lambda.interaction.request.rotation.visibilty.lookAt
+import com.lambda.interaction.request.rotating.Rotation
+import com.lambda.interaction.request.rotating.RotationConfig
+import com.lambda.interaction.request.rotating.RotationManager.onRotate
+import com.lambda.interaction.request.rotating.RotationMode
+import com.lambda.interaction.request.rotating.visibilty.lookAt
 import com.lambda.module.Module
 import com.lambda.module.modules.client.GuiSettings
 import com.lambda.module.modules.player.Replay.InputAction.Companion.toAction
@@ -71,6 +71,8 @@ import net.minecraft.util.math.Vec3d
 import java.io.File
 import java.lang.reflect.Type
 import java.time.format.DateTimeFormatter
+import kotlin.collections.removeFirstOrNull
+import kotlin.collections.take
 import kotlin.io.path.pathString
 import kotlin.time.Duration
 import kotlin.time.Duration.Companion.milliseconds
diff --git a/src/main/kotlin/com/lambda/module/modules/player/Scaffold.kt b/src/main/kotlin/com/lambda/module/modules/player/Scaffold.kt
index 7d11e2616..a32cd7642 100644
--- a/src/main/kotlin/com/lambda/module/modules/player/Scaffold.kt
+++ b/src/main/kotlin/com/lambda/module/modules/player/Scaffold.kt
@@ -17,6 +17,7 @@
 
 package com.lambda.module.modules.player
 
+import com.lambda.config.groups.InteractSettings
 import com.lambda.config.groups.InteractionSettings
 import com.lambda.config.groups.RotationSettings
 import com.lambda.context.SafeContext
@@ -30,18 +31,18 @@ import com.lambda.graphics.renderer.esp.builders.ofBox
 import com.lambda.interaction.blockplace.PlaceFinder.Companion.buildPlaceInfo
 import com.lambda.interaction.blockplace.PlaceInfo
 import com.lambda.interaction.blockplace.PlaceInteraction.placeBlock
-import com.lambda.interaction.request.rotation.Rotation
-import com.lambda.interaction.request.rotation.Rotation.Companion.angleDifference
-import com.lambda.interaction.request.rotation.Rotation.Companion.dist
-import com.lambda.interaction.request.rotation.Rotation.Companion.rotationTo
-import com.lambda.interaction.request.rotation.Rotation.Companion.wrap
-import com.lambda.interaction.request.rotation.RotationManager.currentRotation
-import com.lambda.interaction.request.rotation.RotationManager.onRotate
-import com.lambda.interaction.request.rotation.RotationRequest
-import com.lambda.interaction.request.rotation.visibilty.VisibilityChecker.getVisibleSurfaces
-import com.lambda.interaction.request.rotation.visibilty.VisibilityChecker.scanSurfaces
-import com.lambda.interaction.request.rotation.visibilty.blockHit
-import com.lambda.interaction.request.rotation.visibilty.lookAtHit
+import com.lambda.interaction.request.rotating.Rotation
+import com.lambda.interaction.request.rotating.Rotation.Companion.angleDifference
+import com.lambda.interaction.request.rotating.Rotation.Companion.dist
+import com.lambda.interaction.request.rotating.Rotation.Companion.rotationTo
+import com.lambda.interaction.request.rotating.Rotation.Companion.wrap
+import com.lambda.interaction.request.rotating.RotationManager.onRotate
+import com.lambda.interaction.request.rotating.RotationManager.activeRotation
+import com.lambda.interaction.request.rotating.RotationRequest
+import com.lambda.interaction.request.rotating.visibilty.VisibilityChecker.getVisibleSurfaces
+import com.lambda.interaction.request.rotating.visibilty.VisibilityChecker.scanSurfaces
+import com.lambda.interaction.request.rotating.visibilty.blockHit
+import com.lambda.interaction.request.rotating.visibilty.lookAtHit
 import com.lambda.module.Module
 import com.lambda.module.modules.client.GuiSettings
 import com.lambda.module.modules.client.TaskFlowModule
@@ -86,6 +87,7 @@ object Scaffold : Module(
     private val optimalPitch by setting("Optimal Pitch", 81.0, 70.0..85.0, 0.05).group(Group.Rotation)
 
     private val interactionConfig = InteractionSettings(this, Group.Interaction, InteractionMask.Block)
+    private val interactConfig = InteractSettings(this, listOf(Group.Interact))
 
     // Placement
     private var placeInfo: PlaceInfo? = null
@@ -116,6 +118,7 @@ object Scaffold : Module(
     private enum class Group(override val displayName: String): NamedEnum {
         General("General"),
         Rotation("Rotation"),
+        Interact("Interact"),
         Interaction("Interaction")
     }
 
@@ -215,14 +218,14 @@ object Scaffold : Module(
         val assumedYaw = assumeYawByDirection(moveYaw)
 
         // No need to rotate, already looking correctly
-        val lookingCorrectly = castRotation(currentRotation, info) != null
-        val isYawStable = angleDifference(currentRotation.yaw, assumedYaw) < YAW_THRESHOLD
-        if (lookingCorrectly && isYawStable) return currentRotation
+        val lookingCorrectly = castRotation(activeRotation, info) != null
+        val isYawStable = angleDifference(activeRotation.yaw, assumedYaw) < YAW_THRESHOLD
+        if (lookingCorrectly && isYawStable) return activeRotation
 
         // Dividing the surface by segments and iterating through them
         val pointScan = mutableSetOf().apply {
             val box = Box(info.clickPos)
-            val sides = if (TaskFlowModule.interact.checkSideVisibility) {
+            val sides = if (TaskFlowModule.interaction.checkSideVisibility) {
                 box.getVisibleSurfaces(eye)
             } else Direction.entries.toSet()
             scanSurfaces(
@@ -264,7 +267,7 @@ object Scaffold : Module(
 
         val optimalRotation = when {
             // Placing supporting block
-            info.placeSteps > 0 && !isDiagonal -> currentRotation
+            info.placeSteps > 0 && !isDiagonal -> activeRotation
 
             // Placing base block
             else -> assumedRotation
@@ -300,7 +303,7 @@ object Scaffold : Module(
         }
 
         // Run placement
-        placeBlock(blockResult ?: return, Hand.MAIN_HAND, interactionConfig.swingHand)
+        placeBlock(blockResult ?: return, Hand.MAIN_HAND, interactConfig.swingHand)
         renderInfo.add(info to currentTime)
     }
 
@@ -315,7 +318,7 @@ object Scaffold : Module(
         val isNearLedge = world.isBlockSpaceEmpty(player, predictedBox)*/
 
         val sneak = lastRotation?.let {
-            currentRotation dist it > YAW_THRESHOLD && player.isOnGround
+            activeRotation dist it > YAW_THRESHOLD && player.isOnGround
         } ?: (sneakTicks > 0 && placeInfoAge < 4)
 
         if (sneak) sneakTicks = 3
diff --git a/src/main/kotlin/com/lambda/module/modules/render/FreeLook.kt b/src/main/kotlin/com/lambda/module/modules/render/FreeLook.kt
index f7163f9a3..3a22d59d7 100644
--- a/src/main/kotlin/com/lambda/module/modules/render/FreeLook.kt
+++ b/src/main/kotlin/com/lambda/module/modules/render/FreeLook.kt
@@ -17,11 +17,10 @@
 
 package com.lambda.module.modules.render
 
-import com.lambda.Lambda
 import com.lambda.Lambda.mc
 import com.lambda.event.events.PlayerEvent
 import com.lambda.event.listener.SafeListener.Companion.listen
-import com.lambda.interaction.request.rotation.Rotation
+import com.lambda.interaction.request.rotating.Rotation
 import com.lambda.module.Module
 import com.lambda.module.tag.ModuleTag
 import com.lambda.util.extension.rotation
diff --git a/src/main/kotlin/com/lambda/module/modules/render/WorldColors.kt b/src/main/kotlin/com/lambda/module/modules/render/WorldColors.kt
index 0b472e813..7eafd1d60 100644
--- a/src/main/kotlin/com/lambda/module/modules/render/WorldColors.kt
+++ b/src/main/kotlin/com/lambda/module/modules/render/WorldColors.kt
@@ -24,7 +24,7 @@ import net.minecraft.util.math.Vec3d
 import java.awt.Color
 
 object WorldColors : Module(
-    name = "World Colors",
+    name = "WorldColors",
     description = "Changes the color of the sky",
     tag = ModuleTag.RENDER,
 ) {
diff --git a/src/main/kotlin/com/lambda/task/Task.kt b/src/main/kotlin/com/lambda/task/Task.kt
index 3f9d29ff7..b86240929 100644
--- a/src/main/kotlin/com/lambda/task/Task.kt
+++ b/src/main/kotlin/com/lambda/task/Task.kt
@@ -166,6 +166,7 @@ abstract class Task : Nameable, Muteable {
 
     @Ta5kBuilder
     fun cancel() {
+        runSafe { onCancel() }
         cancelSubTasks()
         if (this is RootTask) return
         if (state == State.COMPLETED || state == State.CANCELLED) return
@@ -184,9 +185,7 @@ abstract class Task : Nameable, Muteable {
     }
 
     @Ta5kBuilder
-    fun failure(message: String) {
-        failure(IllegalStateException(message))
-    }
+    fun failure(message: String) = failure(IllegalStateException(message))
 
     @Ta5kBuilder
     fun failure(
diff --git a/src/main/kotlin/com/lambda/task/tasks/AcquireMaterial.kt b/src/main/kotlin/com/lambda/task/tasks/AcquireMaterial.kt
index 85b8ece35..6914b2dd9 100644
--- a/src/main/kotlin/com/lambda/task/tasks/AcquireMaterial.kt
+++ b/src/main/kotlin/com/lambda/task/tasks/AcquireMaterial.kt
@@ -17,11 +17,11 @@
 
 package com.lambda.task.tasks
 
-import com.lambda.config.groups.InventoryConfig
 import com.lambda.context.SafeContext
 import com.lambda.interaction.material.StackSelection
 import com.lambda.interaction.material.container.ContainerManager
 import com.lambda.interaction.material.container.ContainerManager.findContainerWithMaterial
+import com.lambda.interaction.request.inventory.InventoryConfig
 import com.lambda.module.modules.client.TaskFlowModule
 import com.lambda.task.Task
 
@@ -33,7 +33,7 @@ class AcquireMaterial @Ta5kBuilder constructor(
         get() = "Acquiring $selection"
 
     override fun SafeContext.onStart() {
-        findContainerWithMaterial(selection, inventory)
+        selection.findContainerWithMaterial(inventory)
             ?.withdraw(selection)
             ?.finally {
                 success(selection)
diff --git a/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt b/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt
index 164972945..163640b6d 100644
--- a/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt
+++ b/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt
@@ -21,12 +21,8 @@ import baritone.api.pathing.goals.GoalBlock
 import com.lambda.Lambda.LOG
 import com.lambda.config.groups.BuildConfig
 import com.lambda.config.groups.InteractionConfig
-import com.lambda.config.groups.InventoryConfig
 import com.lambda.context.SafeContext
-import com.lambda.event.events.EntityEvent
-import com.lambda.event.events.MovementEvent
 import com.lambda.event.events.TickEvent
-import com.lambda.event.events.WorldEvent
 import com.lambda.event.listener.SafeListener.Companion.listen
 import com.lambda.interaction.construction.blueprint.Blueprint
 import com.lambda.interaction.construction.blueprint.Blueprint.Companion.toStructure
@@ -35,10 +31,10 @@ import com.lambda.interaction.construction.blueprint.StaticBlueprint.Companion.t
 import com.lambda.interaction.construction.blueprint.TickingBlueprint
 import com.lambda.interaction.construction.context.BreakContext
 import com.lambda.interaction.construction.context.BuildContext
-import com.lambda.interaction.construction.context.PlaceContext
 import com.lambda.interaction.construction.result.BreakResult
 import com.lambda.interaction.construction.result.BuildResult
 import com.lambda.interaction.construction.result.Drawable
+import com.lambda.interaction.construction.result.InteractResult
 import com.lambda.interaction.construction.result.Navigable
 import com.lambda.interaction.construction.result.PlaceResult
 import com.lambda.interaction.construction.result.Resolvable
@@ -47,22 +43,23 @@ import com.lambda.interaction.construction.simulation.BuildSimulator.simulate
 import com.lambda.interaction.construction.simulation.Simulation.Companion.simulation
 import com.lambda.interaction.construction.verify.TargetState
 import com.lambda.interaction.material.transfer.TransactionExecutor.Companion.transfer
-import com.lambda.interaction.request.rotation.RotationConfig
-import com.lambda.interaction.request.rotation.RotationManager.onRotate
+import com.lambda.interaction.request.breaking.BreakRequest.Companion.breakRequest
+import com.lambda.interaction.request.hotbar.HotbarConfig
+import com.lambda.interaction.request.interacting.InteractRequest
+import com.lambda.interaction.request.inventory.InventoryConfig
+import com.lambda.interaction.request.placing.PlaceRequest
+import com.lambda.interaction.request.rotating.RotationConfig
 import com.lambda.module.modules.client.TaskFlowModule
 import com.lambda.task.Task
 import com.lambda.util.BaritoneUtils
-import com.lambda.util.Communication.info
-import com.lambda.util.Communication.warn
 import com.lambda.util.Formatting.string
-import com.lambda.util.collections.LimitedDecayQueue
 import com.lambda.util.extension.Structure
 import com.lambda.util.extension.inventorySlots
 import com.lambda.util.item.ItemUtils.block
-import com.lambda.util.player.MovementUtils.sneaking
 import com.lambda.util.player.SlotUtils.hotbarAndStorage
 import net.minecraft.entity.ItemEntity
 import net.minecraft.util.math.BlockPos
+import java.util.concurrent.ConcurrentLinkedQueue
 
 class BuildTask @Ta5kBuilder constructor(
     private val blueprint: Blueprint,
@@ -70,122 +67,63 @@ class BuildTask @Ta5kBuilder constructor(
     private val collectDrops: Boolean = TaskFlowModule.build.collectDrops,
     private val build: BuildConfig = TaskFlowModule.build,
     private val rotation: RotationConfig = TaskFlowModule.rotation,
-    private val interact: InteractionConfig = TaskFlowModule.interact,
+    private val interactionConfig: InteractionConfig = TaskFlowModule.interaction,
     private val inventory: InventoryConfig = TaskFlowModule.inventory,
-) : Task() {
+    private val hotbar: HotbarConfig = TaskFlowModule.hotbar,
+) : Task() {
     override val name: String get() = "Building $blueprint with ${(breaks / (age / 20.0 + 0.001)).string} b/s ${(placements / (age / 20.0 + 0.001)).string} p/s"
 
-    private val pendingInteractions = LimitedDecayQueue(
-        build.maxPendingInteractions, build.interactionTimeout * 50L
-    ) { info("${it::class.simpleName} at ${it.expectedPos.toShortString()} timed out") }
-    private var currentInteraction: BuildContext? = null
-    private val instantBreaks = mutableSetOf()
+    private val pendingInteractions = ConcurrentLinkedQueue()
+    private val emptyPendingInteractionSlots
+        get() = (build.maxPendingInteractions - pendingInteractions.size).coerceAtLeast(0)
+    private val atMaxPendingInteractions
+        get() = pendingInteractions.size >= build.maxPendingInteractions
 
     private var placements = 0
     private var breaks = 0
     private val dropsToCollect = mutableSetOf()
-//    private var goodPositions = setOf()
+
+    private val onItemDrop: ((item: ItemEntity) -> Unit)?
+        get() = if (collectDrops) {
+            item -> dropsToCollect.add(item)
+        } else null
 
     override fun SafeContext.onStart() {
-        (blueprint as? PropagatingBlueprint)?.next()
+        iteratePropagating()
     }
 
     init {
         listen {
-            currentInteraction?.let { context ->
-//                TaskFlowModule.drawables = listOf(context)
-                if (context.shouldRotate(build) && !context.rotation.done) return@let
-                if (context is PlaceContext && context.sneak && !player.isSneaking) return@let
-                context.interact(interact.swingHand)
-            }
-            instantBreaks.forEach { context ->
-                context.interact(interact.swingHand)
-                pendingInteractions.add(context)
-            }
-            instantBreaks.clear()
-
-            dropsToCollect.firstOrNull()?.let { itemDrop ->
-                if (!world.entities.contains(itemDrop)) {
-                    dropsToCollect.remove(itemDrop)
-                    BaritoneUtils.cancel()
-                    return@listen
-                }
-
-                val noInventorySpace = player.hotbarAndStorage.none { it.isEmpty }
-                if (noInventorySpace) {
-                    val stackToThrow = player.currentScreenHandler.inventorySlots.firstOrNull {
-                        it.stack.item.block in TaskFlowModule.inventory.disposables
-                    } ?: run {
-                        failure("No item in inventory to throw but inventory is full and cant pick up item drop")
-                        return@listen
-                    }
-                    transfer(player.currentScreenHandler) {
-                        throwStack(stackToThrow.id)
-                    }.execute(this@BuildTask)
-                    return@listen
-                }
-
-                BaritoneUtils.setGoalAndPath(GoalBlock(itemDrop.blockPos))
-            }
-        }
+            if (collectDrops()) return@listen
 
-        listen {
-            (blueprint as? TickingBlueprint)?.tick()
+            val results = blueprint.simulate(player.eyePos, interactionConfig, rotation, inventory, build)
 
-            if (finishOnDone && blueprint.structure.isEmpty()) {
-                failure("Structure is empty")
-                return@listen
-            }
-        }
-
-        onRotate {
-            if (collectDrops && dropsToCollect.isNotEmpty()) return@onRotate
-
-//            val sim = blueprint.simulation(interact, rotation, inventory)
-//            BlockPos.iterateOutwards(player.blockPos, 5, 5, 5).forEach { pos ->
-//                sim.simulate(pos.toFastVec())
-//            }
-
-            // ToDo: Simulate for each pair player positions that work
-            val results = blueprint.simulate(player.eyePos, interact, rotation, inventory, build)
-
-            TaskFlowModule.drawables = results.filterIsInstance()
+            TaskFlowModule.drawables = results
+                .filterIsInstance()
                 .plus(pendingInteractions.toList())
-//                .plus(sim.goodPositions())
 
-            if (build.breaksPerTick > 1) {
-                val instantResults = results.filterIsInstance()
-                    .filter { it.context.instantBreak }
-                    .sorted()
-                    .take(build.breaksPerTick)
+            val resultsNotBlocked = results
+                .filter { result -> pendingInteractions.none { it.blockPos == result.blockPos } }
+                .sorted()
 
-                instantBreaks.addAll(instantResults.map { it.context })
-
-                if (instantResults.isNotEmpty()) return@onRotate
-            }
-
-            val resultsWithoutPending = results.filterNot { result ->
-                result.blockPos in pendingInteractions.map { it.expectedPos }
-            }
-            val bestResult = resultsWithoutPending.minOrNull() ?: return@onRotate
+            val bestResult = resultsNotBlocked.firstOrNull() ?: return@listen
+            if (bestResult !is BuildResult.Contextual && pendingInteractions.isNotEmpty())
+                return@listen
             when (bestResult) {
                 is BuildResult.Done,
                 is BuildResult.Ignored,
                 is BuildResult.Unbreakable,
                 is BuildResult.Restricted,
                 is BuildResult.NoPermission -> {
-                    if (pendingInteractions.isNotEmpty()) return@onRotate
-                    if (blueprint is PropagatingBlueprint) {
-                        blueprint.next()
-                        return@onRotate
-                    }
-                    if (finishOnDone) success()
+                    if (iteratePropagating()) return@listen
+
+                    if (finishOnDone) success(blueprint.structure)
                 }
 
                 is BuildResult.NotVisible,
                 is PlaceResult.NoIntegrity -> {
-                    if (!build.pathing) return@onRotate
-                    val sim = blueprint.simulation(interact, rotation, inventory, build)
+                    if (!build.pathing) return@listen
+                    val sim = blueprint.simulation(interactionConfig, rotation, inventory, build)
                     val goal = BuildGoal(sim, player.blockPos)
                     BaritoneUtils.setGoalAndPath(goal)
                 }
@@ -195,9 +133,53 @@ class BuildTask @Ta5kBuilder constructor(
                 }
 
                 is BuildResult.Contextual -> {
-                    if (pendingInteractions.size >= build.maxPendingInteractions) return@onRotate
-
-                    currentInteraction = bestResult.context
+                    if (atMaxPendingInteractions) return@listen
+                    when (bestResult) {
+                        is BreakResult.Break -> {
+                            val breakResults = resultsNotBlocked.filterIsInstance()
+                            val requestContexts = arrayListOf()
+
+                            if (build.breaking.breaksPerTick > 1) {
+                                breakResults
+                                    .filter { it.context.instantBreak }
+                                    .take(emptyPendingInteractionSlots)
+                                    .let { instantBreakResults ->
+                                        requestContexts.addAll(instantBreakResults.map { it.context })
+                                    }
+                            }
+
+                            if (requestContexts.isEmpty()) {
+                                requestContexts.addAll(breakResults.map { it.context })
+                            }
+
+                            breakRequest(
+                                requestContexts, pendingInteractions, rotation, hotbar, interactionConfig, inventory, build,
+                            ) {
+                                onStop { breaks++ }
+                                onItemDrop?.let { onItemDrop ->
+                                    onItemDrop { onItemDrop(it) }
+                                }
+                            }.submit()
+                            return@listen
+                        }
+                        is PlaceResult.Place -> {
+                            val placeResults = resultsNotBlocked
+                                .filterIsInstance()
+                                .distinctBy { it.blockPos }
+                                .take(emptyPendingInteractionSlots)
+
+                            PlaceRequest(placeResults.map { it.context }, build, rotation, hotbar, pendingInteractions) { placements++ }.submit()
+                        }
+                        is InteractResult.Interact -> {
+                            val interactResults = resultsNotBlocked
+                                .filterIsInstance()
+                                .distinctBy { it.blockPos }
+                                .take(emptyPendingInteractionSlots)
+                                .map { it.context }
+
+                            InteractRequest(interactResults, null, pendingInteractions, build.interacting, build, hotbar, rotation).submit()
+                        }
+                    }
                 }
 
                 is Resolvable -> {
@@ -206,53 +188,55 @@ class BuildTask @Ta5kBuilder constructor(
                     bestResult.resolve().execute(this@BuildTask)
                 }
             }
-
-            if (!build.rotateForPlace) return@onRotate
-            val rotateTo = currentInteraction?.rotation ?: return@onRotate
-
-            rotation.request(rotateTo)
         }
 
-        listen {
-            val context = currentInteraction ?: return@listen
-            if (context !is PlaceContext) return@listen
-            if (context.sneak) it.input.sneaking = true
-        }
+        listen {
+            if (blueprint is TickingBlueprint) {
+                blueprint.tick() ?: failure("Failed to tick the ticking blueprint")
+            }
 
-        listen { event ->
-            val context = currentInteraction ?: return@listen
-            if (context.expectedPos != event.pos) return@listen
-            currentInteraction = null
-            pendingInteractions.add(context)
+            if (finishOnDone && blueprint.structure.isEmpty()) {
+                failure("Structure is empty")
+                return@listen
+            }
         }
+    }
 
-        listen(alwaysListen = true) { event ->
-            pendingInteractions.firstOrNull { it.expectedPos == event.pos }?.let { context ->
-                pendingInteractions.remove(context)
-                if (!context.targetState.matches(event.newState, event.pos, world)) {
-                    this@BuildTask.warn("Update at ${event.pos.toShortString()} was rejected with ${event.newState} instead of ${context.targetState}")
-                    return@let
+    private fun SafeContext.collectDrops() =
+        dropsToCollect
+            .firstOrNull()
+            ?.let { itemDrop ->
+                if (pendingInteractions.isNotEmpty()) return@let true
+
+                if (!world.entities.contains(itemDrop)) {
+                    dropsToCollect.remove(itemDrop)
+                    BaritoneUtils.cancel()
+                    return@let true
                 }
-                when (context) {
-                    is BreakContext -> breaks++
-                    is PlaceContext -> placements++
+
+                val noInventorySpace = player.hotbarAndStorage.none { it.isEmpty }
+                if (noInventorySpace) {
+                    val stackToThrow = player.currentScreenHandler.inventorySlots.firstOrNull {
+                        it.stack.item.block in TaskFlowModule.inventory.disposables
+                    } ?: run {
+                        failure("No item in inventory to throw but inventory is full and cant pick up item drop")
+                        return@let true
+                    }
+                    transfer(player.currentScreenHandler) {
+                        throwStack(stackToThrow.id)
+                    }.execute(this@BuildTask)
+                    return@let true
                 }
-            }
-        }
 
-        // ToDo: Dependent on the tracked data order. When set stack is called after position it wont work
-        listen {
-            if (!collectDrops) return@listen
-            if (it.entity !is ItemEntity) return@listen
-            pendingInteractions.find { context ->
-                val inRange = context.expectedPos.toCenterPos().isInRange(it.entity.pos, 0.5)
-                val correctMaterial = context.checkedState.block == it.entity.stack.item.block
-                inRange && correctMaterial
-            }?.let { context ->
-                dropsToCollect.add(it.entity)
-            }
-        }
-    }
+                BaritoneUtils.setGoalAndPath(GoalBlock(itemDrop.blockPos))
+                return@let true
+            } ?: false
+
+    fun iteratePropagating() =
+        if (blueprint is PropagatingBlueprint) {
+            blueprint.next() ?: failure("Failed to propagate the next blueprint")
+            true
+        } else false
 
     companion object {
         @Ta5kBuilder
@@ -261,7 +245,7 @@ class BuildTask @Ta5kBuilder constructor(
             collectDrops: Boolean = TaskFlowModule.build.collectDrops,
             build: BuildConfig = TaskFlowModule.build,
             rotation: RotationConfig = TaskFlowModule.rotation,
-            interact: InteractionConfig = TaskFlowModule.interact,
+            interact: InteractionConfig = TaskFlowModule.interaction,
             inventory: InventoryConfig = TaskFlowModule.inventory,
             blueprint: () -> Blueprint,
         ) = BuildTask(blueprint(), finishOnDone, collectDrops, build, rotation, interact, inventory)
@@ -272,7 +256,7 @@ class BuildTask @Ta5kBuilder constructor(
             collectDrops: Boolean = TaskFlowModule.build.collectDrops,
             build: BuildConfig = TaskFlowModule.build,
             rotation: RotationConfig = TaskFlowModule.rotation,
-            interact: InteractionConfig = TaskFlowModule.interact,
+            interact: InteractionConfig = TaskFlowModule.interaction,
             inventory: InventoryConfig = TaskFlowModule.inventory,
         ) = BuildTask(toBlueprint(), finishOnDone, collectDrops, build, rotation, interact, inventory)
 
@@ -282,7 +266,7 @@ class BuildTask @Ta5kBuilder constructor(
             collectDrops: Boolean = TaskFlowModule.build.collectDrops,
             build: BuildConfig = TaskFlowModule.build,
             rotation: RotationConfig = TaskFlowModule.rotation,
-            interact: InteractionConfig = TaskFlowModule.interact,
+            interact: InteractionConfig = TaskFlowModule.interaction,
             inventory: InventoryConfig = TaskFlowModule.inventory,
         ) = BuildTask(this, finishOnDone, collectDrops, build, rotation, interact, inventory)
 
@@ -293,7 +277,7 @@ class BuildTask @Ta5kBuilder constructor(
             collectDrops: Boolean = true,
             build: BuildConfig = TaskFlowModule.build,
             rotation: RotationConfig = TaskFlowModule.rotation,
-            interact: InteractionConfig = TaskFlowModule.interact,
+            interact: InteractionConfig = TaskFlowModule.interaction,
             inventory: InventoryConfig = TaskFlowModule.inventory,
         ) = BuildTask(
             blockPos.toStructure(TargetState.Air).toBlueprint(),
@@ -307,7 +291,7 @@ class BuildTask @Ta5kBuilder constructor(
             collectDrops: Boolean = TaskFlowModule.build.collectDrops,
             build: BuildConfig = TaskFlowModule.build,
             rotation: RotationConfig = TaskFlowModule.rotation,
-            interact: InteractionConfig = TaskFlowModule.interact,
+            interact: InteractionConfig = TaskFlowModule.interaction,
             inventory: InventoryConfig = TaskFlowModule.inventory,
         ) = BuildTask(
             blockPos.toStructure(TargetState.Air).toBlueprint(),
diff --git a/src/main/kotlin/com/lambda/task/tasks/OpenContainer.kt b/src/main/kotlin/com/lambda/task/tasks/OpenContainer.kt
index c336847ad..d1b2b4522 100644
--- a/src/main/kotlin/com/lambda/task/tasks/OpenContainer.kt
+++ b/src/main/kotlin/com/lambda/task/tasks/OpenContainer.kt
@@ -21,8 +21,9 @@ import com.lambda.config.groups.InteractionConfig
 import com.lambda.event.events.InventoryEvent
 import com.lambda.event.events.TickEvent
 import com.lambda.event.listener.SafeListener.Companion.listen
-import com.lambda.interaction.request.rotation.RotationConfig
-import com.lambda.interaction.request.rotation.visibilty.lookAtBlock
+import com.lambda.interaction.request.interacting.InteractConfig
+import com.lambda.interaction.request.rotating.RotationConfig
+import com.lambda.interaction.request.rotating.visibilty.lookAtBlock
 import com.lambda.module.modules.client.TaskFlowModule
 import com.lambda.task.Task
 import com.lambda.util.world.raycast.RayCastUtils.blockResult
@@ -34,9 +35,9 @@ import net.minecraft.util.math.Direction
 class OpenContainer @Ta5kBuilder constructor(
     private val blockPos: BlockPos,
     private val waitForSlotLoad: Boolean = true,
-    private val rotate: Boolean = true,
     private val rotation: RotationConfig = TaskFlowModule.rotation,
-    private val interact: InteractionConfig = TaskFlowModule.interact,
+    private val interact: InteractConfig = TaskFlowModule.build.interacting,
+    private val interactionConfig: InteractionConfig = TaskFlowModule.interaction,
     private val sides: Set = Direction.entries.toSet(),
 ) : Task() {
     override val name get() = "${containerState.description(inScope)} at ${blockPos.toShortString()}"
@@ -83,8 +84,8 @@ class OpenContainer @Ta5kBuilder constructor(
         listen {
             if (containerState != State.SCOPING) return@listen
 
-            val target = lookAtBlock(blockPos, sides, config = interact)
-            if (rotate && !target.requestBy(rotation).done) return@listen
+            val target = lookAtBlock(blockPos, sides, config = interactionConfig)
+            if (interact.rotate && !target.requestBy(rotation).done) return@listen
 
             val hitResult = target.hit?.hitIfValid()?.blockResult ?: return@listen
             interaction.interactBlock(player, Hand.MAIN_HAND, hitResult)
diff --git a/src/main/kotlin/com/lambda/task/tasks/PlaceContainer.kt b/src/main/kotlin/com/lambda/task/tasks/PlaceContainer.kt
index c139719fe..26968983f 100644
--- a/src/main/kotlin/com/lambda/task/tasks/PlaceContainer.kt
+++ b/src/main/kotlin/com/lambda/task/tasks/PlaceContainer.kt
@@ -19,19 +19,21 @@ package com.lambda.task.tasks
 
 import com.lambda.config.groups.BuildConfig
 import com.lambda.config.groups.InteractionConfig
-import com.lambda.config.groups.InventoryConfig
 import com.lambda.context.SafeContext
 import com.lambda.interaction.construction.blueprint.Blueprint.Companion.toStructure
 import com.lambda.interaction.construction.blueprint.StaticBlueprint.Companion.toBlueprint
+import com.lambda.interaction.construction.blueprint.TickingBlueprint.Companion.tickingBlueprint
 import com.lambda.interaction.construction.result.BuildResult
 import com.lambda.interaction.construction.result.PlaceResult
 import com.lambda.interaction.construction.simulation.BuildSimulator.simulate
 import com.lambda.interaction.construction.verify.TargetState
-import com.lambda.interaction.request.rotation.RotationConfig
+import com.lambda.interaction.request.ManagerUtils
+import com.lambda.interaction.request.inventory.InventoryConfig
+import com.lambda.interaction.request.rotating.RotationConfig
 import com.lambda.module.modules.client.TaskFlowModule
 import com.lambda.task.Task
 import com.lambda.task.tasks.BuildTask.Companion.build
-import com.lambda.util.BlockUtils.blockPos
+import com.lambda.util.BlockUtils.blockState
 import com.lambda.util.item.ItemUtils.shulkerBoxes
 import net.minecraft.block.ChestBlock
 import net.minecraft.entity.mob.ShulkerEntity
@@ -44,44 +46,52 @@ class PlaceContainer @Ta5kBuilder constructor(
     val stack: ItemStack,
     val build: BuildConfig = TaskFlowModule.build,
     val rotation: RotationConfig = TaskFlowModule.rotation,
-    val interact: InteractionConfig = TaskFlowModule.interact,
-    val inventory: InventoryConfig = TaskFlowModule.inventory,
+    val interact: InteractionConfig = TaskFlowModule.interaction,
+    val inventory: InventoryConfig = TaskFlowModule.inventory
 ) : Task() {
     private val startStack: ItemStack = stack.copy()
     override val name: String get() = "Placing container ${startStack.name.string}"
 
     override fun SafeContext.onStart() {
-        val results = BlockPos.iterateOutwards(player.blockPos, 4, 3, 4)
-            .map { it.blockPos }
-            .flatMap {
-                it.blockPos
-                    .toStructure(TargetState.Stack(startStack))
-                    .toBlueprint()
-                    .simulate(player.eyePos)
+        tickingBlueprint { current ->
+            if (current.isNotEmpty() &&
+                (current.all { it.value.matches(blockState(it.key), it.key, world) } ||
+                ManagerUtils.positionBlockingManagers.any { it.blockedPositions.isNotEmpty() }))
+            {
+                return@tickingBlueprint current
             }
 
-        val succeeds = results.filterIsInstance().filter {
-            canBeOpened(startStack, it.blockPos, it.context.result.side)
-        }
-        val wrongStacks = results.filterIsInstance().filter {
-            canBeOpened(startStack, it.blockPos, it.context.result.side)
-        }
-        (succeeds + wrongStacks).minOrNull()?.let { result ->
-            build(
-                build = build,
-                rotation = rotation,
-                interact = interact,
-                inventory = inventory,
-            ) {
-                result.blockPos
-                    .toStructure(TargetState.Stack(startStack))
-                    .toBlueprint()
-            }.finally {
-                success(result.blockPos)
-            }.execute(this@PlaceContainer)
-        } ?: {
-            failure("No valid placement found")
-        }
+            val results = BlockPos.iterateOutwards(player.blockPos, 4, 3, 4)
+                .flatMap {
+                    it.toStructure(TargetState.Stack(startStack))
+                        .toBlueprint()
+                        .simulate(player.eyePos)
+                }
+
+            // ToDo: Check based on if we can move the player close enough rather than y level once the custom pathfinder is merged
+            val succeeds = results.filterIsInstance().filter {
+                canBeOpened(startStack, it.blockPos, it.context.result.side) && it.blockPos.y == player.blockPos.y
+            }
+            val wrongStacks = results.filterIsInstance().filter {
+                canBeOpened(startStack, it.blockPos, it.context.result.side) && it.blockPos.y == player.blockPos.y
+            }
+            (succeeds + wrongStacks).minOrNull()
+                ?.blockPos
+                ?.toStructure(TargetState.Stack(startStack))
+        }.build(
+            finishOnDone = true,
+            collectDrops = false,
+            build = build,
+            rotation = rotation,
+            interact = interact,
+            inventory = inventory
+        ).finally {
+            val pos = it.keys.firstOrNull() ?: run {
+                failure("The returned structure was empty")
+                return@finally
+            }
+            success(pos)
+        }.execute(this@PlaceContainer)
     }
 
     private fun SafeContext.canBeOpened(
diff --git a/src/main/kotlin/com/lambda/threading/Threading.kt b/src/main/kotlin/com/lambda/threading/Threading.kt
index 95bdb0f08..08dfab0a2 100644
--- a/src/main/kotlin/com/lambda/threading/Threading.kt
+++ b/src/main/kotlin/com/lambda/threading/Threading.kt
@@ -41,7 +41,7 @@ import java.util.concurrent.CompletableFuture
  * @return The result of the block execution if the context is safe, null otherwise.
  */
 inline fun  runSafe(block: SafeContext.() -> T) =
-    ClientContext().toSafe()?.let { block(it) }
+    ClientContext().toSafe()?.run(block)
 
 /**
  * This function is used to execute a block of code on a new thread running asynchronously to the game thread.
diff --git a/src/main/kotlin/com/lambda/util/BlockUtils.kt b/src/main/kotlin/com/lambda/util/BlockUtils.kt
index 88f8520b7..39dd07e39 100644
--- a/src/main/kotlin/com/lambda/util/BlockUtils.kt
+++ b/src/main/kotlin/com/lambda/util/BlockUtils.kt
@@ -18,6 +18,8 @@
 package com.lambda.util
 
 import com.lambda.context.SafeContext
+import com.lambda.util.EnchantmentUtils.getEnchantment
+import com.lambda.util.player.gamemode
 import net.minecraft.block.AbstractCauldronBlock
 import net.minecraft.block.AbstractFurnaceBlock
 import net.minecraft.block.AbstractSignBlock
@@ -79,14 +81,19 @@ import net.minecraft.block.StructureBlock
 import net.minecraft.block.SweetBerryBushBlock
 import net.minecraft.block.TntBlock
 import net.minecraft.block.TrapdoorBlock
+import net.minecraft.enchantment.Enchantments
+import net.minecraft.entity.attribute.EntityAttributes
+import net.minecraft.entity.effect.StatusEffectUtil
+import net.minecraft.entity.effect.StatusEffects
+import net.minecraft.entity.player.PlayerEntity
 import net.minecraft.fluid.FluidState
 import net.minecraft.fluid.Fluids
 import net.minecraft.item.Item
-import net.minecraft.util.math.BlockPos
-import net.minecraft.util.math.Direction
-import net.minecraft.util.math.EightWayDirection
-import net.minecraft.util.math.Vec3d
-import net.minecraft.util.math.Vec3i
+import net.minecraft.item.ItemStack
+import net.minecraft.registry.tag.FluidTags
+import net.minecraft.state.property.Property
+import net.minecraft.util.math.*
+import net.minecraft.world.BlockView
 
 object BlockUtils {
 
@@ -148,7 +155,7 @@ object BlockUtils {
 
     val allSigns = signs + wallSigns + hangingSigns + hangingWallSigns
 
-    val interactionClasses = setOf(
+    val interactionBlocks = setOf(
         AbstractCauldronBlock::class,
         AbstractFurnaceBlock::class,
         AbstractSignBlock::class,
@@ -221,11 +228,83 @@ object BlockUtils {
     fun SafeContext.fluidState(pos: BlockPos): FluidState = world.getFluidState(pos)
     fun SafeContext.blockEntity(pos: BlockPos) = world.getBlockEntity(pos)
 
-    fun SafeContext.instantBreakable(blockState: BlockState, blockPos: BlockPos): Boolean {
-        val ticksNeeded = 1 / blockState.calcBlockBreakingDelta(player, world, blockPos)
-        return (ticksNeeded <= 1 && ticksNeeded != 0f) || player.isCreative
+    fun BlockState.matches(state: BlockState, ignoredProperties: Collection> = emptySet()) =
+         this.block == state.block && this.properties.all {
+             this[it] == state[it] || it in ignoredProperties
+        }
+
+    fun SafeContext.instantBreakable(blockState: BlockState, blockPos: BlockPos, breakThreshold: Float): Boolean {
+        val ticksNeeded = 1 / (blockState.calcBlockBreakingDelta(player, world, blockPos) / breakThreshold)
+        return (ticksNeeded <= 1 && ticksNeeded != 0f) || gamemode.isCreative
+    }
+
+    fun SafeContext.instantBreakable(blockState: BlockState, blockPos: BlockPos, item: ItemStack, breakThreshold: Float): Boolean {
+        val ticksNeeded = 1 / (blockState.calcItemBlockBreakingDelta(player, world, blockPos, item) / breakThreshold)
+        return (ticksNeeded <= 1 && ticksNeeded != 0f) || gamemode.isCreative
+    }
+
+    fun BlockState.calcItemBlockBreakingDelta(
+        player: PlayerEntity,
+        world: BlockView,
+        blockPos: BlockPos,
+        item: ItemStack,
+        ignoreEfficiency: Boolean = false
+    ): Float {
+        val hardness = getHardness(world, blockPos)
+        return if (hardness == -1.0f) 0.0f else {
+            val harvestMultiplier = if (item.canHarvest(this)) 30 else 100
+            player.getItemBlockBreakingSpeed(this, item, ignoreEfficiency) / hardness / harvestMultiplier
+        }
     }
 
+    fun ItemStack.canHarvest(blockState: BlockState) =
+        !blockState.isToolRequired || isSuitableFor(blockState)
+
+    fun PlayerEntity.getItemBlockBreakingSpeed(blockState: BlockState, item: ItemStack, ignoreEfficiency: Boolean = false): Float {
+        var speedMultiplier = item.getMiningSpeedMultiplier(blockState)
+        if (speedMultiplier > 1.0f) {
+            val level = if (ignoreEfficiency) 0 else item.getEnchantment(Enchantments.EFFICIENCY)
+            if (level > 0 && !item.isEmpty) {
+                speedMultiplier += (level * level + 1)
+            }
+        }
+
+        if (StatusEffectUtil.hasHaste(this)) {
+            speedMultiplier *= 1.0f + (StatusEffectUtil.getHasteAmplifier(this) + 1) * 0.2f
+        }
+
+        getStatusEffect(StatusEffects.MINING_FATIGUE)?.amplifier?.let { fatigue ->
+            val fatigueMultiplier = when (fatigue) {
+                0 -> 0.3f
+                1 -> 0.09f
+                2 -> 0.0027f
+                3 -> 8.1E-4f
+                else -> 8.1E-4f
+            }
+
+            speedMultiplier *= fatigueMultiplier
+        }
+
+        if (isSubmergedIn(FluidTags.WATER)) {
+            getAttributeInstance(EntityAttributes.SUBMERGED_MINING_SPEED)?.let { speed ->
+                speedMultiplier *= speed.getValue().toFloat()
+            }
+        }
+
+        if (!isOnGround) {
+            speedMultiplier /= 5.0f
+        }
+
+        return speedMultiplier
+    }
+
+    val BlockState.isEmpty get() = matches(emptyState)
+    val BlockState.isNotEmpty get() = !isEmpty
+    val BlockState.hasFluid get() = !fluidState.isEmpty
+    val BlockState.emptyState: BlockState get() = fluidState.blockState
+    fun isBroken(oldState: BlockState, newState: BlockState) = oldState.isNotEmpty && oldState.emptyState.matches(newState)
+    fun isNotBroken(oldState: BlockState, newState: BlockState) = !isBroken(oldState, newState)
+
     val Vec3i.blockPos: BlockPos get() = BlockPos(this)
     val Block.item: Item get() = asItem()
     fun BlockPos.vecOf(direction: Direction): Vec3d = toCenterPos().add(Vec3d.of(direction.vector).multiply(0.5))
diff --git a/src/main/kotlin/com/lambda/util/EnchantmentUtils.kt b/src/main/kotlin/com/lambda/util/EnchantmentUtils.kt
new file mode 100644
index 000000000..2e9571e09
--- /dev/null
+++ b/src/main/kotlin/com/lambda/util/EnchantmentUtils.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2025 Lambda
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see .
+ */
+
+package com.lambda.util
+
+import net.minecraft.component.DataComponentTypes
+import net.minecraft.component.type.ItemEnchantmentsComponent
+import net.minecraft.enchantment.Enchantment
+import net.minecraft.entity.EquipmentSlot
+import net.minecraft.entity.LivingEntity
+import net.minecraft.item.ItemStack
+import net.minecraft.registry.RegistryKey
+import net.minecraft.registry.entry.RegistryEntry
+
+object EnchantmentUtils {
+    /**
+     * Returns the list of enchantments from a given [ItemStack]
+     */
+    val ItemStack.enchantments: ItemEnchantmentsComponent
+        get() = getOrDefault(DataComponentTypes.ENCHANTMENTS, ItemEnchantmentsComponent.DEFAULT)
+
+    /**
+     * Returns whether the given [ItemStack] has enchantments
+     */
+    val ItemStack.hasEnchantments: Boolean
+        get() = !getOrDefault(DataComponentTypes.ENCHANTMENTS, ItemEnchantmentsComponent.DEFAULT).isEmpty
+                || getOrDefault(DataComponentTypes.STORED_ENCHANTMENTS, ItemEnchantmentsComponent.DEFAULT).isEmpty
+
+    /**
+     * Returns the given enchantment level from a [net.minecraft.item.ItemStack]
+     */
+    fun ItemStack.getEnchantment(key: RegistryKey) =
+        getOrDefault(DataComponentTypes.ENCHANTMENTS, ItemEnchantmentsComponent.DEFAULT)
+            .enchantmentEntries.find { it.key?.matchesKey(key) == true }
+            ?.intValue
+            ?: 0
+
+
+    /**
+     * Iterates over all the enchantments for the given [ItemStack]
+     */
+    fun  ItemStack.forEachEnchantment(block: (RegistryEntry, Int) -> T) =
+        enchantments.enchantmentEntries.asSequence()
+            .map { block(it.key, it.intValue) }
+
+    /**
+     * Iterates over all the enchantments of the given [net.minecraft.entity.LivingEntity]'s [EquipmentSlot]
+     */
+    fun  LivingEntity.forEachSlot(vararg slots: EquipmentSlot, block: (entry: RegistryEntry, level: Int) -> T) =
+        slots.flatMap { getEquippedStack(it).forEachEnchantment(block) }
+}
diff --git a/src/main/kotlin/com/lambda/util/Nameable.kt b/src/main/kotlin/com/lambda/util/Nameables.kt
similarity index 94%
rename from src/main/kotlin/com/lambda/util/Nameable.kt
rename to src/main/kotlin/com/lambda/util/Nameables.kt
index 6bb9145cc..ed6cbae06 100644
--- a/src/main/kotlin/com/lambda/util/Nameable.kt
+++ b/src/main/kotlin/com/lambda/util/Nameables.kt
@@ -28,3 +28,7 @@ interface Nameable {
 interface NamedEnum {
     val displayName: String
 }
+
+interface Describable {
+    val description: String
+}
diff --git a/src/main/kotlin/com/lambda/util/OneSetPerTick.kt b/src/main/kotlin/com/lambda/util/OneSetPerTick.kt
new file mode 100644
index 000000000..157db9a27
--- /dev/null
+++ b/src/main/kotlin/com/lambda/util/OneSetPerTick.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2025 Lambda
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see .
+ */
+
+package com.lambda.util
+
+import com.lambda.event.events.TickEvent
+import com.lambda.event.listener.SafeListener.Companion.listen
+import kotlin.reflect.KProperty
+
+class OneSetPerTick(
+    private var value: T,
+    private val throwOnLimitBreach: Boolean = false,
+    private val resetAfterTick: Boolean = false
+) {
+    val defaultValue = value
+
+    var setThisTick = false
+        private set
+
+    var destroyed = false
+        private set
+
+    init {
+        instances.add(this)
+    }
+
+    @Suppress("Unused")
+    operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
+        if (destroyed) throw IllegalStateException("Value accessed after being destroyed")
+        return value ?: throw UninitializedPropertyAccessException()
+    }
+
+    @Suppress("Unused")
+    operator fun setValue(thisRef: Any?, property: KProperty<*>, newValue: T) = set(newValue)
+
+    fun set(newValue: T) {
+        if (destroyed) throw IllegalStateException("Value set after being destroyed")
+        if (setThisTick && newValue != value) {
+            if (throwOnLimitBreach) throw IllegalStateException("Value already written this tick")
+            return
+        }
+        setThisTick = true
+        value = newValue
+    }
+
+    private fun reset() {
+        value = defaultValue
+    }
+
+    fun destroy() {
+        destroyed = true
+        instances.remove(this)
+    }
+
+    companion object {
+        private val instances = linkedSetOf>()
+
+        init {
+            listen(priority = Int.MIN_VALUE) {
+                instances.forEach {
+                    it.setThisTick = false
+                    if (it.resetAfterTick) it.reset()
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/lambda/util/collections/LimitedDecayQueue.kt b/src/main/kotlin/com/lambda/util/collections/LimitedDecayQueue.kt
index b8802530b..39c50a5ea 100644
--- a/src/main/kotlin/com/lambda/util/collections/LimitedDecayQueue.kt
+++ b/src/main/kotlin/com/lambda/util/collections/LimitedDecayQueue.kt
@@ -26,7 +26,7 @@ import java.util.concurrent.ConcurrentLinkedQueue
  *
  * @param E The type of elements held in this collection.
  * @property sizeLimit The maximum number of elements the queue can hold at any given time.
- * @property maxAge The age (in milliseconds) after which elements are considered expired and are removed from the queue.
+ * @property maxAge The activeRequestAge (in milliseconds) after which elements are considered expired and are removed from the queue.
  * @property onDecay Lambda function that is executed on decay of element [E].
  */
 class LimitedDecayQueue(
@@ -121,7 +121,7 @@ class LimitedDecayQueue(
 
     /**
      * Sets the decay time for the elements in the queue. The decay time determines the
-     * maximum age that any element in the queue can have before being considered expired
+     * maximum activeRequestAge that any element in the queue can have before being considered expired
      * and removed. Updates the internal state and triggers a cleanup of expired elements.
      *
      * @param decayTime The decay time in milliseconds. Must be a non-negative value.
@@ -131,7 +131,7 @@ class LimitedDecayQueue(
         cleanUp()
     }
 
-    private fun cleanUp() {
+    fun cleanUp() {
         val now = Instant.now()
         while (queue.isNotEmpty() && now.minusMillis(maxAge).isAfter(queue.peek().second)) {
             onDecay(queue.poll().first)
diff --git a/src/main/kotlin/com/lambda/util/extension/Entity.kt b/src/main/kotlin/com/lambda/util/extension/Entity.kt
index 6c1481a64..56eb488b4 100644
--- a/src/main/kotlin/com/lambda/util/extension/Entity.kt
+++ b/src/main/kotlin/com/lambda/util/extension/Entity.kt
@@ -17,7 +17,7 @@
 
 package com.lambda.util.extension
 
-import com.lambda.interaction.request.rotation.Rotation
+import com.lambda.interaction.request.rotating.Rotation
 import net.minecraft.entity.Entity
 import net.minecraft.entity.LivingEntity
 import net.minecraft.util.math.Vec3d
diff --git a/src/main/kotlin/com/lambda/util/item/ItemStackUtils.kt b/src/main/kotlin/com/lambda/util/item/ItemStackUtils.kt
index b2d419933..dd4325a73 100644
--- a/src/main/kotlin/com/lambda/util/item/ItemStackUtils.kt
+++ b/src/main/kotlin/com/lambda/util/item/ItemStackUtils.kt
@@ -17,48 +17,63 @@
 
 package com.lambda.util.item
 
+import com.lambda.context.SafeContext
 import com.lambda.util.collections.Cacheable.Companion.cacheable
 import net.minecraft.component.DataComponentTypes
+import net.minecraft.component.type.AttributeModifiersComponent
+import net.minecraft.entity.LivingEntity
 import net.minecraft.entity.attribute.EntityAttributes
-import net.minecraft.entity.player.PlayerEntity
 import net.minecraft.item.ItemStack
 
 object ItemStackUtils {
+    // FixMe: Change this fucking retarded stuff when mojang wake up from their coma and realize they fucked this shit up
+    //  - The client and the server entity attributes are not synced,
+    //  - Enchantments do not change attributes,
+    //  - All enchantment utils are bound to the server
+
+    /**
+     * Returns the attack damage for the given [stack], the value is affected by potion effects and enchantments
+     */
+    fun SafeContext.attackDamage(entity: LivingEntity = player, stack: ItemStack = entity.mainHandStack) = entity.attackDamage(stack)
+
+    /**
+     * Returns the attack damage for the given [stack], the value is affected by potion effects and enchantments
+     */
+    fun LivingEntity.attackDamage(stack: ItemStack = mainHandStack) =
+        (stack.getOrDefault(DataComponentTypes.ATTRIBUTE_MODIFIERS, AttributeModifiersComponent.DEFAULT)
+            .modifiers.find { it.attribute == EntityAttributes.ATTACK_DAMAGE }?.modifier?.value ?: 0.0) +
+                getAttributeValue(EntityAttributes.ATTACK_DAMAGE)
     /**
-     * Returns the full attack damage of the main hand item.
+     * Returns the attack speed for the given [stack], the value is affected by potion effects
      *
-     * The player attack damage base value is 1 and can be modified by potion effects such as
-     * strength and these modifications are held into account.
+     * The value represents the number of attacks-per-tick
      */
-    val PlayerEntity.itemAttackDamage: Double
-        get() = getAttributeValue(EntityAttributes.ATTACK_DAMAGE) + mainHandStack.attackDamage
+    fun SafeContext.attackSpeed(entity: LivingEntity = player, stack: ItemStack = entity.mainHandStack) = entity.attackSpeed(stack)
 
     /**
-     * Returns the full attack speed of the main hand item.
+     * Returns the attack speed for the given [stack], the value is affected by potion effects
      *
-     * The player attack speed base value is 4 and can be modified by potion effects such as
-     * haste and mining fatigue and these modifications are held into account.
+     * The value represents the number of attacks-per-tick
      */
-    val PlayerEntity.itemAttackSpeed: Double
-        get() = getAttributeValue(EntityAttributes.ATTACK_SPEED) + mainHandStack.attackSpeed
+    fun LivingEntity.attackSpeed(stack: ItemStack = mainHandStack) =
+        (stack.getOrDefault(DataComponentTypes.ATTRIBUTE_MODIFIERS, AttributeModifiersComponent.DEFAULT)
+            .modifiers.find { it.attribute == EntityAttributes.ATTACK_SPEED }?.modifier?.value ?: 0.0) +
+                getAttributeValue(EntityAttributes.ATTACK_SPEED)
 
     /**
-     * Returns the base attack damage of the given [ItemStack] or 2 as a fallback
+     * Returns the mining speed for the given [stack], the value is affected by potion effects and enchantments
      */
-    val ItemStack.attackDamage: Double
-        get() = get(DataComponentTypes.ATTRIBUTE_MODIFIERS)
-            ?.modifiers
-            ?.find { it.attribute == EntityAttributes.ATTACK_DAMAGE }
-            ?.modifier?.value ?: 2.0
+    fun SafeContext.miningSpeed(entity: LivingEntity = player, stack: ItemStack = entity.mainHandStack) = entity.miningSpeed(stack)
 
     /**
-     * Returns the base attack speed of the given [ItemStack] or 4 as a fallback
+     * Returns the mining speed for the given [stack], the value is affected by potion effects and enchantments
      */
-    val ItemStack.attackSpeed: Double
-        get() = get(DataComponentTypes.ATTRIBUTE_MODIFIERS)
-            ?.modifiers
-            ?.find { it.attribute == EntityAttributes.ATTACK_SPEED }
-            ?.modifier?.value ?: 4.0
+    fun LivingEntity.miningSpeed(stack: ItemStack = mainHandStack) =
+        (stack.getOrDefault(DataComponentTypes.ATTRIBUTE_MODIFIERS, AttributeModifiersComponent.DEFAULT)
+            .modifiers.find { it.attribute == EntityAttributes.MINING_EFFICIENCY ||
+                    it.attribute == EntityAttributes.SUBMERGED_MINING_SPEED }?.modifier?.value ?: 0.0) +
+                if (isSubmergedInWater) getAttributeValue(EntityAttributes.SUBMERGED_MINING_SPEED)
+                else getAttributeValue(EntityAttributes.MINING_EFFICIENCY)
 
     val ItemStack.spaceLeft get() = maxCount - count
     val ItemStack.hasSpace get() = spaceLeft > 0
diff --git a/src/main/kotlin/com/lambda/util/item/ItemUtils.kt b/src/main/kotlin/com/lambda/util/item/ItemUtils.kt
index 34e9648cc..1ed212185 100644
--- a/src/main/kotlin/com/lambda/util/item/ItemUtils.kt
+++ b/src/main/kotlin/com/lambda/util/item/ItemUtils.kt
@@ -18,12 +18,14 @@
 package com.lambda.util.item
 
 import net.minecraft.block.Block
+import net.minecraft.block.BlockState
 import net.minecraft.block.Blocks
 import net.minecraft.item.Item
 import net.minecraft.item.Items
 
 object ItemUtils {
 
+
     val pickaxes = setOf(
         Items.WOODEN_PICKAXE,
         Items.STONE_PICKAXE,
@@ -105,6 +107,7 @@ object ItemUtils {
 
     val defaultDisposables = setOf(
         Blocks.DIRT,
+        Blocks.GRASS_BLOCK,
         Blocks.COBBLESTONE,
         Blocks.GRANITE,
         Blocks.DIORITE,
@@ -133,29 +136,19 @@ object ItemUtils {
 
             if (dubs > 0) {
                 append("$dubs dub")
-                if (dubs > 1) {
-                    append("s")
-                }
-                if (shulkers > 0 || remainingItems > 0) {
-                    append(" ")
-                }
+                if (dubs > 1) append("s")
+                if (shulkers > 0 || remainingItems > 0) append(" ")
             }
 
             if (shulkers > 0) {
                 append("$shulkers shulker")
-                if (shulkers > 1) {
-                    append("s")
-                }
-                if (remainingItems > 0) {
-                    append(" ")
-                }
+                if (shulkers > 1) append("s")
+                if (remainingItems > 0) append(" ")
             }
 
             if (remainingItems > 0) {
                 append("$remainingItems item")
-                if (remainingItems > 1) {
-                    append("s")
-                }
+                if (remainingItems > 1) append("s")
             }
         }
     }
diff --git a/src/main/kotlin/com/lambda/util/math/Linear.kt b/src/main/kotlin/com/lambda/util/math/Linear.kt
index c43cff93d..7f8e712eb 100644
--- a/src/main/kotlin/com/lambda/util/math/Linear.kt
+++ b/src/main/kotlin/com/lambda/util/math/Linear.kt
@@ -17,7 +17,7 @@
 
 package com.lambda.util.math
 
-import com.lambda.interaction.request.rotation.Rotation
+import com.lambda.interaction.request.rotating.Rotation
 import net.minecraft.util.math.Box
 import net.minecraft.util.math.Vec3d
 import java.awt.Color
diff --git a/src/main/kotlin/com/lambda/util/player/MovementUtils.kt b/src/main/kotlin/com/lambda/util/player/MovementUtils.kt
index 0a07072b3..d935887c0 100644
--- a/src/main/kotlin/com/lambda/util/player/MovementUtils.kt
+++ b/src/main/kotlin/com/lambda/util/player/MovementUtils.kt
@@ -18,7 +18,7 @@
 package com.lambda.util.player
 
 import com.lambda.context.SafeContext
-import com.lambda.interaction.request.rotation.RotationManager
+import com.lambda.interaction.request.rotating.RotationManager
 import com.lambda.util.math.MathUtils.toDegree
 import com.lambda.util.math.MathUtils.toInt
 import com.lambda.util.math.MathUtils.toRadian
diff --git a/src/main/kotlin/com/lambda/util/player/PlayerUtils.kt b/src/main/kotlin/com/lambda/util/player/PlayerUtils.kt
index c985ca4be..b0d280893 100644
--- a/src/main/kotlin/com/lambda/util/player/PlayerUtils.kt
+++ b/src/main/kotlin/com/lambda/util/player/PlayerUtils.kt
@@ -17,12 +17,19 @@
 
 package com.lambda.util.player
 
+import com.lambda.config.groups.BuildConfig
 import com.lambda.context.SafeContext
 import com.mojang.authlib.GameProfile
 import net.minecraft.client.network.ClientPlayerEntity
 import net.minecraft.client.network.OtherClientPlayerEntity
 import net.minecraft.client.network.PlayerListEntry
 import net.minecraft.entity.player.PlayerEntity
+import net.minecraft.item.ItemStack
+import net.minecraft.network.packet.c2s.play.HandSwingC2SPacket
+import net.minecraft.util.Hand
+
+val SafeContext.gamemode
+    get() = interaction.currentGameMode
 
 fun SafeContext.copyPlayer(entity: ClientPlayerEntity) =
     ClientPlayerEntity(mc, world, mc.networkHandler, null, null, entity.isSneaking, entity.isSprinting).apply {
@@ -59,3 +66,22 @@ fun SafeContext.spawnFakePlayer(
     return entity
 }
 
+fun SafeContext.swingHand(swingType: BuildConfig.SwingType, hand: Hand) =
+    when (swingType) {
+        BuildConfig.SwingType.Vanilla -> {
+            swingHandClient(hand)
+            connection.sendPacket(HandSwingC2SPacket(hand))
+        }
+        BuildConfig.SwingType.Server -> connection.sendPacket(HandSwingC2SPacket(hand))
+        BuildConfig.SwingType.Client -> swingHandClient(hand)
+    }
+
+fun SafeContext.swingHandClient(hand: Hand) {
+    if (!player.handSwinging || player.handSwingTicks >= player.handSwingDuration / 2 || player.handSwingTicks < 0) {
+        player.handSwingTicks = -1
+        player.handSwinging = true
+        player.preferredHand = hand
+    }
+}
+
+fun SafeContext.isItemOnCooldown(stack: ItemStack) = player.itemCooldownManager.isCoolingDown(stack)
diff --git a/src/main/kotlin/com/lambda/util/player/prediction/PredictionEntity.kt b/src/main/kotlin/com/lambda/util/player/prediction/PredictionEntity.kt
index 6523aba02..ed5780a89 100644
--- a/src/main/kotlin/com/lambda/util/player/prediction/PredictionEntity.kt
+++ b/src/main/kotlin/com/lambda/util/player/prediction/PredictionEntity.kt
@@ -19,7 +19,7 @@ package com.lambda.util.player.prediction
 
 import com.lambda.Lambda.mc
 import com.lambda.context.SafeContext
-import com.lambda.interaction.request.rotation.Rotation
+import com.lambda.interaction.request.rotating.Rotation
 import com.lambda.module.modules.movement.SafeWalk.isNearLedge
 import com.lambda.threading.runSafe
 import com.lambda.util.BlockUtils.blockState
diff --git a/src/main/kotlin/com/lambda/util/player/prediction/PredictionTick.kt b/src/main/kotlin/com/lambda/util/player/prediction/PredictionTick.kt
index 4256ca6c4..f0e108d3f 100644
--- a/src/main/kotlin/com/lambda/util/player/prediction/PredictionTick.kt
+++ b/src/main/kotlin/com/lambda/util/player/prediction/PredictionTick.kt
@@ -17,7 +17,7 @@
 
 package com.lambda.util.player.prediction
 
-import com.lambda.interaction.request.rotation.Rotation
+import com.lambda.interaction.request.rotating.Rotation
 import net.minecraft.util.math.Box
 import net.minecraft.util.math.Vec3d
 
diff --git a/src/main/kotlin/com/lambda/util/world/WorldUtils.kt b/src/main/kotlin/com/lambda/util/world/WorldUtils.kt
index c3947ed27..cf89801c1 100644
--- a/src/main/kotlin/com/lambda/util/world/WorldUtils.kt
+++ b/src/main/kotlin/com/lambda/util/world/WorldUtils.kt
@@ -36,8 +36,6 @@ object WorldUtils {
             ChunkSectionPos.getSectionCoord(pos.x), ChunkSectionPos.getSectionCoord(pos.z)
         )
 
-    val BlockState.hasFluid get() = !fluidState.isEmpty
-
     /**
      * Gets all entities of type [T] within a specified distance from a position.
      *
diff --git a/src/main/kotlin/com/lambda/util/world/raycast/RayCastUtils.kt b/src/main/kotlin/com/lambda/util/world/raycast/RayCastUtils.kt
index 608e0b382..d53588414 100644
--- a/src/main/kotlin/com/lambda/util/world/raycast/RayCastUtils.kt
+++ b/src/main/kotlin/com/lambda/util/world/raycast/RayCastUtils.kt
@@ -19,7 +19,7 @@ package com.lambda.util.world.raycast
 
 import com.lambda.Lambda.mc
 import com.lambda.context.SafeContext
-import com.lambda.interaction.request.rotation.Rotation
+import com.lambda.interaction.request.rotating.Rotation
 import com.lambda.util.math.distSq
 import net.minecraft.client.network.ClientPlayerEntity
 import net.minecraft.entity.Entity
diff --git a/src/main/resources/lambda.accesswidener b/src/main/resources/lambda.accesswidener
index 258eef1db..9b6ded8d2 100644
--- a/src/main/resources/lambda.accesswidener
+++ b/src/main/resources/lambda.accesswidener
@@ -18,6 +18,11 @@ accessible field net/minecraft/client/world/ClientChunkManager$ClientChunkMap ch
 accessible field net/minecraft/client/network/ClientPlayerInteractionManager currentBreakingProgress F
 accessible field net/minecraft/client/network/ClientPlayerInteractionManager blockBreakingCooldown I
 accessible field net/minecraft/client/network/ClientPlayerInteractionManager currentBreakingPos Lnet/minecraft/util/math/BlockPos;
+accessible method net/minecraft/item/BlockItem place (Lnet/minecraft/item/ItemPlacementContext;Lnet/minecraft/block/BlockState;)Z
+accessible method net/minecraft/item/BlockItem placeFromNbt (Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/world/World;Lnet/minecraft/item/ItemStack;Lnet/minecraft/block/BlockState;)Lnet/minecraft/block/BlockState;
+accessible method net/minecraft/item/BlockItem postPlacement (Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/world/World;Lnet/minecraft/entity/player/PlayerEntity;Lnet/minecraft/item/ItemStack;Lnet/minecraft/block/BlockState;)Z
+accessible method net/minecraft/item/BlockItem getPlaceSound (Lnet/minecraft/block/BlockState;)Lnet/minecraft/sound/SoundEvent;
+accessible field net/minecraft/state/State owner Ljava/lang/Object;
 
 # Entity
 accessible field net/minecraft/entity/projectile/FireworkRocketEntity shooter Lnet/minecraft/entity/LivingEntity;
@@ -36,6 +41,8 @@ accessible field net/minecraft/entity/player/PlayerInventory equipment Lnet/mine
 accessible method net/minecraft/client/network/ClientPlayerEntity applyMovementSpeedFactors (Lnet/minecraft/util/math/Vec2f;)Lnet/minecraft/util/math/Vec2f;
 accessible field net/minecraft/entity/EntityEquipment map Ljava/util/EnumMap;
 accessible method net/minecraft/entity/LivingEntity getHandSwingDuration ()I
+accessible method net/minecraft/client/network/ClientPlayerInteractionManager syncSelectedSlot ()V
+accessible method net/minecraft/util/math/Direction listClosest (Lnet/minecraft/util/math/Direction;Lnet/minecraft/util/math/Direction;Lnet/minecraft/util/math/Direction;)[Lnet/minecraft/util/math/Direction;
 
 # Camera
 accessible method net/minecraft/client/render/Camera setPos (DDD)V
@@ -75,6 +82,8 @@ accessible field net/minecraft/network/packet/c2s/login/LoginKeyC2SPacket nonce
 accessible method net/minecraft/network/packet/c2s/play/PlayerInteractEntityC2SPacket  (IZLnet/minecraft/network/packet/c2s/play/PlayerInteractEntityC2SPacket$InteractTypeHandler;)V
 accessible field net/minecraft/network/packet/c2s/play/PlayerInteractEntityC2SPacket ATTACK Lnet/minecraft/network/packet/c2s/play/PlayerInteractEntityC2SPacket$InteractTypeHandler;
 accessible method net/minecraft/network/packet/BrandCustomPayload write (Lnet/minecraft/network/PacketByteBuf;)V
+accessible method net/minecraft/client/network/ClientPlayerInteractionManager sendSequencedPacket (Lnet/minecraft/client/world/ClientWorld;Lnet/minecraft/client/network/SequencedPacketCreator;)V
+accessible field net/minecraft/client/world/ClientWorld pendingUpdateManager Lnet/minecraft/client/network/PendingUpdateManager;
 
 # Math
 accessible method net/minecraft/util/math/Vec3i setX (I)Lnet/minecraft/util/math/Vec3i;
diff --git a/src/test/kotlin/PlaceDirectionTest.kt b/src/test/kotlin/PlaceDirectionTest.kt
new file mode 100644
index 000000000..9f76d1b9a
--- /dev/null
+++ b/src/test/kotlin/PlaceDirectionTest.kt
@@ -0,0 +1,380 @@
+/*
+ * Copyright 2025 Lambda
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see .
+ */
+
+import com.lambda.interaction.request.rotating.Rotation
+import com.lambda.interaction.request.rotating.visibilty.PlaceDirection
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertFalse
+import kotlin.test.assertTrue
+
+/**
+ * Tests for the PlaceDirection class
+ */
+class PlaceDirectionTest {
+
+    // Tests for snapToArea method - Horizontal directions
+
+    @Test
+    fun `test pitch snapping for East direction`() {
+        val direction = PlaceDirection.East
+        val rot = Rotation(-90.0, 90.0)
+        val snapped = direction.snapToArea(rot)
+
+        // Verify that the pitch is snapped to the boundary
+        assertEquals(direction, PlaceDirection.fromRotation(snapped))
+    }
+
+    @Test
+    fun `test pitch snapping for West direction`() {
+        val direction = PlaceDirection.West
+        val rot = Rotation(90.0, 90.0)
+        val snapped = direction.snapToArea(rot)
+
+        // Verify that the pitch is snapped to the boundary
+        assertEquals(direction, PlaceDirection.fromRotation(snapped))
+    }
+
+    @Test
+    fun `test pitch snapping for North direction`() {
+        val direction = PlaceDirection.North
+        val rot = Rotation(-180.0, 90.0)
+        val snapped = direction.snapToArea(rot)
+
+        // Verify that the pitch is snapped to the boundary
+        assertEquals(direction, PlaceDirection.fromRotation(snapped))
+    }
+
+    @Test
+    fun `test pitch snapping for South direction`() {
+        val direction = PlaceDirection.South
+        val rot = Rotation(0.0, 90.0)
+        val snapped = direction.snapToArea(rot)
+
+        // Verify that the pitch is snapped to the boundary
+        assertEquals(direction, PlaceDirection.fromRotation(snapped))
+    }
+
+    // Tests for snapToArea method - Up directions
+
+    @Test
+    fun `test pitch snapping for UpEast direction`() {
+        val direction = PlaceDirection.UpEast
+        val rot = Rotation(-90.0, 0.0)
+        val snapped = direction.snapToArea(rot)
+
+        // Verify that the pitch is snapped to the boundary
+        assertEquals(direction, PlaceDirection.fromRotation(snapped))
+    }
+
+    @Test
+    fun `test pitch snapping for UpWest direction`() {
+        val direction = PlaceDirection.UpWest
+        val rot = Rotation(90.0, 0.0)
+        val snapped = direction.snapToArea(rot)
+
+        // Verify that the pitch is snapped to the boundary
+        assertEquals(direction, PlaceDirection.fromRotation(snapped))
+    }
+
+    @Test
+    fun `test pitch snapping for UpNorth direction`() {
+        val direction = PlaceDirection.UpNorth
+        val rot = Rotation(-180.0, 0.0)
+        val snapped = direction.snapToArea(rot)
+
+        // Verify that the pitch is snapped to the boundary
+        assertEquals(direction, PlaceDirection.fromRotation(snapped))
+    }
+
+    @Test
+    fun `test pitch snapping for UpSouth direction`() {
+        val direction = PlaceDirection.UpSouth
+        val rot = Rotation(0.0, 0.0)
+        val snapped = direction.snapToArea(rot)
+
+        // Verify that the pitch is snapped to the boundary
+        assertEquals(direction, PlaceDirection.fromRotation(snapped))
+    }
+
+    // Tests for snapToArea method - Down directions
+
+    @Test
+    fun `test pitch snapping for DownEast direction`() {
+        val direction = PlaceDirection.DownEast
+        val rot = Rotation(-90.0, 0.0)
+        val snapped = direction.snapToArea(rot)
+
+        // Verify that the pitch is snapped to the boundary
+        assertEquals(direction, PlaceDirection.fromRotation(snapped))
+    }
+
+    @Test
+    fun `test pitch snapping for DownWest direction`() {
+        val direction = PlaceDirection.DownWest
+        val rot = Rotation(90.0, 0.0)
+        val snapped = direction.snapToArea(rot)
+
+        // Verify that the pitch is snapped to the boundary
+        assertEquals(direction, PlaceDirection.fromRotation(snapped))
+    }
+
+    @Test
+    fun `test pitch snapping for DownNorth direction`() {
+        val direction = PlaceDirection.DownNorth
+        val rot = Rotation(-180.0, 0.0)
+        val snapped = direction.snapToArea(rot)
+
+        // Verify that the pitch is snapped to the boundary
+        assertEquals(direction, PlaceDirection.fromRotation(snapped))
+    }
+
+    @Test
+    fun `test pitch snapping for DownSouth direction`() {
+        val direction = PlaceDirection.DownSouth
+        val rot = Rotation(0.0, 0.0)
+        val snapped = direction.snapToArea(rot)
+
+        // Verify that the pitch is snapped to the boundary
+        assertEquals(direction, PlaceDirection.fromRotation(snapped))
+    }
+
+    // Tests for snapToArea method - Pure Up/Down directions
+
+    @Test
+    fun `test pitch snapping for UpSouth direction with extreme pitch`() {
+        val direction = PlaceDirection.UpSouth
+        val rot = Rotation(0.0, -45.0)
+        val snapped = direction.snapToArea(rot)
+
+        // Verify that the pitch is snapped to the boundary
+        assertEquals(direction, PlaceDirection.fromRotation(snapped))
+    }
+
+    @Test
+    fun `test pitch snapping for DownSouth direction with extreme pitch`() {
+        val direction = PlaceDirection.DownSouth
+        val rot = Rotation(0.0, 45.0)
+        val snapped = direction.snapToArea(rot)
+
+        // Verify that the pitch is snapped to the boundary
+        assertEquals(direction, PlaceDirection.fromRotation(snapped))
+    }
+
+    @Test
+    fun `test yaw and pitch snapping for East direction from up`() {
+        val direction = PlaceDirection.East
+        val rot = Rotation(0.0, -90.0)
+        val snapped = direction.snapToArea(rot)
+
+        // Verify that the yaw and pitch are snapped to the boundary
+        assertEquals(direction, PlaceDirection.fromRotation(snapped))
+    }
+
+    @Test
+    fun `test yaw and pitch snapping for South direction from up`() {
+        val direction = PlaceDirection.South
+        val rot = Rotation(90.0, -90.0)
+        val snapped = direction.snapToArea(rot)
+
+        // Verify that the yaw and pitch are snapped to the boundary
+        assertEquals(direction, PlaceDirection.fromRotation(snapped))
+    }
+
+    @Test
+    fun `test yaw and pitch snapping for West direction from up`() {
+        val direction = PlaceDirection.West
+        val rot = Rotation(-180.0, -90.0)
+        val snapped = direction.snapToArea(rot)
+
+        // Verify that the yaw and pitch are snapped to the boundary
+        assertEquals(direction, PlaceDirection.fromRotation(snapped))
+    }
+
+    @Test
+    fun `test yaw and pitch snapping for North direction from up`() {
+        val direction = PlaceDirection.North
+        val rot = Rotation(-90.0, -90.0)
+        val snapped = direction.snapToArea(rot)
+
+        // Verify that the yaw and pitch are snapped to the boundary
+        assertEquals(direction, PlaceDirection.fromRotation(snapped))
+    }
+
+    @Test
+    fun `test yaw and pitch snapping for East direction from down`() {
+        val direction = PlaceDirection.East
+        val rot = Rotation(0.0, 90.0)
+        val snapped = direction.snapToArea(rot)
+
+        // Verify that the yaw and pitch are snapped to the boundary
+        assertEquals(direction, PlaceDirection.fromRotation(snapped))
+    }
+
+    @Test
+    fun `test yaw and pitch snapping for South direction from down`() {
+        val direction = PlaceDirection.South
+        val rot = Rotation(90.0, 90.0)
+        val snapped = direction.snapToArea(rot)
+
+        // Verify that the yaw and pitch are snapped to the boundary
+        assertEquals(direction, PlaceDirection.fromRotation(snapped))
+    }
+
+    @Test
+    fun `test yaw and pitch snapping for West direction from down`() {
+        val direction = PlaceDirection.West
+        val rot = Rotation(-180.0, 90.0)
+        val snapped = direction.snapToArea(rot)
+
+        // Verify that the yaw and pitch are snapped to the boundary
+        assertEquals(direction, PlaceDirection.fromRotation(snapped))
+    }
+
+    @Test
+    fun `test yaw and pitch snapping for North direction from down`() {
+        val direction = PlaceDirection.North
+        val rot = Rotation(-90.0, 90.0)
+        val snapped = direction.snapToArea(rot)
+
+        // Verify that the yaw and pitch are snapped to the boundary
+        assertEquals(direction, PlaceDirection.fromRotation(snapped))
+    }
+
+    @Test
+    fun `test yaw and pitch snapping from one snap to another starting with East from up`() {
+        val direction = PlaceDirection.East
+        val rot = Rotation(0.0, -90.0)
+        val firstSnapped = direction.snapToArea(rot)
+
+        val nextDirection = PlaceDirection.South
+        val secondSnapped = nextDirection.snapToArea(firstSnapped)
+
+        // Verify that the yaw and pitch are snapped to the boundary
+        assertEquals(nextDirection, PlaceDirection.fromRotation(secondSnapped))
+    }
+
+    @Test
+    fun `test yaw and pitch snapping from one snap to another starting with South from up`() {
+        val direction = PlaceDirection.South
+        val rot = Rotation(90.0, -90.0)
+        val firstSnapped = direction.snapToArea(rot)
+
+        val nextDirection = PlaceDirection.West
+        val secondSnapped = nextDirection.snapToArea(firstSnapped)
+
+        // Verify that the yaw and pitch are snapped to the boundary
+        assertEquals(nextDirection, PlaceDirection.fromRotation(secondSnapped))
+    }
+
+    @Test
+    fun `test yaw and pitch snapping from one snap to another starting with West from up`() {
+        val direction = PlaceDirection.West
+        val rot = Rotation(-180.0, -90.0)
+        val firstSnapped = direction.snapToArea(rot)
+
+        val nextDirection = PlaceDirection.North
+        val secondSnapped = nextDirection.snapToArea(firstSnapped)
+
+        // Verify that the yaw and pitch are snapped to the boundary
+        assertEquals(nextDirection, PlaceDirection.fromRotation(secondSnapped))
+    }
+
+    @Test
+    fun `test yaw and pitch snapping from one snap to another starting with North from up`() {
+        val direction = PlaceDirection.North
+        val rot = Rotation(-90.0, -90.0)
+        val firstSnapped = direction.snapToArea(rot)
+
+        val nextDirection = PlaceDirection.East
+        val secondSnapped = nextDirection.snapToArea(firstSnapped)
+
+        // Verify that the yaw and pitch are snapped to the boundary
+        assertEquals(nextDirection, PlaceDirection.fromRotation(secondSnapped))
+    }
+
+    // Tests for when rotation is already in the area
+
+    @Test
+    fun `test no snapping when rotation is already in area`() {
+        val direction = PlaceDirection.East
+        // Create a rotation that's already in the East area
+        val rot = Rotation(-90.0, 0.0)
+
+        // Verify it's already in the area
+        assertEquals(direction, PlaceDirection.fromRotation(rot))
+
+        val snapped = direction.snapToArea(rot)
+
+        // Verify that the rotation is unchanged
+        assertEquals(rot, snapped)
+    }
+
+    // Tests for isInArea method
+
+    @Test
+    fun `test isInArea method`() {
+        val eastRot = Rotation(-90.0, 0.0)
+        val northRot = Rotation(-180.0, 0.0)
+
+        assertTrue(PlaceDirection.East.isInArea(eastRot))
+        assertFalse(PlaceDirection.East.isInArea(northRot))
+
+        assertTrue(PlaceDirection.North.isInArea(northRot))
+        assertFalse(PlaceDirection.North.isInArea(eastRot))
+    }
+
+    // Tests for fromRotation method
+
+    @Test
+    fun `test fromRotation for horizontal directions`() {
+        assertEquals(PlaceDirection.East, PlaceDirection.fromRotation(Rotation(-90.0, 0.0)))
+        assertEquals(PlaceDirection.West, PlaceDirection.fromRotation(Rotation(90.0, 0.0)))
+        assertEquals(PlaceDirection.North, PlaceDirection.fromRotation(Rotation(-180.0, 0.0)))
+        assertEquals(PlaceDirection.South, PlaceDirection.fromRotation(Rotation(0.0, 0.0)))
+    }
+
+    @Test
+    fun `test fromRotation for up directions`() {
+        assertEquals(PlaceDirection.UpEast, PlaceDirection.fromRotation(Rotation(-90.0, -60.0)))
+        assertEquals(PlaceDirection.UpWest, PlaceDirection.fromRotation(Rotation(90.0, -60.0)))
+        assertEquals(PlaceDirection.UpNorth, PlaceDirection.fromRotation(Rotation(-180.0, -60.0)))
+        assertEquals(PlaceDirection.UpSouth, PlaceDirection.fromRotation(Rotation(0.0, -60.0)))
+    }
+
+    @Test
+    fun `test fromRotation for down directions`() {
+        assertEquals(PlaceDirection.DownEast, PlaceDirection.fromRotation(Rotation(-90.0, 60.0)))
+        assertEquals(PlaceDirection.DownWest, PlaceDirection.fromRotation(Rotation(90.0, 60.0)))
+        assertEquals(PlaceDirection.DownNorth, PlaceDirection.fromRotation(Rotation(-180.0, 60.0)))
+        assertEquals(PlaceDirection.DownSouth, PlaceDirection.fromRotation(Rotation(0.0, 60.0)))
+    }
+
+    // Edge case tests
+
+    @Test
+    fun `test edge case with yaw at boundaries`() {
+        // Test with yaw at the boundaries between directions
+        val boundaryYaw = -135.0 // Boundary between North and East
+
+        // Slightly to the East side
+        assertEquals(PlaceDirection.East, PlaceDirection.fromRotation(Rotation(boundaryYaw + 0.1, 0.0)))
+
+        // Slightly to the North side
+        assertEquals(PlaceDirection.North, PlaceDirection.fromRotation(Rotation(boundaryYaw - 0.1, 0.0)))
+    }
+}
diff --git a/src/test/kotlin/RotationTest.kt b/src/test/kotlin/RotationTest.kt
new file mode 100644
index 000000000..a74d2dadf
--- /dev/null
+++ b/src/test/kotlin/RotationTest.kt
@@ -0,0 +1,305 @@
+/*
+ * Copyright 2025 Lambda
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see .
+ */
+
+import com.lambda.interaction.request.rotating.Rotation
+import com.lambda.interaction.request.rotating.Rotation.Companion.angleDifference
+import com.lambda.interaction.request.rotating.Rotation.Companion.dist
+import com.lambda.interaction.request.rotating.Rotation.Companion.lerp
+import com.lambda.interaction.request.rotating.Rotation.Companion.slerp
+import com.lambda.interaction.request.rotating.Rotation.Companion.wrap
+import kotlin.math.abs
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertTrue
+import kotlin.test.assertFalse
+import kotlin.test.assertNotEquals
+import kotlin.math.hypot
+
+/**
+ * Tests for the Rotation class
+ * Note: Tests only cover methods that don't require SafeContext or Minecraft classes that can't be mocked easily
+ */
+class RotationTest {
+
+    @Test
+    fun `test constructors and basic properties`() {
+        // Test double constructor
+        val rotation1 = Rotation(45.0, 30.0)
+        assertEquals(45.0, rotation1.yaw)
+        assertEquals(30.0, rotation1.pitch)
+
+        // Test float constructor
+        val rotation2 = Rotation(45f, 30f)
+        assertEquals(45.0, rotation2.yaw)
+        assertEquals(30.0, rotation2.pitch)
+
+        // Test yawF and pitchF
+        assertEquals(45f, rotation1.yawF)
+        assertEquals(30f, rotation1.pitchF)
+
+        // Test float property
+        val floatArray = rotation1.float
+        assertEquals(2, floatArray.size)
+        assertEquals(45f, floatArray[0])
+        assertEquals(30f, floatArray[1])
+    }
+
+    @Test
+    fun `test equalFloat method`() {
+        val rotation1 = Rotation(45.0, 30.0)
+        val rotation2 = Rotation(45.0, 30.0)
+        val rotation3 = Rotation(45.0, 31.0)
+        val rotation4 = Rotation(46.0, 30.0)
+
+        assertTrue(rotation1.equalFloat(rotation2))
+        assertFalse(rotation1.equalFloat(rotation3))
+        assertFalse(rotation1.equalFloat(rotation4))
+    }
+
+    @Test
+    fun `test equals and hashCode methods`() {
+        val rotation1 = Rotation(45.0, 30.0)
+        val rotation2 = Rotation(45.0, 30.0)
+        val rotation3 = Rotation(45.0, 31.0)
+        val rotation4 = Rotation(46.0, 30.0)
+        val rotation5 = Rotation(46.0, 31.0)
+
+        // Test equals method
+        assertEquals(rotation1, rotation2)
+        assertNotEquals(rotation1, rotation3)
+        assertNotEquals(rotation1, rotation4)
+        assertNotEquals(rotation1, rotation5)
+        assertNotEquals(rotation3, rotation4)
+        assertNotEquals(rotation3, rotation5)
+        assertNotEquals(rotation4, rotation5)
+
+        // Test hashCode method
+        assertEquals(rotation1.hashCode(), rotation2.hashCode())
+        assertNotEquals(rotation1.hashCode(), rotation3.hashCode())
+        assertNotEquals(rotation1.hashCode(), rotation4.hashCode())
+        assertNotEquals(rotation1.hashCode(), rotation5.hashCode())
+
+        // Test equals with null and other types
+        assertNotEquals(rotation1, null)
+        assertNotEquals(rotation1, "Not a Rotation")
+    }
+
+    @Test
+    fun `test withDelta method`() {
+        val rotation = Rotation(45.0, 30.0)
+
+        // Test with both yaw and pitch delta
+        val result1 = rotation.withDelta(10.0, 20.0)
+        assertEquals(55.0, result1.yaw)
+        assertEquals(50.0, result1.pitch)
+
+        // Test with only yaw delta
+        val result2 = rotation.withDelta(10.0)
+        assertEquals(55.0, result2.yaw)
+        assertEquals(30.0, result2.pitch)
+
+        // Test with only pitch delta
+        val result3 = rotation.withDelta(pitch = 20.0)
+        assertEquals(45.0, result3.yaw)
+        assertEquals(50.0, result3.pitch)
+
+        // Test pitch clamping (upper bound)
+        val result4 = rotation.withDelta(pitch = 70.0)
+        assertEquals(45.0, result4.yaw)
+        assertEquals(90.0, result4.pitch) // Clamped to 90.0
+
+        // Test pitch clamping (lower bound)
+        val result5 = rotation.withDelta(pitch = -130.0)
+        assertEquals(45.0, result5.yaw)
+        assertEquals(-90.0, result5.pitch) // Clamped to -90.0
+    }
+
+    @Test
+    fun `test companion object constants`() {
+        assertEquals(0.0, Rotation.ZERO.yaw)
+        assertEquals(0.0, Rotation.ZERO.pitch)
+
+        assertEquals(0.0, Rotation.DOWN.yaw)
+        assertEquals(90.0, Rotation.DOWN.pitch)
+
+        assertEquals(0.0, Rotation.UP.yaw)
+        assertEquals(-90.0, Rotation.UP.pitch)
+    }
+
+    @Test
+    fun `test wrap method`() {
+        // Test wrapping positive angles
+        assertEquals(0.0, wrap(0.0), 0.001)
+        assertEquals(90.0, wrap(90.0), 0.001)
+        assertEquals(-180.0, wrap(180.0), 0.001)
+        assertEquals(-170.0, wrap(190.0), 0.001) // 190 wraps to -170
+        assertEquals(0.0, wrap(360.0), 0.001)
+        assertEquals(10.0, wrap(370.0), 0.001)
+
+        // Test wrapping negative angles
+        assertEquals(0.0, wrap(-0.0), 0.001)
+        assertEquals(-90.0, wrap(-90.0), 0.001)
+        assertEquals(-180.0, wrap(-180.0), 0.001)
+        assertEquals(170.0, wrap(-190.0), 0.001) // -190 wraps to 170
+        assertEquals(0.0, wrap(-360.0), 0.001)
+        assertEquals(-10.0, wrap(-370.0), 0.001)
+
+        // Test wrapping large angles
+        assertEquals(10.0, wrap(370.0), 0.001)
+        assertEquals(10.0, wrap(730.0), 0.001) // 730 = 2*360 + 10
+        assertEquals(-10.0, wrap(-370.0), 0.001)
+        assertEquals(-10.0, wrap(-730.0), 0.001) // -730 = -2*360 - 10
+    }
+
+    @Test
+    fun `test lerp method`() {
+        val rotation1 = Rotation(0.0, 0.0)
+        val rotation2 = Rotation(90.0, 45.0)
+
+        // Test with delta = 0 (should return rotation1)
+        val result1 = rotation1.lerp(rotation2, 0.0)
+        assertEquals(rotation1.yaw, result1.yaw, 0.001)
+        assertEquals(rotation1.pitch, result1.pitch, 0.001)
+
+        // Test with delta = 1 (should return rotation2)
+        val result2 = rotation1.lerp(rotation2, 1.0)
+        assertEquals(rotation2.yaw, result2.yaw, 0.001)
+        assertEquals(rotation2.pitch, result2.pitch, 0.001)
+
+        // Test with delta = 0.5 (should return midpoint)
+        val result3 = rotation1.lerp(rotation2, 0.5)
+        assertEquals(45.0, result3.yaw, 0.001)
+        assertEquals(22.5, result3.pitch, 0.001)
+    }
+
+    @Test
+    fun `test lerp with angle wrapping`() {
+        // Test lerp across the -180/180 boundary
+        val rotation1 = Rotation(170.0, 0.0)
+        val rotation2 = Rotation(-170.0, 0.0)
+
+        // The shortest path from 170 to -170 is to go clockwise (not counterclockwise)
+        // So the midpoint should be -180 (or 180, they're equivalent)
+        val midpoint = rotation1.lerp(rotation2, 0.5)
+
+        // Check that the result is either -180 or 180 (they're equivalent)
+        assertTrue(
+            abs(midpoint.yaw - 180.0) < 0.001 ||
+            abs(midpoint.yaw + 180.0) < 0.001
+        )
+    }
+
+    @Test
+    fun `test slerp method`() {
+        val rotation1 = Rotation(0.0, 0.0)
+        val rotation2 = Rotation(90.0, 45.0)
+
+        // Test with speed = 0 (should return rotation1)
+        val result1 = rotation1.slerp(rotation2, 0.0)
+        assertEquals(rotation1.yaw, result1.yaw, 0.001)
+        assertEquals(rotation1.pitch, result1.pitch, 0.001)
+
+        // Test with very high speed (should return rotation2)
+        val result2 = rotation1.slerp(rotation2, 1000.0)
+        assertEquals(rotation2.yaw, result2.yaw, 0.001)
+        assertEquals(rotation2.pitch, result2.pitch, 0.001)
+
+        // Test with limited speed
+        val result3 = rotation1.slerp(rotation2, 10.0)
+        assertTrue(result3.yaw > rotation1.yaw)
+        assertTrue(result3.pitch > rotation1.pitch)
+        assertTrue(result3.yaw < rotation2.yaw)
+        assertTrue(result3.pitch < rotation2.pitch)
+    }
+
+    @Test
+    fun `test slerp with angle wrapping`() {
+        // Test slerp across the -180/180 boundary
+        val rotation1 = Rotation(170.0, 0.0)
+        val rotation2 = Rotation(-170.0, 0.0)
+
+        // With a very high speed, should go directly to rotation2
+        val result = rotation1.slerp(rotation2, 1000.0)
+        assertEquals(rotation2.yaw, result.yaw, 0.001)
+        assertEquals(rotation2.pitch, result.pitch, 0.001)
+
+        // With a limited speed, should move in the correct direction (clockwise)
+        val partialResult = rotation1.slerp(rotation2, 10.0)
+
+        // The yaw should be greater than 170 (moving towards 180/-180)
+        // or less than -170 (already crossed the boundary)
+        assertTrue(partialResult.yaw > 170.0 || partialResult.yaw < -170.0)
+    }
+
+    @Test
+    fun `test dist method`() {
+        val rotation1 = Rotation(0.0, 0.0)
+        val rotation2 = Rotation(90.0, 0.0)
+        val rotation3 = Rotation(0.0, 90.0)
+        val rotation4 = Rotation(90.0, 90.0)
+
+        // Test distance between same rotations
+        assertEquals(0.0, rotation1 dist rotation1, 0.001)
+
+        // Test distance with only yaw difference
+        assertEquals(90.0, rotation1 dist rotation2, 0.001)
+
+        // Test distance with only pitch difference
+        assertEquals(90.0, rotation1 dist rotation3, 0.001)
+
+        // Test distance with both yaw and pitch difference
+        assertEquals(hypot(90.0, 90.0), rotation1 dist rotation4, 0.001)
+    }
+
+    @Test
+    fun `test dist method with angle wrapping`() {
+        // Test distance across the -180/180 boundary
+        val rotation1 = Rotation(170.0, 0.0)
+        val rotation2 = Rotation(-170.0, 0.0)
+
+        // The distance should be 20 degrees (not 340 degrees)
+        assertEquals(20.0, rotation1 dist rotation2, 0.001)
+    }
+
+    @Test
+    fun `test angleDifference method`() {
+        // Test with angles in the same direction
+        assertEquals(10.0, angleDifference(10.0, 0.0), 0.001)
+        assertEquals(10.0, angleDifference(0.0, 10.0), 0.001)
+
+        // Test with angles in opposite directions
+        assertEquals(20.0, angleDifference(-10.0, 10.0), 0.001)
+        assertEquals(20.0, angleDifference(10.0, -10.0), 0.001)
+
+        // Test with angles that wrap around
+        assertEquals(20.0, angleDifference(170.0, -170.0), 0.001)
+        assertEquals(20.0, angleDifference(-170.0, 170.0), 0.001)
+    }
+
+    @Test
+    fun `test angleDifference with extreme values`() {
+        // Test with angles at the boundaries
+        assertEquals(0.0, angleDifference(180.0, 180.0), 0.001)
+        assertEquals(0.0, angleDifference(-180.0, -180.0), 0.001)
+        assertEquals(0.0, angleDifference(180.0, -180.0), 0.001) // These are equivalent
+
+        // Test with large angles that need wrapping
+        assertEquals(10.0, angleDifference(365.0, 375.0), 0.001)
+        assertEquals(10.0, angleDifference(-365.0, -375.0), 0.001)
+        assertEquals(20.0, angleDifference(370.0, -370.0), 0.001)
+    }
+}