fix(engine): lift card z during CardAnim to prevent corner bleed-through

When a card slides to a foundation slot already occupied, both card entities
share the same x,y for the duration of the tween. With STACK_FAN_FRAC only
0.003 apart, the incoming card partially occludes the stationary one, making
the two exposed corners look like a single mismatched card.

Elevate every CardAnim-driven card to target.z + 50 during transit so it
fully occludes any card resting at the destination. On completion the card
snaps to the correct resting z. The value sits below DRAG_Z (500) so dragged
cards still render above animated ones.

Closes #implicitly-related-to-corner-mismatch-investigation

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
funman300
2026-05-19 11:31:32 -07:00
parent cd792b20b2
commit c66ff26d1d
+16 -1
View File
@@ -72,6 +72,17 @@ const TIME_ATTACK_TOAST_SECS: f32 = 5.0;
const CHALLENGE_TOAST_SECS: f32 = 3.0;
const VOLUME_TOAST_SECS: f32 = 1.4;
/// Z added to a card's render depth while its `CardAnim` is in-flight.
///
/// Foundation and tableau cards share x,y during the slide (destination equals
/// a slot that already holds a card). Without this lift the incoming card's
/// bottom-right corner overlaps the stationary card's top-left, which the
/// player perceives as a single card with mismatched rank/suit indices.
///
/// 50.0 sits comfortably above the highest pile depth (~1.04) and well below
/// `DRAG_Z` (500), so a dragged card always renders above an animated one.
const CARD_ANIM_Z_LIFT: f32 = 50.0;
/// Per-card stagger interval for the win cascade at Normal speed (seconds).
///
/// Sourced from `ui_theme::MOTION_CASCADE_STAGGER_SECS` so all motion timing
@@ -254,7 +265,11 @@ fn advance_card_anims(
// shared `CardAnim` struct stays a simple linear-tween container — the
// upgrade is one extra `sample_curve` call per advancing animation.
let s = sample_curve(MotionCurve::SmoothSnap, t);
transform.translation = anim.start.lerp(anim.target, s);
let mut pos = anim.start.lerp(anim.target, s);
// Elevate z during transit so the moving card always renders in front
// of any card already resting at the destination position.
pos.z = anim.target.z + CARD_ANIM_Z_LIFT;
transform.translation = pos;
if t >= 1.0 {
transform.translation = anim.target;
commands.entity(entity).remove::<CardAnim>();