Commit Graph

191 Commits

Author SHA1 Message Date
notevil
23b249dcd2 Fix SMELL-API-01 + log levels : dispatchCodec API hygiene + INFO armature loaded
Reviewer P3 review convergent findings (LOW severity).

SMELL-API-01 : CodecDispatchRegistry.dispatchCodec() is public (legitimate
for cross-package interface CODEC fields) but consumers might inadvertently
call it twice and reason about identity. Added @apiNote stating canonical
single-call usage + memoization to guarantee idempotent identity.

ArmatureReloadListener log level : promoted 'Registered armature: X' from
DEBUG to INFO. Datapack-loaded custom armatures are visible at default log
level — gameday smoke test for D6 wiring. Added summary line
'Datapack armature reload : N custom armatures registered (builtin BIPED active)'
at end of apply().
2026-04-27 00:41:28 +02:00
notevil
25a9251959 Fix SMELL-CODEC-01 : extract CodecDispatchRegistry<T> base
Reviewer P3 review convergent finding (LOW severity).

4 dispatch registries (AnimationActionRegistry, PoseModifierRegistry,
PlaybackSpeedModifierRegistry, PlaybackTimeModifierRegistry) had
structurally identical implementations — register/getCodec/types/
dispatchCodec + dedup IAE — ~50 LOC each, ~200 LOC total duplicated.

Extract abstract CodecDispatchRegistry<T> base. Each subclass becomes
a singleton with just static init + registryName() override. Future
registries (Phase 4 conditional actions, etc.) inherit the contract
trivially.

Net : -150 LOC (200 dup → 50 base + 4×~10 subclass scaffolding).
2026-04-27 00:36:00 +02:00
notevil
d90ff14668 RISK-003 self-heal coverage : extract shouldRebindIdle helper + tests
Le reviewer flagait une race entre PlayerPatch.initAnimator (bind IDLE
au capability attach time) et AnimationManager.apply (datapack reload).
En investigant : la race existe architecturalement mais le self-heal
est deja en place via maybePlayIdle — quand resolveWithFallback retourne
EMPTY pre-apply, le bind EMPTY est mis dans la map (EMPTY_ANIMATION a
isPresent()==true donc passe checkNull). Au tick suivant, une fois
apply tourne et le registry populé, maybePlayIdle detecte
currentIdleBind == EMPTY et rebind sur la vraie anim.

Le bind EMPTY est intentionnel : ClientAnimator.postInit appelle
playAnimationInstantly(livingAnimations.get(IDLE)) immediatement, et
skipper le bind crasherait postInit (NPE sur null.get()). EMPTY a une
pose identity (no-op visuel) → bootstrap propre, self-heal post-reload.

Le symptome runtime "F6 bindings: 1 mais idle silent" decrit dans la
review vient d'un cause differente : AnimationManager n'est pas encore
register comme reload listener (Phase 2 wiring pending, tracked sous
BUG-RACE-01 dans AnimationManager.apply javadoc). Une fois cable, le
self-heal fonctionne — verrouille par les 5 nouveaux tests.

Changements :
- RigAnimationTickHandler : extract shouldRebindIdle(currentBind, target)
  pure helper package-private. La logique inline dans maybePlayIdle
  reste identique, juste deleguee au helper pour testabilite.
- PlayerPatch.initAnimator : javadoc enrichi (race + raison du bind
  EMPTY + reference test coverage).
- RigAnimationTickHandlerTest : +5 cases sur shouldRebindIdle (null+real,
  empty+real RACE, real+real, two real instances, *+empty).

Tests : 459 → 464 (+5), tous green.
2026-04-27 00:20:01 +02:00
notevil
7c994a9ffa Fix SMELL-LOG-01 : dedup SpawnParticleAction joint-not-found WARN
Reviewer P3 review convergent finding (MEDIUM severity).

Period events fire at 20 Hz across the period duration. A typo in
'at: "wrongJoint"' would log WARN every tick × duration — log flood
that pollutes latest.log and masks real issues.

Fix : ConcurrentHashMap.newKeySet() keyed by armature+joint, log WARN
only once per unique miss. Reset hook wired into
TiedUpRigRegistryReloadListener.apply() so /reload re-enables warn (in
case modder fixes their JSON between reloads).

Pattern identical to TiedUpAnimationRegistry.WARNED_MISSING_ANIMS.
2026-04-27 00:09:19 +02:00
notevil
9171ff0def Fix RISK-001 : PlaySoundAction SoundSource codec via comapFlatMap
Reviewer P3 review convergent finding (HIGH severity).

Codec.xmap doesn't wrap IllegalArgumentException in DataResult.error.
A typo like 'category: ambiant' (instead of 'ambient') would propagate
the IAE up to AnimationManager.apply which catches it but silently
skips the entire animation without clear field-level error.

Fix : Codec.STRING.comapFlatMap with explicit try/catch + descriptive
error message listing valid SoundSource values. Helps modders debug
typos at parse-time instead of mysterious silent skips.

Two surprises during implementation that required deeper changes :

1. SoundSource.name() != getName() — RECORDS/BLOCKS/PLAYERS have
   getName() == record/block/player (singular). The previous encoder
   used getName().toUpperCase() which produced 'BLOCK' but the decoder
   needed 'BLOCKS'. The new codec is roundtrip-safe via getName() on
   both sides.

2. DFU 6.0.8 OptionalFieldCodec.decode is lenient by default — a
   present-but-malformed field is silently mapped to Optional.empty()
   instead of propagating DataResult.error. The strict 'lenient=false'
   flag was added in a later DFU release. To surface the error at
   parse-time the optional category field is now decoded as
   Codec.STRING.optionalFieldOf().flatXmap(parseSoundSource), which
   propagates errors correctly.

+4 tests : invalid SoundSource returns error, uppercase input accepted,
error message lists valid values, encode/roundtrip uses lowercase
getName form.
2026-04-27 00:03:08 +02:00
notevil
fdf7330523 BUG-RACE-01 : document Phase 2 listener registration ordering
Reviewer flagged race between AnimationManager parsing (LIVING_MOTION_CODEC.getOrThrow)
and LivingMotionReloadListener.apply(). Investigation confirmed :
- AnimationManager is NOT currently registered as a reload listener (Phase 2 pending)
- Forge 1.20.1 has no PreparableReloadListener.getDependencies() — that's a Fabric/NeoForge API
- 1.20.1 ordering mechanism = registration list order via SimpleReloadInstance.barrier.wait()

Fix : preventive comment in AnimationManager.apply() documenting that
when Phase 2 wires this listener, it MUST be registered AFTER
LivingMotionReloadListener / ArmatureReloadListener / PoseTypeReloadListener
in TiedUpMod.ForgeEvents.onAddReloadListeners + V2ClientSetup.onRegisterReloadListeners.

Drive-by : add docs.tar.gz to .gitignore (artist export bundle).

No actionable race today — pure documentation hardening.
2026-04-26 22:07:18 +02:00
notevil
227cf5f346 D1 remaining property codecs : bondage-relevant subset serialized
Audit of 29 remaining properties without name+codec :
- Category A (trivial data types) : 7 properties — direct Codec added
  (fixed_head_rotation, reset_living_motion, no_gravity_time, move_time,
  coord_update_time, coord_start_keyframe_index, coord_dest_keyframe_index).
  Two new shared codecs : LIVING_MOTION_CODEC (ExtendableEnum string lookup)
  and TIME_PAIR_LIST_CODEC (even-length float list, odd-count = codec error).
- Category B (functional/lambda) : 3 properties — dispatch registry pattern
  (like D2 AnimationAction) with base impls for immediate artist use.
  pose_modifier, play_speed_modifier, elapsed_time_modifier. The property
  static type stays the wider functional interface for backward compat with
  existing consumers (AnimationPlayer.tick, StaticAnimation.modifyPose) via
  a subinterface + xmap upcast.
- Category C (combat-only, EF legacy stripped) : 14 properties skipped with
  explicit TODO markers referencing V3-REW-11. Covers all 4 AttackPhase
  properties (their super-constructor is already commented out) plus
  ON_ITEM_CHANGE_EVENT, COORD, COORD_SET_{BEGIN,TICK}, COORD_GET,
  DEST_LOCATION_PROVIDER, ENTITY_YROT_PROVIDER, DEST_COORD_YROT_PROVIDER.
- Category D (already sub-file parsed / baked at load) : 4 properties
  skipped to avoid duplication : TRANSITION_ANIMATIONS_FROM/TO (need
  AnimationAccessor resolution, chicken-and-egg), IK_DEFINITION,
  BAKED_IK_DEFINITION.

After this commit : ~31 / 47 properties serializable (baseline 21 pre-D1).
Non-serialized remainder is combat-legacy or intentionally sub-file-only.

Registries created :
- PoseModifierRegistry (3 impls : joint_rotation_offset,
  joint_translation_offset, chain)
- PlaybackSpeedModifierRegistry (2 impls : constant_factor, linear_ramp)
- PlaybackTimeModifierRegistry (1 impl : loop_section)

Design note : ChainedPoseModifier.CODEC uses a hand-rolled Codec.of(...)
with inlined Encoder/Decoder bodies rather than RecordCodecBuilder, to
break a static-init cycle between the chain codec and the pose modifier
dispatch codec. Mojang DFU 6.0.8 has no Codec.recursive() — a lazy closure
inside encode/decode is the cleanest workaround.

Artist impact : locomotion tweaks (pose_modifier, play_speed_modifier,
elapsed_time_modifier, reset_living_motion, no_gravity_time and the coord
keyframe indexes) now controllable from JSON. Per-joint rotation /
translation nudges unlock bondage pose constraints without recompiling.

Tests : +41 (411 → 452 GREEN).
2026-04-24 23:48:34 +02:00
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