Mécaniques d'Agrégation en Méga-Piles (MC 26.2)
Ce document fournit une analyse technique et mathématique approfondie de l'agrégation d'items, du calcul de rayon de recherche, de la prévention de débordement 64 bits et des optimisations de mémoire dans Item Clumps pour Minecraft 26.2.
📊 Fiche Technique de Fonctionnalité
| Propriété | Spécification |
|---|---|
| Nom du Système | Agrégateur de Méga-Piles d'Items au Sol |
| Classe Cible | net.minecraft.world.entity.item.ItemEntity |
| Plafond par Défaut | 9 999 items (Configurable jusqu'à $2,147,483,647$) |
| Rayon de Recherche ($r$) | Horizontal $1\text{ à }10\text{ blocs}$ (Défaut : $1\text{ bloc}$) |
| Surcharge par Tick | $0.00\text{ ms}$ sur items immobiles (appel uniquement sur setItem()) |
| Allocation Mémoire | Zéro allocation d'objets lors de la recherche et absorption (copyWithCount(int)) |
| GameRules de Contrôle | item_clumps:enable_clumping, item_clumps:max_clump_size, item_clumps:merge_radius |
⚙️ Fonctionnement de l'Agrégation en Méga-Piles
Dans Minecraft Vanilla, les items au sol appellent mergeWithNeighbours() à chaque tick pour chercher des items identiques à proximité. Vanilla interdit strictement la fusion si la quantité dépasse itemStack.getMaxStackSize() (généralement 64, ou 16 pour les perles de l'Ender et les œufs).
Item Clumps intercepte cette vérification via ItemEntityMixin.java afin de lever cette contrainte artificielle tout en préservant strictement la sécurité des composants de données.
┌──────────────────────────────┐
│ 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.│
└───────────────────┘ └───────────────────┘📐 Formules Mathématiques & Calculs de Précision
1. Élargissement Horizontal de la Boîte Englobante
Vanilla utilise une boîte de collision minuscule : $\text{AABB}_{\text{vanilla}} = \text{AABB}.\text{inflate}(0.5, 0.0, 0.5)$.
Item Clumps élargit dynamiquement la boîte de collision horizontale via une injection @Redirect sans allocation mémoire :
$$\text{AABB}_{\text{clump}} = \text{boundingBox}.\text{inflate}(r, y, r)$$
Où :
- $r = \text{DynamicGameRuleManager}.\text{getInt}(\text{level}, \text{MERGE_RADIUS}) \in [1, 10]$ blocs.
- $y = 0.0$ (la hauteur de recherche verticale est strictement préservée pour éviter que des items sur des étages différents ou au-dessus d'entonnoirs distincts ne fusionnent).
2. Somme Entière 64-Bit & Sécurité Contre le Débordement Arithmétique
Lorsque deux amas massifs fusionnent sur de gros serveurs (ex. plafond à 2 milliards), l'addition classique en entier 32 bits peut déborder en nombres négatifs ($\text{Integer.MAX_VALUE} + 1 = -2,147,483,648$), corrompant les entités.
Item Clumps calcule la somme avec une précision 64 bits :
$$S = (\text{long}),\text{thisCount} + (\text{long}),\text{otherCount}$$
- Absorption Complète ($S \le \text{maxClumpSize}$) : $$\text{count}_{\text{merged}} = (\text{int}),S$$
- Absorption Partielle ($S > \text{maxClumpSize}$) : $$\Delta = \text{maxClumpSize} - \text{thisCount}$$ $$\text{thisCount}' = \text{maxClumpSize}, \quad \text{otherCount}' = \text{otherCount} - \Delta$$
3. Rigueur des Composants & de l'État
Les items ne fusionnent jamais à moins que tous leurs composants de données (enchantements, durabilité restante, noms, finitions d'armure, effets de potion) ne soient strictement identiques au bit près :
$$\text{ItemStack}.\text{isSameItemSameComponents}(\text{stack}_A, \text{stack}_B) == \text{true}$$
💻 Implémentation du Code Source Java Réel
Depuis ItemEntityMixin.java dans 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();
}