Mega-Stapel-Aggregationsmechanik (MC 26.2)
Dieses Dokument liefert eine umfassende technische und mathematische Aufschlüsselung der Item-Aggregation, Suchradiusberechnung, 64-Bit-Überlaufsicherung und Speicheroptimierungen in Item Clumps für Minecraft 26.2.
📊 Feature-Infobox
| Eigenschaft | Spezifikation |
|---|---|
| Systemname | Mega-Stapel-Aggregator für Boden-Items |
| Zielklasse | net.minecraft.world.entity.item.ItemEntity |
| Standard-Verschmelzungslimit | 9.999 Items (Konfigurierbar bis $2.147.483.647$) |
| Suchradius ($r$) | Horizontal $1\text{ bis }10\text{ Blöcke}$ (Standard: $1\text{ Block}$) |
| Tick-Overhead | $0.00\text{ ms}$ bei ruhenden Items (Aufruf nur bei setItem()) |
| Speicherallokation | Null Objektallokationen bei Suche & Aufnahme (copyWithCount(int)) |
| Steuernde GameRules | item_clumps:enable_clumping, item_clumps:max_clump_size, item_clumps:merge_radius |
⚙️ Funktionsweise der Mega-Stapel-Aggregation
In Vanilla Minecraft rufen gedroppte Items während ihres Ticks mergeWithNeighbours() auf, um nach gleichartigen Items in der Nähe zu suchen. Vanilla verbietet das Verschmelzen jedoch strikt, wenn die Anzahl itemStack.getMaxStackSize() überschreiten würde (üblicherweise 64, bzw. 16 bei Enderperlen/Eiern).
Item Clumps fängt diese Prüfung über ItemEntityMixin.java ab, um diese künstliche Einschränkung aufzuheben, während die Datenkomponentensicherheit strikt gewahrt bleibt.
┌──────────────────────────────┐
│ 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.│
└───────────────────┘ └───────────────────┘📐 Mathematische Formeln & Präzisionsberechnungen
1. Horizontale Bounding-Box-Vergrößerung
Vanilla durchsucht eine winzige Bounding-Box: $\text{AABB}_{\text{vanilla}} = \text{AABB}.\text{inflate}(0.5, 0.0, 0.5)$.
Item Clumps erweitert die horizontale Bounding-Box dynamisch über eine speicherfreie @Redirect-Injektion:
$$\text{AABB}_{\text{clump}} = \text{boundingBox}.\text{inflate}(r, y, r)$$
Wobei:
- $r = \text{DynamicGameRuleManager}.\text{getInt}(\text{level}, \text{MERGE_RADIUS}) \in [1, 10]$ Blöcke.
- $y = 0.0$ (vertikale Suchhöhe bleibt strikt unberührt, damit Items auf verschiedenen Etagen oder über Trichtern nicht fehlerhaft verschmelzen).
2. 64-Bit-Ganzzahlsumme & Schutz vor arithmetischem Überlauf
Wenn zwei riesige Stapel auf Großservern verschmelzen (z. B. Limit bei 2 Milliarden), kann eine normale 32-Bit-int-Addition in negative Zahlen überlaufen ($\text{Integer.MAX_VALUE} + 1 = -2,147,483,648$), was Entitätszustände beschädigt.
Item Clumps berechnet die Summe mit 64-Bit-Präzision:
$$S = (\text{long}),\text{thisCount} + (\text{long}),\text{otherCount}$$
- Vollständige Absorption ($S \le \text{maxClumpSize}$): $$\text{count}_{\text{merged}} = (\text{int}),S$$
- Partielle Absorption ($S > \text{maxClumpSize}$): $$\Delta = \text{maxClumpSize} - \text{thisCount}$$ $$\text{thisCount}' = \text{maxClumpSize}, \quad \text{otherCount}' = \text{otherCount} - \Delta$$
3. Strenge Komponenten- & Zustandskonsistenz
Items verschmelzen niemals, es sei denn, alle Datenkomponenten (Verzauberungen, Haltbarkeit, Namen, Rüstungsbesätze, Trankeffekte) stimmen bitgenau zu 100 % überein:
$$\text{ItemStack}.\text{isSameItemSameComponents}(\text{stack}_A, \text{stack}_B) == \text{true}$$
💻 Reale Java-Quellcode-Implementierung
Aus 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();
}