From 94a6feb5dbe9b62cd9a175b121a4d2f131ac1147 Mon Sep 17 00:00:00 2001 From: funman300 Date: Fri, 26 Jun 2026 13:56:31 -0700 Subject: [PATCH] fix(engine): raise dynamic tableau fan cap to fill tall viewports MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MAX_DYNAMIC_FAN_FRAC 0.6 -> 0.9 so the dynamic tableau fill spreads further on very tall / narrow viewports (e.g. a foldable cover screen), which were left ~40% empty at the cap. Fills the unfolded near-square screen to ~100% and lets the cover screen fill further (and the rest fills as columns deepen during play). Normal phones are unaffected — their fill fraction is already below the cap. apply_dynamic_tableau_fan still floors at TABLEAU_FAN_FRAC and deeper columns drive the fraction down, so nothing overflows and hit-testing stays in sync. Vertical centring of the residual was investigated but dropped: a 21:9 phone is aspect-identical to the cover screen, so centring can't be targeted to foldables without also disconnecting the board from the HUD on tall phones. Co-Authored-By: Claude Opus 4.8 (1M context) --- CHANGELOG.md | 4 +++- solitaire_engine/src/layout.rs | 22 +++++++++------------- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b96d2de..672b5b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,7 +41,9 @@ project follows [Semantic Versioning](https://semver.org/). each column's total depth (face-down cards included) and refills on the cold-start deal, every move, and on resize (incl. the Android safe-area-inset resize and fold/unfold), so a near-square viewport such as an unfolded Galaxy - Fold no longer leaves the bottom of the screen empty. + Fold no longer leaves the bottom of the screen empty. The fan spread cap was + raised so very tall / narrow viewports (e.g. a foldable cover screen) fill + further. - **Card-move animation jank.** A move now rebuilds a card's child visuals only when its appearance changes (flip / resize / accessibility) instead of despawning and respawning every card's children each `StateChangedEvent`, diff --git a/solitaire_engine/src/layout.rs b/solitaire_engine/src/layout.rs index d61d384..1b5d73a 100644 --- a/solitaire_engine/src/layout.rs +++ b/solitaire_engine/src/layout.rs @@ -99,7 +99,7 @@ const MAX_TABLEAU_CARDS: f32 = 13.0; /// clear overlap (a readable stack) while still filling most of a near-square / /// unfolded-foldable screen. Tunable purely for feel — no effect on correctness /// or hit-testing. -pub(crate) const MAX_DYNAMIC_FAN_FRAC: f32 = 0.6; +pub(crate) const MAX_DYNAMIC_FAN_FRAC: f32 = 0.9; /// Vertical pixel band reserved at the top of the play area for the HUD /// (action buttons, Score / Moves / Timer readouts). The card grid starts @@ -285,13 +285,12 @@ pub fn compute_layout( ); } - // Adaptive tableau fan fraction. On height-limited (desktop) windows the - // height-based sizing already ensures a worst-case 13-card column fits at - // TABLEAU_FAN_FRAC (0.25), so the formula returns ≈0.25 and the clamp - // keeps it there — no change from prior behaviour. On width-limited - // (portrait phone) windows card_size is small and lots of vertical space - // is unused; we solve for the fraction that exactly fills the available - // space to the bottom margin. + // Adaptive tableau fan fraction. On height-limited windows the height-based + // sizing already ensures a worst-case 13-card column fits at TABLEAU_FAN_FRAC, + // so the formula returns the minimum and the clamp keeps it there. On + // width-limited (portrait phone) windows card_size is small and lots of + // vertical space is unused; solve for the fraction that fills the available + // space. `apply_dynamic_tableau_fan` later refines this for the actual deal. // // avail = distance from the top of the first tableau card to the bottom // margin — i.e. the space available for 12 fan steps. @@ -302,20 +301,17 @@ pub fn compute_layout( } else { TABLEAU_FAN_FRAC }; - // Never go below the desktop minimum — avoids shrinking the fan on - // degenerate near-square windows where the formula might undershoot. let tableau_fan_frac = ideal_fan_frac.max(TABLEAU_FAN_FRAC); - // Scale the face-down fraction proportionally so rendering and hit-testing - // stay in sync (TABLEAU_FACEDOWN_FAN_FRAC / TABLEAU_FAN_FRAC = 0.48 ratio). let facedown_scale = TABLEAU_FACEDOWN_FAN_FRAC / TABLEAU_FAN_FRAC; let tableau_facedown_fan_frac = tableau_fan_frac * facedown_scale; + let available_tableau_height = avail; Layout { card_size, pile_positions, tableau_fan_frac, tableau_facedown_fan_frac, - available_tableau_height: avail, + available_tableau_height, } }