P0-BUG-001 — Ordre IDs joints Elbow/Hand/Tool ≠ EF
TiedUpArmatures.buildBiped() assignait Arm_R=11, Elbow_R=12, Hand_R=13,
Tool_R=14 (idem gauche 16/17/18/19) alors que VanillaModelTransformer.RIGHT_ARM
encode upperJoint=11, lowerJoint=12, middleJoint=14 — donc Arm_R=11, Hand_R=12,
Elbow_R=14. Résultat : lowerJoint=17 (SimpleTransformer qui attache les
vertices à Hand) pointait en fait vers Elbow_L → bras tordus au rendu.
Fix : réassigner les IDs dans buildBiped() pour matcher le layout EF
(Arm_R=11, Hand_R=12, Tool_R=13, Elbow_R=14 ; symétrique 16-19).
VanillaModelTransformer non touché (source de vérité EF).
P0-BUG-002 — Singleton BIPED non thread-safe
if (BIPED_INSTANCE == null) BIPED_INSTANCE = buildBiped() est un double-init
race. En SP intégré (client + server threads sur la même JVM), deux threads
peuvent entrer simultanément dans le if null et créer deux HumanoidArmature
distincts — pose matrices incohérentes selon les call sites.
Fix : Holder idiome (static inner class). Le class-init lock JVM garantit
(JLS §12.4.1) qu'une seule init, visible à tous les threads, sans
synchronized ni volatile. Zero overhead après init.
P1 — Nouveau TiedUpArmaturesTest (4 tests)
- bipedHas20Joints : BIPED.get().getJointNumber() == 20
- searchBipedJointByNameReturnsNonNull : vérifie les 20 noms EF
- jointIdsMatchEfLayout : verrou P0-BUG-001 (id=12→Hand_R, id=14→Elbow_R,
etc.) — aurait attrapé le bug en review initiale
- bipedSingleton : BIPED.get() == BIPED.get() (verrou P0-BUG-002)
P2 backlog tracé dans docs/plans/rig/PHASE0_DEGRADATIONS.md :
biped collapsed visuellement jusqu'à Phase 2.7, PlayerPatch.yBodyRot sans
lerp, ridingEntity non géré, isFirstPersonHidden nommage ambigu,
ServerPlayerPatch hérite méthodes client-only sans @OnlyIn.
Tests : 15 GREEN (11 bridge pré-existants + 4 nouveaux biped).
Compile clean.