18af49c0f3
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>