Mecânicas de Agrupamento em Mega-Pilhas (MC 26.2)
Este documento fornece um detalhamento técnico e matemático exaustivo da agregação de itens no chão, cálculo de raio de busca, prevenção de transbordamento de 64 bits e otimizações de memória no Item Clumps para Minecraft 26.2.
📊 Ficha Técnica do Recurso
| Propriedade | Especificação |
|---|---|
| Nome do Sistema | Agregador de Mega-Pilhas de Itens no Chão |
| Classe Alvo | net.minecraft.world.entity.item.ItemEntity |
| Limite Padrão de Fusão | 9.999 itens (Configurável até $2.147.483.647$) |
| Raio de Busca ($r$) | Horizontal de $1\text{ a }10\text{ blocos}$ (Padrão: $1\text{ bloco}$) |
| Custo por Tick | $0.00\text{ ms}$ em itens imóveis (despacho exclusivo em setItem()) |
| Alocação de Memória | Zero alocação de objetos na busca e absorção (copyWithCount(int)) |
| GameRules Controladoras | item_clumps:enable_clumping, item_clumps:max_clump_size, item_clumps:merge_radius |
⚙️ Como Funciona a Agregação em Mega-Pilhas
No Minecraft Vanilla, itens caídos chamam mergeWithNeighbours() durante seu ciclo de ticks, procurando itens próximos do mesmo tipo. No entanto, o vanilla proíbe estritamente a fusão se a contagem da entidade alvo exceder itemStack.getMaxStackSize() (normalmente 64, ou 16 para pérolas do ender / ovos).
O Item Clumps intercepta essa verificação através do ItemEntityMixin.java para remover essa restrição artificial enquanto preserva a segurança estrita dos componentes de dados.
┌──────────────────────────────┐
│ 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 e Cálculos de Precisão
1. Expansão Horizontal da Caixa Delimitadora
O vanilla busca em uma caixa delimitadora minúscula: $\text{AABB}_{\text{vanilla}} = \text{AABB}.\text{inflate}(0.5, 0.0, 0.5)$.
O Item Clumps expande dinamicamente a caixa delimitadora horizontal usando uma injeção @Redirect com zero alocação:
$$\text{AABB}_{\text{clump}} = \text{boundingBox}.\text{inflate}(r, y, r)$$
Onde:
- $r = \text{DynamicGameRuleManager}.\text{getInt}(\text{level}, \text{MERGE_RADIUS}) \in [1, 10]$ blocos.
- $y = 0.0$ (a altura de busca vertical é estritamente preservada para evitar que itens em andares verticais diferentes ou funis se fundam incorretamente).
2. Soma de Inteiros de 64 Bits e Segurança contra Transbordamento Aritmético
Quando dois agrupamentos massivos se fundem em configurações de servidores grandes (ex.: limite máximo configurado para $2\text{ bilhões}$), a adição comum de int de 32 bits pode transbordar para inteiros negativos ($\text{Integer.MAX_VALUE} + 1 = -2,147,483,648$), corrompendo o estado das entidades.
O Item Clumps calcula a soma com precisão de 64 bits:
$$S = (\text{long}),\text{thisCount} + (\text{long}),\text{otherCount}$$
- Absorção Total ($S \le \text{maxClumpSize}$): $$\text{count}_{\text{merged}} = (\text{int}),S$$
- Absorção Parcial ($S > \text{maxClumpSize}$): $$\Delta = \text{maxClumpSize} - \text{thisCount}$$ $$\text{thisCount}' = \text{maxClumpSize}, \quad \text{otherCount}' = \text{otherCount} - \Delta$$
3. Rigor de Componentes e Estado
Os itens nunca se fundem a menos que todos os Componentes de Dados (encantamentos, durabilidade gasta, nomes personalizados, acabamentos de armadura, efeitos de poção) sejam 100% idênticos bit a bit:
$$\text{ItemStack}.\text{isSameItemSameComponents}(\text{stack}_A, \text{stack}_B) == \text{true}$$
💻 Implementação do Código-Fonte Java Real
De ItemEntityMixin.java no 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();
}