DrawMode is a fieldless two-variant enum — it is trivially bitwise-
copyable. Adding Copy + updating choose_winnable_seed to take the value
directly eliminates 13 superfluous .clone() calls across solitaire_core,
solitaire_engine, solitaire_assetgen, and solitaire_wasm.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace all display-name occurrences across web pages, Rust source,
docs, and Cargo metadata. Update localStorage token key from sq_token
to fs_token. Tagline "Klondike Solitaire" retained as genre descriptor.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds DifficultyLevel (Easy/Medium/Hard/Expert/Grandmaster/Random) to
solitaire_core::game_state alongside GameMode::Difficulty(DifficultyLevel).
Five seed catalogs (40 seeds each) are pre-verified by the new
gen_difficulty_seeds binary using tiered solver budgets (1K–200K moves).
DifficultyPlugin resolves StartDifficultyRequestEvent → catalog seed →
NewGameRequestEvent; Random uses a system-time seed and bypasses the
winnable-only filter. The home overlay gets an expandable Difficulty section
between Draw Mode and the mode grid; last-played tier persists in Settings.
Difficulty wins pool into Classic stats. 5 unit tests in difficulty_plugin.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a gen_seeds binary to solitaire_assetgen that brute-searches seeds
for hands solvable in ≤250 moves, then writes the list. The 75 new
seeds (0xCAFEBABE prefix) are appended to CHALLENGE_SEEDS in
solitaire_data::challenge.
Co-Authored-By: Claude Sonnet 4.6 <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>
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>
- pkg/solitaire-quest/PKGBUILD: builds solitaire_app binary, depends on
alsa-lib, libxkbcommon, systemd-libs (Bevy Linux requirements); check()
runs only non-Bevy crates (solitaire_core, solitaire_sync) since Bevy
integration tests require a GPU/display unavailable in chroot
- pkg/solitaire-quest-server/PKGBUILD: builds solitaire_server binary,
installs systemd service unit and hardened environment file template
- pkg/solitaire-quest-server/solitaire-quest-server.service: systemd unit
with ProtectSystem=strict, NoNewPrivileges, dedicated service user
- pkg/solitaire-quest-server/server.env: documented env template installed
to /etc/solitaire-quest-server/server.env (mode 0640, listed in backup=)
- LICENSE: add MIT license
- Cargo.toml: add license = "MIT" to [workspace.package]
- All member crates: add license.workspace = true
Both PKGBUILDs follow the Arch Rust package guidelines:
prepare() uses --locked + cargo fetch
build() uses --frozen --release -p <crate>
RUSTUP_TOOLCHAIN=stable and CARGO_TARGET_DIR=target set in each stage
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>