feat(engine): drag-cancel return tween — smooth ease-out instead of shake

Illegal drops previously snapped each dragged card to its origin
slot and ran a horizontal ShakeAnim wiggle for negative feedback —
which read as punitive on every misclick. The rejection now plays
a 150 ms quintic ease-out glide from the drop location back to the
resting slot. The audio cue (card_invalid.wav) still fires so the
player gets clear "no" feedback; the visual is just gentler.

Both rejection paths in input_plugin (mouse end_drag and touch
end_drag) construct a CardAnimation::slide(drag_pos → target_pos)
with MotionCurve::Responsive — the curve module's own docs
recommend Responsive specifically for invalid snap-back because its
zero overshoot reads forgiving rather than jittery.

card_plugin's update_card_entity gates its snap path on
CardAnimation absence so the StateChangedEvent that follows a
rejection no longer fights the in-flight tween. Mirrors how
resize_cards_in_place already drops in-flight tweens during a
window resize.

ShakeAnim itself stays in feedback_anim_plugin — the right-click
invalid-target and double-click in-place rejection paths still use
it because there's no movement to interpolate, just a "no" wiggle.
Only the drag-rejection path swaps to the smooth tween.

Six new rejection-tween tests pin the contract: CardAnimation is
inserted on every dragged card, start/end positions and z values
match the drag-to-resting transition, duration matches the new
MOTION_DRAG_REJECT_SECS token, and the curve is Responsive. The
two legacy ShakeAnim drag-rejection tests are removed since their
contract is intentionally inverted by this commit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
funman300
2026-05-02 01:34:12 +00:00
parent 69ce9afab9
commit 525fe0fe76
3 changed files with 241 additions and 107 deletions
+5
View File
@@ -333,6 +333,11 @@ pub const MOTION_SHAKE_SECS: f32 = 0.25;
/// Shake angular frequency in rad/s.
pub const MOTION_SHAKE_OMEGA: f32 = 35.0;
/// Duration of the smooth return tween when a drag is rejected by an
/// invalid drop target. Short enough to feel snappy but long enough to
/// read as motion rather than a teleport.
pub const MOTION_DRAG_REJECT_SECS: f32 = 0.15;
/// Card flip — half-time per phase (squash + grow). 100 ms each =
/// 200 ms total. Pair with a ±8° Z-rotation at the midpoint for a 3D
/// feel without 3D rendering.