Skip to content

26.2 Clumping Mechanics and Mega Stacks

Dasik (Rifaditya) edited this page Aug 22, 2026 · 1 revision

Mega-Stack Clumping Mechanics (MC 26.2)

This document provides an exhaustive technical and mathematical breakdown of the ground item aggregation, search radius math, 64-bit overflow prevention, and heap allocation optimizations in Item Clumps for Minecraft 26.2.


πŸ“Š Feature Infobox

Property Specification
System Name Ground Item Mega-Stack Aggregator
Target Class net.minecraft.world.entity.item.ItemEntity
Default Merge Cap 9,999 items (Configurable up to $2,147,483,647$)
Search Radius ($r$) Horizontal $1\text{ to }10\text{ blocks}$ (Default: $1\text{ block}$)
Tick Overhead $0.00\text{ ms}$ on non-moving items (setItem()-only dispatch)
Memory Allocation Zero object allocations on search & absorption (copyWithCount(int))
Controlling GameRules item_clumps:enable_clumping, item_clumps:max_clump_size, item_clumps:merge_radius

βš™οΈ How Mega-Stack Aggregation Works

In vanilla Minecraft, dropped items call mergeWithNeighbours() during their tick cycle, searching for nearby items of the same type. However, vanilla strictly prohibits merging if the target entity's item count would exceed itemStack.getMaxStackSize() (typically 64, or 16 for ender pearls / eggs).

Item Clumps intercepts this check via ItemEntityMixin.java to lift this artificial constraint while preserving strict data component safety.

                  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                  β”‚    ItemEntity A Ticks In     β”‚
                  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                 β”‚
                                 β–Ό
                  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                  β”‚     Fast-Path Validation     β”‚
                  β”‚  - Matching player target?   β”‚
                  β”‚  - Identical DataComponents? β”‚
                  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                 β”‚ (Pass)
                                 β–Ό
                  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                  β”‚   In-Flight Magnet Check     β”‚
                  β”‚ (Is either item magnetized?) β”‚
                  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                 β”‚ (No)
                                 β–Ό
                  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                  β”‚    64-Bit Arithmetic Sum     β”‚
                  β”‚   S = (long) A + (long) B    β”‚
                  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                 β”‚
                 β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                 β”‚                               β”‚
        S <= maxClumpSize                S > maxClumpSize
                 β”‚                               β”‚
                 β–Ό                               β–Ό
       β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”           β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
       β”‚   Complete Merge  β”‚           β”‚   Partial Merge   β”‚
       β”‚ Larger absorbs    β”‚           β”‚ Fill entity A to  β”‚
       β”‚ smaller entity;   β”‚           β”‚ maxClumpSize;     β”‚
       β”‚ Younger age kept. β”‚           β”‚ B keeps remainder.β”‚
       β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜           β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

πŸ“ Mathematical Formulas & Precision Math

1. Horizontal Bounding Box Inflation

Vanilla searches a tiny bounding box: $\text{AABB}_{\text{vanilla}} = \text{AABB}.\text{inflate}(0.5, 0.0, 0.5)$.

Item Clumps dynamically expands the horizontal bounding box using a zero-allocation @Redirect injection:

$$\text{AABB}_{\text{clump}} = \text{boundingBox}.\text{inflate}(r, y, r)$$

Where:

  • $r = \text{DynamicGameRuleManager}.\text{getInt}(\text{level}, \text{MERGE_RADIUS}) \in [1, 10]$ blocks.
  • $y = 0.0$ (vertical search height is strictly preserved to prevent items on different vertical floors or hoppers from cross-merging).

2. 64-Bit Integer Sum & Arithmetic Overflow Safety

When two massive clumps merge under large server configurations (e.g. max clump cap set to $2\text{ billion}$), standard 32-bit int addition can overflow into negative integers ($\text{Integer.MAX_VALUE} + 1 = -2,147,483,648$), leading to corrupted entity states.

Item Clumps computes the sum in 64-bit precision:

$$S = (\text{long}),\text{thisCount} + (\text{long}),\text{otherCount}$$

  • Full Absorption ($S \le \text{maxClumpSize}$): $$\text{count}_{\text{merged}} = (\text{int}),S$$
  • Partial Absorption ($S &gt; \text{maxClumpSize}$): $$\Delta = \text{maxClumpSize} - \text{thisCount}$$ $$\text{thisCount}' = \text{maxClumpSize}, \quad \text{otherCount}' = \text{otherCount} - \Delta$$

3. Component & State Strictness

Items never merge unless all Data Components (enchantments, damaged durability, custom names, trims, potion effects) are 100% bitwise identical:

$$\text{ItemStack}.\text{isSameItemSameComponents}(\text{stack}_A, \text{stack}_B) == \text{true}$$


πŸ’» Ground-Truth Java Source Implementation

From ItemEntityMixin.java in Minecraft 26.2:

@Inject(method = "tryToMerge", at = @At("HEAD"), cancellable = true)
private void item_clumps$customMerge(ItemEntity other, CallbackInfo ci) {
    ItemStack thisStack = this.getItem();
    ItemStack otherStack = other.getItem();

    // Fast-path exit before GameRule lookups
    if (!Objects.equals(this.target, ((ItemEntityMixin)(Object)other).target) || 
        !ItemStack.isSameItemSameComponents(thisStack, otherStack)) {
        return;
    }

    if (!DynamicGameRuleManager.getBoolean(this.level(), ItemClumpsFabric.ENABLE_CLUMPING)) return;

    // Magnet mod in-flight protection
    if (net.fabricmc.loader.api.FabricLoader.getInstance().isModLoaded("magnet")) {
        try {
            java.lang.reflect.Method isMagnetizedMethod;
            try {
                isMagnetizedMethod = this.getClass().getMethod("ig_magnet$isMagnetized");
            } catch (NoSuchMethodException e) {
                isMagnetizedMethod = this.getClass().getMethod("ig$isMagnetized");
            }
            if ((boolean) isMagnetizedMethod.invoke(this) || (boolean) isMagnetizedMethod.invoke(other)) {
                ci.cancel();
                return;
            }
        } catch (Throwable ignored) {}
    }

    int thisCount = thisStack.getCount();
    int otherCount = otherStack.getCount();
    int maxClump = (ItemClumpsFabric.MAX_CLUMP_SIZE == null) 
        ? thisStack.getMaxStackSize() 
        : DynamicGameRuleManager.getInt(this.level(), ItemClumpsFabric.MAX_CLUMP_SIZE);

    long sum = (long) thisCount + (long) otherCount;
    if (sum > (long) maxClump) {
        int spaceLeft = maxClump - thisCount;
        if (spaceLeft > 0) {
            ItemStack thisCopy = thisStack.copyWithCount(maxClump);
            this.setItem(thisCopy);
            
            ItemStack otherCopy = otherStack.copyWithCount(otherCount - spaceLeft);
            other.setItem(otherCopy);

            this.pickupDelay = Math.max(this.pickupDelay, ((ItemEntityMixin)(Object)other).pickupDelay);
            this.age = Math.min(this.age, ((ItemEntityMixin)(Object)other).age);
        }
        ci.cancel();
        return;
    }

    // Full Merge: larger stack absorbs the smaller stack
    if (otherCount < thisCount) {
        ItemStack thisCopy = thisStack.copyWithCount((int) sum);
        this.setItem(thisCopy);
        this.pickupDelay = Math.max(this.pickupDelay, ((ItemEntityMixin)(Object)other).pickupDelay);
        this.age = Math.min(this.age, ((ItemEntityMixin)(Object)other).age);
        other.discard();
    } else {
        ItemStack otherCopy = otherStack.copyWithCount((int) sum);
        other.setItem(otherCopy);
        ((ItemEntityMixin)(Object)other).pickupDelay = Math.max(((ItemEntityMixin)(Object)other).pickupDelay, this.pickupDelay);
        ((ItemEntityMixin)(Object)other).age = Math.min(((ItemEntityMixin)(Object)other).age, this.age);
        this.discard();
    }
    ci.cancel();
}

πŸ”— Related Documentation (MC 26.2)

Clone this wiki locally