The user's first post-migration screenshot showed near-invisible
suit glyphs on every card — the rank rendered at correct size but
the ♠ ♥ ♦ ♣ marks were tiny dots regardless of the requested
20px / 64px font-size.
Root cause: the bundled FiraMono in svg_loader::shared_fontdb
doesn't carry usable Unicode suit glyphs (U+2660-2666). usvg
silently fell back to a substitute rendering at default size,
producing the "tofu" effect.
Fixes by replacing the `<text>` glyph rendering with inline SVG
paths. `suit_path_d(suit)` returns a single closed-perimeter path
authored in a 32 × 32 logical box, then face_svg wraps it in two
`<g transform>` blocks (top-left small + bottom-right rotated
large). Path-based rendering bypasses the font system entirely
— same bytes on every machine, no fontdb dependency, no
substitution risk.
Same path data renders correctly whether filled (♥ ♠) or
outlined (♦ ♣ — the always-on color-blind glyph differentiation
from the design system).
Knock-on changes that must land in this commit per the migration
plan's lockstep rule:
- `EXPECTED` in tests/card_face_svg_pin.rs rebaselined: 52 face
hashes change (text → path), 5 back hashes unchanged
(back_svg untouched). The bootstrap pattern in the test
handled the rebaseline cleanly — empty EXPECTED, re-run,
paste, re-run.
- assets/cards/faces/*.png regenerated (the 52 face PNGs).
- solitaire_engine/assets/themes/default/*_*.svg regenerated
(the 52 theme face SVGs that production rasterises at
startup). Both rendering paths must agree.
Workspace clippy + cargo test --workspace clean. Pin test
passes against the new hashes.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Step 4+5 lockstep commit closing Option D from SESSION_HANDOFF.
The 52 face PNGs + 5 back PNGs in assets/cards/ are regenerated
to the Terminal-aesthetic artwork emitted by the
card_face_generator example (#1a1a1a face, #fb9fb1 / #d0d0d0
suit glyphs, scanline-pattern backs with palette-rotated badge
accents). Resolution drops from 512×768 to 256×384 — sufficient
for ~250 px-wide desktop sprites and ~⅓ the on-disk weight.
Constant fallback path migrated in lockstep so the
constant-fallback tests (under MinimalPlugins) and the PNG path
(production) agree at every commit boundary:
- CARD_FACE_COLOUR → #1a1a1a (was off-white #fafaf2)
- RED_SUIT_COLOUR → #fb9fb1 (was #c71f26)
- BLACK_SUIT_COLOUR → #d0d0d0 (was #141414)
- CARD_FACE_COLOUR_RED_CBM → renamed to RED_SUIT_COLOUR_CBM,
value #6fc2ef (was #d9ebff). Semantic shift: pre-Terminal
this was a face-background tint, now it's a suit-glyph
colour swap. The Terminal face is uniformly CARD_FACE_COLOUR
regardless of CBM; CBM only swaps red suits to cyan in the
glyph itself.
- card_back_colour() → returns the 5 base16-eighties accents
matching card_face_svg::BACK_ACCENTS in lockstep, so the
test-fallback back is the same hue family as the on-disk
PNG art for that index.
Function signatures shift to follow the semantic move:
- text_colour gains a color_blind: bool parameter (returns
RED_SUIT_COLOUR_CBM for red+CBM).
- face_colour deleted entirely. The face is uniform
CARD_FACE_COLOUR; card_sprite inlines the constant. CBM
parameter dropped from card_sprite as a knock-on.
Test updates land in this commit per the migration plan:
- text_colour_is_red_for_hearts_and_diamonds + sibling: pass
`, false` to text_colour calls now that the signature has
the CBM bool.
- 4 face_colour CBM tests replaced with 2 text_colour CBM
tests asserting (a) red-suit cards swap to cyan in CBM and
(b) black-suit cards do not change.
Engine test count: 747 → 745 (net -2 from the test
consolidation — 4 face_colour tests collapsed into 2
text_colour CBM tests).
Sign-off criteria: a human still needs to `cargo run -p
solitaire_app` and confirm Terminal cards render. clippy +
cargo test --workspace clean as of this commit.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Replaces the previous xCards-derived card faces (LGPL-3.0) with
hayeah/playing-cards-assets, which itself derives from the
public-domain vector-playing-cards Google Code project. The whole
package is MIT now — see CREDITS.md for the new attribution table
and the simpler license summary.
solitaire_engine/assets/themes/default/
52 face SVGs (clubs/diamonds/hearts/spades × ace/2-10/jack/queen/
king) — copied from hayeah, renamed to the canonical
`{suit}_{rank}.svg` form `CardKey::manifest_name` produces. The
bundled default theme manifest references each by the same name.
back.svg — original midnight-purple-themed card back, hand-written
to match the project's design tokens (BG_BASE / BG_ELEVATED /
ACCENT_PRIMARY / ACCENT_SECONDARY). MIT, original work.
assets/cards/faces/{RANK}{SUIT}.png
52 PNGs regenerated from the new SVGs at 750-tall via resvg 0.47.
These remain the legacy backwards-compat path that
`card_plugin::load_card_images` reads at startup; once the runtime
theme system finishes loading the embedded default theme, the
CardImageSet's face handles are overwritten with the SVG-rendered
variants and these PNGs become moot. Keeping them in place avoids
a brief blank-card flash before the async theme load completes.
solitaire_engine/src/assets/sources.rs
embed_default_svg!() macro + DEFAULT_THEME_SVGS table that bundles
every face + the back into the binary at compile time via
include_bytes!. populate_embedded_default_theme now iterates the
table so the EmbeddedAssetRegistry is populated under the same
asset paths the manifest references.
CREDITS.md
License summary collapses from MIT + LGPL-3.0 + OFL-1.1 to MIT +
OFL-1.1 (the OFL still applies to FiraMono). The hayeah upstream
URL replaces the previously-blank xCards entry.
cargo build / clippy --workspace --all-targets -- -D warnings / test
--workspace all green (960 passed, 0 failed, 9 ignored).
Replace compile-time include_bytes!() embedding for card faces, backgrounds,
and font with runtime AssetServer::load() calls. Swap in 52 xCards @2x PNGs
(LGPL-3.0) as card face assets and xCards bicycle_blue as back_0.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>