Compare commits
62 Commits
v0.10.0
...
1719fdada0
| Author | SHA1 | Date | |
|---|---|---|---|
| 1719fdada0 | |||
| 8dda9541a3 | |||
| 60a80369d4 | |||
| dbe6c60133 | |||
| 74597a8c84 | |||
| 5d57b67934 | |||
| 220e3f040c | |||
| 54d34972d4 | |||
| 0c86cac2d5 | |||
| 2e080d02ce | |||
| 73e210b243 | |||
| f866299021 | |||
| b78a493a0c | |||
| 51d3454344 | |||
| 12789529a1 | |||
| c1bde18a2c | |||
| fd7fb7b6da | |||
| 138436558f | |||
| 65d595ad12 | |||
| abeb4e5cdf | |||
| b082bd65a6 | |||
| 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.
|
- **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.
|
- **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.
|
- **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
|
├── Dockerfile # Multi-stage server build
|
||||||
├── docker-compose.yml # Server + Caddy reverse proxy
|
├── 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/
|
│ ├── cards/
|
||||||
│ │ ├── faces/{rank}_{suit}.png # 52 individual card faces (120×168, generated by solitaire_assetgen)
|
│ │ ├── faces/{RANK}{SUIT}.png # 52 card faces — xCards @2x artwork (LGPL-3.0)
|
||||||
│ │ └── backs/back_0.png – back_4.png # placeholder patterns
|
│ │ └── 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 # placeholder textures
|
│ ├── backgrounds/bg_0.png – bg_4.png # generated textures
|
||||||
│ ├── fonts/main.ttf # FiraMono-Medium (170K, OFL)
|
│ ├── fonts/main.ttf # FiraMono-Medium (170K, OFL)
|
||||||
│ └── audio/
|
│ └── audio/
|
||||||
│ ├── card_deal.wav
|
│ ├── card_deal.wav
|
||||||
@@ -132,7 +133,7 @@ Owns:
|
|||||||
- `SyncProvider` trait — implemented by `SolitaireServerClient`
|
- `SyncProvider` trait — implemented by `SolitaireServerClient`
|
||||||
|
|
||||||
### `solitaire_engine`
|
### `solitaire_engine`
|
||||||
**Dependencies:** `bevy`, `bevy_kira_audio`, `solitaire_core`, `solitaire_data`.
|
**Dependencies:** `bevy`, `kira`, `solitaire_core`, `solitaire_data`.
|
||||||
|
|
||||||
All Bevy-specific code. Structured as a collection of Plugins that `solitaire_app` registers.
|
All Bevy-specific code. Structured as a collection of Plugins that `solitaire_app` registers.
|
||||||
|
|
||||||
@@ -144,7 +145,7 @@ Owns:
|
|||||||
- All Bevy UI screens (Home, Stats, Achievements, Settings, Profile)
|
- All Bevy UI screens (Home, Stats, Achievements, Settings, Profile)
|
||||||
- Audio playback systems
|
- Audio playback systems
|
||||||
- Sync status display
|
- 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`
|
### `solitaire_server`
|
||||||
**Dependencies:** `solitaire_sync`, `axum`, `sqlx`, `jsonwebtoken`, `bcrypt`, `tower-governor`, `tracing`, `tokio`, `dotenvy`.
|
**Dependencies:** `solitaire_sync`, `axum`, `sqlx`, `jsonwebtoken`, `bcrypt`, `tower-governor`, `tracing`, `tokio`, `dotenvy`.
|
||||||
@@ -235,20 +236,22 @@ Done
|
|||||||
|
|
||||||
### Bevy Plugins
|
### 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 |
|
| `CardPlugin` | — | Card entity spawning, sprite management, drag-and-drop |
|
||||||
| `TablePlugin` | — | Pile markers, background, layout calculation |
|
| `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 |
|
| `AnimationPlugin` | — | Slide, flip, win cascade, toast animations |
|
||||||
| `FeedbackAnimPlugin` | — | Shake, settle, and deal-stagger animations |
|
| `FeedbackAnimPlugin` | — | Shake, settle, and deal-stagger animations |
|
||||||
| `AutoCompletePlugin` | Enter | Executes auto-complete when the HUD badge is lit |
|
| `AutoCompletePlugin` | Enter | Executes auto-complete when the HUD badge is lit |
|
||||||
| `AudioPlugin` | — | Sound effect and music playback via bevy_kira_audio |
|
| `AudioPlugin` | — | Sound effect and music playback via kira |
|
||||||
| `InputPlugin` | — | Keyboard and mouse input routing |
|
| `InputPlugin` | — | Keyboard and mouse input routing |
|
||||||
| `CursorPlugin` | — | Custom cursor sprite during drag |
|
| `CursorPlugin` | — | Custom cursor sprite during drag |
|
||||||
| `SelectionPlugin` | — | Keyboard-driven card selection |
|
| `SelectionPlugin` | — | Keyboard-driven card selection |
|
||||||
| `GamePlugin` | N | Core game state resource, new-game flow, win/game-over overlays |
|
| `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 |
|
| `StatsPlugin` | S | Stats overlay and persistence |
|
||||||
| `ProgressPlugin` | — | XP/level system, persistence |
|
| `ProgressPlugin` | — | XP/level system, persistence |
|
||||||
| `AchievementPlugin` | A | Unlock evaluation, toast events, persistence |
|
| `AchievementPlugin` | A | Unlock evaluation, toast events, persistence |
|
||||||
@@ -296,7 +299,7 @@ struct CardImageSet {
|
|||||||
backs: [Handle<Image>; 5], // indexed by selected_card_back setting
|
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>);
|
struct FontResource(Handle<Font>);
|
||||||
|
|
||||||
// Pre-loaded background PNG handles
|
// Pre-loaded background PNG handles
|
||||||
@@ -751,7 +754,7 @@ Levels 11+: level = 10 + floor((total_xp - 5000) / 1000)
|
|||||||
|
|
||||||
## 13. Audio System
|
## 13. Audio System
|
||||||
|
|
||||||
Audio uses `bevy_kira_audio`. All sound files are `.wav`.
|
Audio uses `kira`. All sound files are `.wav`.
|
||||||
|
|
||||||
| File | Trigger |
|
| File | Trigger |
|
||||||
|---|---|
|
|---|---|
|
||||||
@@ -762,7 +765,7 @@ Audio uses `bevy_kira_audio`. All sound files are `.wav`.
|
|||||||
| `win_fanfare.wav` | Game won |
|
| `win_fanfare.wav` | Game won |
|
||||||
| `ambient_loop.wav` | Looping background music |
|
| `ambient_loop.wav` | Looping background music |
|
||||||
|
|
||||||
Volume is controlled by two independent sliders in Settings (`sfx_volume`, `music_volume`), each stored in `Settings` and applied as `bevy_kira_audio` channel volumes.
|
Volume is controlled by two independent sliders in Settings (`sfx_volume`, `music_volume`), each stored in `Settings` and applied as `kira` channel volumes.
|
||||||
|
|
||||||
Audio systems listen for Bevy events and never block the game thread.
|
Audio systems listen for Bevy events and never block the game thread.
|
||||||
|
|
||||||
@@ -772,11 +775,13 @@ Audio systems listen for Bevy events and never block the game thread.
|
|||||||
|
|
||||||
### Rendering approach
|
### 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:
|
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 |
|
| `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 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 |
|
| 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 |
|
| 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 |
|
||||||
| 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 |
|
| 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.
|
- `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>`.
|
- 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()`.
|
- 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.
|
- 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.
|
- 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.
|
- 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.
|
- 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.
|
- 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,104 @@
|
|||||||
|
# Credits
|
||||||
|
|
||||||
|
Solitaire Quest is MIT-licensed (see [LICENSE](LICENSE)). It is built on top of
|
||||||
|
the work of many open-source projects and a small handful of third-party
|
||||||
|
assets. This file lists every component that ships in the binary or in the
|
||||||
|
`assets/` directory.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Code & Framework
|
||||||
|
|
||||||
|
| Component | License | Role |
|
||||||
|
|---|---|---|
|
||||||
|
| [Bevy 0.18](https://bevyengine.org/) | MIT OR Apache-2.0 | Game engine, ECS, rendering, UI |
|
||||||
|
| [kira 0.12](https://crates.io/crates/kira) | MIT OR Apache-2.0 | Audio playback (mixer, sub-tracks, looping ambient) |
|
||||||
|
| [serde](https://crates.io/crates/serde) / [serde_json](https://crates.io/crates/serde_json) | MIT OR Apache-2.0 | Serialization for save files and the sync API |
|
||||||
|
| [tokio](https://crates.io/crates/tokio) | MIT | Async runtime for the sync client and server |
|
||||||
|
| [axum 0.8](https://crates.io/crates/axum) | MIT | HTTP framework for the self-hosted sync server |
|
||||||
|
| [sqlx 0.8](https://crates.io/crates/sqlx) | MIT OR Apache-2.0 | Compile-time-checked SQLite access on the server |
|
||||||
|
| [reqwest 0.13](https://crates.io/crates/reqwest) | MIT OR Apache-2.0 | HTTP client for the sync provider |
|
||||||
|
| [jsonwebtoken 10](https://crates.io/crates/jsonwebtoken) | MIT | JWT issuance and validation |
|
||||||
|
| [bcrypt 0.19](https://crates.io/crates/bcrypt) | MIT | Password hashing on the server |
|
||||||
|
| [keyring 4](https://crates.io/crates/keyring) | MIT OR Apache-2.0 | OS keychain integration for credential storage |
|
||||||
|
| [tower-governor 0.8](https://crates.io/crates/tower-governor) | MIT | Rate limiting on `/api/auth/*` |
|
||||||
|
| [chrono](https://crates.io/crates/chrono) | MIT OR Apache-2.0 | Date / time handling |
|
||||||
|
| [uuid](https://crates.io/crates/uuid) | MIT OR Apache-2.0 | User and session identifiers |
|
||||||
|
| [thiserror](https://crates.io/crates/thiserror) | MIT OR Apache-2.0 | Error type derive |
|
||||||
|
| [rand 0.9](https://crates.io/crates/rand) | MIT OR Apache-2.0 | Seeded shuffler in `solitaire_core` |
|
||||||
|
| [png 0.17](https://crates.io/crates/png) | MIT OR Apache-2.0 | PNG encoder used by `solitaire_assetgen` |
|
||||||
|
| [ab_glyph 0.2](https://crates.io/crates/ab_glyph) | Apache-2.0 | Glyph rasterization for generated card art |
|
||||||
|
|
||||||
|
The full transitive dependency tree (several hundred crates) is captured in
|
||||||
|
`Cargo.lock` and reachable via `cargo tree`. Every crate brought in is
|
||||||
|
MIT, Apache-2.0, BSD-style, or a dual-licensed combination thereof — no
|
||||||
|
copyleft code is statically linked into the game binary.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Assets
|
||||||
|
|
||||||
|
### Card artwork
|
||||||
|
|
||||||
|
| File(s) | Source | License |
|
||||||
|
|---|---|---|
|
||||||
|
| `assets/cards/faces/{RANK}{SUIT}.png` (52 PNGs) | xCards @2x artwork | LGPL-3.0 |
|
||||||
|
| `assets/cards/backs/back_0.png` (bicycle_blue) | xCards @2x artwork | LGPL-3.0 |
|
||||||
|
| `assets/cards/backs/back_1.png` – `back_4.png` | Original — generated by `solitaire_assetgen::gen_art` | MIT (this project) |
|
||||||
|
|
||||||
|
xCards is the playing-card artwork bundle by Huub de Beer, published under the
|
||||||
|
LGPL-3.0. The art is consumed as unmodified PNG files at runtime; the game
|
||||||
|
binary statically links no LGPL code, so distribution as a self-contained
|
||||||
|
binary plus the `assets/` directory satisfies the LGPL's relinking clause.
|
||||||
|
|
||||||
|
### Backgrounds
|
||||||
|
|
||||||
|
| File(s) | Source | License |
|
||||||
|
|---|---|---|
|
||||||
|
| `assets/backgrounds/bg_0.png` – `bg_4.png` | Original — generated by `solitaire_assetgen::gen_art` | MIT (this project) |
|
||||||
|
|
||||||
|
### Typography
|
||||||
|
|
||||||
|
| File | Source | License |
|
||||||
|
|---|---|---|
|
||||||
|
| `assets/fonts/main.ttf` (FiraMono-Medium) | [mozilla/Fira](https://github.com/mozilla/Fira) | SIL Open Font License 1.1 |
|
||||||
|
|
||||||
|
The OFL permits redistribution and embedding in software so long as the font
|
||||||
|
file itself is not sold standalone. The file ships unmodified.
|
||||||
|
|
||||||
|
### Audio
|
||||||
|
|
||||||
|
All six WAV files in `assets/audio/` are **original work** — there are no
|
||||||
|
third-party audio samples in this project. They are synthesized
|
||||||
|
programmatically by `solitaire_assetgen/src/bin/gen_sfx.rs`, which writes
|
||||||
|
44.1 kHz mono 16-bit PCM WAVs using a hand-rolled WAV writer (no `hound` or
|
||||||
|
`dasp` dependency). The synthesis stack is entirely additive: sine /
|
||||||
|
square waves, layered harmonics, deterministic LCG noise, AR envelopes,
|
||||||
|
and a slow LFO for the ambient track.
|
||||||
|
|
||||||
|
| File | Synthesis approach |
|
||||||
|
|---|---|
|
||||||
|
| `card_deal.wav` | Filtered LCG noise with a sweeping low-pass cutoff for a "whoosh" |
|
||||||
|
| `card_flip.wav` | High-passed LCG noise under a fast AR envelope |
|
||||||
|
| `card_place.wav` | 120 Hz sine body + filtered noise click |
|
||||||
|
| `card_invalid.wav` | Two dissonant square tones (196 Hz + 207.65 Hz) beating against each other |
|
||||||
|
| `win_fanfare.wav` | C-major arpeggio (C5 / E5 / G5 / C6) with sine + 2nd harmonic |
|
||||||
|
| `ambient_loop.wav` | 55 Hz fundamental with 2nd and 3rd harmonics, modulated by a 0.2 Hz LFO; loop length is chosen so the tone and LFO both complete an integer number of cycles for seamless looping |
|
||||||
|
|
||||||
|
Audio files are MIT-licensed alongside the rest of this project.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## License Summary
|
||||||
|
|
||||||
|
- **Project code:** MIT — see [LICENSE](LICENSE).
|
||||||
|
- **xCards card artwork (52 faces + `back_0.png`):** LGPL-3.0, redistributed
|
||||||
|
unmodified. The LGPL applies only to those PNG files; it does not extend to
|
||||||
|
the game binary, which links no LGPL code.
|
||||||
|
- **FiraMono-Medium font:** SIL Open Font License 1.1, redistributed unmodified.
|
||||||
|
- **All other assets** (backgrounds, generated card backs, every audio file)
|
||||||
|
are original work covered by this project's MIT license.
|
||||||
|
|
||||||
|
If you redistribute Solitaire Quest, you must ship this `CREDITS.md` and the
|
||||||
|
`LICENSE` file alongside the binary so the LGPL and OFL notices remain
|
||||||
|
visible to end users.
|
||||||
@@ -68,6 +68,14 @@ cargo test -p solitaire_core -p solitaire_sync -p solitaire_data -p solitaire_se
|
|||||||
cargo clippy --workspace -- -D warnings
|
cargo clippy --workspace -- -D warnings
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Credits
|
||||||
|
|
||||||
|
Built on [Bevy](https://bevyengine.org/) and the wider Rust ecosystem (Tokio,
|
||||||
|
Axum, sqlx, Serde, kira, and many more). Card faces and the default card back
|
||||||
|
use xCards artwork (LGPL-3.0); the UI font is FiraMono-Medium (OFL). All audio
|
||||||
|
is synthesized programmatically by this project. See [CREDITS.md](CREDITS.md)
|
||||||
|
for the full list and license details.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
MIT — see [LICENSE](LICENSE).
|
MIT — see [LICENSE](LICENSE).
|
||||||
|
|||||||
@@ -0,0 +1,142 @@
|
|||||||
|
# Solitaire Quest — UX Overhaul Session Handoff
|
||||||
|
|
||||||
|
**Last updated:** 2026-04-30 — Phase 3 complete + Phase 4 polish landed. v1 release-readiness scope is largely done; remaining work is final smoke test, push, and tag.
|
||||||
|
|
||||||
|
## Status at pause
|
||||||
|
|
||||||
|
- **HEAD:** `5d57b67` — local master is **16 commits ahead of `origin/master`** (unpushed).
|
||||||
|
- **Working tree:** modified but uncommitted edits in `solitaire_engine/src/hud_plugin.rs` and `solitaire_engine/src/settings_plugin.rs` — an in-flight tooltip-popover extension threaded onto the Settings sliders/togglers/pickers. Not staged, not built against; review and finish-or-revert before resuming new work.
|
||||||
|
- **Build:** `cargo build --workspace` and `cargo clippy --workspace -- -D warnings` clean as of last commit.
|
||||||
|
- **Tests:** **872 passed / 0 failed / 9 ignored** across the workspace.
|
||||||
|
|
||||||
|
## Where we are
|
||||||
|
|
||||||
|
Phase 3 of the UX overhaul (design tokens, modal scaffold, animation curves) shipped earlier in the session and is unchanged. Phase 4 (release-grade polish) layered another 22 commits on top: window polish, modal animation, score feedback, three phases of focus rings, Home repurposed as a mode launcher, tooltip infrastructure + HUD wiring, branded splash screen, achievement integration tests, microcopy unification, leaderboard error/idle states, first-launch empty-state polish, hit-target accessibility fix, CREDITS.md, ARCHITECTURE doc-rot fix.
|
||||||
|
|
||||||
|
### Design direction (unchanged)
|
||||||
|
|
||||||
|
- **Tone:** Balatro — chunky readable type, theatrical hierarchy, satisfying micro-interactions.
|
||||||
|
- **Palette:** Midnight Purple base + Balatro yellow primary + warm magenta secondary.
|
||||||
|
- See [memory/project_ux_overhaul_2026-04.md](.claude/projects/-home-manage-Rusty-Solitare/memory/project_ux_overhaul_2026-04.md) for full direction.
|
||||||
|
|
||||||
|
## Phase 3 (shipped)
|
||||||
|
|
||||||
|
- `solitaire_engine/src/ui_theme.rs` — every design token: colours, type scale, spacing scale, radius rungs, z-index hierarchy, motion durations.
|
||||||
|
- `solitaire_engine/src/ui_modal.rs` — `spawn_modal` scaffold + button-variant helpers + `paint_modal_buttons` system.
|
||||||
|
- All 12 overlays migrated to the modal scaffold with real Primary/Secondary/Tertiary buttons (no more Y/N debug prompts).
|
||||||
|
- HUD restructured into a 4-tier vertical stack with progressive disclosure.
|
||||||
|
- Animation upgrades: `SmoothSnap` slide curves, scoped settle bounce, deal jitter, win-cascade rotation.
|
||||||
|
|
||||||
|
## Phase 4 (shipped this session)
|
||||||
|
|
||||||
|
| Area | Commit | What landed |
|
||||||
|
|---|---|---|
|
||||||
|
| Workspace lint | `9bfca92` | Test-only clippy warnings under `--all-targets` resolved. |
|
||||||
|
| App / window | `5f5aba8` | WM_CLASS, centered-on-primary window, panic hook → `crash.log`. |
|
||||||
|
| Modal animation | `71999e1` | `ModalEntering` + ease-out scrim fade and 0.96→1.0 card scale over `MOTION_MODAL_SECS`; `Instant` collapses to zero. |
|
||||||
|
| Score feedback | `dcfa976` | `ScorePulse` triangular 1.0→1.1→1.0; floating "+N" for jumps ≥ `SCORE_FLOATER_THRESHOLD`. |
|
||||||
|
| Hit targets | `b082bd6` | `ICON_BUTTON_PX` 28 → 32; settings sync status reads "local only" not "not configured". |
|
||||||
|
| Microcopy | `abeb4e5` | Help "Close" → "Done"; final onboarding CTA → "Let's play". |
|
||||||
|
| Empty states | `65d595a` | First-launch em-dash zero-stats grid + welcome line on Profile. |
|
||||||
|
| Leaderboard | `1384365` | Idle/Loaded/Error enum; local-only guard replaces opt-in/out buttons. |
|
||||||
|
| Credits | `fd7fb7b`, `f866299` | CREDITS.md added (xCards, FiraMono, Bevy, kira, Rust deps); README links it. |
|
||||||
|
| Home | `c1bde18` | Home repurposed as Mode Launcher: 5 mode cards, level-5 lock state, dispatches existing request events. |
|
||||||
|
| Focus rings (Phase 1) | `1278952` | Tab/Shift-Tab/Enter on every modal button; auto-focus primary; overlay tracks `GlobalTransform` above scrim. |
|
||||||
|
| Focus rings (Phase 2) | `51d3454` | HUD action bar (hover-gated) and Home mode cards. |
|
||||||
|
| Focus rings (Phase 3) | `b78a493` | Settings: icon buttons, swatches, toggles; arrow-key navigation in `FocusRow`; auto-scroll keeps focused control in viewport. |
|
||||||
|
| Achievement tests | `2e080d0` | Integration coverage for `draw_three_master` and `zen_winner` — every advertised achievement now has a full-flow unlock test. |
|
||||||
|
| Microcopy | `0c86cac` | Drop "Yes," prefix on destructive confirms — "New game" / "Forfeit" replace "Yes, abandon" / "Yes, forfeit". |
|
||||||
|
| Tooltip infra | `54d3497` | `Tooltip(Cow<'static, str>)` component, hover-delay overlay, `Z_TOOLTIP` rung. |
|
||||||
|
| Tooltip wiring | `220e3f0` | Tooltips on 10 HUD readouts + 6 action-bar buttons; `spawn_action_button` requires a tooltip parameter. |
|
||||||
|
| Splash | `5d57b67` | Branded splash overlay (fade-in 300ms / hold ~1s / fade-out 300ms); board deals behind; any keypress dismisses. |
|
||||||
|
| Doc-rot | `73e210b` | ARCHITECTURE.md `bevy_kira_audio` references → `kira` to match Cargo.toml. |
|
||||||
|
| Doc | `de52c8a` | Mid-session SESSION_HANDOFF refresh after first batch of Phase 4 landed. |
|
||||||
|
|
||||||
|
## Commits this session, chronological
|
||||||
|
|
||||||
|
```
|
||||||
|
9bfca92 chore(workspace): satisfy clippy --all-targets in test code
|
||||||
|
5f5aba8 feat(app): window polish — WM_CLASS, centered window, crash log hook
|
||||||
|
71999e1 feat(engine): modal open animation — fade + scale with ease-out
|
||||||
|
dcfa976 feat(engine): score change feedback — pulse and floating delta
|
||||||
|
de52c8a docs: update SESSION_HANDOFF for completed phase-4 polish tracks
|
||||||
|
b082bd6 feat(engine): bump icon-button hit target to 32px and clarify local-only sync status
|
||||||
|
abeb4e5 feat(engine): unify dismiss verb to Done and warm onboarding CTA to Let's play
|
||||||
|
65d595a feat(engine): first-launch polish — em-dash zero stats and welcome line on profile
|
||||||
|
1384365 feat(engine): leaderboard error and idle states plus local-only guard
|
||||||
|
fd7fb7b docs: add CREDITS.md and link from README
|
||||||
|
c1bde18 feat(engine): repurpose Home as mode launcher
|
||||||
|
1278952 feat(engine): keyboard focus rings on modal buttons (Phase 1)
|
||||||
|
51d3454 feat(engine): keyboard focus on HUD action bar and Home mode cards (Phase 2)
|
||||||
|
b78a493 feat(engine): keyboard focus on Settings panel with arrow-key pickers (Phase 3)
|
||||||
|
f866299 docs: drop xCards URL placeholder from CREDITS.md
|
||||||
|
73e210b docs: replace bevy_kira_audio references with kira in ARCHITECTURE.md
|
||||||
|
2e080d0 test(engine): integration coverage for draw_three_master and zen_winner
|
||||||
|
0c86cac feat(engine): unify destructive-confirm verbs — drop "Yes," prefix
|
||||||
|
54d3497 feat(engine): tooltip infrastructure with hover delay (foundation only)
|
||||||
|
220e3f0 feat(engine): tooltips on every HUD readout and action button
|
||||||
|
5d57b67 feat(engine): branded splash screen on launch
|
||||||
|
```
|
||||||
|
|
||||||
|
(Phase 3 commits `e14852c` through `54e024c` and the prior handoff update `0066ca6` are already pushed — see git history for full audit trail.)
|
||||||
|
|
||||||
|
## Open punch list for v1
|
||||||
|
|
||||||
|
Polish is essentially complete. Concretely scoped follow-ups:
|
||||||
|
|
||||||
|
1. **Smoke-test pass.** Run the game end-to-end with the original Phase 3 checklist plus the Phase 4 additions (splash dismiss, focus rings on every screen, tooltip hover, mode launcher, leaderboard error state, first-launch em-dashes).
|
||||||
|
2. **xCards upstream URL** in CREDITS.md is intentionally absent (`f866299`). One-line fill-in when the project owner picks a canonical mirror/fork; LGPL notice obligations are already satisfied without it.
|
||||||
|
3. **Push to origin.** Local master is 16 commits ahead of `origin/master`. `git push origin master` (interactive credentials on `git.aleshym.co`).
|
||||||
|
4. **Tag `v0.1.0`** once the smoke test passes and the push lands.
|
||||||
|
5. **Release packaging** per ARCHITECTURE.md §17 — Docker compose for the server is documented; desktop client packaging (icon, .ico/.icns, signing, AppImage) is not yet done.
|
||||||
|
|
||||||
|
### Optional, deferred
|
||||||
|
|
||||||
|
- Animated focus ring (currently a static overlay; could pulse on focus change).
|
||||||
|
- Splash skip-on-subsequent-launches — currently every launch shows the full ~1.6s splash.
|
||||||
|
- Achievement onboarding pass — show first-time players the achievement panel after their first win.
|
||||||
|
- In-flight Settings tooltip popovers in the working tree — finish or revert.
|
||||||
|
|
||||||
|
## Resume prompt
|
||||||
|
|
||||||
|
```
|
||||||
|
You are a senior Rust + Bevy developer finishing v1 of Solitaire
|
||||||
|
Quest. Working directory: /home/manage/Rusty_Solitare. Branch:
|
||||||
|
master. Polish phase is complete; the remaining work is release prep,
|
||||||
|
not new features.
|
||||||
|
|
||||||
|
State: HEAD=5d57b67. Local master is 16 commits ahead of
|
||||||
|
origin/master and unpushed. Working tree has uncommitted in-flight
|
||||||
|
tooltip work in solitaire_engine/src/hud_plugin.rs and
|
||||||
|
solitaire_engine/src/settings_plugin.rs — review and finish or revert
|
||||||
|
before opening anything new.
|
||||||
|
|
||||||
|
Build: cargo build / clippy --workspace -- -D warnings clean as of
|
||||||
|
HEAD. Tests: 872 passed / 0 failed / 9 ignored.
|
||||||
|
|
||||||
|
READ FIRST (in order, before doing anything):
|
||||||
|
1. SESSION_HANDOFF.md — full state and punch list
|
||||||
|
2. CLAUDE.md — hard rules (UI-first, no panics, etc.)
|
||||||
|
3. ARCHITECTURE.md §15, §17 — platform targets, deployment guide
|
||||||
|
4. ~/.claude/projects/-home-manage-Rusty-Solitare/memory/MEMORY.md
|
||||||
|
— saved feedback / project context
|
||||||
|
|
||||||
|
PUNCH LIST (resolve in roughly this order):
|
||||||
|
1. Decide on the in-flight settings_plugin/hud_plugin tooltip work.
|
||||||
|
2. Smoke-test the binary end-to-end. If anything regresses, fix it
|
||||||
|
before opening anything new.
|
||||||
|
3. Confirm or fill the xCards upstream URL in CREDITS.md.
|
||||||
|
4. git push origin master (16 commits unpushed; interactive creds).
|
||||||
|
5. Tag v0.1.0.
|
||||||
|
6. Release packaging per ARCHITECTURE.md §17 — desktop client icon,
|
||||||
|
bundling, signing are not yet wired.
|
||||||
|
|
||||||
|
WORKFLOW NOTES:
|
||||||
|
- Commits use:
|
||||||
|
git -c user.name=funman300 -c user.email=root@vscode.infinity commit -m "..."
|
||||||
|
- Sub-agents stage + verify only; orchestrator commits.
|
||||||
|
- Every commit must pass build / clippy / test.
|
||||||
|
|
||||||
|
OPEN AT THE START: ask which punch-list item to start on. Don't pick
|
||||||
|
unilaterally — release-readiness ordering is the user's call.
|
||||||
|
```
|
||||||
|
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 |