Phase 2.4 review fixes : P0-BUG-001 joint order + P0-BUG-002 singleton + tests
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.
This commit is contained in:
114
src/test/java/com/tiedup/remake/rig/TiedUpArmaturesTest.java
Normal file
114
src/test/java/com/tiedup/remake/rig/TiedUpArmaturesTest.java
Normal file
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import com.tiedup.remake.rig.armature.HumanoidArmature;
|
||||
|
||||
/**
|
||||
* Tests du biped procédural TiedUp.
|
||||
*
|
||||
* <p>Ces tests verrouillent en particulier le mapping {@code id → joint name}
|
||||
* qui doit rester aligné sur les constantes {@code RIGHT_ARM} / {@code LEFT_ARM}
|
||||
* de {@code VanillaModelTransformer} — sinon les bras rendent tordus (ex :
|
||||
* {@code lowerJoint=17} pointerait vers Elbow_L au lieu de Hand_L).</p>
|
||||
*
|
||||
* <p>La review Phase 2.4 (P0-BUG-001) a montré que l'ordre précédent
|
||||
* Arm_R=11, Elbow_R=12, Hand_R=13, Tool_R=14 cassait silencieusement le
|
||||
* transformer. {@link #jointIdsMatchEfLayout()} aurait attrapé ce bug.</p>
|
||||
*/
|
||||
class TiedUpArmaturesTest {
|
||||
|
||||
@Test
|
||||
void bipedHas20Joints() {
|
||||
assertEquals(20, TiedUpArmatures.BIPED.get().getJointNumber(),
|
||||
"Le biped EF standard doit avoir exactement 20 joints");
|
||||
}
|
||||
|
||||
@Test
|
||||
void searchBipedJointByNameReturnsNonNull() {
|
||||
HumanoidArmature biped = TiedUpArmatures.BIPED.get();
|
||||
|
||||
String[] expectedNames = {
|
||||
"Root", "Head",
|
||||
"Torso", "Chest",
|
||||
"Shoulder_L", "Shoulder_R",
|
||||
"Arm_L", "Arm_R",
|
||||
"Hand_L", "Hand_R",
|
||||
"Elbow_L", "Elbow_R",
|
||||
"Tool_L", "Tool_R",
|
||||
"Thigh_L", "Thigh_R",
|
||||
"Leg_L", "Leg_R",
|
||||
"Knee_L", "Knee_R"
|
||||
};
|
||||
|
||||
for (String name : expectedNames) {
|
||||
assertNotNull(biped.searchJointByName(name),
|
||||
"Joint '" + name + "' introuvable dans le biped — alignement EF cassé");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verrou explicite contre P0-BUG-001. Les constantes EF
|
||||
* {@code VanillaModelTransformer.RIGHT_ARM} encodent
|
||||
* {@code upperJoint=11, lowerJoint=12, middleJoint=14}, i.e.
|
||||
* Arm_R=11, Hand_R=12, Elbow_R=14 (symétrique gauche : 16/17/19).
|
||||
*
|
||||
* <p>Ce test DOIT échouer si quelqu'un réordonne les IDs dans
|
||||
* {@link TiedUpArmatures#buildBiped()} sans mettre à jour
|
||||
* {@code VanillaModelTransformer} en miroir.</p>
|
||||
*/
|
||||
@Test
|
||||
void jointIdsMatchEfLayout() {
|
||||
HumanoidArmature biped = TiedUpArmatures.BIPED.get();
|
||||
|
||||
// Right arm layout (VanillaModelTransformer.RIGHT_ARM : 11, 12, _, 14)
|
||||
assertEquals("Arm_R", biped.searchJointById(11).getName(),
|
||||
"id=11 doit être Arm_R (VanillaModelTransformer.RIGHT_ARM.upperJoint)");
|
||||
assertEquals("Hand_R", biped.searchJointById(12).getName(),
|
||||
"id=12 doit être Hand_R (VanillaModelTransformer.RIGHT_ARM.lowerJoint)");
|
||||
assertEquals("Elbow_R", biped.searchJointById(14).getName(),
|
||||
"id=14 doit être Elbow_R (VanillaModelTransformer.RIGHT_ARM.middleJoint)");
|
||||
|
||||
// Left arm layout (VanillaModelTransformer.LEFT_ARM : 16, 17, _, 19)
|
||||
assertEquals("Arm_L", biped.searchJointById(16).getName(),
|
||||
"id=16 doit être Arm_L (VanillaModelTransformer.LEFT_ARM.upperJoint)");
|
||||
assertEquals("Hand_L", biped.searchJointById(17).getName(),
|
||||
"id=17 doit être Hand_L (VanillaModelTransformer.LEFT_ARM.lowerJoint)");
|
||||
assertEquals("Elbow_L", biped.searchJointById(19).getName(),
|
||||
"id=19 doit être Elbow_L (VanillaModelTransformer.LEFT_ARM.middleJoint)");
|
||||
|
||||
// Tool_R=13, Tool_L=18 (pas dans les LimbPartTransformer mais utilisé
|
||||
// par StaticAnimation itemInHand — on verrouille l'emplacement aussi).
|
||||
assertEquals("Tool_R", biped.searchJointById(13).getName(),
|
||||
"id=13 doit être Tool_R (attaché après Hand_R dans la chaîne)");
|
||||
assertEquals("Tool_L", biped.searchJointById(18).getName(),
|
||||
"id=18 doit être Tool_L (attaché après Hand_L dans la chaîne)");
|
||||
}
|
||||
|
||||
/**
|
||||
* Verrou contre P0-BUG-002. La première implémentation utilisait un
|
||||
* double-null-check non thread-safe — SP intégré (client + server threads
|
||||
* concurrent) pouvait créer deux instances distinctes.
|
||||
*
|
||||
* <p>Le Holder idiome (static inner class) garantit via le class-init lock
|
||||
* JVM qu'il n'y a qu'une seule init. Ce test vérifie que deux appels
|
||||
* successifs de {@code BIPED.get()} renvoient bien la même référence —
|
||||
* ne prouve pas l'atomicité mais couvre le cas régression "retour à un
|
||||
* nouveau build à chaque get".</p>
|
||||
*/
|
||||
@Test
|
||||
void bipedSingleton() {
|
||||
HumanoidArmature first = TiedUpArmatures.BIPED.get();
|
||||
HumanoidArmature second = TiedUpArmatures.BIPED.get();
|
||||
assertSame(first, second,
|
||||
"BIPED.get() doit retourner la même instance (singleton Holder)");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user