diff --git a/src/main/java/com/tiedup/remake/rig/mixin/client/MixinEntityRenderer.java b/src/main/java/com/tiedup/remake/rig/mixin/client/MixinEntityRenderer.java
new file mode 100644
index 0000000..1f393c2
--- /dev/null
+++ b/src/main/java/com/tiedup/remake/rig/mixin/client/MixinEntityRenderer.java
@@ -0,0 +1,36 @@
+/*
+ * Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
+ * by the Epic Fight Team, licensed under GPLv3.
+ * Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
+ */
+
+package com.tiedup.remake.rig.mixin.client;
+
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.gen.Invoker;
+
+import com.mojang.blaze3d.vertex.PoseStack;
+
+import net.minecraft.client.renderer.MultiBufferSource;
+import net.minecraft.client.renderer.entity.EntityRenderer;
+import net.minecraft.network.chat.Component;
+import net.minecraft.world.entity.Entity;
+
+/**
+ * Expose les méthodes protected {@code shouldShowName(Entity)} et
+ * {@code renderNameTag(...)} de {@link EntityRenderer} pour que
+ * {@link com.tiedup.remake.rig.render.PatchedEntityRenderer#render}
+ * puisse rendre le nameplate sans les hard-coder.
+ *
+ *
Pure @Invoker mixin — pas de logique injectée, équivalent EF de
+ * {@code yesman.epicfight.mixin.client.MixinEntityRenderer}.
+ */
+@Mixin(value = EntityRenderer.class)
+public interface MixinEntityRenderer {
+
+ @Invoker("shouldShowName")
+ boolean invokeShouldShowName(Entity entity);
+
+ @Invoker("renderNameTag")
+ void invokeRenderNameTag(Entity entity, Component name, PoseStack poseStack, MultiBufferSource bufferSource, int packedLight);
+}
diff --git a/src/main/java/com/tiedup/remake/rig/mixin/client/MixinLivingEntityRenderer.java b/src/main/java/com/tiedup/remake/rig/mixin/client/MixinLivingEntityRenderer.java
new file mode 100644
index 0000000..e948191
--- /dev/null
+++ b/src/main/java/com/tiedup/remake/rig/mixin/client/MixinLivingEntityRenderer.java
@@ -0,0 +1,36 @@
+/*
+ * Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
+ * by the Epic Fight Team, licensed under GPLv3.
+ * Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
+ */
+
+package com.tiedup.remake.rig.mixin.client;
+
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.gen.Invoker;
+
+import net.minecraft.client.renderer.RenderType;
+import net.minecraft.client.renderer.entity.LivingEntityRenderer;
+import net.minecraft.world.entity.LivingEntity;
+
+/**
+ * Expose les méthodes protected {@code isBodyVisible}, {@code getRenderType}
+ * et {@code getBob} de {@link LivingEntityRenderer} pour que
+ * {@link com.tiedup.remake.rig.render.PatchedLivingEntityRenderer#render}
+ * puisse les appeler sans dépendre d'access transformers lourds.
+ *
+ * Pure @Invoker mixin — équivalent EF de
+ * {@code yesman.epicfight.mixin.client.MixinLivingEntityRenderer}.
+ */
+@Mixin(value = LivingEntityRenderer.class)
+public interface MixinLivingEntityRenderer {
+
+ @Invoker("isBodyVisible")
+ boolean invokeIsBodyVisible(LivingEntity entity);
+
+ @Invoker("getRenderType")
+ RenderType invokeGetRenderType(LivingEntity entity, boolean isVisible, boolean isVisibleToPlayer, boolean isGlowing);
+
+ @Invoker("getBob")
+ float invokeGetBob(LivingEntity entity, float partialTicks);
+}
diff --git a/src/main/java/com/tiedup/remake/rig/render/PHumanoidRenderer.java b/src/main/java/com/tiedup/remake/rig/render/PHumanoidRenderer.java
new file mode 100644
index 0000000..d656efc
--- /dev/null
+++ b/src/main/java/com/tiedup/remake/rig/render/PHumanoidRenderer.java
@@ -0,0 +1,92 @@
+/*
+ * Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
+ * by the Epic Fight Team, licensed under GPLv3.
+ * Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
+ */
+
+package com.tiedup.remake.rig.render;
+
+import net.minecraft.client.model.HumanoidModel;
+import net.minecraft.client.renderer.entity.EntityRendererProvider;
+import net.minecraft.client.renderer.entity.LivingEntityRenderer;
+import net.minecraft.client.renderer.entity.layers.HumanoidArmorLayer;
+import net.minecraft.client.renderer.entity.layers.ItemInHandLayer;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.LivingEntity;
+import net.minecraftforge.api.distmarker.Dist;
+import net.minecraftforge.api.distmarker.OnlyIn;
+
+import com.tiedup.remake.rig.anim.Pose;
+import com.tiedup.remake.rig.armature.Armature;
+import com.tiedup.remake.rig.armature.JointTransform;
+import com.tiedup.remake.rig.asset.AssetAccessor;
+import com.tiedup.remake.rig.math.OpenMatrix4f;
+import com.tiedup.remake.rig.math.Vec3f;
+import com.tiedup.remake.rig.mesh.HumanoidMesh;
+import com.tiedup.remake.rig.patch.LivingEntityPatch;
+
+/**
+ * Niveau intermédiaire humanoïde — s'utilise pour tout ce qui rentre dans le
+ * moule biped (player, kidnapper, damsel, maid, master). Ajoute :
+ *
+ *
+ * - Un mesh par défaut injecté au constructor (évite la duplication de
+ * {@link #getDefaultMesh()} dans chaque sous-classe)
+ * - Le scale baby Head 1.25x via {@link #setJointTransforms} — pattern EF
+ * strict, le modèle vanilla scale le head au rendu, donc on doit matcher
+ * côté pose pour que les items (gags etc.) restent attachés
+ * - Les stubs {@link PatchedArmorLayer} et {@link PatchedItemInHandLayer}
+ * pour "manger" les layers vanilla équivalents (sinon rendu double)
+ * - {@link #getDefaultLayerHeightCorrection()} = 0.75F (hauteur standard
+ * humanoïde pour les particles overlays)
+ *
+ *
+ * @param entité humanoïde
+ * @param patch RIG associé
+ * @param HumanoidModel vanilla
+ * @param LivingEntityRenderer vanilla qui gère {@code M}
+ * @param mesh humanoïde (typiquement {@link HumanoidMesh} direct ou
+ * dérivé)
+ */
+@OnlyIn(Dist.CLIENT)
+public class PHumanoidRenderer, M extends HumanoidModel, R extends LivingEntityRenderer, AM extends HumanoidMesh> extends PatchedLivingEntityRenderer {
+
+ private final AssetAccessor mesh;
+
+ public PHumanoidRenderer(AssetAccessor mesh, EntityRendererProvider.Context context, EntityType> entityType) {
+ super(context, entityType);
+
+ this.mesh = mesh;
+
+ // RIG Phase 2.5 : les 2 layers vanilla pertinents (armor + main items)
+ // sont mangés par des stubs no-op. Voir PatchedArmorLayer / PatchedItemInHandLayer
+ // et V3-REW-04 (armor) / Phase 3 (item-in-hand réel).
+ //
+ // ElytraLayer, CustomHeadLayer volontairement non-mappés Phase 2.5 — le
+ // layer vanilla les dessinera directement (couche vanilla séparée du
+ // mesh RIG). Cohérent avec la stratégie "on mange juste les layers dont
+ // la géométrie entre en conflit avec le mesh skinné".
+ this.addPatchedLayer(HumanoidArmorLayer.class, new PatchedArmorLayer());
+ this.addPatchedLayer(ItemInHandLayer.class, new PatchedItemInHandLayer());
+ }
+
+ @Override
+ public void setJointTransforms(T entitypatch, Armature armature, Pose pose, float partialTicks) {
+ if (entitypatch.getOriginal().isBaby()) {
+ // Compensation du scale 1.25x que le HumanoidModel vanilla applique sur
+ // la tête quand young==true — sans ça les items tête (gags, masks) se
+ // détachent visuellement du mesh.
+ pose.orElseEmpty("Head").frontResult(JointTransform.scale(new Vec3f(1.25F, 1.25F, 1.25F)), OpenMatrix4f::mul);
+ }
+ }
+
+ @Override
+ protected float getDefaultLayerHeightCorrection() {
+ return 0.75F;
+ }
+
+ @Override
+ public AssetAccessor getDefaultMesh() {
+ return this.mesh;
+ }
+}
diff --git a/src/main/java/com/tiedup/remake/rig/render/PatchedArmorLayer.java b/src/main/java/com/tiedup/remake/rig/render/PatchedArmorLayer.java
new file mode 100644
index 0000000..6b7688b
--- /dev/null
+++ b/src/main/java/com/tiedup/remake/rig/render/PatchedArmorLayer.java
@@ -0,0 +1,38 @@
+/*
+ * Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
+ * by the Epic Fight Team, licensed under GPLv3.
+ * Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
+ */
+
+package com.tiedup.remake.rig.render;
+
+import net.minecraft.client.model.HumanoidModel;
+import net.minecraft.client.renderer.entity.layers.HumanoidArmorLayer;
+import net.minecraft.world.entity.LivingEntity;
+import net.minecraftforge.api.distmarker.Dist;
+import net.minecraftforge.api.distmarker.OnlyIn;
+
+import com.tiedup.remake.rig.patch.LivingEntityPatch;
+
+/**
+ * Stub RIG Phase 2.5 — remplace le {@link HumanoidArmorLayer} vanilla pour
+ * les entités riggées.
+ *
+ * Actuellement no-op : le render d'armure n'est pas implémenté
+ * tant que V3-REW-04 n'est pas livré (Phase 3). Enregistré dans
+ * {@link PHumanoidRenderer} pour que la table {@code patchedLayers}
+ * "mange" le layer vanilla sans aucun visuel intermédiaire — évite le
+ * double-rendu armor vanilla par-dessus le mesh skinné.
+ *
+ * Voir {@code docs/plans/rig/V3_REWORK_BACKLOG.md} V3-REW-04 et
+ * {@code PHASE0_DEGRADATIONS.md} S-03.
+ *
+ * @param entité vivante rigg-compatible
+ * @param patch RIG associé
+ * @param HumanoidModel vanilla
+ */
+@OnlyIn(Dist.CLIENT)
+public class PatchedArmorLayer, M extends HumanoidModel> extends PatchedLayer> {
+ // renderLayer hérité = no-op (voir super). Phase 3 : wire armor bake via
+ // HumanoidMesh.getHumanoidArmorModel + WearableItemLayer-like drawing.
+}
diff --git a/src/main/java/com/tiedup/remake/rig/render/PatchedEntityRenderer.java b/src/main/java/com/tiedup/remake/rig/render/PatchedEntityRenderer.java
index 0116073..681edb6 100644
--- a/src/main/java/com/tiedup/remake/rig/render/PatchedEntityRenderer.java
+++ b/src/main/java/com/tiedup/remake/rig/render/PatchedEntityRenderer.java
@@ -6,10 +6,90 @@
package com.tiedup.remake.rig.render;
+import com.mojang.blaze3d.vertex.PoseStack;
+
+import net.minecraft.client.renderer.MultiBufferSource;
+import net.minecraft.client.renderer.entity.EntityRenderer;
+import net.minecraft.client.renderer.entity.LivingEntityRenderer;
+import net.minecraft.world.entity.LivingEntity;
+import net.minecraftforge.api.distmarker.Dist;
+import net.minecraftforge.api.distmarker.OnlyIn;
+import net.minecraftforge.client.event.RenderNameTagEvent;
+import net.minecraftforge.common.MinecraftForge;
+import net.minecraftforge.eventbus.api.Event.Result;
+
+import com.tiedup.remake.rig.anim.Pose;
+import com.tiedup.remake.rig.armature.Armature;
+import com.tiedup.remake.rig.asset.AssetAccessor;
+import com.tiedup.remake.rig.math.MathUtils;
+import com.tiedup.remake.rig.math.OpenMatrix4f;
+import com.tiedup.remake.rig.math.QuaternionUtils;
+import com.tiedup.remake.rig.mesh.SkinnedMesh;
+import com.tiedup.remake.rig.mixin.client.MixinEntityRenderer;
+import com.tiedup.remake.rig.patch.LivingEntityPatch;
+
/**
- * Stub RIG Phase 0 — renderer EF substituant {@code EntityRenderer} vanilla
- * pour les entités riggées. Implémentation complète Phase 2 (pipeline :
- * pose → armature → skinning → VertexConsumer).
+ * Base abstraite du pipeline de rendu RIG patché — remplace l'{@link EntityRenderer}
+ * vanilla pour les entités dont le patch retourne {@code overrideRender() == true}.
+ *
+ * Fournit trois responsabilités :
+ *
+ * - Render nameplate (via {@link RenderNameTagEvent} Forge + mixin invoker)
+ * - Application de la model matrix entité (rotation 180° + yaw corps) au {@link PoseStack}
+ * - Baking de la pose courante sur l'{@link Armature} (pour que
+ * {@code armature.getPoseMatrices()} soit à jour avant le draw du mesh)
+ *
+ *
+ * Fork verbatim de {@code yesman.epicfight.client.renderer.patched.entity.PatchedEntityRenderer}
+ * (EF 62 LOC). Imports rewrités vers les packages RIG TiedUp.
+ *
+ * @param entité vivante ({@code LivingEntity}+)
+ * @param patch RIG associé
+ * @param renderer vanilla ({@code EntityRenderer})
+ * @param mesh skinné dérivé
*/
-public abstract class PatchedEntityRenderer {
+@OnlyIn(Dist.CLIENT)
+public abstract class PatchedEntityRenderer, R extends EntityRenderer, AM extends SkinnedMesh> {
+
+ public void render(E entity, T entitypatch, R renderer, MultiBufferSource buffer, PoseStack poseStack, int packedLight, float partialTicks) {
+ RenderNameTagEvent renderNameplateEvent = new RenderNameTagEvent(entity, entity.getDisplayName(), renderer, poseStack, buffer, packedLight, partialTicks);
+ MinecraftForge.EVENT_BUS.post(renderNameplateEvent);
+
+ MixinEntityRenderer entityRendererAccessor = (MixinEntityRenderer) renderer;
+
+ if ((entityRendererAccessor.invokeShouldShowName(entity) || renderNameplateEvent.getResult() == Result.ALLOW) && renderNameplateEvent.getResult() != Result.DENY) {
+ entityRendererAccessor.invokeRenderNameTag(entity, renderNameplateEvent.getContent(), poseStack, buffer, packedLight);
+ }
+ }
+
+ public void mulPoseStack(PoseStack poseStack, Armature armature, E entity, T entitypatch, float partialTicks) {
+ OpenMatrix4f modelMatrix = entitypatch.getModelMatrix(partialTicks);
+ poseStack.mulPose(QuaternionUtils.YP.rotationDegrees(180.0F));
+ MathUtils.mulStack(poseStack, modelMatrix);
+
+ if (LivingEntityRenderer.isEntityUpsideDown(entity)) {
+ poseStack.translate(0.0D, entity.getBbHeight() + 0.1F, 0.0D);
+ poseStack.mulPose(QuaternionUtils.ZP.rotationDegrees(180.0F));
+ }
+ }
+
+ public void setArmaturePose(T entitypatch, Armature armature, float partialTicks) {
+ Pose pose = entitypatch.getAnimator().getPose(partialTicks);
+ this.setJointTransforms(entitypatch, armature, pose, partialTicks);
+ armature.setPose(pose);
+ }
+
+ public AssetAccessor getMeshProvider(T entitypatch) {
+ return this.getDefaultMesh();
+ }
+
+ public abstract AssetAccessor getDefaultMesh();
+
+ /**
+ * Override pour modifier la pose juste avant le bake (ex. scale de la tête
+ * pour les babies). Developers shouldn't implement any interpolations in
+ * this method — utilise {@link LivingEntityPatch#poseTick} à la place.
+ */
+ public void setJointTransforms(T entitypatch, Armature armature, Pose pose, float partialTicks) {
+ }
}
diff --git a/src/main/java/com/tiedup/remake/rig/render/PatchedItemInHandLayer.java b/src/main/java/com/tiedup/remake/rig/render/PatchedItemInHandLayer.java
new file mode 100644
index 0000000..9a247a4
--- /dev/null
+++ b/src/main/java/com/tiedup/remake/rig/render/PatchedItemInHandLayer.java
@@ -0,0 +1,37 @@
+/*
+ * Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
+ * by the Epic Fight Team, licensed under GPLv3.
+ * Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
+ */
+
+package com.tiedup.remake.rig.render;
+
+import net.minecraft.client.model.ArmedModel;
+import net.minecraft.client.model.EntityModel;
+import net.minecraft.client.renderer.entity.layers.ItemInHandLayer;
+import net.minecraft.world.entity.LivingEntity;
+import net.minecraftforge.api.distmarker.Dist;
+import net.minecraftforge.api.distmarker.OnlyIn;
+
+import com.tiedup.remake.rig.patch.LivingEntityPatch;
+
+/**
+ * Stub RIG Phase 2.5 — remplace le {@link ItemInHandLayer} vanilla pour les
+ * entités riggées (items tenus en main, main gauche/droite).
+ *
+ * Actuellement no-op : l'item-in-hand RIG-attached arrive en
+ * Phase 3 (items bondage GLB + rendu via tool joints {@code Tool_L} /
+ * {@code Tool_R} du biped). Enregistré dans {@link PHumanoidRenderer}
+ * pour empêcher le layer vanilla de dessiner l'item ailleurs pendant
+ * que le mesh est en pose RIG.
+ *
+ * @param entité tenant un item
+ * @param patch RIG associé
+ * @param {@link EntityModel} qui implémente aussi {@link ArmedModel}
+ * (contrainte vanilla {@link ItemInHandLayer})
+ */
+@OnlyIn(Dist.CLIENT)
+public class PatchedItemInHandLayer, M extends EntityModel & ArmedModel> extends PatchedLayer> {
+ // renderLayer hérité = no-op. Phase 3 : draw hand item via pose matrix du
+ // Tool_L/Tool_R joint sur la base du RenderItemBase existant.
+}
diff --git a/src/main/java/com/tiedup/remake/rig/render/PatchedLayer.java b/src/main/java/com/tiedup/remake/rig/render/PatchedLayer.java
new file mode 100644
index 0000000..c08cd53
--- /dev/null
+++ b/src/main/java/com/tiedup/remake/rig/render/PatchedLayer.java
@@ -0,0 +1,58 @@
+/*
+ * Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
+ * by the Epic Fight Team, licensed under GPLv3.
+ * Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
+ */
+
+package com.tiedup.remake.rig.render;
+
+import com.mojang.blaze3d.vertex.PoseStack;
+
+import net.minecraft.client.model.EntityModel;
+import net.minecraft.client.renderer.MultiBufferSource;
+import net.minecraft.client.renderer.entity.layers.RenderLayer;
+import net.minecraft.world.entity.LivingEntity;
+import net.minecraftforge.api.distmarker.Dist;
+import net.minecraftforge.api.distmarker.OnlyIn;
+
+import com.tiedup.remake.rig.math.OpenMatrix4f;
+import com.tiedup.remake.rig.patch.LivingEntityPatch;
+
+/**
+ * Base abstraite pour les layers additionnels (armor, item-in-hand, cape...)
+ * rendus par dessus le mesh principal. Équivalent EF
+ * {@code client.renderer.patched.layer.PatchedLayer}.
+ *
+ * RIG Phase 2.5 : infrastructure minimale — la méthode
+ * {@link #renderLayer} est no-op par défaut, et les 2 sous-classes concrètes
+ * (armor / item-in-hand) sont stubs. Implémentations réelles en Phase 3
+ * (V3-REW-04 armor, V3-REW-08/09 cape/arrows).
+ *
+ * @param entité vanilla
+ * @param patch RIG de {@code E}
+ * @param EntityModel vanilla
+ * @param RenderLayer vanilla concret (ex. {@code HumanoidArmorLayer})
+ */
+@OnlyIn(Dist.CLIENT)
+public abstract class PatchedLayer, M extends EntityModel, L extends RenderLayer> {
+
+ /**
+ * Méthode appelée par {@link PatchedLivingEntityRenderer#renderLayer} pour
+ * chaque layer patché enregistré. Stub no-op par défaut.
+ *
+ * @param entity entité rendue
+ * @param entitypatch patch RIG associé
+ * @param originalRender layer vanilla original (peut être {@code null} pour les customLayers)
+ * @param poseStack pose stack courant (déjà en espace entité)
+ * @param buffer buffer source
+ * @param packedLight lumière packée
+ * @param poses matrices de pose armature (index = jointId)
+ * @param bob phase de bobbing vanilla
+ * @param yRotHeadDelta delta entre yRot body et yRot head
+ * @param pitch pitch (xRot) view
+ * @param partialTicks partial ticks
+ */
+ public void renderLayer(E entity, T entitypatch, L originalRender, PoseStack poseStack, MultiBufferSource buffer, int packedLight, OpenMatrix4f[] poses, float bob, float yRotHeadDelta, float pitch, float partialTicks) {
+ // no-op — subclasses override
+ }
+}
diff --git a/src/main/java/com/tiedup/remake/rig/render/PatchedLivingEntityRenderer.java b/src/main/java/com/tiedup/remake/rig/render/PatchedLivingEntityRenderer.java
new file mode 100644
index 0000000..cb9fd4f
--- /dev/null
+++ b/src/main/java/com/tiedup/remake/rig/render/PatchedLivingEntityRenderer.java
@@ -0,0 +1,255 @@
+/*
+ * Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
+ * by the Epic Fight Team, licensed under GPLv3.
+ * Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
+ */
+
+package com.tiedup.remake.rig.render;
+
+import java.util.List;
+import java.util.Map;
+
+import org.joml.Vector4f;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.mojang.blaze3d.vertex.PoseStack;
+
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.model.EntityModel;
+import net.minecraft.client.renderer.MultiBufferSource;
+import net.minecraft.client.renderer.RenderType;
+import net.minecraft.client.renderer.entity.EntityRendererProvider;
+import net.minecraft.client.renderer.entity.LivingEntityRenderer;
+import net.minecraft.client.renderer.entity.layers.RenderLayer;
+import net.minecraft.client.renderer.texture.OverlayTexture;
+import net.minecraft.util.Mth;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.LivingEntity;
+import net.minecraftforge.api.distmarker.Dist;
+import net.minecraftforge.api.distmarker.OnlyIn;
+import net.minecraftforge.common.MinecraftForge;
+
+import com.tiedup.remake.rig.armature.Armature;
+import com.tiedup.remake.rig.asset.AssetAccessor;
+import com.tiedup.remake.rig.event.PrepareModelEvent;
+import com.tiedup.remake.rig.math.MathUtils;
+import com.tiedup.remake.rig.math.OpenMatrix4f;
+import com.tiedup.remake.rig.mesh.SkinnedMesh;
+import com.tiedup.remake.rig.mixin.client.MixinLivingEntityRenderer;
+import com.tiedup.remake.rig.patch.LivingEntityPatch;
+
+/**
+ * Orchestrateur principal du rendu RIG pour les entités vivantes — remplace
+ * {@link LivingEntityRenderer} vanilla pour les entités patched.
+ *
+ * Flow de {@link #render} :
+ *
+ * - super.render : nameplate
+ * - mulPoseStack : applique modelMatrix (rotation body + scale + upsideDown)
+ * - prepareVanillaModel : setup les champs vanilla du {@link EntityModel} (riding/young,
+ * walkAnimation, head yRot) — utile pour que les layers vanilla non patchés voient
+ * le même état que s'ils étaient rendus par vanilla
+ * - setArmaturePose : bake la pose courante dans {@code armature.getPoseMatrices()}
+ * - prepareModel : mesh.initialize() (détecte hidden parts)
+ * - mesh.draw : dispatch vers shader compute ou CPU skinning via {@code drawPosed}
+ * - renderLayer : itère les layers vanilla mappés vers des {@link PatchedLayer} (ex.
+ * armor, item-in-hand, cape) et les customLayers enregistrés manuellement
+ * - Debug hitboxes si F3+B actif
+ *
+ *
+ * Stripped vs EF source (volontaire, Phase 2.5) :
+ *
+ * - Chargement JSON datapack-driven des {@code animated_layers/{entity_type}.json} —
+ * TiedUp n'a pas de customLayers data-driven pour l'instant
+ * - {@code LayerRenderer} interface (registre public des addPatchedLayer/addCustomLayer) —
+ * conservé en API mais pas exposé via interface dédiée
+ * - {@code entitypatch.getEntityDecorations()} — decoration overlays (glow custom, elite
+ * tint) pas implémentés côté {@link LivingEntityPatch} TiedUp. Couleur et overlay
+ * utilisent les defaults vanilla.
+ *
+ *
+ * @param entité vivante
+ * @param patch RIG associé
+ * @param EntityModel vanilla
+ * @param renderer vanilla ({@code LivingEntityRenderer})
+ * @param mesh skinné dérivé
+ */
+@OnlyIn(Dist.CLIENT)
+public abstract class PatchedLivingEntityRenderer, M extends EntityModel, R extends LivingEntityRenderer, AM extends SkinnedMesh> extends PatchedEntityRenderer {
+
+ protected final Map, PatchedLayer>> patchedLayers = Maps.newHashMap();
+ protected final List>> customLayers = Lists.newArrayList();
+
+ protected PatchedLivingEntityRenderer(EntityRendererProvider.Context context, EntityType> entityType) {
+ // RIG Phase 2.5 : chargement JSON datapack des animated_layers strippé
+ // (EF L55-81). Si besoin futur d'customLayers data-driven, réimplémenter
+ // via FileToIdConverter.json("animated_layers/" + type.getPath()).
+ }
+
+ @Override
+ public void render(E entity, T entitypatch, R renderer, MultiBufferSource buffer, PoseStack poseStack, int packedLight, float partialTicks) {
+ super.render(entity, entitypatch, renderer, buffer, poseStack, packedLight, partialTicks);
+
+ Minecraft mc = Minecraft.getInstance();
+ MixinLivingEntityRenderer livingEntityRendererAccessor = (MixinLivingEntityRenderer) renderer;
+
+ boolean isVisible = livingEntityRendererAccessor.invokeIsBodyVisible(entity);
+ boolean isVisibleToPlayer = !isVisible && !entity.isInvisibleTo(mc.player);
+ boolean isGlowing = mc.shouldEntityAppearGlowing(entity);
+ RenderType renderType = livingEntityRendererAccessor.invokeGetRenderType(entity, isVisible, isVisibleToPlayer, isGlowing);
+ Armature armature = entitypatch.getArmature();
+
+ poseStack.pushPose();
+ this.mulPoseStack(poseStack, armature, entity, entitypatch, partialTicks);
+ this.prepareVanillaModel(entity, renderer.getModel(), renderer, partialTicks);
+ this.setArmaturePose(entitypatch, armature, partialTicks);
+
+ if (renderType != null) {
+ AM mesh = this.getMeshProvider(entitypatch).get();
+ this.prepareModel(mesh, entity, entitypatch, renderer);
+
+ PrepareModelEvent prepareModelEvent = new PrepareModelEvent(this, mesh, entitypatch, buffer, poseStack, packedLight, partialTicks);
+
+ if (!MinecraftForge.EVENT_BUS.post(prepareModelEvent)) {
+ // RIG Phase 2.5 : EntityDecorations (color/light/overlay modifiers) pas
+ // implémentées côté LivingEntityPatch TiedUp. On utilise les defaults
+ // vanilla : alpha 0.15 si invisible-mais-visible-pour-player, sinon 1.0.
+ Vector4f color = new Vector4f(1.0F, 1.0F, 1.0F, isVisibleToPlayer ? 0.15F : 1.0F);
+
+ mesh.draw(poseStack, buffer, renderType, packedLight, color.x(), color.y(), color.z(), color.w(), this.getOverlayCoord(entity, entitypatch, partialTicks), armature, armature.getPoseMatrices());
+ }
+ }
+
+ if (!entity.isSpectator()) {
+ this.renderLayer(renderer, entitypatch, entity, armature.getPoseMatrices(), buffer, poseStack, packedLight, partialTicks);
+ }
+
+ if (renderType != null) {
+ if (mc.getEntityRenderDispatcher().shouldRenderHitBoxes() && entitypatch.getClientAnimator() != null) {
+ entitypatch.getClientAnimator().renderDebuggingInfoForAllLayers(poseStack, buffer, partialTicks);
+ }
+ }
+
+ poseStack.popPose();
+ }
+
+ protected void prepareVanillaModel(E entity, M model, LivingEntityRenderer renderer, float partialTicks) {
+ boolean shouldSit = entity.isPassenger() && (entity.getVehicle() != null && entity.getVehicle().shouldRiderSit());
+ model.riding = shouldSit;
+ model.young = entity.isBaby();
+ float f = Mth.rotLerp(partialTicks, entity.yBodyRotO, entity.yBodyRot);
+ float f1 = Mth.rotLerp(partialTicks, entity.yHeadRotO, entity.yHeadRot);
+ float f2 = f1 - f;
+
+ if (shouldSit && entity.getVehicle() instanceof LivingEntity livingentity) {
+ f = Mth.rotLerp(partialTicks, livingentity.yBodyRotO, livingentity.yBodyRot);
+ f2 = f1 - f;
+ float f3 = Mth.wrapDegrees(f2);
+
+ if (f3 < -85.0F) {
+ f3 = -85.0F;
+ }
+
+ if (f3 >= 85.0F) {
+ f3 = 85.0F;
+ }
+
+ f = f1 - f3;
+ if (f3 * f3 > 2500.0F) {
+ f += f3 * 0.2F;
+ }
+
+ f2 = f1 - f;
+ }
+
+ float f6 = Mth.lerp(partialTicks, entity.xRotO, entity.getXRot());
+
+ if (LivingEntityRenderer.isEntityUpsideDown(entity)) {
+ f6 *= -1.0F;
+ f2 *= -1.0F;
+ }
+
+ float f7 = ((MixinLivingEntityRenderer) renderer).invokeGetBob(entity, partialTicks);
+ float f8 = 0.0F;
+ float f5 = 0.0F;
+
+ if (!shouldSit && entity.isAlive()) {
+ f8 = entity.walkAnimation.speed(partialTicks);
+ f5 = entity.walkAnimation.position() - entity.walkAnimation.speed() * (1.0F - partialTicks);
+
+ if (entity.isBaby()) {
+ f5 *= 3.0F;
+ }
+
+ if (f8 > 1.0F) {
+ f8 = 1.0F;
+ }
+ }
+
+ model.prepareMobModel(entity, f5, f8, partialTicks);
+ model.setupAnim(entity, f5, f8, f7, f2, f6);
+ }
+
+ protected void prepareModel(AM mesh, E entity, T entitypatch, R renderer) {
+ mesh.initialize();
+ }
+
+ @SuppressWarnings("unchecked")
+ protected void renderLayer(LivingEntityRenderer renderer, T entitypatch, E entity, OpenMatrix4f[] poses, MultiBufferSource buffer, PoseStack poseStack, int packedLight, float partialTicks) {
+ float f = MathUtils.lerpBetween(entity.yBodyRotO, entity.yBodyRot, partialTicks);
+ float f1 = MathUtils.lerpBetween(entity.yHeadRotO, entity.yHeadRot, partialTicks);
+ float f2 = f1 - f;
+ float f7 = entity.getViewXRot(partialTicks);
+ float bob = ((MixinLivingEntityRenderer) renderer).invokeGetBob(entity, partialTicks);
+
+ for (RenderLayer layer : renderer.layers) {
+ Class> layerClass = layer.getClass();
+
+ if (layerClass.isAnonymousClass()) {
+ layerClass = layerClass.getSuperclass();
+ }
+
+ if (this.patchedLayers.containsKey(layerClass)) {
+ @SuppressWarnings("rawtypes")
+ PatchedLayer patchedLayer = this.patchedLayers.get(layerClass);
+ patchedLayer.renderLayer(entity, entitypatch, layer, poseStack, buffer, packedLight, poses, bob, f2, f7, partialTicks);
+ }
+ }
+
+ for (PatchedLayer> patchedLayer : this.customLayers) {
+ @SuppressWarnings("rawtypes")
+ PatchedLayer raw = patchedLayer;
+ raw.renderLayer(entity, entitypatch, null, poseStack, buffer, packedLight, poses, bob, f2, f7, partialTicks);
+ }
+ }
+
+ protected int getOverlayCoord(E entity, T entitypatch, float partialTicks) {
+ // RIG Phase 2.5 : EntityDecorations.modifyOverlay strippé — utilise
+ // les defaults vanilla (hurt flash + death fade).
+ int initU = 0;
+ int initV = OverlayTexture.v(entity.hurtTime > 0 || entity.deathTime > 0);
+ return OverlayTexture.pack(initU, initV);
+ }
+
+ @Override
+ public void mulPoseStack(PoseStack poseStack, Armature armature, E entity, T entitypatch, float partialTicks) {
+ super.mulPoseStack(poseStack, armature, entity, entitypatch, partialTicks);
+
+ if (entity.isCrouching()) {
+ poseStack.translate(0.0D, 0.15D, 0.0D);
+ }
+ }
+
+ public void addPatchedLayer(Class> originalLayerClass, PatchedLayer> patchedLayer) {
+ this.patchedLayers.putIfAbsent(originalLayerClass, patchedLayer);
+ }
+
+ public void addCustomLayer(PatchedLayer> patchedLayer) {
+ this.customLayers.add(patchedLayer);
+ }
+
+ protected float getDefaultLayerHeightCorrection() {
+ return 1.15F;
+ }
+}
diff --git a/src/main/java/com/tiedup/remake/rig/render/TiedUpPlayerRenderer.java b/src/main/java/com/tiedup/remake/rig/render/TiedUpPlayerRenderer.java
new file mode 100644
index 0000000..310724a
--- /dev/null
+++ b/src/main/java/com/tiedup/remake/rig/render/TiedUpPlayerRenderer.java
@@ -0,0 +1,97 @@
+/*
+ * Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
+ * by the Epic Fight Team, licensed under GPLv3.
+ * Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
+ */
+
+package com.tiedup.remake.rig.render;
+
+import net.minecraft.client.model.PlayerModel;
+import net.minecraft.client.player.AbstractClientPlayer;
+import net.minecraft.client.renderer.entity.EntityRendererProvider;
+import net.minecraft.client.renderer.entity.player.PlayerRenderer;
+import net.minecraft.world.entity.EntityType;
+import net.minecraftforge.api.distmarker.Dist;
+import net.minecraftforge.api.distmarker.OnlyIn;
+
+import com.tiedup.remake.rig.asset.AssetAccessor;
+import com.tiedup.remake.rig.mesh.HumanoidMesh;
+import com.tiedup.remake.rig.mesh.Meshes;
+import com.tiedup.remake.rig.mesh.SkinnedMesh;
+import com.tiedup.remake.rig.patch.ClientPlayerPatch;
+
+/**
+ * Renderer RIG patché pour les joueurs (local + remote).
+ *
+ * Dispatch slim vs default via {@link #getMeshProvider} — suit la règle
+ * vanilla {@code entity.getModelName().equals("slim")} → Alex mesh, sinon
+ * Steve/biped mesh.
+ *
+ * Override de {@link #prepareModel} pour propager les flags de visibilité
+ * des {@link PlayerModel#head head / hat / jacket / sleeves / pants} vers les
+ * {@link HumanoidMesh} parts correspondants — nécessaire pour que les
+ * toggles client MC ("Cape", "Left Sleeve" etc.) fonctionnent avec le rendu
+ * RIG.
+ *
+ * Layers skipés vs EF (Phase 2.5, à rework Phase 3) :
+ *
+ * - {@code ArrowLayer} — flèches plantées dans le player (V3-REW-09)
+ * - {@code BeeStingerLayer} — dards d'abeille plantés (V3-REW-09)
+ * - {@code CapeLayer} — cape vanilla (V3-REW-08, compat skin layers)
+ * - {@code PlayerItemInHandLayer} — items en main (version player-spécifique,
+ * distincte de {@link net.minecraft.client.renderer.entity.layers.ItemInHandLayer}) —
+ * le layer vanilla continue de dessiner jusqu'à la Phase 3
+ *
+ *
+ * Fork conceptuel de {@code yesman.epicfight.client.renderer.patched.entity.PPlayerRenderer}
+ * (EF 60 LOC) — signatures identiques mais sans patched layers combat EF.
+ */
+@OnlyIn(Dist.CLIENT)
+public class TiedUpPlayerRenderer extends PHumanoidRenderer, PlayerModel, PlayerRenderer, HumanoidMesh> {
+
+ public TiedUpPlayerRenderer(EntityRendererProvider.Context context, EntityType> entityType) {
+ super(Meshes.BIPED, context, entityType);
+ }
+
+ @Override
+ protected void prepareModel(HumanoidMesh mesh, AbstractClientPlayer entity, ClientPlayerPatch entitypatch, PlayerRenderer renderer) {
+ super.prepareModel(mesh, entity, entitypatch, renderer);
+
+ // Renderer vanilla : lit les player options (modelParts toggle UI)
+ // pour mettre à jour model.{head,hat,jacket,leftArm,...}.visible.
+ renderer.setModelProperties(entity);
+ PlayerModel model = renderer.getModel();
+
+ // Propage vers le mesh RIG : chaque part peut être hidden indépendamment.
+ // HumanoidMesh peut avoir des parts null (legacy biped sans sleeves) —
+ // on protège, sinon NPE au runtime sur les meshes partiels.
+ setHiddenSafe(mesh.head, !model.head.visible);
+ setHiddenSafe(mesh.hat, !model.hat.visible);
+ setHiddenSafe(mesh.jacket, !model.jacket.visible);
+ setHiddenSafe(mesh.torso, !model.body.visible);
+ setHiddenSafe(mesh.leftArm, !model.leftArm.visible);
+ setHiddenSafe(mesh.leftLeg, !model.leftLeg.visible);
+ setHiddenSafe(mesh.leftPants, !model.leftPants.visible);
+ setHiddenSafe(mesh.leftSleeve, !model.leftSleeve.visible);
+ setHiddenSafe(mesh.rightArm, !model.rightArm.visible);
+ setHiddenSafe(mesh.rightLeg, !model.rightLeg.visible);
+ setHiddenSafe(mesh.rightPants, !model.rightPants.visible);
+ setHiddenSafe(mesh.rightSleeve, !model.rightSleeve.visible);
+ }
+
+ private static void setHiddenSafe(SkinnedMesh.SkinnedMeshPart part, boolean hidden) {
+ if (part != null) {
+ part.setHidden(hidden);
+ }
+ }
+
+ @Override
+ public AssetAccessor getMeshProvider(ClientPlayerPatch entitypatch) {
+ return entitypatch.getOriginal().getModelName().equals("slim") ? Meshes.ALEX : Meshes.BIPED;
+ }
+
+ @Override
+ public AssetAccessor getDefaultMesh() {
+ return Meshes.BIPED;
+ }
+}
diff --git a/src/main/resources/tiedup-rig.mixins.json b/src/main/resources/tiedup-rig.mixins.json
index 3ca0e1c..c80ba1a 100644
--- a/src/main/resources/tiedup-rig.mixins.json
+++ b/src/main/resources/tiedup-rig.mixins.json
@@ -6,7 +6,9 @@
"refmap": "tiedup-rig.refmap.json",
"mixins": [],
"client": [
- "client.MixinAgeableListModel"
+ "client.MixinAgeableListModel",
+ "client.MixinEntityRenderer",
+ "client.MixinLivingEntityRenderer"
],
"injectors": {
"defaultRequire": 1