feat(engine): art pass — PNG assets, custom font, and keyring v4 upgrade

Art pass (Phase 4):
- Generate placeholder PNG assets: face.png, back_0–4.png, bg_0–4.png via
  solitaire_assetgen gen_art binary (16×16 RGBA, embedded via include_bytes!)
- Add FiraMono-Medium font (assets/fonts/main.ttf) embedded at compile time
- Add FontPlugin: loads font at startup, exposes FontResource; gracefully
  falls back to default handle when Assets<Font> absent (MinimalPlugins tests)
- Wire CardImageSet into card_plugin: face/back PNGs replace solid-colour
  sprites when available; tests continue using colour fallback via MinimalPlugins
- Wire BackgroundImageSet into table_plugin: bg PNGs replace solid-colour
  background; empty set inserted when Assets<Image> absent in tests
- Fix hint highlight system (input_plugin): tint sprite.color directly instead
  of replacing the whole Sprite (which would discard the image handle)
- Export FontPlugin, FontResource, CardImageSet from solitaire_engine::lib
- Register FontPlugin in solitaire_app before other plugins

Dependency upgrades (latest releases):
- keyring "2" → keyring "4" + keyring-core "1" (v4 split architecture into
  separate core library crate)
- auth_tokens.rs: Entry::new now returns Result; delete_password →
  delete_credential; NoDefaultStore error variant handled
- solitaire_app: add keyring::use_native_store(true) at startup for Linux
  Secret Service / macOS Keychain / Windows Credential Store selection

ARCHITECTURE.md: fix Edition 2025→2021, update asset pipeline section,
add FontPlugin/CardImageSet/BackgroundImageSet to plugin and resource tables,
update Section 14 to reflect actual include_bytes!() rendering approach,
add Decision Log entries for embedded PNG and font decisions

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
funman300
2026-04-29 00:30:55 +00:00
parent 41d75b50de
commit 18ac5adef5
27 changed files with 2405 additions and 489 deletions
+6 -8
View File
@@ -252,7 +252,7 @@ fn handle_keyboard_hint(
mut confirm: ResMut<KeyboardConfirmState>,
mut hint_cycle: ResMut<HintCycleIndex>,
mut commands: Commands,
card_entities: Query<(Entity, &CardEntity, &Sprite)>,
mut card_entities: Query<(Entity, &CardEntity, &mut Sprite)>,
mut info_toast: MessageWriter<InfoToastEvent>,
mut hint_visual: MessageWriter<HintVisualEvent>,
) {
@@ -308,16 +308,14 @@ fn handle_keyboard_hint(
.and_then(|p| p.cards.last().filter(|c| c.face_up))
.map(|c| c.id);
if let Some(card_id) = top_card_id {
for (entity, card_entity, _sprite) in card_entities.iter() {
for (entity, card_entity, mut sprite) in card_entities.iter_mut() {
if card_entity.card_id == card_id {
// Tint the card gold without replacing the Sprite (which would
// discard the image handle set by CardImageSet).
sprite.color = Color::srgba(1.0, 1.0, 0.4, 1.0);
commands.entity(entity)
.insert(HintHighlight { remaining: 2.0 })
.insert(HintHighlightTimer(2.0))
.insert(Sprite {
color: Color::srgba(1.0, 1.0, 0.4, 1.0),
custom_size: Some(layout_res.0.card_size),
..default()
});
.insert(HintHighlightTimer(2.0));
break;
}
}