docs: CHANGELOG + SESSION_HANDOFF refresh for v0.14.0

CHANGELOG gains a [0.14.0] section covering 18 commits since v0.13.0
across three threads: the v0.13.0-era UX candidates that missed the
v0.13.0 tag (theme thumbnails, daily-challenge calendar, Time Attack
auto-save, per-mode bests, time-bonus slider), Quat's three bug
fixes from a smoke-test round (multi-card lift validation, softlock
detection, deal-tween information leak), and the major new replay
pipeline (record → persist → upload → web viewer with a new
solitaire_wasm crate).

The bottom-of-file compare links thread the new tag into the chain.
Test count updated to 1134.

SESSION_HANDOFF rewritten as the session 9 / post-v0.14.0 doc.
The session 8 changelog table is preserved alongside a new "v0.14.0
shipped" rollup. The next-round candidates list seeds six fresh
ideas (deferred Bevy audio trim, solver toggle, in-engine replay
playback, per-replay history, solver-driven hints, "won via
replay" achievement). Resume prompt asks A–F about smoke-test,
audio trim, solver toggle, in-engine playback, fresh UX, or
packaging.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
funman300
2026-05-05 19:44:03 +00:00
parent 1fcd032b0a
commit d948fa862a
2 changed files with 161 additions and 66 deletions
+66 -65
View File
@@ -1,20 +1,24 @@
# Solitaire Quest — UX Overhaul Session Handoff
# Solitaire Quest — Session Handoff
**Last updated:** 2026-05-02 (session 8, post-Quat smoke test) — Quat playtested and reported four bugs + two investigation tasks. Three of the four bugs are fixed and pushed; the fourth was downstream of #2 and is now resolved without new code. Replay-feature WIP is back in the working tree.
**Last updated:** 2026-05-02 (session 9, post-v0.14.0 release prep) — v0.14.0 cut. The Quat bug fixes, the rest of the v0.13.0 candidate list, and the entire replay → upload → web-viewer pipeline are all bundled in this release. Direction now opens for the next round.
## Status at pause
- **HEAD on origin:** `2716472`. Three bug-fix commits pushed (`f1aeb24`, `3eabc14`, `2716472`) on top of the v0.13.0-era HEAD `0001432`.
- **Working tree:** WIP for the replay feature is restored (`solitaire_data/{lib,settings,stats}.rs`, `solitaire_data/src/replay.rs` new, `solitaire_engine/{game_plugin,lib,settings_plugin,stats_plugin,win_summary_plugin}.rs`, `solitaire_sync/{merge,stats}.rs`). Plus untracked `CARD_PLAN.md` (intentional).
- **Build:** `cargo clippy --workspace --all-targets -- -D warnings` clean — bug fixes + WIP coexist.
- **Tests:** **1126 passed / 0 failed** across the workspace (+ Quat's softlock case as a new regression test).
- **Tags on origin:** `v0.9.0`, `v0.10.0`, `v0.11.0`, `v0.12.0`. v0.13.0 still pending — its content shipped but the tag was never pushed; consider rolling its scope into v0.14.0 along with the bug fixes + replay feature.
- **HEAD on origin:** v0.14.0's tag commit (CHANGELOG + handoff refresh).
- **Working tree:** clean apart from untracked `CARD_PLAN.md` (intentional).
- **Build:** `cargo clippy --workspace --all-targets -- -D warnings` clean.
- **Tests:** **1134 passed / 0 failed** across the workspace.
- **Tags on origin:** `v0.9.0`, `v0.10.0`, `v0.11.0`, `v0.12.0`, `v0.13.0`, `v0.14.0`.
## Where we are
Post-v0.12.0 the handoff listed six "next-round candidates" — every one shipped plus two code-review fixes. v0.13.0 was prepared but not pushed. Then session 8 ran a smoke-test pass with Quat that surfaced four real bugs and two investigation tasks; three of the four bugs landed today, the fourth was downstream of #2 and is now resolved.
v0.14.0 is the largest release since the card-theme system. Three threads land together:
Direction is open.
1. **The remaining v0.13.0-era UX candidates** — theme thumbnails, daily-challenge calendar, Time Attack auto-save, per-mode bests, time-bonus multiplier slider.
2. **Quat smoke-test bug fixes** — multi-card move validation, softlock detection, deal-tween information leak.
3. **The replay pipeline** — record on win, persist to disk, upload to server, view in browser via a new `solitaire_wasm` crate. The biggest single feature since the card-theme system.
The card-flight web animations and replay E2E test coverage close out the pipeline.
### Design direction (unchanged)
@@ -26,61 +30,64 @@ Direction is open.
`github.com/funman300/Rusty_Solitaire` is the canonical repo. Always push there.
## Session 7 round 3 (shipped 2026-05-02 late-late) — v0.13.0
## Session 8 + 9 (shipped 2026-05-02) — v0.14.0
### v0.13.0-era UX candidates (had landed but missed v0.13.0's tag)
| Area | Commit | What landed |
|---|---|---|
| Font fix | `17f9b51` | Code-review fix: bundle FiraMono via `include_bytes!()` in both `font_plugin` and `svg_loader`; drop `load_system_fonts`, drop the lenient resolver, drop the CSS-generic fallbacks. New `bundled_font_resolver` always returns the single bundled face. Parse failure aborts with a clear error. |
| sccache removal | `13dd44b` | Code-review fix: deleted `.cargo/config.toml` and the `.cargo` directory. Plain `cargo build` works without per-project setup. |
| Wave 1 bundle | `ddc8f27` | **Tooltip-delay slider** in Settings → Gameplay (0.01.5 s, 0.1 s steps, "Instant" label at zero). **Win-streak fire animation** at thresholds [3, 5, 10] via new `WinStreakMilestoneEvent`. **Score-breakdown reveal on win modal** with per-row stagger (Base / Time bonus / No-undo / Multiplier / Total), respects `AnimSpeed::Instant`. |
| Card-back theming | `7ed4f2c` | The active theme's `back.svg` now actually drives the face-down sprite. Legacy `back_N.png` picker remains as a fallback for themes without a back; Settings caption surfaces when the override is in effect. |
| Drag-with-keyboard | `a0fc0d2` | Tab → Enter → arrows → Enter completes a move without a mouse. New `KeyboardDragState` resource; mutual exclusion with mouse drag via `KEYBOARD_DRAG_TOUCH_ID` sentinel. Help + onboarding hotkey lists updated. |
| Right-click radial | `b37f0cb` | Hold RMB on a face-up card → ring of icons at the cursor, one per legal destination; release over an icon → `MoveRequestEvent`. New `RadialMenuPlugin`. Help controls reference gains a "Mouse" section. |
| Theme thumbnails | `ba527de` | Each Settings → Cosmetic theme chip renders an Ace + back preview pair via `rasterize_svg`. Cached per theme. Missing-SVG themes show a transparent placeholder rather than crashing. |
| Daily-challenge calendar | `1a10476` | 14-dot horizontal calendar in the Profile modal. Today is ringed, completed days fill `STATE_SUCCESS`, missed days fill `BG_ELEVATED`. Caption: "Current streak: N · Longest: M". `PlayerProgress` gains `daily_challenge_history` (capped at 365) and `daily_challenge_longest_streak`. |
| Time Attack auto-save | `0001432` | New sibling `time_attack_session.json` next to `game_state.json`. Atomic .tmp + rename. 30 s auto-save while active + on `AppExit`. Sessions whose 10-min window expired in real time while the app was closed are discarded on load. |
| Per-mode bests | `3984231` | StatsSnapshot gains six `#[serde(default)]` fields (Classic / Zen / Challenge × best_score + fastest_win_seconds). Stats screen renders a "Per-mode bests" section. Lifetime totals continue to roll all modes together. |
| Time-bonus slider | `89c51ab` | Settings → Gameplay slider 0.02.0, default 1.0, "Off" at zero. Multiplies the time-bonus shown in the win modal. Cosmetic only — does NOT affect achievement unlock thresholds. |
## Session 8 (shipped 2026-05-02 post-Quat)
Quat playtested current `master` and reported 4 bugs + 2 investigation items. Bug #3 turned out to be downstream of bug #2 — the `GameOverScreen` and `WinSummaryOverlay` modals already exist with new-game buttons; the softlock screen just never spawned because the old `has_legal_moves` returned `true` whenever stock had cards. With #2 fixed, the existing screen will fire for Quat's case. Smoke-test verification on the player side is the last step.
### Quat smoke-test bug fixes
| Area | Commit | What landed |
|---|---|---|
| Move validation (#1) | `f1aeb24` | `solitaire_core::rules::is_valid_tableau_sequence(&[Card]) -> bool` checks every adjacent pair in a moved stack descends one rank with alternating colour. Wired into `move_cards`'s tableau-destination branch. Closes the bug where a player could lift an arbitrary multi-card selection and drop it as long as the bottom landed legally. One focused test (single-card / valid-run / same-colour / rank-gap). |
| Deal-tween leak (#4) | `3eabc14` | `handle_new_game` now snaps every existing card sprite to the stock pile's position before writing `StateChangedEvent`. The downstream slide tween in `card_plugin` reads the stock position as its source, so all 52 cards animate from a single point — reads as "dealing from the deck" with no information leak. Gated on `Option<Res<LayoutResource>>` for headless tests. |
| Softlock detection (#2) | `2716472` | `has_legal_moves` rewritten: replaces the early-return-on-non-empty-stock heuristic with a single pass over every card that could ever become a move source (every stock card, every waste card, the face-up top of every tableau column). Each is checked against every foundation and every tableau. Returns `true` only if some card anywhere can land somewhere — otherwise the player is genuinely stuck no matter how many recycles. Fresh-game test renamed; new test reproduces Quat's exact case (foundation 0 at 10, stock holds Hearts 25, no legal landing). |
| End-game screen (#3) | — | Resolved as downstream of #2. Verified `GameOverScreen` (game_plugin.rs:636) shows "No more moves available" + final score + Undo + New Game buttons; `WinSummaryOverlay` (win_summary_plugin.rs) shows the breakdown + Play Again. Both pre-existed; the softlock path just wasn't being reached. |
| Move validation (#1) | `f1aeb24` | `solitaire_core::rules::is_valid_tableau_sequence(&[Card]) -> bool` checks every adjacent pair in a moved stack descends one rank with alternating colour. Wired into `move_cards`. Closes the bug where any multi-card lift could be dropped as long as the bottom landed legally. |
| Deal-tween leak (#4) | `3eabc14` | New-game snaps every card sprite to the stock pile position before writing `StateChangedEvent`, so all 52 cards animate from a single deck point during the deal. Previously sprites started from previous-game positions, briefly revealing the prior deal. |
| Softlock detection (#2) | `2716472` | `has_legal_moves` rewritten: walks every potential move source (every stock card, every waste card, the face-up top of every tableau column) against every foundation and every tableau. Previous heuristic returned `true` whenever stock had cards, hiding genuine softlocks. `GameOverScreen` now actually fires for true softlocks. |
| End-game screen (#3) | — | Resolved as downstream of #2. The pre-existing `GameOverScreen` and `WinSummaryOverlay` already cover the close-out paths; the softlock screen just never spawned because the old `has_legal_moves` lied. |
### Investigation findings
### Replay pipeline (the major feature)
**Solver / unwinnable-deals decision (Quat report):** still open — needs your call. Three options Quat outlined: (a) accept some deals are unwinnable, (b) run a solver at deal-time and only ship winnable layouts, (c) offer a "winnable deals only" mode. (b) is the modern-Solitaire standard but adds a dependency or hand-rolled solver (~5001500 LOC). (c) is the lightest middle ground — keep classic deals available, add a Settings toggle. Recommendation: defer until other UX work settles; doesn't block any release.
| Area | Commit | What landed |
|---|---|---|
| Replay storage | `42535f5` | `solitaire_data::replay::Replay` (seed + draw_mode + mode + score + time + recorded date + ordered move list) and atomic save/load helpers under `<data_dir>/latest_replay.json`. Schema v1; `load` returns None for any other version. |
| Engine recording | `57d1c58` | `RecordingReplay` resource + `ReplayPath` settings. Every successful `MoveRequestEvent` / `DrawRequestEvent` appends to recording; `GameWonEvent` freezes the recording into a `Replay` and persists. Undo intentionally not recorded. New game clears the recording. |
| Stats button | `d9f36bf` | Stats overlay surfaces a "Latest win:" caption + "Watch replay" button. Loads from disk via `LatestReplayResource`. (Full in-engine playback deferred — button currently fires an `InfoToastEvent` describing the replay.) |
| Server upload + fetch | `93182fa` | `POST /api/replays` accepts a `Replay` JSON; `GET /api/replays/:id` returns it. JWT-gated. SQL migration for the new `replays` table. |
| Engine sync | `23c9704` | Engine uploads winning replays automatically when the player has cloud sync configured. Re-uses the existing JWT/refresh-token flow. |
| WASM crate | `5bed43e` | New workspace member `solitaire_wasm` compiles replay-relevant `solitaire_core` types to WebAssembly so a browser can re-execute a replay client-side. `wasm-bindgen` glue. |
| Web viewer | `07b8ecd` | `GET /replays/:id` returns HTML + CSS + the wasm bundle. Browser fetches the replay JSON, rasterises a deal from the seed, and animates the recorded moves. |
| E2E coverage | `3081505` | Server tests covering the full upload → fetch round-trip via `axum::test`. |
| Web flight anim | `1fcd032` | Card-flight tweens on the web side so the browser viewer reads as a real game replay rather than a static dump. |
**Dependency duplicates (Quat report — 1014 deps):** the biggest single bloat is the audio stack split. Bevy's default features pull `bevy_audio → rodio → cpal 0.15 + alsa 0.9 + symphonia N codecs`, while the project actually uses **kira** for sound (`cpal 0.17 + alsa 0.10`). Disabling Bevy's default `bevy_audio` feature would eliminate 20+ transitive crates including the rodio + symphonia chain. Other duplicates are minor (bitflags 1.x via `png` is build-tooling only; multiple hashbrown majors are common in the Bevy/wgpu ecosystem and not actionable). Recommendation: a one-line `default-features = false` swap on the workspace `bevy =` line, then re-enable explicitly the features the engine uses (`render`, `bevy_winit`, `2d`, `bevy_window`, `png`, `bevy_text`, `bevy_ui`, `bevy_log`, `bevy_asset`, `default_font`, `bevy_state`, `webgpu`/`webgl2` if targeting wasm). Worth ~50 fewer crates compiled. Defer until the active feature work settles so churn doesn't conflict.
## Open punch list
## Open punch list — release prep
### Release prep
1. **Smoke-test on the alex machine** after pulling — confirm Quat's three bug fixes hold up in real gameplay, and try the new replay button + web viewer end-to-end.
2. **Desktop packaging** per `ARCHITECTURE.md §17`. The Arch PKGBUILD exists in `/home/manage/solitaire-quest-pkgbuild/` (separate repo). Pending: app icon, macOS `.icns` + notarisation cert, Windows `.ico` + Authenticode cert, AppImage recipe.
1. **Push** the unpushed feature commits to origin (5 still unpushed: `b37f0cb`, `a0fc0d2`, `7ed4f2c`, `ddc8f27`, `13dd44b`/`17f9b51` — those last two were on the v0.13.0 round, never pushed; verify with `git log --oneline origin/master..HEAD` after committing replay).
2. **Roll v0.13.0 + replay + bug fixes into v0.14.0** rather than tagging two close releases. The bug fixes alone aren't a feature release; bundle them with the replay feature when it lands.
3. **Desktop packaging** per `ARCHITECTURE.md §17`. The Arch PKGBUILD exists in `/home/manage/solitaire-quest-pkgbuild/` (separate repo). Pending: app icon, macOS `.icns` + notarisation cert, Windows `.ico` + Authenticode cert, AppImage recipe.
4. **Smoke-test the bug fixes** on the alex machine after pulling: confirm (a) tableau-to-tableau invalid-stack moves are now rejected, (b) the new-game deal animates from a single deck position with no per-card origin leak, (c) softlock with unplayable stock now spawns the GameOverScreen.
### UX iteration (next-round candidates)
## Open punch list — UX iteration (next-round candidates)
Several v0.13.0-era candidates have already shipped to master since the v0.13.0 doc commit: **daily-challenge calendar** (`1a10476`), **card-art thumbnails in the theme picker** (`ba527de`), **auto-save in Time Attack** (`0001432`). Replay is **WIP in the working tree**`solitaire_data/src/replay.rs` plus modifications across stats/settings/win-summary plugins. Not yet committed.
Fresh candidates not yet started:
- **Per-mode high-score readout** in the Stats screen. Currently lifetime stats roll all modes together.
- **Auto-save Zen mode** alongside Time Attack so close-mid-session resumes work in both.
- **Configurable scoring weights** — Settings → Gameplay slider for time-bonus magnitude. Cosmetic but power-user appealing.
- **Solver-at-deal toggle** (Quat's investigation #1, deferred): per the recommendation in the session-8 findings, add a Settings toggle "Winnable deals only" rather than baking solver-only into all deals. Lightest middle ground.
- **Disable Bevy's default audio feature** (Quat's investigation #2, deferred) to drop ~50 transitive crates. One-line workspace edit then re-enable engine features explicitly. Defer until active feature work settles.
- **Solver-at-deal toggle** (Quat investigation #1, still deferred): add a Settings → Gameplay toggle "Winnable deals only" rather than baking solver-only into every deal. Lightest middle ground.
- **Disable Bevy's default audio feature** (Quat investigation #2, still deferred): one-line `default-features = false` swap on the workspace `bevy =` line, re-enable explicitly the features the engine uses (`render`, `bevy_winit`, `2d`, `bevy_window`, `png`, `bevy_text`, `bevy_ui`, `bevy_log`, `bevy_asset`, `default_font`, `bevy_state`). Drops ~50 transitive crates including the rodio + symphonia stack the project doesn't use (kira handles audio).
- **In-engine replay playback** — promote the "Watch replay" button from a stub toast to a real playback overlay that re-runs the recorded moves with `CardAnimation` tweens. The wasm crate already proves the playback math; the in-engine version reuses the same execute logic against the live game state.
- **Per-replay history** — currently single-slot at `latest_replay.json`. A "best replay per mode" bucket or a recent-N rolling list would let players revisit notable wins.
- **Solver-driven hint system** — extend the existing hint toggle so a deal-time solver provides higher-quality hints (currently a heuristic). Requires the solver from the toggle work above.
- **Achievement: "won via replay path"** — track when a player wins a deal whose previously-saved replay also won the same deal. Mostly fun; trivial scope.
## Card-theme system (CARD_PLAN.md, fully shipped)
Seven phases landed across `b8fb3fb``924a1e2` in v0.11.0; v0.13.0's `7ed4f2c` finally consumes the per-theme `back.svg`. End-to-end:
Seven phases landed across `b8fb3fb``924a1e2` in v0.11.0; v0.13.0's `7ed4f2c` consumes the per-theme `back.svg`; v0.14.0's `ba527de` adds preview thumbnails. End-to-end:
- **Bundled default theme** ships in the binary via `embedded://` — 52 hayeah/playing-cards-assets SVGs + a midnight-purple `back.svg`.
- **User themes** under `themes://`. Drop a directory containing `theme.ron` + 53 SVGs.
- **Importer** at `solitaire_engine::theme::import_theme(zip)` validates archives and atomically unpacks.
- **Picker UI** in Settings → Cosmetic; the active theme's `back` overrides the legacy `back_N.png` picker when present.
- **Picker UI** in Settings → Cosmetic; thumbnails + the active theme's `back` override the legacy `back_N.png` picker when present.
## Resume prompt
@@ -88,22 +95,17 @@ Seven phases landed across `b8fb3fb` → `924a1e2` in v0.11.0; v0.13.0's `7ed4f2
You are a senior Rust + Bevy developer working on Solitaire Quest.
Working directory: <Rusty_Solitaire clone path on this machine — local
directory may still be named Rusty_Solitare from earlier; that's fine>.
Branch: master. Direction is OPEN — Quat's smoke-test bug round
landed (3 fixes pushed, 1 resolved as downstream); replay feature is
WIP in the working tree.
Branch: master. Direction is OPEN — v0.14.0 just shipped covering the
Quat bug fixes, the v0.13.0 candidate tail, and the entire
replay-pipeline feature.
State: origin/master at 2716472 (Quat's softlock fix). Working tree
has uncommitted replay-feature WIP across solitaire_data,
solitaire_engine, solitaire_sync — `solitaire_data/src/replay.rs` is
new. Plus untracked CARD_PLAN.md (intentional). Five feature commits
from v0.13.0 round are unpushed (b37f0cb, a0fc0d2, 7ed4f2c, ddc8f27,
plus the v0.13.0 doc commits) — verify with
`git log --oneline origin/master..HEAD`.
State: HEAD at v0.14.0. Working tree clean apart from untracked
CARD_PLAN.md (intentional).
Build: cargo clippy --workspace --all-targets -- -D warnings clean.
Tests: 1126 passed / 0 failed (includes Quat's softlock regression).
Tests: 1134 passed / 0 failed.
READ FIRST (in order, before doing anything):
1. SESSION_HANDOFF.md — session 8 changelog + punch list
1. SESSION_HANDOFF.md — v0.14.0 changelog + open punch list
2. CHANGELOG.md — release-by-release record
3. CLAUDE.md — hard rules (UI-first, no panics, etc.)
4. ARCHITECTURE.md — crate responsibilities + data flow
@@ -112,15 +114,14 @@ READ FIRST (in order, before doing anything):
may be missing on a fresh machine)
DECISION TO ASK THE PLAYER FIRST:
A. Finish the replay-feature WIP, commit, then bundle everything
(replay + v0.13.0 round + Quat fixes) into v0.14.0.
B. Smoke-test the bug fixes on alex first to confirm Quat's three
issues are resolved in real gameplay.
C. Take the deferred Bevy-audio-feature trim (Quat investigation
#2): drop default-features and re-enable explicitly. Worth ~50
fewer crates.
D. Take the deferred solver toggle (Quat investigation #1): add
"Winnable deals only" Settings toggle.
A. Smoke-test v0.14.0 on the alex machine first to confirm the
three Quat bug fixes hold up in real gameplay and the replay
pipeline works end-to-end (record → upload → web viewer).
B. Take the deferred Bevy-audio-feature trim (Quat investigation
#2) — one-line workspace edit, ~50 fewer transitive crates.
C. Take the deferred solver toggle (Quat investigation #1): add
"Winnable deals only" Settings toggle. Larger.
D. Promote the in-engine "Watch replay" button to real playback.
E. Pick from the remaining "next-round candidates" in this doc.
F. Take the deferred desktop-packaging item (needs artwork +
signing certs from the user).