92a5ebb15e
`compute_layout` runs `window.max(MIN_WINDOW)`, which acts as a component-wise floor: any window smaller than MIN_WINDOW on either axis gets clamped up. The previous floor of 800x600 was set with desktop in mind, but on Android the OS-provided window size is the device resolution (~360 dp wide on a typical phone) and the clamp silently re-laid the board for an 800 dp width. Side effect: total grid width (9 * card_width) became ~800 px on a 360 dp viewport, so the leftmost foundation x-position fell past -180 and the rightmost tableau pile past +180 — both clipped at the visible edges, matching the v0.22.3 hardware screenshot. Lowered MIN_WINDOW to 320x400, below the smallest reasonable phone (~360x640), so every real device flows through compute_layout unclamped. The floor is preserved as a sentinel against degenerate windows (Bevy can briefly report 0-size during startup or after minimisation on some compositors). Desktop's "minimum supported playable size" is enforced separately via WindowResizeConstraints in solitaire_app. Updates `layout_below_minimum_clamps_to_minimum` to use values below the new floor, and adds a new regression test `phone_portrait_layout_fits_horizontally` that asserts all 13 piles fit inside a 360 x 800 dp viewport. Closes P0 #4 of docs/android/PLAYABILITY_TODO.md. 855 engine tests pass; clippy clean. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>