Commit Graph

184 Commits

Author SHA1 Message Date
notevil
86d35c4b5d D2 Animation events data-driven : actions registry + JSON codecs
Biggest artist unlock Phase 3 — modders can now trigger actions at
animation begin/end/time-frame from JSON datapack, zero Java needed.

Infrastructure :
- AnimationAction interface + dispatch codec via 'type' field
- AnimationActionRegistry with 4 core actions registered at init
- SerializedEvent records (simple/time/period) with bidirectional
  adapters to runtime SimpleEvent/InTimeEvent/InPeriodEvent
- ON_BEGIN_EVENTS / ON_END_EVENTS / TICK_EVENTS now serializable
  via name+codec (previously threw IllegalStateException)

4 core actions :
- play_sound (sound, volume, pitch, category)
- spawn_particle (particle, at joint, count, speed, offset_xyz)
- apply_effect (effect, duration_ticks, amplifier, ambient, show_*)
- damage_entity (amount, source: 15 vanilla source whitelist)

Out of scope for this commit (follow-up) :
- set_animation_variable (coupled with D1 properties)
- swap_item_visibility (needs render layer integration)
- Conditional actions ('when': ...)
- Joint-world-position resolution for spawn_particle 'at' field
  (needs partialTick plumb through AnimationAction.execute)
2026-04-24 21:20:31 +02:00
notevil
a7a1c774f7 D6 Custom armatures via datapack
Modders can now define custom armatures (quadruped, centaur, neko, etc.)
via data/<ns>/tiedup/armatures/*.json — zero Java code required.

Format :
- root_joint : name of root joint
- joints : map<name, JointDefinition> with id, parent, translation,
  rotation quaternion (xyzw), children array

Validation enforces :
- unique joint IDs, contiguous from 0
- all children/parent references exist in the map
- DAG structure (no cycles, no duplicate reachability)
- bidirectional parent/child coherence (A.children lists B <=> B.parent == A)
- max 128 joints (MAX_JOINTS limit)
- exactly one root (the declared root_joint has parent = null, all others
  have a non-null parent)

TiedUpArmatures.get() now delegates to ArmatureReloadListener for
unknown IDs — builtin BIPED remains hardcoded for performance +
VanillaModelTransformer compat. InstantiateInvoker.getArmature()
automatically resolves datapack armatures via the same path, no change
needed there.

Listener registered server-side via AddReloadListenerEvent and
client-side via RegisterClientReloadListenersEvent (same pattern as
LivingMotionReloadListener).

Tests : 21 new tests (13 for ArmatureDefinition validation + runtime
conversion, 8 for the reload listener + TiedUpArmatures delegation).
342 -> 363 GREEN.
2026-04-24 15:16:25 +02:00
notevil
c9d5271102 Wave B data-driven : LivingMotion + PoseType datapack extensions
D4 — LivingMotion custom via datapack :
- DataDrivenLivingMotion implements LivingMotion interface
- LivingMotionReloadListener scans data/<ns>/tiedup/living_motions/*.json
- Stable ordinals cross-reload via PERSISTENT_REGISTRY map
- Parser resolveMotionByName now falls back to registry lookup
- Modders can add custom LivingMotions purely via JSON

D11 — PoseType registry additive :
- PoseTypeRegistry maps canonical IDs to builtin enum values
- DataDrivenPoseType allows datapack extensions without touching
  the 17 V1 legacy call-sites (MixinCamera, DogPoseRenderHandler, etc.)
- PoseTypeReloadListener scans data/<ns>/tiedup/pose_types/*.json
- Builtin enum semantics preserved — modder custom types coexist

Artist impact : new LivingMotions + pose types addable without any
Java code. Phase 3 pipeline fully consumes both paths.
2026-04-24 14:42:58 +02:00
notevil
76587c0393 Wave A data-driven : JSON-serializable properties + equip_sound + metadata
Unlocks 3 artist-freedom quick wins :

1. ClientAnimationProperties.LAYER_TYPE / PRIORITY / JOINT_MASK now
   serializable via name + Codec. Modders can override these in their
   anim JSON 'properties' block (previously threw IllegalStateException).

2. AnimationBindings.equipSound / unequipSound — per-item JSON override
   for on_equip/on_unequip audio feedback. ClientRigEquipmentHandler
   resolves via ForgeRegistries.SOUND_EVENTS, falls back to vanilla
   ARMOR_EQUIP_LEATHER if unknown.

3. AnimationBindings.restraintLevel / tooltipOverride — metadata fields
   for UX consumers (HUD severity indicator, tooltip customization).
   Parsed but not yet rendered — available for Phase 4 features without
   schema change.

All additive. Existing items without these fields work unchanged.
2026-04-24 14:02:02 +02:00
notevil
ed0fb49792 Full data-driven : migrate CONTEXT_STAND_IDLE to datapack path
Remove the last Java-hardcoded StaticAnimation registration. context_stand_idle.json
now has the EF-native 'constructor' block so AnimationManager picks it up at
datapack reload, same as the 5 Phase 3 placeholders.

Consumers (PlayerPatch, LivingEntityPatch, RigAnimationTickHandler, TiedUpMod,
tests) refactored to lookup via resolveWithFallback(CONTEXT_STAND_IDLE_ID)
instead of direct static field access. resolveWithFallback handles the pre-
reload window by returning EMPTY_ANIMATION — no more null-checks in consumers.

TiedUpAnimationRegistry keeps only :
- CONTEXT_STAND_IDLE_ID constant (canonical ID)
- resolveWithFallback() helper
- WARNED_MISSING_ANIMS dedup + reset hook

Design goal : zero Java code required for any modder to add bondage anims.
Drop a JSON in assets/<modid>/animmodels/animations/, reference it from an
item JSON binding, /reload, see it fire.
2026-04-24 13:27:45 +02:00
notevil
a0b6ac5b04 Implement datapack armature registry — enable auto-register of anim JSONs
Replace InstantiateInvoker.getArmature() throw with real lookup in
TiedUpArmatures registry. This unblocks the datapack auto-register
path for anim JSONs placed in assets/tiedup/animmodels/animations/.

5 Phase 3 placeholder JSONs updated with EF-native 'constructor' block
— they now auto-register via AnimationManager.readResourcepackAnimation
without any Java-side hardcoding.

Data-driven pipeline now fully working end-to-end : modder drops a
JSON in their datapack's animmodels folder, it's automatically picked
up at reload, and referenced via resolveWithFallback() from item
bindings. No recompile needed for new items/anims.
2026-04-24 13:01:30 +02:00
notevil
efba00d005 Bondage test items : placeholder assets for Phase 3 pipeline validation
Adds minimum viable assets to exercise the Phase 3 anim pipeline
in-game :

- 5 placeholder anim JSON files in assets/tiedup/animmodels/animations/
  (identity keyframes matching context_stand_idle template) :
  armbinder_idle, armbinder_walk, armbinder_struggle,
  armbinder_equip_oneshot, classic_collar_idle
- armbinder.json enriched with 'animations' block binding IDLE + WALK
  + WALK_BOUND + STRUGGLE_BOUND living_motions + on_equip oneshot
- classic_collar.json enriched with IDLE binding for multi-item
  composition test

Purpose : validate end-to-end that the data-driven pipeline fires
correctly (rebuild, motion switch, oneshot sound, rehydrate) via F6
debug overlay. Visual pose does not change (identity anims) — body
keyframe authoring remains blocked on Blender artist assets.

Runtime note : the placeholder JSONs lack the EF 'constructor' block,
so readResourcepackAnimation does not register them in
AnimationManager.animationByName. resolveWithFallback will return
EMPTY_ANIMATION with a dedup WARN per missing ID — this exercises
the binding resolution path without needing a real StaticAnimation
(the map livingAnimations still receives the accessor, F6 overlay
still reports bindings:N). Real registration will land when either
InstantiateInvoker.getArmature() is implemented (Phase 2 armature
registry) or a dedicated AnimationRegistryEvent handler is added.

Gameday verification steps documented in docs/plans/rig/GAMEDAY_CHECKLIST.md
(gitignored, local only).
2026-04-24 11:57:15 +02:00
notevil
dd30a8d4f9 P3 cross-check fixes : composite motion + dead code
HIGH fix : currentCompositeMotion was initialized to IDLE and never
written — any COMPOSITE_LAYER overlay bound to a non-IDLE motion
would never fire (e.g. cuffs_arms_idle overlay on WALK_BOUND). Set
composite = currentLivingMotion in updateMotion, matching EF pattern
AbstractClientPlayerPatch:129-141.

MEDIUM cleanup : triggerOneshot<T> was 85% duplicate of triggerOneshotById
and had 0 prod callers (tests only). Removed + migrated 6 tests to
triggerOneshotById.

LOW doc fix : ExtendableEnumManager.synchronized javadoc over-sold the
risk as 'BUG-001 review fix' — it's actually conservative defense
against a theoretical cross-classloader race. EF upstream runs without
sync since 2020. Kept synchronized (no cost, no harm) but documented
honestly.
2026-04-24 10:48:57 +02:00
notevil
4648107ebe Revert Option B — IDLE bindings are first-class
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.
2026-04-24 03:46:12 +02:00
notevil
e969131ad2 P3-12 review fixes : reorder preconditions + document 400ms decision
HIGH BUG-001 + MEDIUM RISK-001 : spam timestamp was burned BEFORE
preconditions were validated. Null registryName meant server played
but clients got no broadcast (state desync). Null animator meant
400ms lockout for an entity that never played. Reorder : validate
registryName + animator FIRST, record spam only after preconditions
pass, then server play + broadcast.

MEDIUM RISK-002 : document 400ms global guard as INTENTIONAL design
for cinematic one-shots. Edge case hit+fall-damage same-tick drops
the 2nd — acceptable trade-off. Per-category rate limiting is Phase 4
if gameplay needs it.

LOW SMELL-001 : merge duplicate javadoc on LAST_SYNC_PLAY_BY_ENTITY.
2026-04-24 03:10:23 +02:00
notevil
d39a9d5ebc P3-12 : PacketPlayRigAnim handler réel + LivingEntityPatch.playAnimationSync
Client handler : resolve entity via Minecraft.getInstance().level,
cast to LivingEntity, lookup LivingEntityPatch via TiedUpCapabilities,
call animator.playAnimation avec l'accessor résolu. Null-cascade guards
(level / entity / patch / animator) + debug logs. Logic pure extraite en
resolveAndPlay(level, id, animId, transition, patchLookup) — testable
sans bootstrap MC grâce au patchLookup injectable (évite
TiedUpCapabilities.<clinit> qui cascade sur Registries).

Server-side playAnimationSync : local play sur l'animator (ServerAnimator
track l'état pour StateSpectrum/EntityState même sans rendu visuel) +
broadcast via PacketDistributor TRACKING_ENTITY. Anti-spam guard 400ms
par entity pour prévenir le master-click-spam flood (hit/slap). Cleanup
automatique via WeakHashMap (weak ref sur l'entity → auto-removed au GC
post-despawn, pas de leak sur long-running server).

priorityOrdinal reste informationnel côté client (ClientAnimator.playAnimation
n'accepte pas de priority — intrinsèque au StaticAnimation JSON via
ClientAnimationProperties.PRIORITY). Encodé dans le packet pour future
use / logging.

Débloque les 3 triggers non-fonctionnels : NPC capture cinematic,
hit/slap feedback, unconscious animation.

Tests +7 (277 → 284 GREEN) :
- PacketPlayRigAnimHandlerTest : nullLevel + signature reachability
  (les autres branches requièrent mock ClientLevel/LivingEntity qui
  triggers Bootstrap — gameday-only, documenté)
- LivingEntityPatchSpamGuardTest : first pass, within-window drop,
  boundary 400ms exact pass, cross-entity isolation, constant sanity
2026-04-24 02:44:43 +02:00
notevil
7281548a6a P3 Wave α hardening : snapshot consistency + parse warn + reload hook
HIGH RISK-001 : snapshot update moved between reset+applyDefinitions
and equip oneshots. If an equip oneshot throws, animator state matches
snapshot (currentKeys already applied). Prevents next-tick desync.

MEDIUM Option B warn : parser now logs WARN if modder binds IDLE.
Points to BONDAGE_ANIMATION_DESIGN §5.1 convention. Binding still
accepted (functional), just non-idiomatic.

MEDIUM WARNED_MISSING_ANIMS reload reset : new
TiedUpRigRegistryReloadListener wires resetWarnedMissing() on
AddReloadListenerEvent + RegisterClientReloadListenersEvent. Prevents
unbounded set growth in long-running sessions with datapack swaps.

LOW naming : unified MOD_ID in Wave α files (BondageRehydrateListener
switched from TiedUpRigConstants.MODID to TiedUpMod.MOD_ID for
consistency with sibling BondageEquipmentChangeListener ; both
constants hold the literal "tiedup" so no behavioral change).
2026-04-24 02:16:28 +02:00
notevil
5428f13f98 P3-07 review fixes : key-based diff + first-rebuild guard
CRITICAL BUG-001 reviewer : identity-based diff triggered sound 2x per
sync because V2BondageEquipment.deserializeNBT regenerates ItemStack
instances. 19 call-sites (respawn, dialogue, lock toggle, whip) do full
resync → every interaction = sound cacophony. Switch LAST_EQUIPPED from
Set<ItemStack> (identity) to Map<BodyRegionV2, ResourceLocation> keyed
by (region, itemId). Same region+item = no change, ignored.

HIGH RISK-001 reviewer : first rebuild per player had LAST_EQUIPPED
empty → all items detected as newly equipped → sound spam at login
(5 items = 5 sounds). Add FIRST_REBUILD_DONE WeakHashMap flag to
skip oneshots+sounds on very first rebuild per player.

MEDIUM SMELL-001/002 : javadoc of diffBy removed (assumed identity
sharing which never happens). Padlock NBT patch detection promise
removed — would require NBT hash in snapshot key (Phase 4).

Tests refactored : key-based (region, itemId) semantics.
2026-04-24 01:13:00 +02:00
notevil
e15ab7b831 P3-19 review polish : drop redundant distinct + reorder keybind guard
MEDIUM SMELL-01 reviewer : V2EquipmentHelper.getAllEquipped already
identity-dedupes (IdentityHashMap upstream in V2BondageEquipment:84).
The extra .mapToInt(identityHashCode).distinct() was dead defensive
code. Simplified to direct .count().

MEDIUM SMELL-02 reviewer : keybind guard 'mc.player == null || mc.level
== null' fired BEFORE RIG_DEBUG_KEY consumeClick loop. Main menu clicks
were queued and flushed on world-join (phantom toggle). Move toggle
consumption BEFORE the guard — it's a client-side boolean flip, needs
no world context.

LOW RISK-01 skipped : pin test on Layer.toString() format would require
either registry init (TiedUpRigRegistry.EMPTY_ANIMATION in AnimationPlayer
ctor) or Mockito ; test env doesn't currently init the RIG registry and
layerPriorityName() already has a defensive "?" fallback. Gameday check
is a tighter feedback loop than a flaky unit test here.
2026-04-24 01:05:33 +02:00
notevil
cae3572488 P3-20 : respawn/dim-change rehydrate (UX P0)
Wire ClientRigEquipmentHandler.rebuildBondageAnimations on :
- ClientPlayerNetworkEvent.LoggingIn (client connects)
- EntityJoinLevelEvent (spawn + dim change, filtered to LocalPlayer)

PlayerRespawnEvent path already covered server-side via
PlayerStateEventHandler.onPlayerRespawn -> V2EquipmentHelper.sync ->
PacketSyncV2Equipment -> rebuild (hook P3-06). This listener fills the
client-only gaps.

Without this, a player who logs in with bondage items equipped (or
teleports cross-dimension) sees their livingAnimations map empty until
the next V2 capability sync or armor slot change arrives. Visible
incoherence : item still equipped, bondage anim dropped, player plays
vanilla WALK. Demo-killing regression.

Handlers are idempotent and fire 2-3x at startup (login + spawn + first
chunk load) — rebuildBondageAnimations is designed for that :
resetLivingAnimations + re-apply current bindings, no state drift.

Pure filter extracted to shouldRehydrate(entity, level, ePred, lPred)
generic overload — mirrors the isBondageItem pattern in the sibling
BondageEquipmentChangeListener. Lets tests bypass the MC bootstrap
crash on Mockito.mock(LocalPlayer.class).

Removed the TODO(P3-20) block in BondageEquipmentChangeListener that
pointed to this task.

5 tests, all paths green (shouldRehydrate short-circuits, production
predicate wiring + null-safety).
2026-04-24 00:33:35 +02:00
notevil
de691e24ec P3-19 : RIG debug overlay (F6 toggle)
Scope reduced to overlay only — E2E integration test deferred.

Overlay displays current living motion + composite motion, equipped bondage
count, livingAnimations bindings count, active layers w/ priority + anim.
Critical for dev-visible feedback when placeholder identity anims don't
change the visual rig but the pipeline is running correctly.

Keybind : F6 (configurable in Controls menu, category TiedUp!). F6 chosen
because unused by vanilla and preserves F3 for debug screen.

No new accessor needed on Animator — getLivingAnimations() already exists
(returns ImmutableMap.copyOf, safe for debug read). Layer priority reading
falls back on Layer.toString() parsing for composite layers where the
priority field is protected ; base layer uses the public getBaseLayerPriority().

Tests : 6 pure unit tests (toggle flag, null-player branch, motionName on
vanilla+tiedup enums+null). Real render path is MC-runtime-bound and
validated gameday only.
2026-04-24 00:31:26 +02:00
notevil
a2bcfe2dda P3-07 : on_equip/on_unequip oneshot triggers + audio feedback
Diff detection tick-to-tick via WeakHashMap<Player, Set<ItemStack>>
snapshot captured at start of rebuildBondageAnimations. Identity-based
set (IdentityHashMap) respects convention V2 "un stack = une
occurrence" : deux stacks content-equals mais refs distinctes restent
distincts.

Unequip oneshots fire BEFORE rebuild (play sur l'old state,
livingAnimations encore config precedente), equip oneshots AFTER (play
sur new state, bindings de l'item pushes dans la map). Vanilla
SoundEvents.ARMOR_EQUIP_LEATHER fallback pour MVP — future per-item
custom via field equip_sound dans DataDrivenItemDefinition.

Helpers pure + generiques <T> :
  - uniqueByIdentity(Iterable<T>) : dedup identity + skip nulls
  - diffBy(Set<T>, Set<T>) : a \ b par ==, ordre stable
  - triggerOneshot(...) : no-op si def/bindings/oneshotId null ; sinon
    resolve via animResolver + play avec transition 0.15s constant

Decision actee : priority param non forwarde a playAnimation. La
priority vient de StaticAnimation.getPriority() intrinseque (property
JSON du fichier anim). PacketPlayRigAnim.priorityOrdinal (P3-11)
reste informational / loggable, priority override = TODO Phase 4.

Sound defaultEquipSound() resolu lazy via method (pas static final
field) — SoundEvents.<clinit> exige MC Bootstrap, un field eager
bloquait tous les unit tests de la classe (ExceptionInInitializerError).

Tests : +11 unit tests (28 total dans le fichier handler) :
  - diffBy empty / identity / a==b
  - uniqueByIdentity dedup + nulls skipped
  - triggerOneshot no-def / null-bindings / null-onEquip / happy
    onEquip / happy onUnequip / null-stack

Suite complete 257 GREEN, 0 failed.
2026-04-24 00:28:12 +02:00
notevil
13b0f8f590 P3-06 : wire ClientRigEquipmentHandler from 2 event sources
Trigger rebuildBondageAnimations from :
1. PacketSyncV2Equipment.handleOnClient -- after deserializeNBT capability
2. LivingEquipmentChangeEvent -- filtered to bondage items, client only

Third source (LoggingIn / dimension change) scoped to P3-20 with TODO.

body : idempotent si double-fire, dedup via isBondageRelevant filter.
Caveat Forge doc LivingEquipmentChangeEvent = server-side only : le
handler est neanmoins enregistre @Dist.CLIENT comme filet defensif
pour les edge cases creative/admin. Le path canonique reste
PacketSyncV2Equipment qui couvre 100%% des changements V2 via la
capability dediee (V2 n'utilise pas les slots armor vanilla).

isBondageItem pattern : DataDrivenItemRegistry.get(stack) != null
(coherent avec ClientRigEquipmentHandler.extractSortedDefinitions).
Refactor generic <T> pour testabilite sans MC bootstrap (Mockito
ItemStack crash la static init registries).
2026-04-23 22:41:44 +02:00
notevil
aebc7f3868 P3-08 : PlayerPatch.updateMotion override — route currentLivingMotion per state
Branche decision tree :
- vehicle furniture → POSE_FURNITURE_SEAT
- sleeping bound → POSE_SLEEP_BOUND (UX P0)
- dog pose → POSE_DOG
- struggling → STRUGGLE_BOUND
- falling bound → FALL_BOUND (UX P1)
- in water → SWIM vanilla
- walking/sneaking bound → WALK_BOUND / SNEAK_BOUND
- idle → LivingMotions.IDLE vanilla (Option B)

Pure function resolveMotion extracted for testability — 14 tests cover
every branch + priority ordering edge cases. Combined with P3-05 + P3-06,
TRUE first animation visible milestone reached.

Override lives in abstract PlayerPatch (not subclasses) — la logique lit
purement Player state, pas side-specific. ServerPlayerPatch /
ClientPlayerPatch / LocalPlayerPatch héritent.

considerInaction respecté via animator.getEntityState().inaction() —
aligné sur le pattern EF ClientAnimator.tick().
2026-04-23 22:24:38 +02:00
notevil
e37dad18aa 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.
2026-04-23 22:11:33 +02:00
notevil
9a31f21b55 P3-05 JALON : ClientRigEquipmentHandler.rebuildBondageAnimations
First visible bondage animation pipeline orchestrator. Consumes the
primitives added in P3-01..P3-04 + P3-09 to rebuild the livingAnimations
map of a player based on currently equipped bondage items.

Design decisions :
- IdentityHashMap dedup (armbinder covers N regions -> 1 unique stack).
  Defensive even though the capability already dedupes (so we don't
  depend on an upstream invariant that might regress).
- Sort by posePriority ASC so highest-priority iterates last -> wins
  conflicts (Map.put last-write-wins semantics in livingAnimations).
- Option B : JSON items don't bind IDLE, let EF defaults flow through
  after resetLivingAnimations() which re-pushes default motions.
- null-check on animations() : 99% of V2 legacy items lack the JSON
  block and return null from the parser, must be skipped silently.
- @OnlyIn(Dist.CLIENT) at class level : ClientAnimator is client-only,
  server class-loader must never touch this handler.
- Extracted testable methods (extractSortedDefinitions + applyDefinitions)
  with functional callbacks (Runnable + LivingAnimationAdder + Function
  resolvers). Generic <T> on extractSortedDefinitions lets tests pass
  Object dummies without MC ItemStack bootstrap.

Tests (15) covering :
- rebuildBondageAnimations null-safety
- extractSortedDefinitions : empty input, null resolver result, identity
  dedup (multi-region), ASC priority sort, null entries skipped
- applyDefinitions : reset-only on empty, null/empty animations skipped,
  multi-binding single item, two-item conflict last-write-wins, reset
  ordering before any adder, end-to-end multi-region dedup, animResolver
  contract (no internal dereference)

Note : this commit provides the HANDLER. The first visible bondage
animation still requires P3-06 (hook it to PacketSyncV2Equipment +
LivingEquipmentChangeEvent) and P3-08 (updateMotion state machine) to
fully light up. Jalon = pipeline viable, not yet wired.

212 tests GREEN (197 baseline + 15 new).
2026-04-23 17:16:19 +02:00
notevil
921a028a53 P3-03 + P3-09 review fixes : tighten RL check + correct thread-safety doc
HIGH P3-03 : strict RL check laissait passer ':path', 'mod:', ':'
silencieusement (tryParse les accepte en 1.20.1). Fix combo check
(isEmpty || colonIdx<=0 || colonIdx>=length-1) rejette tous les cas
edge. 3 nouveaux tests couvrent les malformed variants.

MEDIUM P3-09 : javadoc de BondageStateHelpers affirmait ConcurrentHashMap
alors que le backing V2EquipmentHelper utilise EnumMap via pattern MC
main-thread + packet sync. Correction doc pour refléter la réalité.
2026-04-23 17:03:42 +02:00
notevil
744aef63b5 P3-03 : parse DataDrivenItem JSON animations block
Wire le parser JSON pour le bloc "animations" (living_motions + on_equip
+ on_unequip). Résolution motion cross-enum (LivingMotions vanilla EF +
TiedUpLivingMotions custom). Fuzzy-match Levenshtein pour suggestion
en cas de typo modder (Did you mean 'IDLE'?).

Remplace le null hardcodé à DataDrivenItemParser:352 (P3-02 TODO).

body : tolerance malformed RL, non-string values, unknown motions.
2026-04-23 16:01:30 +02:00
notevil
c1ecfd75c7 P3-09 : add BondageStateHelpers (isBound, isDogPose, isStruggling, etc.)
5 static helpers pour que PlayerPatch.updateMotion (P3-08) puisse
router vers les TiedUpLivingMotions appropriées.

Null-safe partout. Pas d'état interne, pas de cache : toute fraicheur
est déléguée aux sources backing existantes (capability V2 /
PlayerBindState).

API backing :
- isBound            -> V2EquipmentHelper.getAllEquipped + instanceof IV2BondageItem
- isDogPoseActive    -> PlayerBindState.getEquipment(ARMS) + PoseTypeHelper.getPoseType == DOG
                        (convention partagée par MixinCamera, DogPoseRenderHandler,
                        LeashProxyClientHandler, FirstPersonMittensRenderer)
- isStrugglingClient -> PlayerBindState.isStruggling() (StruggleSnapshot volatile,
                        sync client par PacketSyncStruggleState)
- isSleepingBound    -> player.isSleeping() && isBound (UX P0 POSE_SLEEP_BOUND)
- isFallingBound     -> !onGround() && deltaMovement.y < 0 && isBound (UX P1 FALL_BOUND)

Les helpers fonctionnent aussi server-side par construction (même APIs),
le naming "Client" reflète l'intention d'appel (patch client Epic Fight)
sans side-gating strict.

Tests : 6 null-safety tests, 194/194 GREEN (suite full). Les branches
happy-path nécessitent MC bootstrap (Player réel + capability attachée)
donc couvertes implicitement par les tests d'intégration / runClient.
2026-04-23 15:56:51 +02:00
notevil
639e9e94f7 P3-11 review followup : isolate client-only handler behind @OnlyIn
Prepare P3-12 consumption of Minecraft.getInstance() — refactor
handleOnClient() to delegate into a private ClientHandler inner class
annotated @OnlyIn(Dist.CLIENT). Server-side class-loader will not
resolve Minecraft.* references since the inner class isn't touched.

Direction guard inchangé. Import NetworkDirection passé top-level.

Issue : review transversale Phase 3 (architect agent), recommendation #3.
2026-04-23 15:49:46 +02:00
notevil
ab93dc80be P3-11 review fixes : direction guard + warn on priority out-of-range
HIGH RISK-001 : ModNetwork.reg() helper n enforce pas NetworkDirection,
guard défensif ajouté dans handleOnClient (reject non-S→C + WARN).
Fix systémique du helper tracé en backlog séparé.

LOW SMELL-001 + SMELL-002 : priority() fallback silencieux + idx<0 dead
après masking. WARN log ajouté sur out-of-range. Condition nettoyée.
2026-04-23 15:28:28 +02:00
notevil
2a4ec170ef P3-11 : add PacketPlayRigAnim + ModNetwork registration (handler stub)
Record packet S->C pour animations cinematic one-shot (capture NPC, hit
stun, death). Carries entityId, animId (ResourceLocation), transitionTime
(float), et priorityOrdinal (byte = Layer.Priority.ordinal()).

Encode via writeVarInt/writeResourceLocation/writeFloat/writeByte ; decode
symétrique. Factory PacketPlayRigAnim.of() pour build lisible avec
Layer.Priority, et getter priority() avec fallback LOWEST si ordinal
hors-range (masking & 0xFF pour gérer byte négatif).

Handler client = stub qui LOGGER.debug + setPacketHandled. Le corps
complet (resolve entity, lookup LivingEntityPatch, animator.playAnimation)
viendra en P3-12 avec la méthode serveur playAnimationSync().

Registration ajoutée en fin de ModNetwork.register() — ID sequential 75
(76 packets total).

7 tests unitaires PacketPlayRigAnimTest : roundtrip nominal, entityId
boundary (0/MAX/MIN), transitionTime zero+negative (pas de validation
silencieuse), loop sur les 5 Layer.Priority, fallback LOWEST sur ordinal
malformé (99, -1, PRIORITY_COUNT), ResourceLocation avec underscores et
slashes, et sanity check equals.

Rig tests : 65 -> 72 GREEN. Full suite : 162 GREEN, 0 failure.
2026-04-23 15:11:08 +02:00
notevil
5d108f51b4 P3-02 : add AnimationBindings record + DataDrivenItemDefinition.animations field
New record AnimationBindings (v2/bondage/datadriven/) carries the JSON
item -> rig animation bindings that P3-03 (parser) will populate and
P3-05 (ClientRigEquipmentHandler.rebuildBondageAnimations) will consume
to rebuild the player's livingAnimations map on equip/unequip.

Fields :
- livingMotions : Map<LivingMotion, ResourceLocation> (immutable, never null,
  Map.copyOf defensive copy in compact ctor, null input collapsed to emptyMap)
- onEquip / onUnequip : optional nullable one-shot triggers
- EMPTY constant + isEmpty() for the "no binding" fast path

DataDrivenItemDefinition gains a nullable "animations" field (null = vanilla
behavior, preserves backward compat for items without rig bindings).
DataDrivenItemParser call-site updated to pass null for now — wiring happens
in P3-03. Single call-site updated (parser only), no factories or tests
constructed the record directly.

7 unit tests cover construction paths, null tolerance, defensive map copy,
isEmpty semantics and the EMPTY constant. Full rig suite still 65 GREEN.
2026-04-23 13:22:25 +02:00
notevil
ddaa25b971 P3-01 review fixes : thread-safe assign() + enforce enum load order
BUG-001 reviewer: ExtendableEnumManager.assign() non thread-safe,
synchronize la méthode. Pas d impact perf (N×11 calls au bootstrap).

RISK-001 reviewer: load-order LivingMotions vanilla vs TiedUpLivingMotions
custom pouvait faire coller les ordinals si vanilla pas pre-loadé.
Force explicit LivingMotions.values() avant TiedUpLivingMotions.values()
dans commonSetup.

Test renommé pour refléter honnêtement ce qui est testé (vs load order
implicitement correct dans le test).
2026-04-23 13:16:47 +02:00
notevil
cef589aac1 P3-04 : add TiedUpAnimationRegistry.resolveWithFallback + EMPTY_ANIMATION stub
Nouveau helper statique consomme par ClientRigEquipmentHandler.rebuildBondageAnimations
(P3-05) et PacketPlayRigAnim.handleOnClient (P3-12) pour resoudre un anim ID avec
fallback safe si miss.

Design :
- Lookup delegue a AnimationManager.byKey(ResourceLocation) — l'API existante
  pour la resolution par registry name.
- Fallback = TiedUpRigRegistry.EMPTY_ANIMATION (singleton canonique) plutot
  qu'un stub empty_fallback separe. Les sites runtime (Layer#off, AnimationPlayer#isEmpty,
  LayerOffAnimation#getNextAnimation) comparent via == EMPTY_ANIMATION — retourner
  une autre instance provoquerait des false-negatives sur ces checks d'identite.
- Dedup WARN via ConcurrentHashMap.newKeySet() : un ID donne ne log qu'une fois
  par session, evite le spam si le miss vient d'un item data-driven appele tick
  apres tick. Pattern inspire de RigAnimationTickHandler.LOGGED_ERRORS.
- resetWarnedMissing() expose pour tests + runtime reload (F3+T datapack).
- Branche defensive : id=null swallow + log (cas pathologique caller).

4 tests unitaires :
- happy path (ID enregistre via AnimationManager.AnimationBuilder → assertSame)
- fallback safe (ID inconnu → EMPTY_ANIMATION, non-null)
- no-throw (ID inconnu + null swallow sans exception)
- dedup observable (reset puis re-call sur meme ID re-warn, sanity check fake
  accessor distinct de EMPTY_ANIMATION)

65 tests rig GREEN (57 baseline + 4 nouveaux + autres).
2026-04-23 13:04:31 +02:00
notevil
15e405f5b0 P3-01 : add TiedUpLivingMotions enum (11 motions + UX additions)
Nouvelle enum custom etendant LivingMotion — partage le meme ENUM_MANAGER
que LivingMotions (vanilla EF), ordinals assignes a la suite sans collision.

8 motions design RIG :
- POSE_DOG
- POSE_PET_BED_SIT
- POSE_PET_BED_SLEEP
- POSE_FURNITURE_SEAT
- POSE_KNEEL_BOUND
- STRUGGLE_BOUND
- WALK_BOUND
- SNEAK_BOUND

3 ajouts UX (P0/P1) :
- POSE_SLEEP_BOUND  — sleep avec restraints (P0)
- POSE_UNCONSCIOUS  — steady-state post-capture (P0)
- FALL_BOUND        — no flailing en chute (P1)

Class-load force dans TiedUpMod.commonSetup via values() — sans ca, les
ordinals ne sont pas assignes tant que l'enum n'est pas touche (init lazy
JLS). LivingMotions (vanilla) est class-loaded naturellement par les
patches rig, pas besoin de force.

Tests : 3 cas (11 entries, ordinals uniques intra-enum, pas de collision
avec LivingMotions apres class-load croise).
2026-04-23 13:02:44 +02:00
notevil
1fa291563c Audit-10 : add rig/ test coverage (37 new tests)
Added:
- TransformSheetTest (10 tests) : binary search getInterpolationInfo edge
  cases (empty, negative-wrap, exact boundary, multi-keyframe segment),
  maxFrameTime sentinel, copyAll/copy subrange
- PoseTest (10 tests) : interpolatePose merge + lerp correctness at 0/0.5/1,
  orElseEmpty fallback, load() all three LoadOperation modes, disableAllJoints
- LivingMotionIsSameTest (7 tests) : IDLE==INACTION symmetry, uniqueness of
  universalOrdinal across all LivingMotions values
- TimePairListTest (7 tests) : odd-arg rejection, empty list, inclusive begin /
  exclusive end boundary, multi-pair gap
- RigAnimationTickHandlerTest (2 tests) : resetLoggedErrors idempotency

Skipped (MC runtime dep):
- TiedUpCapabilityEventsTest : AttachCapabilitiesEvent + live ForgeEventBus
- EntityPatchProviderInvalidateTest : LazyOptional is a Forge runtime class
- LivingEntityPatch.onConstructed : requires real LivingEntity hierarchy
- RigAnimationTickHandler.tickPlayer/maybePlayIdle : require
  TiedUpCapabilities.getEntityPatch + ClientAnimator + LivingEntityPatch

Bug flagged (no fix) :
- TransformSheet.getFirstFrame() calls copy(0,2) without guarding size >= 2;
  a single-keyframe sheet would throw ArrayIndexOutOfBoundsException
2026-04-23 09:11:32 +02:00
notevil
05cc07a97d Audit-9 review : drop MovementAnimation from phase0 extract script
Reviewer flagged scripts/rig-extract-phase0.sh:49 as stale after Audit-9
deleted rig/anim/types/MovementAnimation.java. The script is one-shot
tooling, but keep it coherent with the actual fork.
2026-04-23 06:04:38 +02:00
notevil
d3a3b400aa Audit-9 : strip Vec2i + exceptions + HitEntityList + MovementAnimation, document datapack-surface keeps
Deletes (5 files, 132 lines) :
- rig/anim/types/MovementAnimation.java (zero callers outside self-decl)
- rig/exception/DatapackException.java (dead)
- rig/exception/ShaderParsingException.java (dead)
- rig/math/Vec2i.java (dead)
- rig/util/HitEntityList.java (only consumer was HIT_PRIORITY, retiré)

Modifs :
- rig/anim/property/AnimationProperty.java : retire la constante
  AttackPhaseProperty<Priority> HIT_PRIORITY et l'import HitEntityList.Priority.

Docs (3 Javadocs retenues pour surface datapack / Phase 3) :
- CapabilityItem : précise l'usage Phase 3 (data-driven item anims,
  ON_ITEM_CHANGE_EVENT + HumanoidMobPatch.modifyLivingMotionByCurrentItem).
- AnimationProperty.ON_ITEM_CHANGE_EVENT : documenté comme hook datapack
  pour réagir au changement d'item porté.
- MoveCoordFunctions : doc class-level clarifie que les 8 constantes
  MoveCoord* sont consommées par réflection (StaticFieldArgument) côté
  datapack EF tiers — ne pas purger individuellement.

Compile GREEN, 20/20 rig tests GREEN.
2026-04-23 06:02:36 +02:00
notevil
687b810a0e Audit-8 : strip anim core dead methods (-222 LOC)
TransformSheet (-53 LOC, 4 methods): getStartTransform,
getInterpolatedRotation, getCorrectedModelCoord, extendsZCoord.
Skipped: extend (live internal caller in transformToWorldCoordOriginAsDest),
getInterpolationInfo (live internal callers in getInterpolatedTranslation /
getInterpolatedTransform).

ClientAnimator (-44 LOC, 6 methods): getAllLayers, iterVisibleLayers,
isAiming, getOwner, getJumpAnimation, getPriorityFor. Also removed now-unused
java.util.Collection import. Skipped: getCompositeLivingMotion (live callers
in tick/forceResetBeforeAction/resetCompositeMotion), applyBindModifier (live
internal callers in getPose/getComposedLayerPoseBelow/recursion), compareMotion
(live caller in tick), compareCompositeMotion (live caller in tick), iterAllLayers
(live callers in setSoftPause/renderDebuggingInfoForAllLayers), getLivingMotion
(live internal callers in tick/forceResetBeforeAction).

StaticAnimation (-125 LOC, 17 methods): getFileHash instance, idBetween,
in(StaticAnimation[]), in(AnimationAccessor[]), setResourceLocation,
invalidate, isInvalid, removeProperty, addEvents (both overloads),
newTimePair, newConditionalTimePair, addState, removeState,
addConditionalState, addStateRemoveOld, addStateIfNotExist. Also removed
now-unused imports (Collection, Function, Stream).
Skipped @Override/contract methods: getState, getStatesMap, modifyPose,
doesHeadRotFollowEntityHead, getId, equals, getRegistryName, getCoord,
getAccessor (all @Override DynamicAnimation/Object — removing breaks
polymorphism contract). Skipped methods with live callers: getFileHash static
(called in ctor line 116), addProperty (15+ external callers in registry and
AnimationSubFileReader), getModifiedLinkState (3 callers in LinkAnimation),
getSubAnimations (callers in AnimationManager and 5 internal self-calls),
setAccessor (called by AnimationManager.apply line 352), loadAnimation
(called by kept getAnimationClip), createSimulationData (implements
InverseKinematicsProvider interface contract — hot-path risk skip).

20/20 rig tests GREEN.
2026-04-23 05:53:01 +02:00
notevil
06ec7c7c5f Audit-7 : strip ParseUtil + TiedUpRenderTypes dead utilities (-682 LOC)
ParseUtil.java (-232 LOC) :
  Removed 18 zero-caller helpers inherited from Epic Fight :
  toAttributeModifier, nullOrToString, nullOrApply, nvl,
  snakeToSpacedCamel, compareNullables, nullParam, getRegistryName,
  getOrSupply, isParsableAllowingMinus, isParsable, valueOfOmittingType,
  parseOrGet, mapEntryToPair, remove, convertToJsonObject,
  parseCharacterToNumber, parseTagOrThrow.
  Kept toVector3f/toVector3d (unused but reserved for forked callers
  type TrailInfo), toLowerCase, toUpperCase, getBytesSHA256Hash,
  enumValueOfOrNull, orElse, getOrDefaultTag, and the array helpers
  actually wired into JsonAssetLoader / SkinnedMesh / StaticMesh.
  Pruned imports : AttributeModifier, IForgeRegistry, TagParser, Pair,
  JsonOps, ByteTag, CommandSyntaxException, Collectors, Nullable, UUID,
  Function, ArrayList, Collection, Set, Map.

TiedUpRenderTypes.java (-450 LOC) :
  Removed 16 zero-caller render types :
  entityUIColor, entityUITexture, debugCollider, guiTriangle,
  entityAfterimageStencil/Translucent/White, itemAfterimageStencil/
  Translucent/White, blockHighlight, coloredGlintWorldRendertype
  (both overloads), freeUnusedWorldRenderTypes, clearWorldRenderTypes,
  addRenderType(String,ResourceLocation,RenderType), makeTriangulated.
  Also dropped the private fields feeding them : ENTITY_UI_COLORED,
  ENTITY_UI_TEXTURE, OBB, GUI_TRIANGLE, ENTITY_AFTERIMAGE_WHITE,
  ITEM_AFTERIMAGE_WHITE, ENTITY_PARTICLE, ITEM_PARTICLE,
  ENTITY_PARTICLE_STENCIL, ITEM_PARTICLE_STENCIL, BLOCK_HIGHLIGHT,
  WORLD_RENDERTYPES_COLORED_GLINT, plus the newly orphaned
  PARTICLE_SHADER / ShaderColorStateShard / MutableCompositeState
  (only ever used by coloredGlint).
  Kept getTriangulated + replaceTexture (SkinnedMesh / MeshPart / Mesh),
  plus their backing TRIANGULATED_OUTLINE / TRIANGULATED_RENDER_TYPES /
  TRIANGLED_RENDERTYPES_BY_NAME_TEXTURE infra.

debugQuads : DELETED. Only StaticAnimation.renderDebugging /
DynamicAnimation.renderDebugging reference the debug path, and both
were already stripped to empty stubs in Phase 2 ('RIG : debug render
des targets IK strippé. Pas d'IK en TiedUp').

overlayModel : DELETED. PatchedLivingEntityRenderer.getOverlayCoord
explicitly comments out the modifyOverlay path ('RIG Phase 2.5 :
EntityDecorations.modifyOverlay strippé — utilise les defaults
vanilla'), so OVERLAY_MODEL has no reachable caller.

Compile GREEN. 20/20 rig tests GREEN.
2026-04-23 05:38:00 +02:00
notevil
8530671a49 Audit-6 : strip dead math utilities (-300 LOC)
Confirmed-dead method cleanup in rig/math/ following AUDIT_2026-04-22
essentiality cross-check. All removals verified via grep across src/
showing zero live callers.

- MathUtils.java (-242 LOC) : bezierCurve (both overloads), rotWrap,
  lerpDegree, greatest/least (all 6 numeric overloads), translateStack,
  rotateStack, scaleStack (+ their OPEN_MATRIX_BUFFER field),
  lerpMojangVector, setQuaternion, mulQuaternion, getLeastAngleVector
  (idx variant kept, used by ClothSimulator), packColor, unpackColor,
  normalIntValue, wrapClamp, worldToScreenCoord. Unused imports
  (Vector4i, Vector4f, Axis, Camera, Vec2) stripped.

- OpenMatrix4f.java (-56 LOC net) : 3-arg add, mulAsOrigin, createScale,
  ofScale. mulAsOriginInverse (task-KEEP) inlined to no longer depend
  on mulAsOrigin.

- Vec3f.java (-2 LOC net) : toMojangVector, fromMojangVector.

ValueModifier.java NOT deleted despite task instruction : grep found
live references in rig/anim/property/AnimationProperty.java
(4 AttackPhaseProperty constants + import). Task explicitly says
"STOP and report" in this case. The 4 constants have no downstream
readers and could be purged in a follow-up, but that is outside this
task's scope.

20/20 rig tests GREEN (--rerun-tasks verified), ./gradlew compileJava
GREEN after each file group.
2026-04-23 05:26:34 +02:00
notevil
03c28e3332 Audit-3 : align H-04/H-05 signatures vs EF upstream
Deux signatures avaient dérivé du fork EF pendant Phase 0. Les réaligner
maintenant évite du frottement quand on re-porte des fixes EF upstream.

H-04 — ActionAnimation.correctRootJoint
  Avant : correctRootJoint(LinkAnimation linkAnimation, ...)
  Après : correctRootJoint(DynamicAnimation animation, ...)
  LinkAnimation extends DynamicAnimation → widening safe, le call-site
  dans LinkAnimation.modifyPose (ligne 118) passe toujours `this` qui
  type-matche DynamicAnimation sans édition.

H-05 — AttackAnimation.phases
  Avant : public final List<Phase> phases = Collections.emptyList();
  Après : public final Phase[] phases = new Phase[0];
  Le seul caller (JsonAssetLoader.java:477) est un for-each, donc
  array-compatible sans édition. Aucun .size()/.get()/.stream() nulle
  part. Imports java.util.{List,Collections} retirés.

Compile GREEN. 20/20 tests rig GREEN.
2026-04-23 05:03:35 +02:00
notevil
4152f9fc71 Audit-2 : strip EF extension chain + AnimationConfig dead fields (-334 LOC)
H-02 (EF extension chain DEAD) :
- Delete 5 unreferenced EF-derived files :
  - event/EntityPatchRegistryEvent (ModLoader event for third-party patch
    registration — no third-party EF mods to host).
  - event/RegisterResourceLayersEvent (extra renderer layers registration
    event — no consumers).
  - render/layer/LayerUtil (+ empty package) — sole LayerProvider interface
    only used by the deleted RegisterResourceLayersEvent.
  - render/item/RenderItemBase (+ empty package) — abstract weapon renderer
    stub, TiedUp does not host weapon trails.
  - asset/SelfAccessor — AssetAccessor variant never created.
- PatchedRenderersEvent : drop inner classes RegisterItemRenderer (tied to
  RenderItemBase) and Modify (never posted). Keep Add, used by
  TiedUpRenderEngine.
- JsonAssetLoader : remove loadArmature(ArmatureContructor) + its private
  getJoint helper. Armatures are loaded via the GLB path in Phase 1 — this
  EF JSON route has no callers.
- TiedUpRigRegistry : drop inner interface ArmatureContructor (EF-style
  typo preserved) now that loadArmature is gone.
- EntityPatchProvider : drop CUSTOM_CAPABILITIES map + putCustomEntityPatch
  + clearCustom. No third-party extension surface needed ; inline the
  provider lookup to CAPABILITIES only.

H-03 (TiedUpAnimationConfig dead fields) :
- Remove enableOriginalModel, enableAnimatedFirstPersonModel,
  enableCosmetics, enablePovAction, autoSwitchCamera, preferenceWork,
  combatPreferredItems, miningPreferredItems. None had callers — they were
  inherited from EF ClientConfig without a TiedUp consumer.
- Keep activateComputeShader — read at SkinnedMesh.java:249 to toggle the
  GPU skinning path.

Compile green, 20/20 rig tests still green.
2026-04-23 04:55:07 +02:00
notevil
647894508d Audit-1 review : fix stale '6 ticks' comment + track NONE_MASK null
Reviewer flagged two MEDIUM cosmetic items :
- SMELL-001 : TiedUpAnimationRegistry:97 comment still said '6 ticks' —
  updated to '0.15s = 3 ticks' to match the new constant.
- SMELL-002 : JointMaskReloadListener.getNoneMask() can still return
  null if a resource pack omits tiedup:none. Call sites tolerate null
  today; logged in PHASE0_DEGRADATIONS for a Phase 3 fallback JSON +
  warn log.
2026-04-23 04:46:22 +02:00
notevil
f4aae9adb7 Audit-1 : fix transition time + JointMaskReloadListener
C-01 : GENERAL_ANIMATION_TRANSITION_TIME was int 6 while all consumers
expect a float transitionTime in seconds — 6s gave inter-animation
transitions of 120 ticks. Change to float 0.15F (3 ticks).

C-02 : JointMaskReloadListener was never registered, leaving
AnimationSubFileReader callers at lines 170/182/184 to receive null
from getNoneMask()/getJointMaskEntry() with no data loaded. Register
it in V2ClientSetup at LOW priority so it fires after the GLB cache
clear and alongside the other bondage client reload listeners.

M-03 : ASSETS_NEEDED.md JSON example already corrected in the earlier
doc-keeper pass (Torso=7, Chest=8, etc.).

Drive-by: add logs/ to .gitignore — the runClient logs were
accidentally tracked.
2026-04-23 04:41:48 +02:00
notevil
f80dc68c0b Phase 2.8 review fixes : strip player fallback + backlog V3-REW-12-14 + QA edge cases
- BondageAnimationManager : strip total du path joueur, tous les call sites
  'player' retournent null/false avec WARN once-per-UUID. getOrCreateLayer
  court-circuite sur Player direct. Retire dead code factory/furniture
  (FACTORY_ID, FURNITURE_*, npcFurnitureLayers, getPlayerLayer, etc.).
  Javadoc init() reflete la semantique NPC-only.
- DogPoseHelper.applyHeadCompensationClamped : @Deprecated(since=2.8)
  pointant vers V3-REW-07 (dead apres retrait MixinPlayerModel).
- DogPoseRenderHandler.getAppliedRotationDelta + isDogPoseMoving :
  @Deprecated(since=2.8) meme raison.
- Docs (gitignored) : V3_REWORK_BACKLOG.md ajoute V3-REW-12/13/14 (pet bed
  body-lock, human chair yaw clamp, context layer sit/kneel/sneak), tableau
  recap 14 -> 17 items. PHASE2_QA.md ajoute sec 2.5 edge cases + corrige
  le grep pattern 4 -> >=4 lignes. PHASE0_DEGRADATIONS.md ajoute la section
  Phase 2.8 findings.

Compile GREEN. 20/20 tests rig GREEN. Net LOC src : -239 (strip dead code
+ guards player).

Call sites player no-op (attendu par design) :
- PacketSyncPetBedState.playAnimation -> V3-REW-12
- PacketPlayTestAnimation.playAnimation (debug) -> V3-REW-14
- FurnitureClientAnimator.playFurniture -> furniture seat rework V3
2026-04-23 01:10:02 +02:00
notevil
5a39fb0c1c Phase 2.8 : V2 player rendering cleanup + QA checklist doc
Cut wholesale du pipeline V2 player-anim suite au bascule RIG (pas d'opt-in,
pas de rollback — accepté par le mainteneur).

- rm MixinPlayerModel : le renderer RIG patched ne passe plus par
  PlayerModel.setupAnim, donc l'injection @TAIL devenait dead code.
  Les features dog pose head compensation seront ré-exprimées en
  StaticAnimation pose_dog.json (V3-REW-07).
- Strip tiedup.mixins.json : retiré client/MixinPlayerModel, restent
  MixinCamera + MixinLivingEntitySleeping.
- BondageAnimationManager.init() : retiré les 3 PlayerAnimationFactory
  registrations (context / item / furniture), le path joueur n'en
  dépend plus. Factory IDs conservés car les getPlayerLayer*() sont
  tolérantes au null retour via try/catch existants — et restent
  utilisées par le cache fallback remote. Les NPCs continuent
  d'utiliser cette classe via l'accès direct animation stack
  (IAnimatedPlayer.getAnimationStack().addAnimLayer), inchangé.
- TiedUpMod.onClientSetup : suppression de l'appel BondageAnimationManager.init()
  (la méthode est maintenant un log no-op, conservée pour la signature
  publique + doc du changement).
- AnimationTickHandler.onClientTick : retrait de la boucle
  mc.level.players() + updatePlayerAnimation + tickFurnitureSafety +
  cold-cache furniture retry. Les joueurs sont ticked par
  RigAnimationTickHandler (Phase 2.7). Conservé : le cleanup
  périodique ClothesClientCache (hygiène mémoire indépendante), le
  hook onPlayerLogout (cleanup per-UUID des caches NPC restants), et
  le hook onWorldUnload (caches V2 encore utilisés par NpcAnimationTickHandler).
  Imports unused strippés.
- DogPoseHelper : mis à jour la javadoc pour refléter le retrait
  du path player (NPCs only désormais).

Compile GREEN. 20/20 tests rig GREEN.

QA runtime : cf. docs/plans/rig/PHASE2_QA.md (non commit — fichier
working doc sous docs/plans/ gitignored par convention repo).

Net LOC : -276.
2026-04-23 00:42:10 +02:00
notevil
b494b60d60 Phase 2.7 review fixes : resetLoggedErrors wiring + IDLE self-heal + JSON cleanup + tests 2026-04-23 00:27:26 +02:00
notevil
08808dbcc1 Phase 2.7 : animation registry + tick handler + placeholder assets
TiedUpAnimationRegistry.CONTEXT_STAND_IDLE instancié au FMLCommonSetupEvent
via DirectStaticAnimation + armature BIPED. Placeholder JSON 2-keyframes
identity (joueur statique) à assets/tiedup/animmodels/animations/ jusqu'à
ce qu'un export Blender authored remplace. biped.json de même (hiérarchie
identity) placé dans assets/tiedup/armatures/ — parse via JsonAssetLoader
mais pas encore chargé au runtime (l'armature reste procédurale côté Java).

RigAnimationTickHandler tick chaque player visible côté client (phase END) :
- patch.getAnimator().tick() → avance les layers EF
- trigger playAnimation(CONTEXT_STAND_IDLE, 0.2f) quand motion=IDLE et anim
  courante ≠ CONTEXT_STAND_IDLE (idempotent)
- try/catch per-entity avec dedup des erreurs par UUID (pattern
  TiedUpRenderEngine.loggedRenderErrors)
- skip si level null / paused

PlayerPatch.initAnimator bind désormais IDLE → CONTEXT_STAND_IDLE quand le
registry est prêt (fallback EMPTY_ANIMATION si patch construit avant setup).

Voir docs/plans/rig/ASSETS_NEEDED.md pour la spec des assets authored
définitifs (anim idle swing respiration 3 keyframes + offsets biped
anatomiques).
2026-04-23 00:07:32 +02:00
notevil
73264db3c6 Phase 2.6 review fixes : level guard + thread-safety + priority revert + V3-REW-11 track
RISK-001 : early-return si entity.level() == null dans onRenderLiving
  (NPE guard sur detach race despawn / dim teleport). Aligne sur EF.

RISK-004 : clear loggedRenderErrors en tête de onAddLayers() — reset
  du set anti-spam sur reload resources (F3+T), borne aussi la taille
  en session longue.

RISK-005 : remplace HashSet par ConcurrentHashMap.newKeySet() pour
  loggedRenderErrors — render thread + loader thread + test runner
  muteront le set.

RISK-002 : retire priority = EventPriority.HIGH sur onRenderLiving,
  revert au défaut NORMAL aligné EF upstream. Aucun cas concret ne
  justifie HIGH aujourd'hui ; on réévaluera si conflit d'interception
  apparaît Phase 3+. Import EventPriority supprimé.

RISK-003 : tracké V3-REW-11 dans V3_REWORK_BACKLOG.md (inventory GUI
  special-case EF pas porté — rotation/scale/pose preview inventaire).

Docs locaux (gitignored) mis à jour :
  - PHASE0_DEGRADATIONS.md : nouvelle section Phase 2.6 findings
  - V3_REWORK_BACKLOG.md : V3-REW-11 ajouté section MAJEURE

Tests : compileJava GREEN, 18/18 rig tests GREEN.
2026-04-22 23:41:07 +02:00
notevil
987efde86b Phase 2.6 : TiedUpRenderEngine dispatch (RenderLivingEvent hook)
Câble le dispatch renderer RIG via deux subscribers auto-registered (split
MOD bus pour AddLayers, FORGE bus pour RenderLivingEvent.Pre) — pas besoin
de wiring explicite dans TiedUpMod.

onAddLayers (MOD bus) : construit la map entityRendererProvider avec
EntityType.PLAYER → TiedUpPlayerRenderer (Phase 2) ; poste
PatchedRenderersEvent.Add pour extensions tierces.

onRenderLiving (FORGE bus, priority HIGH) : filtre strict instanceof
Player || AbstractTiedUpNpc (protège MCA villagers cf. V3-REW-10) ;
vérifie patch.overrideRender() ; dispatche vers PatchedEntityRenderer et
cancel l'event. Try/catch robuste : log WARN une seule fois par UUID sur
exception, fallback vanilla (event non-canceled).

3 tests unitaires (pure-logic, sans MC runtime) : null-safety du filtre
et idempotence du reset. Le dispatch complet sera validé Phase 2.8
runClient smoke test.

Le biped armature étant identity (Phase 2.4 stub), le hook rendra le
player effondré à l'origine dès qu'il s'active — attendu, warn déjà en
place depuis Phase 2.5.
2026-04-22 23:28:45 +02:00
notevil
d129983eb7 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).
2026-04-22 23:16:35 +02:00
notevil
8dff4c0e03 WIP Phase 2.5 : fork render pipeline (PatchedEntityRenderer family)
Fork EF client renderer patched/entity/ → rig/render/, imports rewrités.

Nouveaux fichiers :
 - render/PatchedEntityRenderer.java   (~96 LOC, était stub 16 LOC)  — base abstraite,
   mulPoseStack + setArmaturePose + nameplate via mixin invoker
 - render/PatchedLivingEntityRenderer.java  (~230 LOC, EF 294 → stripped)  — hot path
   render, strippé du JSON customLayers loading + EntityDecorations (pas impl. côté
   LivingEntityPatch TiedUp)
 - render/PHumanoidRenderer.java       (~95 LOC, EF 53 → adapté)  — intermediate
   biped, baby head scale, enregistre PatchedArmorLayer + PatchedItemInHandLayer
 - render/TiedUpPlayerRenderer.java    (~95 LOC, EF PPlayerRenderer 60 → adapté)  —
   dispatch slim/default (Meshes.ALEX vs BIPED), propage modelParts visible flags,
   skip cape/bee/arrow layers (V3-REW-08/09)
 - render/PatchedLayer.java            (~55 LOC)  — base no-op layer
 - render/PatchedArmorLayer.java       (~45 LOC)  — stub no-op (V3-REW-04)
 - render/PatchedItemInHandLayer.java  (~45 LOC)  — stub no-op (Phase 3)
 - mixin/client/MixinEntityRenderer.java       (~35 LOC)  — invoker shouldShowName +
   renderNameTag (EF verbatim)
 - mixin/client/MixinLivingEntityRenderer.java (~30 LOC)  — invoker isBodyVisible,
   getRenderType, getBob

Modifs :
 - resources/tiedup-rig.mixins.json : enregistre les 2 nouveaux mixins client

Dépendances stubbées :
 - PatchedArmorLayer / PatchedItemInHandLayer no-op — "mangent" le layer vanilla
   équivalent pour éviter double-rendu, mais ne dessinent rien. À implémenter
   Phase 3 (armor : V3-REW-04 ; item-in-hand : tool joints GLB).
 - EntityDecorations (color/light/overlay modifiers) strippées du render path —
   le patch TiedUp n'expose pas encore cette API. Hurt/death overlay natifs
   conservés. À rework si feature premium glow/tint NPC.

TODOs Phase 2.6 / Phase 3 :
 - animated_layers/*.json datapack-driven loading (strippé du constructor) si
   besoin futur
 - PlayerItemInHandLayer (version player-spécifique, distincte de ItemInHandLayer
   générique) pas encore mangée côté player → layer vanilla continue de draw
 - ElytraLayer / CustomHeadLayer pas mangés (cohérent "on mange juste les layers
   qui entrent en conflit avec le mesh skinné")

Surprise pendant le fork :
 - ItemInHandLayer vanilla MC 1.20.1 exige M extends EntityModel<E> & ArmedModel,
   pas HeadedModel comme PatchedItemInHandLayer EF 1.20.1 pouvait le suggérer
   via ses anciennes versions. Alignement forcé sur ArmedModel.
 - EF utilise 2 mixins @Invoker (MixinEntityRenderer / MixinLivingEntityRenderer)
   pour accéder aux méthodes protected de vanilla — forkés verbatim dans
   rig/mixin/client/.

Tests :
 - ./gradlew compileJava : BUILD SUCCESSFUL
 - ./gradlew test --tests "com.tiedup.remake.rig.*" : 15/15 GREEN
   (TiedUpArmaturesTest 4 + GlbJointAliasTableTest 4 + GltfToSkinnedMeshTest 6 =
   14 rig + 1 utilitaire)
2026-04-22 22:39:41 +02:00
notevil
39f6177595 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.
2026-04-22 22:27:01 +02:00