From 4648107ebe5a4439612d4c8cd0c6b55b04bf868f Mon Sep 17 00:00:00 2001 From: notevil Date: Fri, 24 Apr 2026 03:46:12 +0200 Subject: [PATCH] =?UTF-8?q?Revert=20Option=20B=20=E2=80=94=20IDLE=20bindin?= =?UTF-8?q?gs=20are=20first-class?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit User decision 2026-04-24 : IDLE bindings must be supported because modders need to define idle poses for their items (armbinder can't work without arms-behind-back idle pose). TiedUp will also ship default IDLE bindings for its core items once assets are authored. Technical : EF ClientAnimator has 2 separate maps (livingAnimations for BASE layer + compositeLivingAnimations for COMPOSITE layers). Both support IDLE bindings without race — last-put-wins for base override, or automatic composition via JointMask for partial overlay. The previous WARN was a reviewer over-correction based on misread of the 2-map structure. - Removed WARN parse-time in DataDrivenItemParser.parseAnimationBindings - Updated test : IDLE binding is accepted silently (no warn) - Cleaned 'Option B' mentions in javadoc comments - Design doc (gitignored) updated with 2-path explanation. --- .../tiedup/remake/rig/patch/PlayerPatch.java | 9 ++- .../datadriven/DataDrivenItemParser.java | 28 +++----- .../v2/client/ClientRigEquipmentHandler.java | 23 ++++-- .../DataDrivenItemParserAnimationsTest.java | 71 +++++++++++++++---- 4 files changed, 90 insertions(+), 41 deletions(-) diff --git a/src/main/java/com/tiedup/remake/rig/patch/PlayerPatch.java b/src/main/java/com/tiedup/remake/rig/patch/PlayerPatch.java index 38fbded..ef6f4ab 100644 --- a/src/main/java/com/tiedup/remake/rig/patch/PlayerPatch.java +++ b/src/main/java/com/tiedup/remake/rig/patch/PlayerPatch.java @@ -90,9 +90,12 @@ public abstract class PlayerPatch extends LivingEntityPatch *
  • walk seul → {@link LivingMotions#WALK}
  • * * - *
  • Défaut (idle) → {@link LivingMotions#IDLE} vanilla — les items - * bondage bindent leurs poses spécifiques ailleurs (Option B du - * design doc, §5.1 BONDAGE_ANIMATION_DESIGN.md).
  • + *
  • Défaut (idle) → {@link LivingMotions#IDLE} vanilla. Les items + * bondage peuvent binder IDLE via deux paths : + * BASE layer override (écrase vanilla IDLE dans {@code livingAnimations}) + * ou COMPOSITE overlay avec JointMask (vanilla IDLE joue sur le corps, + * overlay masque seulement certains joints). Voir §5.1 + * BONDAGE_ANIMATION_DESIGN.md.
  • * * *

    Pattern EF : {@code EntityState.inaction()} gate la transition diff --git a/src/main/java/com/tiedup/remake/v2/bondage/datadriven/DataDrivenItemParser.java b/src/main/java/com/tiedup/remake/v2/bondage/datadriven/DataDrivenItemParser.java index 5a219ff..bdf4595 100644 --- a/src/main/java/com/tiedup/remake/v2/bondage/datadriven/DataDrivenItemParser.java +++ b/src/main/java/com/tiedup/remake/v2/bondage/datadriven/DataDrivenItemParser.java @@ -717,24 +717,16 @@ public final class DataDrivenItemParser { ); continue; } - // P3 Wave α — Option B convention warn : binding IDLE fonctionne - // techniquement mais écrase silencieusement le default EF re-injecté - // par ClientAnimator.resetLivingAnimations() post-rebuild. Design - // BONDAGE_ANIMATION_DESIGN.md §5.1 recommande d'utiliser des motions - // custom (POSE_KNEEL_BOUND / STRUGGLE_BOUND / WALK_BOUND) plutôt que - // d'écraser IDLE. On accepte le binding quand même (rétrocompat + - // cas légitimes rares), juste on pointe le modder vers la convention. - if (motion == LivingMotions.IDLE) { - LOGGER.warn( - "[DataDrivenItemParser] Item {} binds IDLE motion. Convention design Option B " - + "(BONDAGE_ANIMATION_DESIGN.md §5.1) recommends not binding IDLE — " - + "ClientAnimator.resetLivingAnimations() restores default EF IDLE after " - + "rebuild, so binding IDLE overwrites the default silently. Prefer custom " - + "motions like POSE_KNEEL_BOUND / STRUGGLE_BOUND / WALK_BOUND. The binding " - + "will still work but may cause surprises.", - itemId - ); - } + // IDLE bindings are first-class citizens (user decision 2026-04-24). + // Two paths supported natively by EF's 2-map ClientAnimator structure : + // - BASE layer override : anim LayerType=BASE_LAYER → written into + // livingAnimations, overrides vanilla IDLE after reset (last-put-wins). + // Use case : armbinder forcing "arms behind back" full-body pose. + // - COMPOSITE overlay : anim LayerType=COMPOSITE_LAYER + JointMask → + // written into compositeLivingAnimations (separate map). Vanilla IDLE + // still plays on base body, overlay masks specified joints only. + // Use case : cuffs posing only the arms while rest stays vanilla. + // No WARN — the parser accepts IDLE bindings without any log noise. ResourceLocation animId = tryParseAnimationRL( entry.getValue(), "living_motions['" + motionName + "']", diff --git a/src/main/java/com/tiedup/remake/v2/client/ClientRigEquipmentHandler.java b/src/main/java/com/tiedup/remake/v2/client/ClientRigEquipmentHandler.java index bbaa14b..79f899c 100644 --- a/src/main/java/com/tiedup/remake/v2/client/ClientRigEquipmentHandler.java +++ b/src/main/java/com/tiedup/remake/v2/client/ClientRigEquipmentHandler.java @@ -85,11 +85,24 @@ import com.tiedup.remake.v2.bondage.datadriven.DataDrivenItemRegistry; * *

    Préconditions

    *
      - *
    • Option B modder convention : les items bondage JSON NE bindent - * PAS {@link com.tiedup.remake.rig.anim.LivingMotions#IDLE}. Le default - * EF IDLE re-injecté par {@code resetLivingAnimations} reste donc - * visible. Laisser un item binder IDLE est toléré (l'anim custom - * écrase) mais c'est un code smell — voir docs plan P3.
    • + *
    • IDLE bindings are first-class (user decision 2026-04-24) : les + * items bondage PEUVENT binder + * {@link com.tiedup.remake.rig.anim.LivingMotions#IDLE}. EF supporte + * nativement deux paths via sa structure 2-map : + *
        + *
      • BASE layer override — anim {@code LayerType=BASE_LAYER} + * écrit dans {@code livingAnimations}, écrase la vanilla IDLE + * re-injectée par {@code resetLivingAnimations} (last-put-wins). + * Usage : armbinder forçant pose "bras derrière dos" totale.
      • + *
      • COMPOSITE overlay — anim {@code LayerType=COMPOSITE_LAYER} + * + {@code JointMask} écrit dans {@code compositeLivingAnimations} + * (map séparée). Vanilla IDLE joue sur le corps, overlay écrase + * seulement les joints masqués. Usage : cuffs bras seulement.
      • + *
      + * Aucune race condition — EF compose les 2 maps via + * {@code ClientAnimator.getPose}. TiedUp fournira des IDLE defaults + * pour ses items core (armbinder, collar, cuffs, shackles) une fois + * les assets Blender authorés.
    • *
    • Appelé depuis le client main thread uniquement. L'animator * n'est pas thread-safe.
    • *
    • Le package {@code v2.client} est strictement client-only malgré diff --git a/src/test/java/com/tiedup/remake/v2/bondage/datadriven/DataDrivenItemParserAnimationsTest.java b/src/test/java/com/tiedup/remake/v2/bondage/datadriven/DataDrivenItemParserAnimationsTest.java index 9eb9f6c..d008eb4 100644 --- a/src/test/java/com/tiedup/remake/v2/bondage/datadriven/DataDrivenItemParserAnimationsTest.java +++ b/src/test/java/com/tiedup/remake/v2/bondage/datadriven/DataDrivenItemParserAnimationsTest.java @@ -169,22 +169,24 @@ class DataDrivenItemParserAnimationsTest { assertNotNull(result.livingMotions().get(TiedUpLivingMotions.STRUGGLE_BOUND)); } - // ========== P3 Wave α : Option B convention warn on IDLE ========== + // ========== IDLE bindings are first-class (user decision 2026-04-24) ========== /** - * Option B convention (design BONDAGE_ANIMATION_DESIGN.md §5.1) : les modders - * ne devraient PAS binder {@link LivingMotions#IDLE} car - * {@code ClientAnimator.resetLivingAnimations()} re-injecte le default EF - * IDLE post-rebuild, et le binding custom l'écrase silencieusement — comportement - * fonctionnel mais non-idiomatique. Le parser log un WARN explicite pour pointer - * le modder vers la convention, mais accepte le binding quand même (rétrocompat). - * - *

      Ce test verrouille le comportement "accepte + ne throw pas" — la présence - * du WARN log n'est pas directement vérifiable sans log capture, mais le - * binding doit être bel et bien présent dans le résultat.

      + * IDLE bindings sont supportés sans warning (user decision 2026-04-24). EF a deux + * maps séparées dans {@code ClientAnimator} : + *
        + *
      • {@code livingAnimations} (BASE layer) — un binding IDLE + * {@code LayerType=BASE_LAYER} écrase le default EF injecté par + * {@code resetLivingAnimations()} via last-put-wins.
      • + *
      • {@code compositeLivingAnimations} (COMPOSITE layers, JointMask) — un + * binding IDLE {@code LayerType=COMPOSITE_LAYER} va dans cette map séparée, + * composition automatique avec vanilla IDLE sur les joints non-masqués.
      • + *
      + * Les 2 paths fonctionnent nativement. Le parser accepte le binding IDLE + * sans log warn. */ @Test - void parseAnimations_IDLEBinding_logsWarnButAccepts() { + void parseAnimations_IDLEBinding_isAcceptedSilently() { String jsonStr = """ { "animations": { @@ -199,13 +201,52 @@ class DataDrivenItemParserAnimationsTest { DataDrivenItemParser.parseAnimationBindings(json(jsonStr), ITEM_ID); assertNotNull(result, - "IDLE binding est tolere (non-idiomatique mais fonctionnel) => result non-null"); + "IDLE binding est accepte (first-class) => result non-null"); assertEquals(1, result.livingMotions().size(), - "Le binding IDLE est accepte apres le WARN de convention"); + "Le binding IDLE est present dans la map livingMotions"); assertEquals( ResourceLocation.fromNamespaceAndPath("tiedup", "arms_cuffed_idle"), result.livingMotions().get(LivingMotions.IDLE), - "Le binding IDLE est bien present dans le resultat malgre le WARN" + "Le binding IDLE resout vers la ResourceLocation attendue" + ); + } + + /** + * Sanity check : un binding IDLE aux côtés d'autres motions (WALK, SNEAK, + * STRUGGLE_BOUND) est parsé sans différence — IDLE est une motion comme les + * autres du point de vue du parser. Aucun traitement spécial. + */ + @Test + void parseAnimations_IDLEBinding_coexistsWithOtherMotions() { + String jsonStr = """ + { + "animations": { + "living_motions": { + "IDLE": "tiedup:arms_cuffed_idle", + "WALK": "tiedup:arms_cuffed_walk", + "SNEAK": "tiedup:arms_cuffed_sneak", + "STRUGGLE_BOUND": "tiedup:arms_cuffed_struggle" + }, + "on_equip": "tiedup:cuffs_equip_oneshot" + } + } + """; + + AnimationBindings result = + DataDrivenItemParser.parseAnimationBindings(json(jsonStr), ITEM_ID); + + assertNotNull(result); + assertEquals(4, result.livingMotions().size(), + "4 motions parsees (IDLE + WALK + SNEAK + STRUGGLE_BOUND)"); + assertTrue(result.livingMotions().containsKey(LivingMotions.IDLE), + "Le binding IDLE est present"); + assertEquals( + ResourceLocation.fromNamespaceAndPath("tiedup", "arms_cuffed_idle"), + result.livingMotions().get(LivingMotions.IDLE) + ); + assertEquals( + ResourceLocation.fromNamespaceAndPath("tiedup", "cuffs_equip_oneshot"), + result.onEquip() ); }