fix(engine): fill tableau fan to viewport on all aspect ratios #113

Merged
funman300 merged 1 commits from fix/foldable-tableau-fan-fill into master 2026-06-26 20:11:34 +00:00
Owner

Problem

On a near-square viewport (Rhys, unfolded Galaxy Fold 7) a fresh deal leaves the bottom ~40% of the screen empty — the tableau is crammed under the HUD.

Root causes:

  • update_tableau_fan_frac measured only face-up column depth and returned early at a fresh deal (face-up depth 1), so it never spread the tableau; the deep face-down stacks were ignored.
  • The cold-start deal is inserted directly without a StateChangedEvent, so the event-driven fill never ran for it.
  • On Android the safe-area-inset resize (frames 1-3) recomputes the layout, resetting the fan to compute_layout's sparse worst-case value.

Fix

Moved the fill into layout::apply_dynamic_tableau_fan, driven by each column's total weighted depth (face-down cards count, scaled by the face-down/face-up step ratio), so the deepest column fills the available height. Run it in three paths so every trigger stays filled:

  • PostStartup → the cold-start deal
  • StateChangedEvent → moves
  • inside on_window_resized after compute_layout → the safe-area resize + fold/unfold

MAX_DYNAMIC_FAN_FRAC (0.6) caps the spread so a near-empty column keeps readable overlap; TABLEAU_FAN_FRAC floors it. Deeper columns drive the fraction down so everything still fits (no overflow). card_position/card_positions read the same fractions, so hit-testing stays in sync.

Also helps normal phones (less empty space at deal). Adds regression tests for the cold-start fill and the resize re-fill. Workspace clippy + tests green.

Verification note: logic is covered by unit tests; the on-screen feel (and the 0.6 cap) wants confirmation on the actual Fold 7 — emulator visual check was blocked (no touch injection on NativeActivity, non-debuggable APK, onboarding modal). Pairs with the merged card-move-jank fix (#112) for the bundled foldable release.

🤖 Generated with Claude Code

## Problem On a near-square viewport (Rhys, **unfolded Galaxy Fold 7**) a fresh deal leaves the bottom ~40% of the screen empty — the tableau is crammed under the HUD. Root causes: - `update_tableau_fan_frac` measured only **face-up** column depth and **returned early at a fresh deal** (face-up depth 1), so it never spread the tableau; the deep face-down stacks were ignored. - The cold-start deal is inserted directly without a `StateChangedEvent`, so the event-driven fill never ran for it. - On Android the **safe-area-inset resize (frames 1-3)** recomputes the layout, resetting the fan to `compute_layout`'s sparse worst-case value. ## Fix Moved the fill into `layout::apply_dynamic_tableau_fan`, driven by each column's **total weighted depth** (face-down cards count, scaled by the face-down/face-up step ratio), so the deepest column fills the available height. Run it in **three** paths so every trigger stays filled: - PostStartup → the cold-start deal - `StateChangedEvent` → moves - inside `on_window_resized` after `compute_layout` → the safe-area resize + fold/unfold `MAX_DYNAMIC_FAN_FRAC` (0.6) caps the spread so a near-empty column keeps readable overlap; `TABLEAU_FAN_FRAC` floors it. Deeper columns drive the fraction down so everything still fits (no overflow). `card_position`/`card_positions` read the same fractions, so hit-testing stays in sync. Also helps normal phones (less empty space at deal). Adds regression tests for the cold-start fill and the resize re-fill. Workspace clippy + tests green. **Verification note:** logic is covered by unit tests; the on-screen *feel* (and the 0.6 cap) wants confirmation on the actual Fold 7 — emulator visual check was blocked (no touch injection on NativeActivity, non-debuggable APK, onboarding modal). Pairs with the merged card-move-jank fix (#112) for the bundled foldable release. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
funman300 added 1 commit 2026-06-26 20:11:19 +00:00
On a near-square viewport (e.g. an unfolded Galaxy Fold) a fresh deal left
the bottom ~40% of the screen empty: the dynamic fan (update_tableau_fan_frac)
measured only face-up column depth and returned early at a fresh deal (face-up
depth 1), so it never spread the tableau, and the deep face-down stacks were
ignored. The cold-start deal also never fired StateChangedEvent, and on Android
the safe-area-inset resize (frames 1-3) reset the fan to compute_layout's
sparse worst-case value, so even the post-move fill was wiped.

Move the fill into layout::apply_dynamic_tableau_fan, driven by each column's
TOTAL weighted depth (face-down cards count, scaled by the face-down/face-up
step ratio) so the deepest column fills the available height. Run it in three
places so every path stays filled: PostStartup (cold-start deal), on
StateChangedEvent (moves), and inside on_window_resized after compute_layout
(safe-area resize + fold/unfold). MAX_DYNAMIC_FAN_FRAC caps the spread so a
near-empty column keeps readable overlap; TABLEAU_FAN_FRAC floors it. Deeper
columns drive the fraction down so everything still fits — no overflow.
card_position/card_positions read the same fractions, so hit-testing stays
in sync.

Adds regression tests: cold-start deal fills the fan, and a resize re-fills it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
funman300 merged commit 0ed91ea24c into master 2026-06-26 20:11:34 +00:00
funman300 deleted branch fix/foldable-tableau-fan-fill 2026-06-26 20:11:34 +00:00
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: funman300/Ferrous-Solitaire#113