Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3ffde038c5 | |||
| ece2a55ffb | |||
| abda354562 | |||
| fbe984cf64 | |||
| efec6f22d5 |
@@ -67,11 +67,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
|
||||||
@@ -144,7 +144,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`.
|
||||||
@@ -239,7 +239,7 @@ Done
|
|||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `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 |
|
||||||
@@ -296,7 +296,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
|
||||||
@@ -772,11 +772,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 +1006,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.
|
||||||
|
|||||||
|
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 |
|
Before Width: | Height: | Size: 6.0 KiB |