Phase 2.5 review fixes : P0 biped warn + CME guard + double-draw prevention
P0-BUG-001 : LOGGER.warn dans TiedUpArmatures.Holder static init quand les joints biped sont en identity (Phase 2.4 stub). Sans ça, Phase 2.6 renderer afficherait un mesh effondré sans signal dev. Warn apparaît une seule fois (class-init lock JVM). P0-BUG-002 : Lists.newArrayList(renderer.layers) copie défensive dans PatchedLivingEntityRenderer.renderLayer. Mods tiers (Wildfire, SkinLayers3D, Cosmetic Armor) peuvent muter renderer.layers runtime via AddLayers event -> CME garantie sans copie. TiedUp va au-dela d'EF upstream. P1-RISK-001 : Pose.orElseEmpty(name) utilise Map.getOrDefault qui retourne un JointTransform.empty() detache non stocke dans la map -> toute mutation .frontResult est perdue si le joint est absent. En pratique l'Animator peuple toujours Head via getComposedLayerPose, mais defensif : on recupere l'instance, mute, puis pose.putJointData(Head, mutatedTransform). Meme bug latent en EF upstream. P1-RISK-003 : PlayerItemInHandLayer extends ItemInHandLayer mais le dispatch patchedLayers.containsKey(layer.getClass()) est strict -> layer player-specifique passe au travers. Ajout du mapping explicite dans TiedUpPlayerRenderer constructor avec le meme stub PatchedItemInHandLayer. Preventif Phase 3. P2-RISK-004 : @SuppressWarnings scope resserre des 2 methodes aux 2 call sites dispatch raw PatchedLayer qui en ont reellement besoin. Commentaire pointant vers l'invariant runtime addPatchedLayer. Tests : 15/15 rig tests GREEN, compile GREEN. Doc : docs/plans/rig/PHASE0_DEGRADATIONS.md section Phase 2.5 findings ajoutee (fichier gitignore, changements locaux pour future session).
This commit is contained in:
@@ -89,7 +89,20 @@ public final class TiedUpArmatures {
|
||||
* le cache.</p>
|
||||
*/
|
||||
private static final class Holder {
|
||||
static final HumanoidArmature INSTANCE = buildBiped();
|
||||
static final HumanoidArmature INSTANCE;
|
||||
static {
|
||||
// Signal visible au dev que les joints sont en identity transform.
|
||||
// Sans ça, Phase 2.6+ câblera le renderer et le mesh apparaîtra
|
||||
// "effondré à l'origine" sans signal — debug cauchemar. Le warn
|
||||
// n'apparaît qu'une fois (class-init lock JVM).
|
||||
TiedUpRigConstants.LOGGER.warn(
|
||||
"TiedUpArmatures.BIPED initialized with IDENTITY joint transforms (Phase 2.4 stub). "
|
||||
+ "Mesh will render collapsed-to-origin until Phase 2.7 provides biped.json "
|
||||
+ "Blender-authored offsets. See docs/plans/rig/PHASE0_DEGRADATIONS.md "
|
||||
+ "Phase 2.4 backlog entry #1."
|
||||
);
|
||||
INSTANCE = buildBiped();
|
||||
}
|
||||
private Holder() {}
|
||||
}
|
||||
|
||||
|
||||
@@ -76,7 +76,20 @@ public class PHumanoidRenderer<E extends LivingEntity, T extends LivingEntityPat
|
||||
// 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);
|
||||
//
|
||||
// P1-RISK-001 (review Phase 2.5) : Pose.orElseEmpty("Head") utilise
|
||||
// Map.getOrDefault(name, JointTransform.empty()) — si "Head" est absent
|
||||
// de la pose, l'instance retournée est détachée (nouvelle, non dans la
|
||||
// map) et toute mutation .frontResult sur elle est perdue au prochain
|
||||
// getOrDefault. En pratique l'Animator peuple toujours "Head" via le
|
||||
// composed layer pose (canonical joints armature), mais on sécurise :
|
||||
// on récupère explicitement l'instance et on la ré-écrit dans la map.
|
||||
//
|
||||
// (Même latent bug en EF upstream — cf. Pose:61 EF, PHumanoidRenderer:41 EF —
|
||||
// masqué par composeLayerPose qui itère armature.rootJoint et insère Head.)
|
||||
JointTransform headTransform = pose.orElseEmpty("Head");
|
||||
headTransform.frontResult(JointTransform.scale(new Vec3f(1.25F, 1.25F, 1.25F)), OpenMatrix4f::mul);
|
||||
pose.putJointData("Head", headTransform);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -195,7 +195,6 @@ public abstract class PatchedLivingEntityRenderer<E extends LivingEntity, T exte
|
||||
mesh.initialize();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected void renderLayer(LivingEntityRenderer<E, M> 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);
|
||||
@@ -203,7 +202,12 @@ public abstract class PatchedLivingEntityRenderer<E extends LivingEntity, T exte
|
||||
float f7 = entity.getViewXRot(partialTicks);
|
||||
float bob = ((MixinLivingEntityRenderer) renderer).invokeGetBob(entity, partialTicks);
|
||||
|
||||
for (RenderLayer<E, M> layer : renderer.layers) {
|
||||
// P0-BUG-002 (review Phase 2.5) : copie défensive de renderer.layers —
|
||||
// la liste est mutable et peut être touchée par des mods tiers (cosmetics,
|
||||
// wildfire gender, skin layers 3D) qui ajoutent un layer runtime via
|
||||
// AddLayers event. Sans copie, CME garantie dès qu'un autre mod mute
|
||||
// la liste pendant notre itération.
|
||||
for (RenderLayer<E, M> layer : Lists.newArrayList(renderer.layers)) {
|
||||
Class<?> layerClass = layer.getClass();
|
||||
|
||||
if (layerClass.isAnonymousClass()) {
|
||||
@@ -211,14 +215,19 @@ public abstract class PatchedLivingEntityRenderer<E extends LivingEntity, T exte
|
||||
}
|
||||
|
||||
if (this.patchedLayers.containsKey(layerClass)) {
|
||||
@SuppressWarnings("rawtypes")
|
||||
// P2-RISK-004 (review Phase 2.5) : cast unchecked scopé aux 2 call
|
||||
// sites qui en ont besoin (raw-typed dispatch PatchedLayer pour
|
||||
// éviter l'explosion de wildcard genericity sur 4 type params).
|
||||
// L'invariant runtime est garanti par addPatchedLayer qui binde
|
||||
// Class<?> → PatchedLayer<E,T,M,? extends RenderLayer<E,M>>.
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
PatchedLayer patchedLayer = this.patchedLayers.get(layerClass);
|
||||
patchedLayer.renderLayer(entity, entitypatch, layer, poseStack, buffer, packedLight, poses, bob, f2, f7, partialTicks);
|
||||
}
|
||||
}
|
||||
|
||||
for (PatchedLayer<E, T, M, ? extends RenderLayer<E, M>> patchedLayer : this.customLayers) {
|
||||
@SuppressWarnings("rawtypes")
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
PatchedLayer raw = patchedLayer;
|
||||
raw.renderLayer(entity, entitypatch, null, poseStack, buffer, packedLight, poses, bob, f2, f7, partialTicks);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ 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.layers.PlayerItemInHandLayer;
|
||||
import net.minecraft.client.renderer.entity.player.PlayerRenderer;
|
||||
import net.minecraft.world.entity.EntityType;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
@@ -38,9 +39,16 @@ import com.tiedup.remake.rig.patch.ClientPlayerPatch;
|
||||
* <li>{@code ArrowLayer} — flèches plantées dans le player (V3-REW-09)</li>
|
||||
* <li>{@code BeeStingerLayer} — dards d'abeille plantés (V3-REW-09)</li>
|
||||
* <li>{@code CapeLayer} — cape vanilla (V3-REW-08, compat skin layers)</li>
|
||||
* <li>{@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</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p><b>Layers mappés Phase 2.5</b> :</p>
|
||||
* <ul>
|
||||
* <li>{@code HumanoidArmorLayer} → {@code PatchedArmorLayer} (hérité de PHumanoidRenderer)</li>
|
||||
* <li>{@code ItemInHandLayer} → {@code PatchedItemInHandLayer} (hérité de PHumanoidRenderer)</li>
|
||||
* <li>{@link PlayerItemInHandLayer} → {@code PatchedItemInHandLayer} (ajouté ici,
|
||||
* P1-RISK-003 review Phase 2.5 : dispatch par classe stricte, PlayerItemInHandLayer
|
||||
* extends ItemInHandLayer mais {@code .getClass()} diffère → sans mapping explicite,
|
||||
* double-draw item quand Phase 3 câblera le vrai rendu)</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>Fork conceptuel de {@code yesman.epicfight.client.renderer.patched.entity.PPlayerRenderer}
|
||||
@@ -51,6 +59,17 @@ public class TiedUpPlayerRenderer extends PHumanoidRenderer<AbstractClientPlayer
|
||||
|
||||
public TiedUpPlayerRenderer(EntityRendererProvider.Context context, EntityType<?> entityType) {
|
||||
super(Meshes.BIPED, context, entityType);
|
||||
|
||||
// P1-RISK-003 (review Phase 2.5) : PlayerItemInHandLayer extends ItemInHandLayer
|
||||
// mais le dispatch `patchedLayers.containsKey(layer.getClass())` fait un
|
||||
// match strict de classe — sans mapping explicite, le stub parent n'est
|
||||
// pas reconnu et le layer vanilla continue de dessiner (double-draw
|
||||
// quand Phase 3 activera le vrai rendu item). On réutilise le même stub
|
||||
// no-op PatchedItemInHandLayer.
|
||||
//
|
||||
// Note : pas de problème Phase 2.5 tant que PatchedItemInHandLayer est
|
||||
// no-op complet (aucun draw dans les 2 layers). Préventif pour Phase 3+.
|
||||
this.addPatchedLayer(PlayerItemInHandLayer.class, new PatchedItemInHandLayer<>());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
Reference in New Issue
Block a user