From 3ffde038c597fc38a556b3c180a425725115e1ae Mon Sep 17 00:00:00 2001 From: funman300 Date: Wed, 29 Apr 2026 21:14:48 +0000 Subject: [PATCH] docs: switch asset pipeline notes to AssetServer model MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Card faces, card backs, board backgrounds, and the UI font are loaded via Bevy's AssetServer at startup (see commit fbe984c). The CLAUDE.md hard rule still claimed cards/backgrounds were rendered procedurally with no AssetServer, and ARCHITECTURE.md §14 / §20 still described PNGs and TTFs as embedded via include_bytes!(). Update both docs: - CLAUDE.md hard rule lists which assets ship in assets/ and notes the Option> fallback used under MinimalPlugins (tests). - ARCHITECTURE.md §2/§3/§5/§14 rewritten to describe the AssetServer loaders for CardImageSet, BackgroundImageSet, and FontResource, and the Text2d / solid-colour fallbacks. - ARCHITECTURE.md §20 decision log replaces the two reversed embed-via-include_bytes!() entries with a single entry covering the switch to AssetServer plus a note that audio remains embedded. Co-Authored-By: Claude Opus 4.7 (1M context) --- ARCHITECTURE.md | 26 ++++++++++++++------------ CLAUDE.md | 4 +++- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 6de3c2f..fab0eb1 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -67,11 +67,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 +144,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`. @@ -239,7 +239,7 @@ Done |---|---|---| | `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 | @@ -296,7 +296,7 @@ struct CardImageSet { backs: [Handle; 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); // Pre-loaded background PNG handles @@ -772,11 +772,13 @@ Audio systems listen for Bevy events and never block the game thread. ### Rendering approach -Cards are Bevy `Sprite` entities with `Handle` 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` 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` from `BackgroundImageSet`. `BackgroundImageSet` is populated at startup from `include_bytes!()`. +Backgrounds are Bevy `Sprite` entities with `Handle` 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>` 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 +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 | | 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>` 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 | diff --git a/CLAUDE.md b/CLAUDE.md index 964b1ed..775498e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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`/`Handle` in the `CardImageSet`, `BackgroundImageSet`, and `FontResource` resources. The `assets/` directory must ship alongside the binary. +- Asset-loading systems take `Option>` 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.