feat(engine): switch card fronts to 4-colour deck

Hearts pink (`#fb9fb1`), Diamonds gold (`#ddb26f`), Clubs lime
(`#acc267`), Spades gray (`#d0d0d0`) — each suit picks up its
own base16-eighties accent so a player scanning the table can
distinguish the suit by hue alone (faster recognition than the
2-colour traditional red/black scheme; common in poker decks).
All four colours already exist in the palette as semantic
state-token accents, so this is a pure remapping at the suit-
glyph site, not a palette extension.

The outlined-glyph differentiation (♦ ♣ outlined, ♥ ♠ filled)
is preserved on top of the colour split — it stays the always-
on colour-blind fallback per `design-system.md` §Accessibility,
and matters more than ever now that CBM hearts (lime) and
default clubs (lime) share a hue.

### Changes

- `card_face_svg.rs`: split `SUIT_RED` / `SUIT_DARK` into four
  per-suit constants (`SUIT_HEART` / `SUIT_DIAMOND` / `SUIT_CLUB`
  / `SUIT_SPADE`). `suit_paint()` returns each suit's own
  colour. Card border picks up the suit colour automatically
  via the existing `(colour, paint)` destructure.
- `card_plugin.rs`: new `DIAMOND_SUIT_COLOUR` + `CLUB_SUIT_COLOUR`
  constants; `text_colour()` rewritten as a per-suit match (was
  red/black bifurcation). Both rendering paths (PNG production +
  constant fallback under MinimalPlugins) stay in lockstep.
- CBM behaviour clarified: only hearts swap to lime now;
  diamonds + clubs + spades are already hue-distinct from
  the heart pink and stay unchanged. Under CBM the heart
  (lime) and club (lime) share a hue but stay distinguishable
  via the always-on filled-vs-outlined glyph differentiation.
- HC behaviour: only hearts (→ HC red) and spades (→ HC white)
  have defined boosts. Diamonds (gold) and clubs (lime) are
  already mid-luminance accents and stay at their default.
  New test `text_colour_diamonds_and_clubs_are_immune_to_accessibility_flags`
  pins all four flag combinations as no-ops for the gold +
  lime suits.
- `design-system.md` §Suit Colors retitled "Four-color deck"
  with the 4-colour table; CBM section text updated to
  describe the hearts-only swap and the hearts/clubs hue
  collision under CBM.
- `card_face_svg_pin.rs` rebaselined: 26 hashes drift
  (13 clubs + 13 diamonds — the two suits whose colours
  changed). Hearts, spades, and the 5 backs all keep their
  prior hashes. Surgical scope, exactly what the pin test
  was designed to surface.

### Tests

1191 passing / 0 failing — net 0 from the prior baseline:
two old 2-colour tests removed
(`text_colour_is_red_for_hearts_and_diamonds`,
`text_colour_is_black_for_clubs_and_spades`), one consolidated
4-colour test added
(`text_colour_4_colour_deck_assigns_each_suit_its_own_hue`)
plus a pairwise-distinct invariant guard, and one new test
covering the gold/lime suits' immunity to CBM/HC flags. Six
existing CBM/HC tests rewritten to use only the suits each flag
actually affects under the new scheme (hearts for CBM, hearts +
spades for HC).

Workspace clippy clean.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
funman300
2026-05-08 12:00:55 -07:00
parent 31139ae455
commit 62b61cc786
56 changed files with 306 additions and 225 deletions
+17 -6
View File
@@ -29,8 +29,19 @@ use solitaire_core::card::{Rank, Suit};
pub const TARGET: UVec2 = UVec2::new(256, 384);
const BG_FACE: &str = "#1a1a1a"; // BG_ELEVATED — face background
const SUIT_RED: &str = "#fb9fb1"; // hearts + diamonds
const SUIT_DARK: &str = "#d0d0d0"; // spades + clubs (also TEXT_PRIMARY)
// Four-colour deck: each suit picks up its own base16-eighties
// accent so a player scanning the table can distinguish the suit
// by hue alone. Faster recognition than the 2-colour traditional
// red/black scheme; common in poker-room decks and online card
// games. The outlined-glyph differentiation (♦ ♣ outlined, ♥ ♠
// filled) is preserved on top of the colour split as the
// always-on colour-blind fallback per `design-system.md`
// §Accessibility.
const SUIT_HEART: &str = "#fb9fb1"; // pink (base08 / RED_SUIT_COLOUR)
const SUIT_DIAMOND: &str = "#ddb26f"; // gold (base09 / STATE_WARNING)
const SUIT_CLUB: &str = "#acc267"; // lime (base0A / STATE_SUCCESS)
const SUIT_SPADE: &str = "#d0d0d0"; // foreground gray (base05 / TEXT_PRIMARY)
const BACK_BG: &str = "#151515";
const BACK_SCANLINE: &str = "#1a1a1a";
@@ -145,10 +156,10 @@ enum GlyphPaint {
fn suit_paint(suit: Suit) -> (&'static str, GlyphPaint) {
match suit {
Suit::Hearts => (SUIT_RED, GlyphPaint::Filled),
Suit::Diamonds => (SUIT_RED, GlyphPaint::Outlined),
Suit::Spades => (SUIT_DARK, GlyphPaint::Filled),
Suit::Clubs => (SUIT_DARK, GlyphPaint::Outlined),
Suit::Hearts => (SUIT_HEART, GlyphPaint::Filled),
Suit::Diamonds => (SUIT_DIAMOND, GlyphPaint::Outlined),
Suit::Spades => (SUIT_SPADE, GlyphPaint::Filled),
Suit::Clubs => (SUIT_CLUB, GlyphPaint::Outlined),
}
}