From c66ff26d1d2c21cba6c2242041b536d3cd722820 Mon Sep 17 00:00:00 2001 From: funman300 Date: Tue, 19 May 2026 11:31:32 -0700 Subject: [PATCH] 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 --- solitaire_engine/src/animation_plugin.rs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/solitaire_engine/src/animation_plugin.rs b/solitaire_engine/src/animation_plugin.rs index ecf93ee..6863cd6 100644 --- a/solitaire_engine/src/animation_plugin.rs +++ b/solitaire_engine/src/animation_plugin.rs @@ -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::();