Phase 2.3 review fixes : P2-BUG-01/02/03 + RISK-02

Résout les findings P0 remontés par la review post-Phase 2.3 (@4780c96).

P2-BUG-01 — Pipeline RIG dormant au runtime
  EntityPatchProvider.registerEntityPatches() et registerEntityPatchesClient()
  étaient définis mais JAMAIS appelés depuis TiedUpMod setups → CAPABILITIES
  map vide → aucun patch ne se crée, pipeline entier dormant.
  Fix :
  - commonSetup : event.enqueueWork(EntityPatchProvider::registerEntityPatches)
  - ClientModEvents.onClientSetup : event.enqueueWork(
    EntityPatchProvider::registerEntityPatchesClient) avant BondageAnimationManager.init

P2-BUG-02 — Memory leak LazyOptional non invalidée
  EntityPatchProvider.optional jamais invalidated → chaque respawn/dim change
  fuit patch + animator + armature.
  Fix :
  - EntityPatchProvider.invalidate() public méthode qui appelle optional.invalidate()
  - TiedUpCapabilityEvents.attachEntityCapability : event.addListener(provider::invalidate)
    après addCapability. Pattern aligné sur V2BondageEquipmentProvider existant.

P2-BUG-03 — NPE latent HumanoidModelBaker.bakeArmor
  Ligne 88 retournait entityMesh.getHumanoidArmorModel(slot).get() mais le
  getter retourne null (Phase 0 strip S-03 : Meshes.HELMET/CHESTPLATE/LEGGINS/BOOTS
  strippés). Null-check + fallback null + commentaire pointant vers V3-REW-04.

P2-RISK-02 — @OnlyIn(Dist.CLIENT) sur transformers
  VanillaModelTransformer, HumanoidModelTransformer, HumanoidModelBaker
  importent HumanoidModel/PoseStack (client-only). Risque NoClassDefFoundError
  si code serveur touche HumanoidModelBaker.VANILLA_TRANSFORMER static field.
  Fix : @OnlyIn(Dist.CLIENT) sur les 3 classes.

P2-RISK-01/03 + SMELL-01/02/03 tracés dans docs/plans/rig/PHASE0_DEGRADATIONS.md
Phase 2.1-2.3 findings section.

Compile BUILD SUCCESSFUL + 11 tests bridge GREEN maintenus.
This commit is contained in:
notevil
2026-04-22 21:42:22 +02:00
parent faad0ced0f
commit ccec6bd87e
6 changed files with 38 additions and 1 deletions

View File

@@ -137,6 +137,9 @@ public class TiedUpMod {
// Register dispenser behaviors (must be on main thread)
event.enqueueWork(DispenserBehaviors::register);
// RIG Phase 2 — dispatcher EntityType → EntityPatch (PLAYER Phase 2, NPCs Phase 5)
event.enqueueWork(com.tiedup.remake.rig.patch.EntityPatchProvider::registerEntityPatches);
}
/**
@@ -185,6 +188,9 @@ public class TiedUpMod {
// Initialize animation system
event.enqueueWork(() -> {
// RIG Phase 2 — override client dispatch PLAYER → Local/Client/ServerPlayerPatch
com.tiedup.remake.rig.patch.EntityPatchProvider.registerEntityPatchesClient();
// Initialize unified BondageAnimationManager
com.tiedup.remake.client.animation.BondageAnimationManager.init();
LOGGER.info("BondageAnimationManager initialized");

View File

@@ -32,11 +32,14 @@ import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.item.ArmorItem;
import net.minecraft.world.item.ItemStack;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import net.minecraftforge.registries.ForgeRegistries;
import com.tiedup.remake.rig.mesh.SkinnedMesh;
import com.tiedup.remake.rig.mesh.HumanoidMesh;
import com.tiedup.remake.rig.TiedUpRigConstants;
@OnlyIn(Dist.CLIENT)
public class HumanoidModelBaker {
static final Map<ResourceLocation, SkinnedMesh> BAKED_MODELS = Maps.newHashMap();
static final List<HumanoidModelTransformer> MODEL_TRANSFORMERS = Lists.newArrayList();
@@ -85,7 +88,12 @@ public class HumanoidModelBaker {
if (!EXCEPTIONAL_MODELS.contains(armorItem)) {
if (forgeModel == originalModel || !(forgeModel instanceof HumanoidModel humanoidModel)) {
return entityMesh.getHumanoidArmorModel(slot).get();
// RIG Phase 0 : Meshes.HELMET/CHESTPLATE/LEGGINS/BOOTS strippés →
// HumanoidMesh.getHumanoidArmorModel(slot) retourne null.
// Safe-guard ici pour éviter NPE si bakeArmor est appelé avant que
// V3-REW-04 soit implémenté (armor rendering rework).
var armorAccessor = entityMesh.getHumanoidArmorModel(slot);
return armorAccessor != null ? armorAccessor.get() : null;
}
for (HumanoidModelTransformer modelTransformer : MODEL_TRANSFORMERS) {

View File

@@ -14,10 +14,14 @@ import com.mojang.blaze3d.vertex.PoseStack;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
import net.minecraft.client.model.HumanoidModel;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import com.tiedup.remake.rig.mesh.MeshPartDefinition;
import com.tiedup.remake.rig.mesh.SingleGroupVertexBuilder;
import com.tiedup.remake.rig.mesh.SkinnedMesh;
@OnlyIn(Dist.CLIENT)
public abstract class HumanoidModelTransformer {
public abstract SkinnedMesh transformArmorModel(HumanoidModel<?> humanoidModel);

View File

@@ -36,8 +36,12 @@ import com.tiedup.remake.rig.math.OpenMatrix4f;
import com.tiedup.remake.rig.math.QuaternionUtils;
import com.tiedup.remake.rig.math.Vec2f;
import com.tiedup.remake.rig.math.Vec3f;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import com.tiedup.remake.rig.mixin.client.MixinAgeableListModel;
@OnlyIn(Dist.CLIENT)
public class VanillaModelTransformer extends HumanoidModelTransformer {
public static final SimpleTransformer HEAD = new SimpleTransformer(AABB.ofSize(new Vec3(0.0D, -4.0D, 0.0D), 8.0D, 8.0D, 8.0D), 9);
public static final SimpleTransformer LEFT_FEET = new SimpleTransformer(AABB.ofSize(new Vec3(0.0D, -4.0D, 0.0D), 8.0D, 8.0D, 8.0D), 5);

View File

@@ -148,4 +148,14 @@ public class EntityPatchProvider implements ICapabilityProvider, NonNullSupplier
public <T> LazyOptional<T> getCapability(Capability<T> cap, Direction side) {
return cap == TiedUpCapabilities.CAPABILITY_ENTITY ? this.optional.cast() : LazyOptional.empty();
}
/**
* Invalide la {@link LazyOptional} portée par ce provider. À appeler depuis
* {@code AttachCapabilitiesEvent.addListener} — sans ça les références vers
* le patch, l'animator et l'armature persistent à chaque respawn / dimension
* change → fuite mémoire.
*/
public void invalidate() {
this.optional.invalidate();
}
}

View File

@@ -69,5 +69,10 @@ public class TiedUpCapabilityEvents {
// l'entity commence son premier tick.
patch.onConstructed(entity);
event.addCapability(ENTITY_CAPABILITY_KEY, provider);
// CRITICAL : invalide la LazyOptional au respawn / dim change, sinon
// chaque mort fuit patch + animator + armature. Pattern Forge standard
// (cf. V2BondageEquipmentProvider.invalidate, CapabilityEventHandler:50).
event.addListener(provider::invalidate);
}
}