diff --git a/CHANGELOG.md b/CHANGELOG.md index 8efe7c6..a4d16f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,100 @@ project follows [Semantic Versioning](https://semver.org/). ## [Unreleased] -_Nothing yet._ +Two threads in flight: closing the v0.19.0 punch list (settings opt-out +for the smart-default sizer, share-link discoverability, the last +async-pull test flake) and a new performance / portability arc (F3 +diagnostics overlay landing first, then a working Android build +target via `cargo apk`). The Android build is the load-bearing +change — `solitaire_app` now compiles into both a desktop `bin` and +an Android `cdylib` from the same source tree, so subsequent work +can iterate on a phone or AVD without forking the codebase. + +### Added + +- **Android build target — first working APK** (`fb8b2ac`). + `cargo apk build -p solitaire_app --target x86_64-linux-android` + now produces a 54 MB debug-signed APK at + `target/debug/apk/solitaire-quest.apk`. Five gating points + resolved end-to-end: + - **`solitaire_app` split into bin + lib.** cargo-apk needs a + `cdylib` to bundle as `libmain.so`; pure-bin crates panic + with "Bin is not compatible with Cdylib". `src/lib.rs` + carries the ECS bootstrap as `pub fn run`; `src/main.rs` is + a 3-line shim that delegates for the desktop path. + - **`[package.metadata.android]`** pins target SDK 34 / min + SDK 26 and points `assets = "../assets"` at the workspace + asset directory so desktop and APK share one set. + - **Workspace `bevy` features** add `android-native-activity` + (target-gated inside bevy_internal — desktop builds compile + it out). Pairs with cargo-apk's NativeActivity wrapper. + - **`arboard` target-gated** to `cfg(not(target_os = + "android"))`. The crate has no Android backend; cargo apk + fails with E0433 on `platform::Clipboard` if left + unconditional. Stats's "Copy share link" surfaces an + informational toast on Android until JNI ClipboardManager + lands in the Phase-Android round. + - **`keyring` + `keyring-core` target-gated.** Bionic doesn't + expose `libc::__errno_location` so the transitive + `rpassword` won't compile. `auth_tokens` ships an Android + stub returning `KeychainUnavailable` for every call — + matches the existing fallback for a Linux box without + Secret Service. + - Cosmetic: cargo-apk panics post-sign when it tries to also + wrap the bin target. The APK on disk is unaffected; + `cargo apk build --lib` is the small workaround. +- **Android developer setup + build runbook** (`59424a3`). + Captures Debian 13 toolchain install (JDK 21, unzip, SDK + licence prompts), the `cargo apk build` invocation, the + cosmetic post-sign panic workaround, and a what-is-wired-vs- + stubbed table for the android target. Runnable on a fresh + clone — no machine-local context required. +- **F3-toggleable FPS / frame-time overlay** (`690e1d2`). + `DiagnosticsHudPlugin` wraps Bevy's `FrameTimeDiagnosticsPlugin` + and renders a corner readout the developer toggles with F3. + Hidden by default; F3 is not gated by pause / modal state. + Reads `smoothed()` so the cell isn't a per-frame jittery + scoreboard. Format: `FPS NN \u{2022} M.MM ms`. Anchored + top-right at `z = Z_SPLASH + 100` above every modal / toast / + splash. Update system bails when hidden so the + diagnostic-store lookup is free when nobody's looking. +- **"Smart window size" Settings toggle** (`e1b8766`). Gameplay + section gains an opt-out toggle for v0.19.0's + `apply_smart_default_window_size` system. New + `Settings::disable_smart_default_size: bool` with + `#[serde(default)]` so legacy `settings.json` files load to + the shipped behaviour (smart sizer enabled). `solitaire_app::main` + reads the flag once at startup and skips the system's + registration when set. Saved window geometry still wins over + both branches; tooltip on the row makes that explicit. +- **"Shareable" badge on the Latest-win caption** (`9b065e5`). + The Stats overlay's Latest-win caption now appends + `\u{2022} Shareable` when the displayed replay carries a + populated `share_url`. Players can see at a glance whether the + Copy share link button will produce a URL or surface the + upload-prerequisite toast. +- **Help overlay covers M / P / Win-Summary-Enter** (`35516d3`). + Three new rows in the Overlays section: M (Home / Mode + launcher), P (Profile), and the Enter accelerator that + dismisses the Win Summary modal. Three post-v0.18 entries + that had drifted out of the cheat sheet are now listed. + +### Fixed + +- **`pull_failure_sets_error_status` test flake** (`67c150b`). + The fixed 5-update budget was the last test still subject to + the AsyncComputeTaskPool starvation mode that v0.19.0's + auto-save fix already cleared. Replaced with a wall-clock- + bounded loop (5-second deadline, `std::thread::yield_now` + between iterations) that exits as soon as the status flips. + Mirrors the auto-save flake fix shape. + +### Stats + +- 1170 passing tests / 0 failing (matches v0.19.0; the + pull-failure flake fix changed the test's pumping shape but + not its count). +- Zero clippy warnings under `--workspace --all-targets -- -D warnings`. ## [0.19.0] — 2026-05-06 diff --git a/SESSION_HANDOFF.md b/SESSION_HANDOFF.md index a37f4d1..3442db4 100644 --- a/SESSION_HANDOFF.md +++ b/SESSION_HANDOFF.md @@ -1,59 +1,58 @@ # Solitaire Quest — Session Handoff -**Last updated:** 2026-05-06 (post-v0.19.0) — Tagged + pushed at -`6037596`. v0.19.0 closes the v0.18.0 punch list (async H-key hint, -persistent replay share URLs), expands desktop platform fit (Wayland -session support + monitor-aware default window size), polishes the -win-celebration and double-click animation paths, and clears two -test-flake contributors. A short-lived "Rusty Pixel" pixel-art card -theme was prototyped and reverted in the same window. +**Last updated:** 2026-05-07 — `[Unreleased]` accumulating v0.20 +candidates. Seven commits sit on top of v0.19.0: three close items +from v0.19.0's punch list (pull-failure flake, smart-window-size +opt-out, Shareable badge), one extends Help cheat-sheet coverage, +and three open a new performance / portability arc — F3 FPS +overlay, Android build target (first working APK), and the matching +developer-setup runbook. The Android build is the load-bearing +change: `solitaire_app` now compiles into both a desktop `bin` and +an Android `cdylib` from the same source. ## Status at pause -- **HEAD on origin:** `6037596` (post-tag commit; the tag itself - points at this commit). +- **HEAD on origin:** `59424a3` (post-Android-runbook commit). - **Working tree:** modified — `CHANGELOG.md` and - `SESSION_HANDOFF.md` carry the v0.19.0 promotion + this refresh, - ready to commit. + `SESSION_HANDOFF.md` carry the v0.20-candidates promotion + this + refresh, ready to commit. `artwork/` remains untracked. - **Build:** `cargo clippy --workspace --all-targets -- -D warnings` clean (verified this session). - **Tests:** **1170 passing / 0 failing** across the workspace - (verified this session). One known flake remains: - `solitaire_engine::sync_plugin::tests::pull_failure_sets_error_status` - occasionally fails when cargo-test parallelism starves the - `AsyncComputeTaskPool` within the test's 5-update budget. Same - shape as the auto-save flake before v0.19.0's hardening; could be - fixed similarly with a wall-clock-bounded loop. -- **Tags on origin:** `v0.9.0` through `v0.18.0` (v0.19.0 ready to - push once committed). + (verified this session). The previously-flaky + `pull_failure_sets_error_status` is fixed; no known flakes remain. +- **Tags on origin:** `v0.9.0` through `v0.19.0`. ## Where we are -v0.18.0's resume-prompt menu (A–D) is closed: +v0.19.0's "Possible next-round candidates" list shipped 3 of 4: -- ~~**A — Tag v0.18.0:**~~ shipped at `bfcd05f`. -- ~~**B — Solver-on-`AsyncComputeTaskPool` for the H-key hint:**~~ - shipped at `3e11e9e`. -- **C — Desktop packaging:** still gated on artwork + signing - certs. Icon export PNGs (11 sizes, 16–1024 px) sit in - `artwork/` from the v0.18-era export; not yet wired into the - Bevy window or assembled into `.icns` / `.ico`. App icon is - the first natural step. -- ~~**D — Persistent share link:**~~ shipped at `42d90b1`. +- ~~**`pull_failure_sets_error_status` flake fix:**~~ shipped at + `67c150b`. +- ~~**Settings UI for smart-default-size opt-out:**~~ shipped at + `e1b8766`. +- ~~**Persistent share link URL on selector caption:**~~ shipped at + `9b065e5` (as a "Shareable" badge on the Latest-win caption — + the Prev/Next selector chips don't have a live spawn site yet, + so the badge attaches to the existing single-replay caption). +- **App icon round** — still open. 11 PNGs generated by + `artwork/Icon Export.html` are *not* in the `artwork/` directory + any more (current `artwork/` holds the reverted Rusty Pixel + card PNGs); icon-export needs to be re-run before this item can + be picked up. -The Rusty Pixel theme arc is documented as a sub-history but -not part of v0.19.0's content: +Two new threads opened that weren't on any prior punch list: -| Commit | Status | -|---|---| -| `de47511` PNG-format thumbnail support | reverted | -| `17e3112` `pixel_art: bool` field + nearest-sampling opt-in | reverted | -| `21ec03b` bundle Rusty Pixel as `embedded://` theme | reverted | -| `aad8bb9` / `e41def8` / `0b3140a` reverts | landed | - -The arc remains in commit history for archaeology but the -codebase reaches v0.19.0's HEAD identical to where it would be if -the arc had never landed. +- **F3 FPS / frame-time overlay** (`690e1d2`). `DiagnosticsHudPlugin` + wraps `FrameTimeDiagnosticsPlugin`; F3 toggles a top-right + readout. Hidden by default; primarily a developer affordance. +- **Android build target** (`fb8b2ac` + `59424a3`). + `solitaire_app` is now a bin + cdylib hybrid. `cargo apk build` + produces a 54 MB APK with full assets. Five gating points + resolved (split, manifest, bevy feature, arboard target-gate, + keyring target-gate). What's *not* yet verified: APK launch on + AVD/phone, `dirs::data_dir()` Android behaviour for the + persistence/sync layer. ### Design direction (unchanged) @@ -67,105 +66,131 @@ the arc had never landed. `github.com/funman300/Rusty_Solitaire` is the canonical repo. Always push there. -## v0.19.0 (2026-05-06) +## v0.20 candidates ([Unreleased] in CHANGELOG) -| Area | Commits | What landed | +| Area | Commit | What landed | |---|---|---| -| Async H-key hint | `3e11e9e` | New `pending_hint.rs` module: `PendingHintTask` resource, `poll_pending_hint_task` + `drop_pending_hint_on_state_change` systems, cancel-on-replace, stale-state guard via `move_count_at_spawn`. Removes the last synchronous solver hot path. | -| Persistent share URLs | `42d90b1` | `Replay.share_url: Option` with `#[serde(default)]`. `poll_replay_upload_result` writes into `replays[0].share_url` + persists. Stats Copy button reads from selected replay. `LastSharedReplayUrl` deleted. | -| Auto-save flake fix | `91b7605` | `test_app` clears `PendingRestoredGame(None)` after plugin build; test re-arms the timer in a bounded loop. No production-code change. | -| Wayland support | `b57db01` | Adds `wayland` to Bevy features. winit prefers Wayland when `WAYLAND_DISPLAY` is set, falls back to X11. Native Wayland surface instead of XWayland frame. | -| Smart default window size | `b57db01` | New `apply_smart_default_window_size` Update system queries `PrimaryMonitor` and resizes the window to ~70 % of monitor's logical size on the first frame. Skipped when saved geometry was applied. | -| Win-celebration cleanup | `55c235b` | Drops the duplicate "You Win" toast that rendered behind the WinSummary modal. Cards-fly-off cascade kept; toast removed. | -| Double-click reject animation | `d7ffb16` | Single-card double-clicks with no destination now play the same shake + sound as multi-card stack misses. Both priorities' failure paths converge on one `MoveRejectedEvent` write. | -| Double-click animation dedup | `6037596` | Drops the redundant `StateChangedEvent` write in `end_drag`'s uncommitted-drag branch; previously raced an in-flight CardAnim and restarted the slide visibly. | +| Async-pull flake fix | `67c150b` | `pull_failure_sets_error_status` swaps a fixed 5-update budget for a 5-second wall-clock deadline + `yield_now` between pumps. Closes the last v0.19-era flake. | +| Smart window size opt-out | `e1b8766` | New `Settings::disable_smart_default_size: bool` (#[serde(default)]). `solitaire_app::main` reads the flag at startup and skips `apply_smart_default_window_size` registration when set. Settings → Gameplay row toggles it; tooltip notes saved geometry always wins. | +| Shareable badge | `9b065e5` | Stats overlay's Latest-win caption gains `\u{2022} Shareable` when the displayed replay carries a `share_url`. Badge appears on the single-replay caption (no Prev/Next live spawn site yet). | +| Help: M / P / Win-Summary-Enter | `35516d3` | Three rows added to F1 Help → Overlays. Closes coverage drift on post-v0.18 keys. | +| F3 FPS overlay | `690e1d2` | `DiagnosticsHudPlugin` + `FrameTimeDiagnosticsPlugin`. Hidden by default; F3 toggle is not gated by pause/modal state. Smoothed FPS / frame_time. Anchored top-right at `Z_SPLASH + 100`. | +| Android first APK | `fb8b2ac` | `solitaire_app` split into bin + lib (cdylib). `[package.metadata.android]` pins SDK 34 / 26. Bevy gains `android-native-activity`. `arboard` and `keyring`/`keyring-core` target-gated to non-Android. 54 MB APK builds; launch on device not yet verified. | +| Android runbook | `59424a3` | Debian 13 toolchain install, `cargo apk build` invocation, post-sign panic workaround, wired-vs-stubbed table. Fresh-clone runnable. | ## Open punch list -### Carried forward +### Carried forward from v0.19.0 -- **Desktop packaging** per `ARCHITECTURE.md §17`. Eleven icon - PNG sizes (16, 24, 32, 48, 64, 96, 128, 192, 256, 512, 1024) - exported via `artwork/Icon Export.html` sit in `artwork/` - pending wiring. Pending: actual Bevy window-icon hookup, - macOS `.icns` assembly via `iconutil`, Windows `.ico` via - `magick convert`, Linux hicolor PNG hierarchy install, - AppImage recipe, macOS notarisation cert, Windows - Authenticode cert. +- **App icon round.** `Window::icon` not yet wired; no `.icns` / + `.ico` / Linux hicolor PNG hierarchy. The 11-size icon export + the v0.19 handoff referenced is *not* currently in + `artwork/` — re-running `artwork/Icon Export.html` is the + prerequisite. Half-day task once the PNGs are back in place. + No cert dependency. -### Possible next-round candidates +### New — Phase Android (opened by `fb8b2ac` + `59424a3`) -- **App icon round** — wire the icon into the Bevy window via - `Window::icon`, generate `.icns` and `.ico` from the existing - PNGs. Half-day task; doesn't depend on signing certs. -- **`pull_failure_sets_error_status` flake fix** — same pattern - as the auto-save flake. Wall-clock-bounded loop instead of - fixed 5-update budget. ~10 lines. -- **Settings UI for "open at this size on launch"** — once the - smart-default-size system is shipping, expose a checkbox to - *disable* it (player who specifically wants 1280×800 every - time). Trivial. -- **Persistent share link URL on selector caption** — surface - whether the currently-selected replay has a `share_url` - populated (e.g. "Replay 3 / 8 \u{2022} Shareable") so players - know which entries the Copy button can copy. +- **APK launch verification on AVD / device.** `adb install` then + `adb logcat` against the bevy_test AVD. Shakes out any + Bevy/winit Android-specific runtime bugs not caught by the + build alone. +- **`dirs::data_dir()` Android port.** Persistence (settings, + stats, achievements, replays, progress) all route through this + function. Android sandboxing usually demands `getFilesDir()` via + JNI. Until verified, the APK may launch but fail to persist + anything across cold starts. +- **JNI ClipboardManager bridge.** Replaces the Android stub for + the Stats "Copy share link" toast. `arboard` doesn't ship an + Android backend, so this is a small custom JNI call. +- **Android Keystore for credentials.** `keyring` is target-gated + to a stub that returns `KeychainUnavailable`; replace with + Android Keystore via JNI when sync auth ships on mobile. +- **Google Play Games (gpgs) integration.** Listed in `solitaire_gpgs` + as a Phase-Android target since Phase 1; now unblocked by the + build target. +- **Cosmetic `cargo apk build --lib` workaround.** The + post-sign panic doesn't affect the APK on disk but produces + noisy stderr. Either upstream a cargo-apk fix or document the + `--lib` invocation as canonical in the runbook. + +### Other small candidates + +- **Cut v0.20.0** — the seven commits are a coherent bundle (3 + punch-list closes, 1 docs polish, 1 perf tool, 2 Android arc + starters). Tag whenever feels right; the Android arc has more + surface to land before it's "done", but the build-works state + is a defensible release point. +- **Prev/Next selector chips spawn site.** `9b065e5` notes + Prev/Next markers exist in `stats_plugin` but no spawn site + renders them today — the Shareable badge therefore lands on + the single-replay caption. If/when Prev/Next is plumbed to the + Stats overlay, the badge will need to follow. ### Process notes (from this round) -- **Async port template (worked again):** the H-key port - followed `d489e7a`'s `PendingNewGameSeed` shape one-to-one - and the second async port required no new infrastructure. - Future async ports (e.g. moving `try_solve_with_first_move`'s - full-search variant, if it ever surfaces in the picker UI) - should follow the same shape. -- **Rusty Pixel reverted cleanly:** `git revert` of three - contiguous feature commits produced a clean three-revert - sequence with no manual conflict resolution. Bisect remains - fast over the full v0.19.0 history because the reverts are - individual commits, not a squash. -- **Defensive event writes pattern:** the - `auto_save_writes_after_30_seconds` flake AND the - `end_drag` double-animation bug shared a root cause: - defensive `MessageWriter` writes that originally covered an - edge case which no longer holds, but became load-bearing - once another system started paying attention to the event. - Worth a periodic pass: any event write that doesn't - correspond to a real state change is a candidate for - removal. +- **Async-test starvation pattern (resolved twice now).** + Auto-save flake (v0.19) and pull-failure flake + (v0.20-candidates) shared the same root cause: a fixed + N-update budget for an `AsyncComputeTaskPool` task to surface + its result, which starves under cargo-test parallelism. The + wall-clock-bounded loop pattern (`std::thread::yield_now()` + between pumps, deadline = `Instant::now() + Duration::from_secs(5)`) + is the canonical fix. Future async-test work should reach for + this shape on the first commit, not after a flake materialises. +- **Bin → lib + thin shim refactor.** The `solitaire_app` split + for cargo-apk is a textbook cdylib-as-NativeActivity pattern. + Worth knowing as a reusable shape for any future Bevy-on-mobile + project: keep `main.rs` to ≤10 lines that delegate to + `pub fn run`, put all setup in `lib.rs`, and let the build + system pick which artifact (`bin` or `cdylib`) it needs. +- **Target-gating defensive defaults.** Three of v0.20-candidates' + Android-arc commits had to disable a default-on dependency + (`arboard`, `keyring`, `keyring-core`) for the new target. + When a future contributor adds a desktop-implicit dependency, + reaching for `[target.'cfg(not(target_os = "android"))'.dependencies]` + preemptively is cheaper than rediscovering the gating during + the next platform port. ## Resume prompt ``` You are a senior Rust + Bevy developer working on Solitaire Quest. Working directory: . -Branch: master. v0.19.0 just shipped. The next natural item is -desktop-packaging follow-through, starting with the app icon. +Branch: master. v0.19.0 is tagged on origin; seven commits sit on +top opening the v0.20 cycle (3 punch-list closes + a perf tool + +the Android build target). -State: HEAD at 6037596 + the v0.19.0 docs commit on top (this -session). Tag v0.19.0 points at the docs commit. +State: HEAD at 59424a3 + the v0.20-candidates docs commit on top +(this session). Working tree may carry the doc updates if not yet +committed. READ FIRST (in order, before doing anything): 1. SESSION_HANDOFF.md — this file - 2. CHANGELOG.md — [Unreleased] is empty; [0.19.0] just landed + 2. CHANGELOG.md — [Unreleased] holds the v0.20 draft 3. CLAUDE.md — unified-3.0 rule set 4. CLAUDE_SPEC.md — formal architecture spec 5. ARCHITECTURE.md — crate responsibilities + data flow - 6. ~/.claude/projects//memory/MEMORY.md + 6. docs/android/* — Android setup + build runbook (59424a3) + 7. ~/.claude/projects//memory/MEMORY.md — saved feedback / project context (machine-local; may be missing on a fresh machine) DECISION TO ASK THE PLAYER FIRST: - A. App icon — wire artwork/icon-{size}.png into Bevy's - Window::icon, generate .icns + .ico, drop into Linux - hicolor hierarchy. Half-day task. No cert dependency. - B. Desktop packaging continued — AppImage recipe, .desktop - file, install scripts. Larger task; unlocks distro - packaging. No cert dependency. - C. macOS / Windows signing cert acquisition — needs user - action; agent can't drive. - D. `pull_failure_sets_error_status` flake fix — small, well- - scoped. Same pattern as the v0.19.0 auto-save flake fix. + A. APK launch verification — `adb install` + `adb logcat` on + bevy_test AVD or a physical x86_64 device. Shakes out + runtime bugs the build can't catch. + B. Phase-Android persistence — port dirs::data_dir() through a + JNI getFilesDir() bridge so the APK can survive a cold + start. Largest unblocked Android piece. + C. App icon — re-run artwork/Icon Export.html, then wire + Window::icon + generate .icns / .ico. Half-day task. No + cert dependency. + D. Cut v0.20.0 — promote [Unreleased] to [0.20.0], tag, + push. Mechanical close-out; the Android arc has more + surface to land but the build-works state is a defensible + release point. WORKFLOW NOTES: - Use the system git config (already correct). @@ -173,8 +198,8 @@ WORKFLOW NOTES: "Quat" not "Rhys" (saved feedback memory). - Sub-agents stage + verify only; orchestrator commits. - Every commit must pass build / clippy / test before pushing. - - Push to GitHub (origin) — gh auth setup-git is already - wired on this machine. + - Push to GitHub (origin) — gh auth setup-git wired on + primary dev box; verify on laptop before first push. OPEN AT THE START: ask which of A–D. Don't pick unilaterally. ```