Mecánicas de agrupación en mega-stacks (MC 26.2)
Este documento proporciona un desglose técnico y matemático exhaustivo de la agregación de objetos en el suelo, el cálculo del radio de búsqueda, la prevención de desbordamiento de 64 bits y las optimizaciones de asignación de memoria en Item Clumps para Minecraft 26.2.
📊 Ficha técnica de características
| Propiedad | Especificación |
|---|---|
| Nombre del sistema | Agregador de mega-stacks de objetos en suelo |
| Clase objetivo | net.minecraft.world.entity.item.ItemEntity |
| Límite de fusión predeterminado | 9,999 objetos (Configurable hasta $2,147,483,647$) |
| Radio de búsqueda ($r$) | Horizontal $1\text{ a }10\text{ bloques}$ (Predeterminado: $1\text{ bloque}$) |
| Sobrecarga por tick | $0.00\text{ ms}$ en objetos estáticos (despacho exclusivo en setItem()) |
| Asignación de memoria | Cero asignaciones de objetos en búsqueda y absorción (copyWithCount(int)) |
| GameRules de control | item_clumps:enable_clumping, item_clumps:max_clump_size, item_clumps:merge_radius |
⚙️ Cómo funciona la agregación en mega-stacks
En Minecraft Vanilla, los objetos caídos llaman a mergeWithNeighbours() durante su ciclo de ticks, buscando objetos cercanos del mismo tipo. Sin embargo, Vanilla prohíbe estrictamente la fusión si el recuento de objetos de la entidad objetivo excediera itemStack.getMaxStackSize() (normalmente 64, o 16 para perlas de ender/huevos).
Item Clumps intercepta esta comprobación a través de ItemEntityMixin.java para eliminar esta restricción artificial manteniendo una estricta seguridad en los componentes de datos.
┌──────────────────────────────┐
│ 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.│
└───────────────────┘ └───────────────────┘📐 Fórmulas matemáticas y cálculo de precisión
1. Expansión horizontal del cuadro delimitador
Vanilla busca en un cuadro delimitador diminuto: $\text{AABB}_{\text{vanilla}} = \text{AABB}.\text{inflate}(0.5, 0.0, 0.5)$.
Item Clumps expande dinámicamente el cuadro delimitador horizontal mediante una inyección @Redirect sin asignación de memoria:
$$\text{AABB}_{\text{clump}} = \text{boundingBox}.\text{inflate}(r, y, r)$$
Donde:
- $r = \text{DynamicGameRuleManager}.\text{getInt}(\text{level}, \text{MERGE_RADIUS}) \in [1, 10]$ bloques.
- $y = 0.0$ (la altura de búsqueda vertical se preserva estrictamente para evitar que objetos en pisos o tolvas verticales distintos se fusionen entre sí).
2. Suma de enteros de 64 bits y seguridad contra desbordamiento aritmético
Cuando dos grupos masivos se fusionan en configuraciones de servidor grandes (por ejemplo, límite máximo establecido en $2\text{ mil millones}$), la suma estándar de int de 32 bits puede desbordarse en enteros negativos ($\text{Integer.MAX_VALUE} + 1 = -2,147,483,648$), lo que corrompe el estado de las entidades.
Item Clumps calcula la suma con precisión de 64 bits:
$$S = (\text{long}),\text{thisCount} + (\text{long}),\text{otherCount}$$
- Absorción completa ($S \le \text{maxClumpSize}$): $$\text{count}_{\text{merged}} = (\text{int}),S$$
- Absorción parcial ($S > \text{maxClumpSize}$): $$\Delta = \text{maxClumpSize} - \text{thisCount}$$ $$\text{thisCount}' = \text{maxClumpSize}, \quad \text{otherCount}' = \text{otherCount} - \Delta$$
3. Rigurosidad de componentes y estado
Los objetos nunca se fusionan a menos que todos los componentes de datos (encantamientos, durabilidad dañada, nombres personalizados, adornos, efectos de pociones) sean 100% idénticos a nivel de bits:
$$\text{ItemStack}.\text{isSameItemSameComponents}(\text{stack}_A, \text{stack}_B) == \text{true}$$
💻 Implementación del código fuente Java real
De ItemEntityMixin.java en 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();
}