Compare commits
41 Commits
v0.10.0
...
de52c8a7b7
| Author | SHA1 | Date | |
|---|---|---|---|
| de52c8a7b7 | |||
| dcfa976dad | |||
| 71999e1062 | |||
| 5f5aba8dff | |||
| 9bfca929cb | |||
| 534870a68a | |||
| 0066ca6205 | |||
| 54e024c1b0 | |||
| 3a01318fbd | |||
| 79d391724e | |||
| ba019c0ba7 | |||
| 18d7c121a3 | |||
| cb93bd9265 | |||
| 6723416a55 | |||
| afb08799e8 | |||
| 3b619b8950 | |||
| 37681cf33e | |||
| 99064ce808 | |||
| de4dba6f98 | |||
| 75fc3aa3d6 | |||
| deb034c5fb | |||
| 242b5fef21 | |||
| 3f922ede28 | |||
| 8da62bd05f | |||
| 73cad7e205 | |||
| e14852c093 | |||
| 6240156fee | |||
| 1d9fb1884a | |||
| 97f38085e3 | |||
| 62cd1cf924 | |||
| b10e1a5a87 | |||
| 366fd6d127 | |||
| 7a77c66f6d | |||
| adece12cf1 | |||
| 2cfbc32715 | |||
| 56b37fc653 | |||
| 3ffde038c5 | |||
| ece2a55ffb | |||
| abda354562 | |||
| fbe984cf64 | |||
| efec6f22d5 |
@@ -51,6 +51,7 @@ Solitaire Quest is a cross-platform Klondike Solitaire game written in Rust, tar
|
||||
- **No panics in game logic.** Every state transition returns `Result<_, MoveError>`. Panics are only acceptable in startup/configuration code.
|
||||
- **One language, one repo.** The game client, sync client, shared types, and sync server are all Rust crates in a single Cargo workspace.
|
||||
- **Plugin-based Bevy architecture.** Each major feature is a Bevy `Plugin`. Systems are small and single-purpose. Cross-system communication uses Bevy `Event`s.
|
||||
- **UI-first interaction.** Every player-triggered action — new game, undo, draw, pause, open stats / settings / help / profile / leaderboard, etc. — must be reachable from a visible UI control. Keyboard shortcuts exist only as optional accelerators for power users; they are never the sole entry point. A player using only mouse or touch must be able to perform every action. New gameplay features ship with the UI control alongside the system that backs it.
|
||||
|
||||
---
|
||||
|
||||
@@ -67,11 +68,11 @@ solitaire_quest/
|
||||
├── Dockerfile # Multi-stage server build
|
||||
├── docker-compose.yml # Server + Caddy reverse proxy
|
||||
│
|
||||
├── assets/ # Assets embedded at compile time via include_bytes!()
|
||||
├── assets/ # Loaded at runtime via AssetServer (audio is embedded via include_bytes!())
|
||||
│ ├── cards/
|
||||
│ │ ├── faces/{rank}_{suit}.png # 52 individual card faces (120×168, generated by solitaire_assetgen)
|
||||
│ │ └── backs/back_0.png – back_4.png # placeholder patterns
|
||||
│ ├── backgrounds/bg_0.png – bg_4.png # placeholder textures
|
||||
│ │ ├── faces/{RANK}{SUIT}.png # 52 card faces — xCards @2x artwork (LGPL-3.0)
|
||||
│ │ └── backs/back_0.png – back_4.png # back_0 = xCards bicycle_blue; back_1–4 are generated patterns
|
||||
│ ├── backgrounds/bg_0.png – bg_4.png # generated textures
|
||||
│ ├── fonts/main.ttf # FiraMono-Medium (170K, OFL)
|
||||
│ └── audio/
|
||||
│ ├── card_deal.wav
|
||||
@@ -144,7 +145,7 @@ Owns:
|
||||
- All Bevy UI screens (Home, Stats, Achievements, Settings, Profile)
|
||||
- Audio playback systems
|
||||
- Sync status display
|
||||
- Card, background, and font asset loading (embedded via `include_bytes!()` — no `AssetServer` dependency)
|
||||
- Card, background, and font asset loading via Bevy `AssetServer` (audio is the lone exception — embedded via `include_bytes!()` in `audio_plugin.rs`)
|
||||
|
||||
### `solitaire_server`
|
||||
**Dependencies:** `solitaire_sync`, `axum`, `sqlx`, `jsonwebtoken`, `bcrypt`, `tower-governor`, `tracing`, `tokio`, `dotenvy`.
|
||||
@@ -235,11 +236,13 @@ Done
|
||||
|
||||
### Bevy Plugins
|
||||
|
||||
| Plugin | Key | Responsibility |
|
||||
The "Shortcut" column lists optional keyboard accelerators. Every action in this table must also be reachable from a visible UI control (button, menu item, on-screen affordance) per the UI-first design principle in §1; the shortcut is a power-user convenience, not the sole entry point.
|
||||
|
||||
| Plugin | Shortcut | Responsibility |
|
||||
|---|---|---|
|
||||
| `CardPlugin` | — | Card entity spawning, sprite management, drag-and-drop |
|
||||
| `TablePlugin` | — | Pile markers, background, layout calculation |
|
||||
| `FontPlugin` | — | Embeds FiraMono-Medium font at compile time; exposes `FontResource` handle |
|
||||
| `FontPlugin` | — | Loads FiraMono-Medium via `AssetServer` at startup; exposes `FontResource` handle |
|
||||
| `AnimationPlugin` | — | Slide, flip, win cascade, toast animations |
|
||||
| `FeedbackAnimPlugin` | — | Shake, settle, and deal-stagger animations |
|
||||
| `AutoCompletePlugin` | Enter | Executes auto-complete when the HUD badge is lit |
|
||||
@@ -248,7 +251,7 @@ Done
|
||||
| `CursorPlugin` | — | Custom cursor sprite during drag |
|
||||
| `SelectionPlugin` | — | Keyboard-driven card selection |
|
||||
| `GamePlugin` | N | Core game state resource, new-game flow, win/game-over overlays |
|
||||
| `HudPlugin` | — | Score, move counter, timer, auto-complete badge |
|
||||
| `HudPlugin` | — | Score, move counter, timer, auto-complete badge, and the top-right action button bar (Undo / Pause / Help / New Game). Each button fires the same request event the corresponding hotkey does. |
|
||||
| `StatsPlugin` | S | Stats overlay and persistence |
|
||||
| `ProgressPlugin` | — | XP/level system, persistence |
|
||||
| `AchievementPlugin` | A | Unlock evaluation, toast events, persistence |
|
||||
@@ -296,7 +299,7 @@ struct CardImageSet {
|
||||
backs: [Handle<Image>; 5], // indexed by selected_card_back setting
|
||||
}
|
||||
|
||||
// Project-wide font handle (FiraMono-Medium embedded at compile time)
|
||||
// Project-wide font handle (FiraMono-Medium loaded via AssetServer at startup)
|
||||
struct FontResource(Handle<Font>);
|
||||
|
||||
// Pre-loaded background PNG handles
|
||||
@@ -772,11 +775,13 @@ Audio systems listen for Bevy events and never block the game thread.
|
||||
|
||||
### Rendering approach
|
||||
|
||||
Cards are Bevy `Sprite` entities with `Handle<Image>` from `CardImageSet`. Face-up cards use one of 52 individual face PNGs selected by `faces[suit][rank]` — rank and suit are baked into each image and no `Text2d` overlay is spawned. Face-down cards use `backs/back_N.png` indexed by `settings.selected_card_back`. `Text2d` labels are only used as a fallback when `CardImageSet` is absent (e.g. tests with `MinimalPlugins`). `CardImageSet` is populated at startup from `include_bytes!()` — no `AssetServer`.
|
||||
Cards are Bevy `Sprite` entities with `Handle<Image>` from `CardImageSet`. Face-up cards use one of 52 individual face PNGs selected by `faces[suit][rank]` — rank and suit are baked into each image and no `Text2d` overlay is spawned. Face-down cards use `backs/back_N.png` indexed by `settings.selected_card_back`. `Text2d` labels are only used as a fallback when `CardImageSet` is absent (e.g. tests with `MinimalPlugins`). `CardImageSet` is populated at startup by `card_plugin::load_card_images` via `AssetServer::load()`.
|
||||
|
||||
Backgrounds are Bevy `Sprite` entities with `Handle<Image>` from `BackgroundImageSet`. `BackgroundImageSet` is populated at startup from `include_bytes!()`.
|
||||
Backgrounds are Bevy `Sprite` entities with `Handle<Image>` from `BackgroundImageSet`. `BackgroundImageSet` is populated at startup by `table_plugin::load_background_images` via `AssetServer::load()`.
|
||||
|
||||
The font `FiraMono-Medium` is embedded via `include_bytes!()` at startup by `FontPlugin` and exposed as `FontResource` for use by all UI and text systems.
|
||||
The font `FiraMono-Medium` is loaded via `AssetServer::load("fonts/main.ttf")` at startup by `FontPlugin` and exposed as `FontResource` for use by all UI and text systems.
|
||||
|
||||
All three loaders take `Option<Res<AssetServer>>` so they degrade cleanly under `MinimalPlugins` in tests: when the server is absent, `CardImageSet`/`BackgroundImageSet` are inserted with empty handle slots and the plugins fall back to `Text2d` rank+suit overlays and solid-colour board backgrounds. The `assets/` directory must ship alongside the binary.
|
||||
|
||||
The `assets/` directory layout:
|
||||
|
||||
@@ -1004,5 +1009,5 @@ Using `axum::test` and an in-memory SQLite database:
|
||||
| `SyncProvider` trait, not `SyncBackend` match arms | `SyncPlugin` stays backend-agnostic and testable; new backends can be added without touching the plugin | 2026-04-20 |
|
||||
| Dropped WebDAV backend | Redundant once the self-hosted server exists; removing it reduces surface area and simplifies settings UI | 2026-04-20 |
|
||||
| Dropped GPGS backend | Redundant with the self-hosted server; adds JNI complexity for no user-visible benefit on the target platforms | 2026-04-28 |
|
||||
| PNG assets embedded via `include_bytes!()` | Using `Image::from_buffer()` in startup systems rather than `AssetServer::load()` keeps the binary self-contained and eliminates runtime file-not-found errors | 2026-04-29 |
|
||||
| FiraMono-Medium font embedded via `include_bytes!()` | Exposed through `FontResource`; avoids runtime font loading errors on headless systems and ensures consistent text rendering across all platforms | 2026-04-29 |
|
||||
| Card, background, and font assets loaded via `AssetServer` | Reverses the earlier embed-via-`include_bytes!()` decision: PNGs and TTFs are loaded at runtime so artwork can be swapped (e.g. xCards @2x faces, alternate card backs, themed backgrounds) without a recompile, and binary size stays small. Loaders take `Option<Res<AssetServer>>` and fall back gracefully under `MinimalPlugins`. The `assets/` directory must ship alongside the binary. | 2026-04-29 |
|
||||
| Audio assets remain embedded via `include_bytes!()` | Audio files are small, change rarely, and the embedded path eliminates a class of runtime-load errors during gameplay; the asset-pipeline reversal does not extend to audio | 2026-04-29 |
|
||||
|
||||
@@ -47,7 +47,9 @@ cargo clippy -p solitaire_core -- -D warnings
|
||||
|
||||
- `solitaire_core` and `solitaire_sync` must never gain Bevy or network dependencies.
|
||||
- No `unwrap()` or `panic!()` in game logic. All state transitions return `Result<_, MoveError>`.
|
||||
- Audio assets are embedded at compile time using `include_bytes!()` in `audio_plugin.rs`. Cards and backgrounds are rendered procedurally (colored `Sprite` entities + text) — no image files are used and no `AssetServer` is needed.
|
||||
- Audio assets are embedded at compile time using `include_bytes!()` in `audio_plugin.rs`.
|
||||
- Card faces (52 PNGs in `assets/cards/faces/`), card backs (`assets/cards/backs/back_N.png`), board backgrounds (`assets/backgrounds/bg_N.png`), and the UI font (`assets/fonts/main.ttf`) are loaded at runtime via `AssetServer::load()` and stored as `Handle<Image>`/`Handle<Font>` in the `CardImageSet`, `BackgroundImageSet`, and `FontResource` resources. The `assets/` directory must ship alongside the binary.
|
||||
- Asset-loading systems take `Option<Res<AssetServer>>` so they degrade cleanly under `MinimalPlugins` (tests). When `CardImageSet` is absent, `card_plugin` falls back to a `Text2d` rank+suit overlay; when `BackgroundImageSet` is absent, the board falls back to a solid colour.
|
||||
- Atomic file writes only: write to `filename.json.tmp`, then `rename()`.
|
||||
- Passwords and tokens are stored in the OS keychain via the `keyring` crate — never in plaintext files or logs.
|
||||
- Sync runs on `AsyncComputeTaskPool` — never block the Bevy main thread.
|
||||
@@ -75,6 +77,7 @@ cargo clippy -p solitaire_core -- -D warnings
|
||||
- Resources own shared state. Events communicate between systems. Components own per-entity data.
|
||||
- All UI screens are built with Bevy UI (`bevy::ui`). Never mix UI layout and game logic in the same system.
|
||||
- Layout is recomputed on `WindowResized` — never assume a fixed window size.
|
||||
- **UI-first.** Every player-triggered action (new game, undo, draw, pause, open stats / settings / help / profile / leaderboard, switch mode, etc.) must be reachable from a visible UI control. Keyboard shortcuts are optional accelerators — never the sole entry point. New gameplay features ship with the UI control alongside the system that backs it; do not merge a feature that is keyboard-only.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -0,0 +1,284 @@
|
||||
# Solitaire Quest — UX Overhaul Session Handoff
|
||||
|
||||
**Last updated:** 2026-04-30 — Phase 3 complete + Phase 4 in progress (Track B landed on disk, Track G subset in flight via background agent).
|
||||
|
||||
## ⚠️ In-progress work at pause time
|
||||
|
||||
Smoke-test passed; Phase 4 was started. Pushed HEAD is `534870a`. The working tree has **uncommitted** work that is NOT pushed:
|
||||
|
||||
### Track B — window polish (on disk, ready to commit)
|
||||
|
||||
- **File:** `solitaire_app/src/main.rs` (+44 lines)
|
||||
- **What landed:**
|
||||
- X11/Wayland WM_CLASS via `Window::name = Some("solitaire-quest".into())`
|
||||
- Default position `WindowPosition::Centered(MonitorSelection::Primary)`
|
||||
- `install_crash_log_hook()` wraps the default panic hook to also append a `crash.log` next to `settings.json`. Uses `std::time::SystemTime` (no new chrono dep). Falls through silently if the data dir is unavailable.
|
||||
- **Skipped this round (deferred):**
|
||||
- App icon hookup — no artwork asset exists yet; add the loader path when art lands.
|
||||
- Persisted window geometry — needs a `Settings` schema migration.
|
||||
- F11 fullscreen toggle — already wired in `input_plugin.rs:114`, no change needed.
|
||||
- **Build status:** `cargo build -p solitaire_app` clean; `cargo clippy -p solitaire_app -- -D warnings` clean.
|
||||
- **Suggested commit subject:** `feat(app): window polish — class name, centered position, crash-log hook`
|
||||
|
||||
### Track G subset — modal open animation + score-change feedback (in flight)
|
||||
|
||||
- A **background agent** (`general-purpose`, no worktree) was launched against this turn's tree to:
|
||||
- Extend `spawn_modal` in `solitaire_engine/src/ui_modal.rs` with a `ModalEntering` component + `advance_modal_enter` system that animates scrim alpha 0 → `SCRIM` and card scale 0.96 → 1.0 over `MOTION_MODAL_SECS`. Respects `AnimSpeed::Instant` via `scaled_duration`. Animate-OUT path is intentionally out of scope.
|
||||
- In `solitaire_engine/src/hud_plugin.rs`, add a `ScorePulse` 1.0→1.1→1.0 readout pulse over `MOTION_SCORE_PULSE_SECS` and a floating "+N" Text2d (only for ≥ +50 jumps) that drifts up ~40 px and fades over `MOTION_SCORE_PULSE_SECS * 2`.
|
||||
- Tests for both behaviours.
|
||||
- **State at pause:** the agent had partial edits in `solitaire_engine/src/ui_modal.rs` (visible via `git status`) — at least one unused-import warning was already surfacing. It had not reported back when this snapshot was taken.
|
||||
- **Resume options for the next session:**
|
||||
1. **Wait for the notification.** The agent runs in background; if Claude Code is still alive, the completion notification will fire.
|
||||
2. **Inspect and finish manually.** `git diff solitaire_engine/src/ui_modal.rs solitaire_engine/src/hud_plugin.rs` to see what landed; finish or revert and restart with a tighter prompt.
|
||||
3. **Discard and restart.** `git restore solitaire_engine/src/ui_modal.rs solitaire_engine/src/hud_plugin.rs` then relaunch the agent with the prompt below.
|
||||
|
||||
### Next-session workflow at pause
|
||||
|
||||
1. Verify the workspace builds cleanly with **all** in-flight changes: `cargo build --workspace && cargo clippy --workspace -- -D warnings && cargo test --workspace`. The Track B `main.rs` change is independent — even if Track G is reverted, B compiles on its own.
|
||||
2. If Track B is clean and Track G is incomplete or broken: commit Track B first using the subject above, then deal with Track G.
|
||||
3. If both are clean: commit each as a separate landing — one feature per commit per project convention.
|
||||
4. Use:
|
||||
```
|
||||
git -c user.name=funman300 -c user.email=root@vscode.infinity commit -m "<subject>"
|
||||
```
|
||||
5. Push with `git push origin master` (requires interactive credentials on `git.aleshym.co`).
|
||||
|
||||
### Original Track G subset prompt (for relaunch if needed)
|
||||
|
||||
The agent's full brief is preserved here verbatim — paste into a fresh agent if the current one is unrecoverable:
|
||||
|
||||
```
|
||||
Two UI/UX polish items from track G. Tree clean at HEAD `534870a`.
|
||||
Sub-agents CANNOT git commit — stage your work; orchestrator commits.
|
||||
|
||||
G1. Modal open animation: extend spawn_modal in ui_modal.rs with a
|
||||
ModalEntering component + advance_modal_enter system that animates
|
||||
scrim alpha 0 → SCRIM and card scale 0.96 → 1.0 over MOTION_MODAL_SECS.
|
||||
Use scaled_duration for AnimSpeed respect; ease-out curve t*(2-t).
|
||||
Register the system in UiModalPlugin::build. Animate-OUT is OUT of
|
||||
scope. Add ≥2 tests covering ModalEntering presence on spawn and
|
||||
removal after duration elapses.
|
||||
|
||||
G2. Score-change feedback in hud_plugin.rs: ScorePulse component that
|
||||
scales the score Text 1.0→1.1→1.0 over MOTION_SCORE_PULSE_SECS using
|
||||
triangular curve. Plus a floating "+N" Text2d (only for ≥ +50 jumps)
|
||||
in ACCENT_PRIMARY that drifts up 40 px and fades over
|
||||
MOTION_SCORE_PULSE_SECS * 2. Add ≥2 tests for floater spawn on +50
|
||||
and despawn after lifetime, plus ≥1 test that +5 does NOT spawn.
|
||||
|
||||
Hard requirements: workspace build + clippy --workspace -- -D warnings
|
||||
+ test --workspace all green. Touch ONLY ui_modal.rs, hud_plugin.rs,
|
||||
optionally ui_theme.rs for new tokens (don't think you'll need any).
|
||||
DO NOT touch solitaire_app/src/main.rs (parallel work).
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Where we are (Phase 3)
|
||||
|
||||
Phase 3 of the UX overhaul brief is **done**. The whole engine has been migrated to the `ui_theme` design-token system + `ui_modal` scaffold. Animation system upgraded. Final literal sweep landed. The work spans 17 commits this session, from the foundation (`e14852c`) through to the final sweep (`54e024c`).
|
||||
|
||||
### Design direction (already saved as project memory)
|
||||
|
||||
- **Tone:** Balatro — chunky readable type, theatrical hierarchy, satisfying micro-interactions.
|
||||
- **Palette:** Midnight Purple base (`BG_BASE` `#1A0F2E` → `BG_ELEVATED` `#2D1B69` → `BG_ELEVATED_HI` `#3A2580` → `BG_ELEVATED_TOP` `#482F97`) + Balatro yellow primary accent (`ACCENT_PRIMARY` `#FFD23F`) + warm magenta secondary (`ACCENT_SECONDARY` `#FF6B9D`).
|
||||
- See [memory/project_ux_overhaul_2026-04.md](.claude/projects/-home-manage-Rusty-Solitare/memory/project_ux_overhaul_2026-04.md) for the full direction.
|
||||
|
||||
### Top complaints from the original smoke test — all closed
|
||||
|
||||
1. **HUD too cluttered.** ✅ Closed by `73cad7e` — readouts now sit in a 4-tier vertical stack with progressive disclosure of penalty/bonus tiers.
|
||||
2. **Y/N keyboard prompts feel like debug panels.** ✅ Closed across Confirm, GameOver, Pause, Forfeit, and Settings modals — every prompt now has real Primary/Secondary/Tertiary buttons with hover/press feedback.
|
||||
|
||||
## Foundation (done)
|
||||
|
||||
- **`solitaire_engine/src/ui_theme.rs`** — every design token: colours, 5-rung typography scale, 4-multiple spacing scale, three radius rungs, monotonically-ordered z-index hierarchy, motion durations with `scaled_duration(speed)` helper.
|
||||
- **`solitaire_engine/src/ui_modal.rs`** — `spawn_modal` scaffold + `spawn_modal_header` / `spawn_modal_body_text` / `spawn_modal_actions` / `spawn_modal_button` helpers + `ButtonVariant` enum (Primary / Secondary / Tertiary) + `paint_modal_buttons` system. `UiModalPlugin` registered in `solitaire_app/src/main.rs`.
|
||||
|
||||
## Commits this session (Phase 3, latest first)
|
||||
|
||||
```
|
||||
54e024c chore(engine): final literal-to-token sweep
|
||||
3a01318 feat(engine): upgrade animations — curves, scoped settle, deal jitter, cascade rotation
|
||||
79d3917 chore(data): derive Copy on AnimSpeed
|
||||
ba019c0 feat(engine): convert SettingsPanel to modal scaffold + Done button
|
||||
18d7c12 feat(engine): convert OnboardingPlugin to 3-slide modal flow
|
||||
cb93bd9 fix(engine): pin modals via GlobalZIndex and surface forfeit-no-op toast
|
||||
6723416 feat(engine): convert PauseScreen to modal + add ForfeitConfirmScreen
|
||||
afb0879 docs: add SESSION_HANDOFF.md mid-overhaul checkpoint
|
||||
3b619b8 feat(engine): convert HomeScreen to modal scaffold + Done button
|
||||
37681cf feat(engine): convert LeaderboardScreen to modal scaffold + Done button
|
||||
99064ce feat(engine): convert ProfileScreen to modal scaffold + Done button
|
||||
de4dba6 feat(engine): convert AchievementsScreen to modal scaffold + Done button
|
||||
75fc3aa feat(engine): convert StatsScreen to modal scaffold + Done button
|
||||
deb034c feat(engine): convert HelpScreen to real-button modal with kbd-chip rows
|
||||
242b5fe feat(engine): convert GameOverScreen to real-button modal
|
||||
3f922ed feat(engine): convert ConfirmNewGameScreen to real-button modal
|
||||
8da62bd feat(engine): add ui_modal primitive (scaffold + button variants)
|
||||
73cad7e feat(engine): restructure HUD into 4-tier layout, adopt design tokens
|
||||
e14852c feat(engine): add ui_theme.rs design-token module
|
||||
```
|
||||
|
||||
**Test status:** `cargo build --workspace` clean, `cargo clippy --workspace -- -D warnings` clean, **819 tests pass / 0 failed / 8 ignored**.
|
||||
|
||||
## Smoke-test checklist
|
||||
|
||||
The whole overhaul is on disk. Worth running through once end-to-end:
|
||||
|
||||
1. **Run the game.** `cargo run -p solitaire_app --features bevy/dynamic_linking`.
|
||||
2. **HUD layout** reads as 4 stacked tiers (Score / Mode / Penalty / Selection) with the new midnight-purple palette.
|
||||
3. **Open every overlay** — `S` (Stats), `A` (Achievements), `P` (Profile), `O` (Settings), `L` (Leaderboard), `M` (Home), `F1` (Help). Each is a centred card on a uniform scrim with a yellow `Done` / `Close` primary button. Hover/press states on every button.
|
||||
4. **Settings.** Four sections (Audio / Gameplay / Cosmetic / Sync). Body scrolls within the modal on small windows; `Done` button stays fixed at the bottom regardless of scroll. Card-back / Background pickers tint the selected swatch with `STATE_SUCCESS`.
|
||||
5. **Confirm flow.** Click `New Game` while a game is in progress — the abandon-current-game modal has real Cancel/Confirm buttons. `Y/Enter` and the yellow primary button start a new game; `N/Esc` and the secondary button cancel.
|
||||
6. **Pause + Forfeit.** Press `Esc` — pause modal shows real Resume / Forfeit buttons. Forfeit button opens a Cancel/Forfeit confirmation modal stacked above the pause modal (z-index ordered correctly via `GlobalZIndex`).
|
||||
7. **First-run onboarding.** Delete `settings.json` (or set `first_run_complete = false`) — three-slide flow shows: Welcome → How to play → Keyboard shortcuts. Navigate with `Next` / `Back` buttons or `→` / `←` accelerators. `Esc` skips on slide 0.
|
||||
8. **Animations.**
|
||||
- Slide a card to a pile — motion curves through `SmoothSnap` (slight overshoot + settle), not linear lerp.
|
||||
- Drop a card on a valid destination — only the moved cards bounce; the rest of the table stays still.
|
||||
- Start a new game — deal stagger is no longer mechanically uniform; cards land with subtle ±10% timing variation.
|
||||
- Win a game — cascade now uses `Expressive` curve with per-card ±15° Z-rotation, screen shake driven by the new `MOTION_WIN_SHAKE_*` tokens.
|
||||
9. **Resize the window** — cards still snap, no "snap-back-and-forth" jitter.
|
||||
10. **Win modal** — restyled with the design tokens: midnight-purple card, yellow `Play Again` button.
|
||||
|
||||
## Open follow-ups (not blockers)
|
||||
|
||||
- **Home / Help redundancy.** Home is still a kbd-reference modal that mostly duplicates Help. Three options: (1) keep as-is, (2) convert into a true mode launcher (Classic / Daily / Zen / Challenge / Time Attack cards, locked options visibly disabled below level 5), (3) drop entirely now that the action bar covers everything Home does. Worth asking the user which direction they want.
|
||||
- **Forfeit countdown toast** is now superseded by the Forfeit modal (`6723416`). Confirm the toast path is no longer reachable when smoke-testing.
|
||||
- **Sub-rung pixel sizes** (1 px borders, 64/80/110/150/160 px fixed widths, 28/36/50 px specific spacings) were intentionally left as literals during the step-10 sweep — they're below the smallest `SPACE_*` rung. If the design system grows a "fine" spacing tier in the future, those become candidates for migration.
|
||||
|
||||
## Resume prompt for the next session
|
||||
|
||||
```
|
||||
You are a senior Rust + Bevy developer working toward a public release
|
||||
of Solitaire Quest. Working directory: /home/manage/Rusty_Solitare.
|
||||
Branch: master. Apply that lens to every decision: prefer shipping
|
||||
quality (polish, packaging, defaults, credits, crash safety) over
|
||||
greenfield features. If something is half-done, the question is
|
||||
"finish for v1 or cut for v1?" not "what else can we add?".
|
||||
|
||||
State: HEAD=0066ca6. Phase 3 of the UX overhaul is shipped. cargo
|
||||
build / clippy --workspace -- -D warnings / test --workspace all
|
||||
green — 819 tests pass / 0 fail / 8 ignored.
|
||||
|
||||
READ FIRST (in order, before doing anything):
|
||||
1. SESSION_HANDOFF.md — full state, smoke-test checklist, follow-ups
|
||||
2. CLAUDE.md — hard rules (UI-first, no panics, etc.)
|
||||
3. ARCHITECTURE.md §1, §15, §17 — design principles, platform
|
||||
targets, deployment guide
|
||||
4. ~/.claude/projects/-home-manage-Rusty-Solitare/memory/MEMORY.md
|
||||
— saved feedback / project context
|
||||
|
||||
GATING SIGNAL — ASK FIRST, DON'T ASSUME:
|
||||
Before proposing new work, ask: "Did the smoke-test (items 1-10 in
|
||||
SESSION_HANDOFF.md) pass, or did anything regress?" If a regression
|
||||
exists, fix it before opening any new thread.
|
||||
|
||||
LIKELY NEXT DIRECTIONS — surface for the user to choose, don't pick
|
||||
unilaterally. All framed through "what does v1 release need?":
|
||||
|
||||
A. Home modal decision (open in SESSION_HANDOFF.md).
|
||||
- keep as kbd-reference (duplicates Help — release-blocking
|
||||
confusion?)
|
||||
- repurpose as mode launcher (Classic / Daily / Zen / Challenge /
|
||||
Time Attack cards, locked options below level 5)
|
||||
- drop (action bar already covers every action)
|
||||
|
||||
B. Window + release polish — `solitaire_app/src/main.rs:34-48`
|
||||
currently sets only title + resolution + min size. For public
|
||||
release the window needs:
|
||||
- app icon (taskbar / dock / alt-tab) — Bevy `Window::window_icon`
|
||||
or platform `set_window_icon`; ship a .png/.ico asset.
|
||||
- window class / app id (`Window::name`) so X11/Wayland and
|
||||
Windows group taskbar entries correctly.
|
||||
- persist size + position across launches (Settings already
|
||||
saves to JSON; add `window_geometry` field).
|
||||
- F11 (or a Settings toggle) wired to real fullscreen mode.
|
||||
- centered default position on first launch (Bevy supports
|
||||
`WindowPosition::Centered`).
|
||||
- present_mode + vsync verification — make sure Linux/macOS
|
||||
don't ship at uncapped 4000 fps.
|
||||
- panic hook (`std::panic::set_hook`) that writes a crash
|
||||
report next to the save files instead of silently exiting.
|
||||
- macOS Info.plist / Windows .ico bundling — ARCHITECTURE.md
|
||||
§17 currently only covers server deploy.
|
||||
|
||||
C. Sound-design audit. The scoped settle bounce (3a01318) means
|
||||
audio_plugin.rs trigger sites may fire less often than before;
|
||||
verify card_place / card_flip / card_invalid still feel right.
|
||||
|
||||
D. Sync flow end-to-end on a real second machine. Server
|
||||
scaffolding exists but the register → push → pull → restore-on-
|
||||
other-device round trip hasn't been exercised against the new
|
||||
Settings sync section.
|
||||
|
||||
E. Achievement unlock completeness. ARCHITECTURE.md §11 lists 18.
|
||||
The three hidden ones (speed_and_skill, comeback, zen_winner)
|
||||
are most likely to be untested. For release, every advertised
|
||||
achievement needs to actually fire.
|
||||
|
||||
F. Release-readiness backlog:
|
||||
- README / store-page copy / screenshots
|
||||
- LICENSE + third-party credits (xCards art, FiraMono, Bevy)
|
||||
- SemVer + a v0.1.0 git tag
|
||||
- itch.io / Steam packaging per platform (ARCHITECTURE.md §15)
|
||||
- App signing — macOS notarization, Windows Authenticode,
|
||||
Linux AppImage
|
||||
- Telemetry / crash reporting — opt-in, off by default; or
|
||||
confirm we ship without and rely on player reports
|
||||
|
||||
G. UI/UX professional polish — Phase 3 shipped the design system;
|
||||
v1 wants the difference between "consistent" and "feels
|
||||
intentional":
|
||||
- Microcopy pass: every button label, empty state, error
|
||||
message, and onboarding line reviewed for voice + clarity.
|
||||
Pick one verb per concept ("Done" vs "Close" vs "OK") and
|
||||
apply it everywhere.
|
||||
- Empty / loading / error states: Leaderboard before any
|
||||
scores, Stats before any games, Sync UI before login.
|
||||
Today these are likely blank panels.
|
||||
- Modal open/close animation: `MOTION_MODAL_SECS` token exists
|
||||
in `ui_theme.rs:255` but isn't wired up — modals
|
||||
appear/disappear instantly. Add scale-from-0.96 + scrim fade
|
||||
per the token's doc comment.
|
||||
- Tooltips on HUD readouts and settings labels. Bevy has no
|
||||
built-in tooltip; build a small one. Hover a number to learn
|
||||
what it counts.
|
||||
- Accessibility: verify the AAA-contrast claim on
|
||||
`ACCENT_PRIMARY` over `BG_BASE` (ui_theme.rs:65). Confirm
|
||||
`AnimSpeed::Instant` disables every new animation (slide
|
||||
curve, scoped settle, deal jitter, cascade rotation). Add
|
||||
focus rings on `Button` entities for keyboard navigation.
|
||||
- Typography choice: FiraMono is one weight, monospace for
|
||||
everything. Consider shipping a second proportional face for
|
||||
body + headings, keep mono for numerics (HUD score, timer).
|
||||
Or commit to mono and lean into the "calm coder" feel — pick
|
||||
deliberately and document the decision.
|
||||
- Onboarding artwork: the 3 slides are text + buttons. For
|
||||
release, stylised illustrations (or simple animated card
|
||||
props on each slide) elevate the first-launch feel.
|
||||
- Score-change feedback: floating "+N" numbers when score
|
||||
jumps; pulse on the readout when value crosses a milestone.
|
||||
`MOTION_SCORE_PULSE_SECS` is already a token.
|
||||
- Splash / loading screen: today the window goes straight to
|
||||
gameplay. A 1-2 second branded splash signals "real game"
|
||||
vs "rust prototype".
|
||||
- Hit-target audit: every interactive element ≥ 32 px on
|
||||
desktop. Settings has 28 px icon buttons (`ICON_BUTTON_PX`
|
||||
in settings_plugin.rs); revisit.
|
||||
- Win-moment design: the cascade is good; consider a score-
|
||||
breakdown reveal, streak callout, "share your time"
|
||||
affordance for v1.
|
||||
|
||||
WORKFLOW NOTES:
|
||||
- Commits use:
|
||||
git -c user.name=funman300 -c user.email=root@vscode.infinity commit -m "..."
|
||||
- Sub-agents can Edit/Write but CANNOT `git commit`. Brief them to
|
||||
stage + verify only; orchestrator commits on their behalf.
|
||||
See memory/feedback_agent_commit_limit.md.
|
||||
- Remote push needs interactive credentials on git.aleshym.co; the
|
||||
user runs `git push origin master` themselves.
|
||||
- Every commit must pass build / clippy / test. Pause-and-verify
|
||||
is the user's preferred cadence — one feature per commit.
|
||||
|
||||
OPEN AT THE START: ask (1) did smoke-test pass, (2) which of A–G to
|
||||
pursue first. Do not assume.
|
||||
```
|
||||
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 2.3 MiB |
|
After Width: | Height: | Size: 212 KiB |
|
After Width: | Height: | Size: 147 KiB |
|
After Width: | Height: | Size: 163 KiB |
|
After Width: | Height: | Size: 186 KiB |
|
Before Width: | Height: | Size: 6.1 KiB |
|
Before Width: | Height: | Size: 6.2 KiB |
|
Before Width: | Height: | Size: 6.0 KiB |
|
Before Width: | Height: | Size: 6.0 KiB |
|
After Width: | Height: | Size: 86 KiB |
|
After Width: | Height: | Size: 69 KiB |
|
After Width: | Height: | Size: 73 KiB |
|
After Width: | Height: | Size: 80 KiB |
|
Before Width: | Height: | Size: 4.5 KiB |
|
Before Width: | Height: | Size: 4.5 KiB |
|
Before Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 4.5 KiB |
|
After Width: | Height: | Size: 106 KiB |
|
After Width: | Height: | Size: 81 KiB |
|
After Width: | Height: | Size: 88 KiB |
|
After Width: | Height: | Size: 97 KiB |
|
Before Width: | Height: | Size: 4.7 KiB |
|
Before Width: | Height: | Size: 4.7 KiB |
|
Before Width: | Height: | Size: 4.6 KiB |
|
Before Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 112 KiB |
|
After Width: | Height: | Size: 81 KiB |
|
After Width: | Height: | Size: 89 KiB |
|
After Width: | Height: | Size: 100 KiB |
|
Before Width: | Height: | Size: 4.8 KiB |
|
Before Width: | Height: | Size: 4.9 KiB |
|
Before Width: | Height: | Size: 4.6 KiB |
|
Before Width: | Height: | Size: 4.8 KiB |
|
After Width: | Height: | Size: 138 KiB |
|
After Width: | Height: | Size: 103 KiB |
|
After Width: | Height: | Size: 112 KiB |
|
After Width: | Height: | Size: 124 KiB |
|
Before Width: | Height: | Size: 5.0 KiB |
|
Before Width: | Height: | Size: 5.1 KiB |
|
Before Width: | Height: | Size: 4.8 KiB |
|
Before Width: | Height: | Size: 5.0 KiB |
|
After Width: | Height: | Size: 161 KiB |
|
After Width: | Height: | Size: 120 KiB |
|
After Width: | Height: | Size: 131 KiB |
|
After Width: | Height: | Size: 145 KiB |
|
Before Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 5.3 KiB |
|
Before Width: | Height: | Size: 5.1 KiB |
|
Before Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 162 KiB |
|
After Width: | Height: | Size: 116 KiB |
|
After Width: | Height: | Size: 127 KiB |
|
After Width: | Height: | Size: 145 KiB |
|
Before Width: | Height: | Size: 5.0 KiB |
|
Before Width: | Height: | Size: 5.1 KiB |
|
Before Width: | Height: | Size: 4.9 KiB |
|
Before Width: | Height: | Size: 5.0 KiB |
|
After Width: | Height: | Size: 196 KiB |
|
After Width: | Height: | Size: 145 KiB |
|
After Width: | Height: | Size: 157 KiB |
|
After Width: | Height: | Size: 177 KiB |
|
Before Width: | Height: | Size: 5.5 KiB |
|
Before Width: | Height: | Size: 5.6 KiB |
|
Before Width: | Height: | Size: 5.3 KiB |
|
Before Width: | Height: | Size: 5.4 KiB |
|
After Width: | Height: | Size: 210 KiB |
|
After Width: | Height: | Size: 152 KiB |
|
After Width: | Height: | Size: 166 KiB |
|
After Width: | Height: | Size: 187 KiB |
|
Before Width: | Height: | Size: 5.5 KiB |
|
Before Width: | Height: | Size: 5.6 KiB |
|
Before Width: | Height: | Size: 5.4 KiB |
|
Before Width: | Height: | Size: 5.5 KiB |
|
After Width: | Height: | Size: 67 KiB |
|
After Width: | Height: | Size: 56 KiB |
|
After Width: | Height: | Size: 59 KiB |
|
After Width: | Height: | Size: 64 KiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 4.6 KiB |
|
Before Width: | Height: | Size: 4.6 KiB |
|
Before Width: | Height: | Size: 4.5 KiB |
|
Before Width: | Height: | Size: 4.6 KiB |
|
Before Width: | Height: | Size: 5.0 KiB |
|
Before Width: | Height: | Size: 5.0 KiB |
|
Before Width: | Height: | Size: 4.9 KiB |
|
Before Width: | Height: | Size: 5.0 KiB |