Step 4's PNG regeneration left the cards looking unchanged at
runtime because the PNGs at assets/cards/ are only the *fallback*
art — production renders the bundled-default theme's SVGs, which
get include_bytes!()-embedded into the binary by
solitaire_engine::assets::sources and applied to CardImageSet at
startup by theme::plugin::apply_theme_to_card_image_set. Those
SVGs were still the legacy vector-playing-cards art.
Extends card_face_generator to write SVGs into both runtime
paths in lockstep:
1. assets/cards/{faces,backs}/*.png — fallback art (unchanged
from step 4).
2. solitaire_engine/assets/themes/default/*.svg — what production
actually renders. 52 face SVGs + 1 back SVG, generated from
the same face_svg / back_svg builders as the PNGs so the two
paths can never visually diverge.
Adds two helper functions to card_face_svg:
- theme_suit_token (clubs/diamonds/hearts/spades — lowercase
full word, matching CardKey::manifest_name)
- theme_rank_token (ace/2..10/jack/queen/king — same)
The theme back uses BACK_ACCENTS[0] (canonical Terminal cyan).
The other four accents only live as PNG fallbacks because the
theme system carries one back per theme.
Net SVG diff: -14884 / +940 lines — the legacy vector-playing-
cards SVGs were ~300 lines each of Inkscape-authored paths;
the Terminal SVGs are ~10 lines of programmatic output.
Workspace clippy + cargo test --workspace clean. Pin test
unaffected (the SVG builders themselves did not change).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Step 3 of the migration plan in docs/ui-mockups/card-face-migration.md.
Extracts face_svg / back_svg + palette constants from the
card_face_generator example into a new
solitaire_engine::assets::card_face_svg module so an integration
test can call them. The example becomes a thin wrapper.
The new tests/card_face_svg_pin.rs hashes the raw RGBA8 pixel
bytes from rasterising every face × suit + every back accent and
compares each FNV-1a fingerprint against an embedded constant.
Catches silent rendering drift if usvg / resvg / tiny_skia / the
bundled FiraMono ever change in a way that perturbs pixels.
Hashing is FNV-1a inline (~5 lines) rather than adding sha2 or
blake3 — cryptographic strength isn't load-bearing here, just
stable byte fingerprints.
When the SVG builders intentionally change, empty EXPECTED to
`&[]` and re-run the test once; it panics with the new hashes
formatted as Rust source ready to paste back in.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Generates 52 face PNGs (4 suits × 13 ranks) + 5 back PNGs
into assets/cards/. Implements step 2 of the migration plan
in docs/ui-mockups/card-face-migration.md — the bytes this
emits are what step 4 commits alongside the card_plugin
constant migration.
Filled vs outlined glyphs (♥♠ filled; ♦♣ outlined) implement
the always-on color-blind glyph differentiation from the
design system. The 5 back themes share the canonical
Terminal scanline pattern but rotate the badge accent
through the base16-eighties palette so all 5 slots stay
distinguishable without leaving the palette.
Run with: cargo run --example card_face_generator --release
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>