P3-05 review fixes : deterministic tiebreaker + reset contract doc
HIGH RISK-001 : secondary comparator on def.id() prevents UB on equal-priority items — no longer depends on BodyRegionV2 enum order. New test asserts lexicographic ordering. HIGH RISK-002 : resetLivingAnimations restores defaultLivingAnimations snapshot. Javadoc now warns against calling setCurrentMotionsAsDefault anywhere in the TiedUp pipeline (would leak custom bindings into defaults, breaking unbind). Grep confirms no call site exists today. Polish : rename tautological null test, precise null-safety doc.
This commit is contained in:
@@ -144,6 +144,26 @@ public final class ClientRigEquipmentHandler {
|
||||
* encore bootstrap)</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p><b>Null-safety</b> : seuls {@code player}, {@code patch} et
|
||||
* {@code animator} sont null-checkés ici. Les autres scénarios null
|
||||
* (bindings null, stack empty dans l'equipment, id null) sont gérés
|
||||
* inline dans {@link #applyDefinitions} et
|
||||
* {@link #extractSortedDefinitions} — chacun skip silencieusement sans
|
||||
* throw.</p>
|
||||
*
|
||||
* <p><b>Contract – ne JAMAIS appeler
|
||||
* {@code animator.setCurrentMotionsAsDefault()} ailleurs dans le
|
||||
* pipeline TiedUp.</b> Cette méthode EF consolide les bindings custom
|
||||
* courants dans la snapshot {@code defaultLivingAnimations} ; un
|
||||
* {@code resetLivingAnimations()} ultérieur les restaurerait alors
|
||||
* comme "defaults" après un unbind, et l'anim custom fuiterait au-delà
|
||||
* de la durée de vie de l'item équipé. En prod EF,
|
||||
* {@code setCurrentMotionsAsDefault} n'est appelé qu'une fois depuis
|
||||
* {@code ClientAnimator.postInit()} pendant le bootstrap de
|
||||
* l'animator, capturant les IDLE/WALK/RUN vanilla comme snapshot
|
||||
* pérenne. Le rebuild TiedUp compte sur cet invariant : tout rebuild
|
||||
* part d'un état EF vanilla stable.</p>
|
||||
*
|
||||
* @param player le player dont on rebuild les anims ; null tolérée
|
||||
*/
|
||||
public static void rebuildBondageAnimations(Player player) {
|
||||
@@ -164,6 +184,17 @@ public final class ClientRigEquipmentHandler {
|
||||
List<DataDrivenItemDefinition> sortedDefs =
|
||||
extractSortedDefinitions(equipped.values(), DataDrivenItemRegistry::get);
|
||||
|
||||
// Reset + restore des defaults EF (IDLE, WALK, RUN, etc.). Les bindings
|
||||
// custom qu'on ajoute après prennent le pas sur ces defaults via la
|
||||
// map livingAnimations (dernière put gagne pour une même LivingMotion).
|
||||
//
|
||||
// IMPORTANT : ne jamais appeler animator.setCurrentMotionsAsDefault()
|
||||
// ailleurs dans le pipeline TiedUp — cela consoliderait les custom
|
||||
// bindings dans les defaults, et un unbind ultérieur laisserait les
|
||||
// anims en place (le reset restaurerait les ex-custom comme "defaults").
|
||||
// Pour un rebuild propre, on compte sur le fait que defaultLivingAnimations
|
||||
// reste la snapshot originale EF (IDLE/WALK/RUN vanilla, capturée une
|
||||
// seule fois dans ClientAnimator.postInit() durant le bootstrap).
|
||||
applyDefinitions(
|
||||
animator::resetLivingAnimations,
|
||||
animator::addLivingAnimation,
|
||||
@@ -195,6 +226,19 @@ public final class ClientRigEquipmentHandler {
|
||||
* même {@link LivingMotion}, on doit itérer le priorité 10 d'abord et
|
||||
* le priorité 20 ensuite (il écrase).</p>
|
||||
*
|
||||
* <p><b>Sort déterministe</b> :</p>
|
||||
* <ol>
|
||||
* <li>Primary : {@code posePriority} ASC → highest-priority itère en
|
||||
* dernier → gagne le conflit (last put wins dans
|
||||
* {@code addLivingAnimation} map semantics).</li>
|
||||
* <li>Secondary : {@code id.toString()} lexicographique → tiebreaker
|
||||
* stable indépendant de l'ordre de déclaration de
|
||||
* {@link BodyRegionV2}. Deux items avec même {@code posePriority}
|
||||
* résolvent sur leur {@link ResourceLocation} — un dev qui
|
||||
* réordonne l'enum ne change plus silencieusement quelle anim
|
||||
* gagne les conflits priority-tied.</li>
|
||||
* </ol>
|
||||
*
|
||||
* <p><b>Note sur la généricité</b> : l'API est paramétrée {@code <T>}
|
||||
* plutôt que fixée à {@link ItemStack} pour permettre un unit test sans
|
||||
* bootstrap MC (le test passe des {@code Object} dummy, la prod passe
|
||||
@@ -231,7 +275,17 @@ public final class ClientRigEquipmentHandler {
|
||||
defs.add(def);
|
||||
}
|
||||
|
||||
defs.sort(Comparator.comparingInt(DataDrivenItemDefinition::posePriority));
|
||||
// Sort déterministe :
|
||||
// 1. Primary : posePriority ASC → highest-priority itère en dernier →
|
||||
// gagne le conflit (last put wins dans addLivingAnimation map
|
||||
// semantics).
|
||||
// 2. Secondary : id.toString() lexicographique → tiebreaker stable
|
||||
// indépendant de BodyRegionV2 enum declaration order. Deux items
|
||||
// avec même posePriority résolvent sur leur ResourceLocation.
|
||||
defs.sort(
|
||||
Comparator.comparingInt(DataDrivenItemDefinition::posePriority)
|
||||
.thenComparing(d -> d.id().toString())
|
||||
);
|
||||
return defs;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user