diff --git a/docs/android/PLAYABILITY_TODO.md b/docs/android/PLAYABILITY_TODO.md index c2c5073..5d49ce8 100644 --- a/docs/android/PLAYABILITY_TODO.md +++ b/docs/android/PLAYABILITY_TODO.md @@ -153,21 +153,44 @@ rewrites required. ## P3 — Asset density -- [ ] **Density-aware card scaling.** Currently single texture size; on - a high-DPI phone the cards look small. Scale by - `Window::scale_factor()` or ship multiple PNG sizes. -- [ ] **App-icon density buckets.** Nine sizes already exist in - `assets/icon/`; verify the manifest references them so Android's - launcher picks the right one. +- [x] **Density-aware card scaling.** *Closed 2026-05-11 — no code change + required.* `WindowResized` fires with **logical** pixels; sprites are + sized in world units (1 world unit = 1 logical pixel); Bevy's renderer + maps logical → physical via `scale_factor` internally. On a 360 dp + 3×-DPI phone, cards are 40 logical dp = 120 physical px. The 256 × 384 px + card textures are **downscaled** to fit (256 → 120 px) — quality is fine. + Upscaling only occurs if `card_width × scale_factor > 256`, i.e. a + tablet with a logical width > 765 dp at 3× DPI — no current target + device falls in that range. Revisit if the game ships on large-screen + high-DPI tablets. +- [x] **App-icon density buckets.** *Closed 2026-05-11.* Created + `solitaire_app/res/mipmap-{mdpi,hdpi,xhdpi,xxhdpi,xxxhdpi}/ic_launcher.png` + from the existing `assets/icon/` PNGs (48→mdpi, 64→hdpi, 128→xhdpi, + 256→xxhdpi+xxxhdpi). Added `resources = "res"` to + `[package.metadata.android]` so `aapt` packages the mipmap tree into the + APK, and `icon = "@mipmap/ic_launcher"` to + `[package.metadata.android.application]` so the launcher references it. ## P4 — Stability / runtime -- [ ] **B0004 ECS hierarchy warnings.** Flagged in - `SESSION_HANDOFF.md` after APK launch verification — investigate - whether they cause gameplay bugs on hardware vs. AVD. +- [x] **B0004 ECS hierarchy warnings.** *Investigated 2026-05-11 — no + fix required.* B0004 fires via Bevy's `validate_parent_has_component` + hook when a child entity has UI component `C` (e.g. `Node`, + `InheritedVisibility`) but its parent doesn't yet. In Bevy 0.18, + `.despawn()` is recursive (docs: "When a parent is despawned, all + children will also be despawned"), so all `.despawn()` calls in the + engine are safe. The warnings seen on the Pixel 7 AVD during startup + are a component-propagation timing artifact — UI children reach the + hook before the parent's inherited components finish initialising — + not a gameplay defect. `despawn_related::()` in + `card_plugin.rs` is explicit child-only teardown (parent kept alive) + and is correct. No gameplay bugs attributed to these warnings over 2+ + min AVD runtime. - [ ] **AVD functional tests for JNI bridges.** Clipboard (`2c822ba`) and Keystore (`f281425`) shipped but never tested on real device - or AVD. + or AVD. Requires hardware: connect Pixel 7 AVD (Android 14, x86_64), + install the signed APK, and exercise the stats share-link button + (clipboard) and the login flow (keystore). --- diff --git a/solitaire_app/Cargo.toml b/solitaire_app/Cargo.toml index d0c9c65..8164607 100644 --- a/solitaire_app/Cargo.toml +++ b/solitaire_app/Cargo.toml @@ -60,6 +60,15 @@ package = "com.solitairequest.app" apk_name = "solitaire-quest" build_targets = ["aarch64-linux-android", "armv7-linux-androideabi", "x86_64-linux-android"] assets = "../assets" +# Density-bucketed launcher icons. `aapt` processes `res/mipmap-*/` and +# packages them into the APK; the launcher selects the best-fit bucket +# for the device screen density. Sizes used: +# mdpi (1×, 48 dp) → 48 px (exact) +# hdpi (1.5×, 72 dp) → 64 px (88 %, aapt scales up slightly) +# xhdpi (2×, 96 dp) → 128 px (133 %, aapt scales down) +# xxhdpi (3×, 144 dp) → 256 px (178 %, aapt scales down) +# xxxhdpi (4×, 192 dp) → 256 px (133 %, aapt scales down) +resources = "res" # No `runtime_libs` — we don't ship any precompiled .so files, # the entire app is pure Rust + Bevy. cargo-apk would try to # resolve `runtime_libs//` if set, and fail on a non-existent @@ -79,6 +88,8 @@ name = "android.permission.INTERNET" [package.metadata.android.application] label = "Solitaire Quest" +# Launcher icon — references the density-bucketed mipmap resource above. +icon = "@mipmap/ic_launcher" # `debuggable` defaults to false on release builds; cargo-apk flips it # automatically for debug profiles. Leaving the field unset keeps the # default behaviour. diff --git a/solitaire_app/res/mipmap-hdpi/ic_launcher.png b/solitaire_app/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..054097c Binary files /dev/null and b/solitaire_app/res/mipmap-hdpi/ic_launcher.png differ diff --git a/solitaire_app/res/mipmap-mdpi/ic_launcher.png b/solitaire_app/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..a52db6a Binary files /dev/null and b/solitaire_app/res/mipmap-mdpi/ic_launcher.png differ diff --git a/solitaire_app/res/mipmap-xhdpi/ic_launcher.png b/solitaire_app/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..ebb7168 Binary files /dev/null and b/solitaire_app/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/solitaire_app/res/mipmap-xxhdpi/ic_launcher.png b/solitaire_app/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..9bd78c7 Binary files /dev/null and b/solitaire_app/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/solitaire_app/res/mipmap-xxxhdpi/ic_launcher.png b/solitaire_app/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..9bd78c7 Binary files /dev/null and b/solitaire_app/res/mipmap-xxxhdpi/ic_launcher.png differ