The post-Option-D screenshot showed Terminal cards correctly but
a green felt play surface — the chrome migration only retuned
in-engine constants, leaving the on-disk PNGs at
assets/backgrounds/bg_*.png as the legacy felt textures.
Adds solitaire_engine/examples/background_generator.rs following
the same regeneratable pattern as card_face_generator. Five solid
near-black variants from the base16-eighties palette:
- bg_0: #151515 (Terminal canonical, BG_PRIMARY)
- bg_1: #0a0a0a (BG_DEEPEST)
- bg_2: #1a1a1a (BG_ELEVATED — same as card face)
- bg_3: #121820 (slight cool tint)
- bg_4: #201812 (slight warm tint)
Per design-system.md the Terminal play surface is *flat* — no
felt, no gradient — so all 5 slots are pure solid colours. Each
PNG is 120 × 168 (matches the legacy tile size; spawn_background
stretches to window_size * 2.0 at runtime so source resolution
is immaterial). On-disk weight drops from ~16KB average to ~100
bytes per tile.
Run with: cargo run --example background_generator --release
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>
Now that foundations are unlocked and "completing" one is a real
moment (rather than a foregone conclusion based on suit assignment),
each Ace-through-King run gets its own small celebration when the
King lands.
Three layers fire on a single FoundationCompletedEvent emitted by
game_plugin's handle_move when a successful move leaves a
PileType::Foundation pile holding 13 cards:
1. King card scale-pulse via a new FoundationFlourish component.
Triangular curve 1.0 → 1.15 → 1.0 over MOTION_FOUNDATION_FLOURISH
_SECS (0.4s) — same shape as the existing ScorePulse so the feel
matches.
2. Pile-marker tint flourish via FoundationMarkerFlourish — the
foundation marker's sprite colour lerps to STATE_SUCCESS for the
first half of the duration then fades back. Reuses the existing
success-signal palette; no new colour token.
3. Audio cue: foundation_complete.wav, a synthesised C6→E6→G6 triad
with 2nd-harmonic warmth and AR decay (~240 ms). Sits an octave
above win_fanfare's root so the layered fourth-completion + win
cascade reads cleanly. Generated via solitaire_assetgen's
foundation_complete() function and embedded via include_bytes!().
The visual systems run .after(GameMutation) so the post-move pile
state is visible when the King is identified. Both flourish
components remove themselves once elapsed time exceeds duration —
no animation queue or scheduler integration needed.
Pure foundation_flourish_scale(elapsed, duration) helper is
unit-tested for the curve, edge clamps, and zero-duration safety.
Three integration tests on the firing logic verify the event fires
exactly once when a King completes a foundation, doesn't fire for
non-foundation moves, and doesn't fire when the foundation is at 12
cards.
The fourth completion still co-occurs with the win cascade — the
two layer cleanly because the flourish's scale is on the King card
sprite while the cascade is a screen-shake + per-card rotation, and
the foundation_complete ping is a higher octave than the win
fanfare's root.
Co-Authored-By: Claude Opus 4.7 (1M context) <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>
Replace 16×16 placeholder PNGs with 120×168 canvas-drawn art matching card
face dimensions. Each card back has a distinctive coloured pattern (blue diamond
grid, red crosshatch, green circle array, purple concentric diamonds, teal
horizontal stripes). Each background has textured detail (green felt weave, wood
plank grain, navy star field, burgundy diagonal tile, charcoal checkerboard).
Removes the now-unused save_small_png/make_small helpers.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace the single shared face.png placeholder with 52 individual card
face images (120×168 px each), generated by the updated gen_art tool:
- solitaire_assetgen: add ab_glyph dep; rewrite gen_art to render each
card with FiraMono rank characters, programmatic suit symbols (heart,
spade, diamond, club drawn via circles/triangles), and standard pip
layout for numbered cards (A–10) plus large face letter for J/Q/K.
- CardImageSet: replace single `face` handle with `faces: [[Handle; 13]; 4]`
indexed by [suit][rank].
- card_sprite(): select the per-card face image by suit/rank indices.
- spawn/update_card_entity: suppress Text2d overlay when PNG faces are
loaded (rank/suit baked into image); keep overlay in solid-colour
fallback for tests.
- gen_sfx.rs: rename `gen` variable to `make` (reserved keyword in 2024).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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>
- New solitaire_assetgen crate with gen_sfx binary: synthesizes
five 44.1kHz mono 16-bit PCM WAVs (flip/place/deal/invalid/fanfare)
from an LCG noise source + sine/square synths. Output committed
under assets/audio/.
- AudioPlugin (engine): embeds the WAVs via include_bytes!, decodes
once with kira::StaticSoundData, plays on Draw / Move / NewGame /
GameWon events. card_invalid is loaded but unused — wiring it
needs a MoveRejectedEvent.
- AudioManager kept on the main thread (NonSend) since cpal is !Send
on some platforms; degrades gracefully if no audio device present.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>