Commit Graph

5 Commits

Author SHA1 Message Date
funman300 e41def8c89 Revert "feat(engine): per-theme nearest-sampling opt-in for pixel-art themes"
This reverts commit 17e3112502.
2026-05-06 19:38:13 -07:00
funman300 aad8bb9c83 Revert "feat(engine): bundle Rusty Pixel as a built-in theme"
This reverts commit 21ec03b157.
2026-05-06 19:38:13 -07:00
funman300 21ec03b157 feat(engine): bundle Rusty Pixel as a built-in theme
The pixel-art card theme generated via Claude Design (53 PNGs at
256x384, ~340 KB total) now ships embedded in the binary alongside
the existing default SVG theme. Players see the new theme in the
picker out of the box without needing to drop files into
~/.local/share/solitaire_quest/themes/.

solitaire_engine/assets/themes/rusty-pixel/:
  - 53 PNGs (52 face cards + 1 back) at 256x384
  - theme.ron declaring meta.id = "rusty-pixel",
    card_aspect = (2, 3), pixel_art = true

assets/sources.rs:
  - New constants RUSTY_PIXEL_THEME_MANIFEST_URL,
    RUSTY_PIXEL_THEME_MANIFEST_PATH,
    RUSTY_PIXEL_THEME_MANIFEST_BYTES.
  - New embed_rusty_pixel_png! macro mirroring embed_default_svg!.
  - New RUSTY_PIXEL_THEME_PNGS table — 53 entries, one per file.
  - New rusty_pixel_theme_png_bytes(filename) lookup helper
    mirroring default_theme_svg_bytes for the thumbnail cache.
  - New populate_embedded_rusty_pixel_theme(app) registers the
    manifest + every PNG into Bevy's EmbeddedAssetRegistry.
  - AssetSourcesPlugin::build now calls both populate functions
    so the picker has both themes loadable from the binary alone.

theme/registry.rs:
  - New rusty_pixel_entry() returns the bundled metadata.
  - build_registry now inserts default + rusty-pixel ahead of the
    user-dir scan, and filters user themes whose id collides with
    a bundled built-in. Bundled wins on collision because it's
    guaranteed complete; the user's overriding copy may be partial
    or stale.
  - Updated existing tests for the new len()=2-instead-of-1 baseline.
  - New test user_theme_id_collision_with_bundled_is_dropped pins
    the dedup contract.

theme/plugin.rs:
  - load_initial_theme + react_to_settings_theme_change now both
    consult a new manifest_url_for(theme_id) helper that routes
    bundled built-ins through embedded:// and unknown ids through
    themes://. Drops the previous hard-coded "default →
    DEFAULT_THEME_MANIFEST_URL else themes://" branch.
  - read_theme_preview_bytes also checks the rusty-pixel embed
    table before falling through to the user-dir filesystem read,
    so the picker chip's thumbnail works on a fresh install where
    the user-dir doesn't exist.

Workspace: 1172 passing tests / 0 failing, was 1171 (+1 net from
the new collision test). cargo clippy --workspace --all-targets
-- -D warnings clean. Binary grows by ~340 KB (the 53 bundled
PNGs).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-06 19:28:53 -07:00
funman300 17e3112502 feat(engine): per-theme nearest-sampling opt-in for pixel-art themes
Bevy's default sprite sampler is bilinear (Linear), which mushes
pixel-art card faces at non-integer scales. The rusty-pixel theme
ships 256x384 source PNGs that get displayed at ~150-200px wide on
typical desktop windows — an aggressive downscale where bilinear
visibly blurs the pixel grid.

Globally flipping ImagePlugin to default_nearest() would also affect
the SVG-rasterised default theme, where bilinear's smoothing is
actually desired (the SVG rasteriser produces a high-res 512x768
pixmap that the GPU has to downscale at draw time).

The fix is a per-theme opt-in:

  - ThemeMeta gains pixel_art: bool with #[serde(default)] for
    backwards compat. Older manifests load with `false`, preserving
    SVG-default behaviour.
  - sync_card_image_set_with_active_theme inspects theme.meta.pixel_art
    after a theme finishes loading. When true, walks every face +
    back Handle<Image> in the active CardTheme and rewrites its
    sampler to ImageSampler::Descriptor(ImageSamplerDescriptor::nearest()).
    The Modified asset event triggers a GPU re-upload with the new
    sampler descriptor.
  - The 12 ThemeMeta struct literals across the engine
    (settings_plugin, card_plugin, theme/{plugin,mod,manifest,
    importer,registry}) all gain `pixel_art: false` to match the
    new field.

The deployed rusty-pixel theme.ron at
~/.local/share/solitaire_quest/themes/rusty-pixel/ now sets
pixel_art: true, so the player's switch-to-pixel-art chip flips to
nearest sampling on the spot.

Workspace: 1171 passing tests / 0 failing. cargo clippy
--workspace --all-targets -- -D warnings clean.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-06 19:21:53 -07:00
funman300 7b59e70192 feat(engine): theme registry + discovery (Card theme phase 6)
CI / Test & Lint (push) Failing after 8s
CI / Release Build (push) Has been skipped
Implements Phase 6 of CARD_PLAN.md — discovers every available card
theme on startup so the future picker UI can list them.

solitaire_engine/src/theme/registry.rs
  ThemeEntry { id, display_name, manifest_url, meta }
  ThemeRegistry — Resource holding the entries; provides
    find(id), iter(), len(), is_empty().
  ThemeRegistryPlugin — Startup system that scans
    user_theme_dir() and populates the registry.
  build_registry(user_dir) — pure helper; takes the dir as a
    parameter so tests use tempfile::tempdir() without touching
    the global OnceLock-based user-theme path.
  refresh_registry(&mut, user_dir) — replaces in-place; called
    after a successful import_theme so a freshly-imported theme
    appears in the picker without an app restart.

The bundled default entry is always inserted (id "default", served
from DEFAULT_THEME_MANIFEST_URL) so the picker has at least one
option even when no user themes exist.

Discovery is best-effort: a directory whose theme.ron is missing,
malformed, or fails ThemeMeta::validate is silently skipped — broken
themes don't poison the registry. Only the meta block is parsed
(via a derive(Deserialize) struct that ignores other manifest
fields), which keeps startup quick even with dozens of themes
installed.

Wired into solitaire_app/main.rs after ThemePlugin so the asset
sources are registered before discovery scans for theme.ron files.

10 new tests covering: empty user dir, nonexistent user dir, valid
user theme registers, full-manifest tolerance via meta-only parser,
malformed theme.ron skipped, invalid-meta theme skipped, directory
without theme.ron ignored, find() returns None for unknown id,
refresh_registry replaces stale entries, default-entry URL matches
the embedded constant.

cargo build / clippy --workspace --all-targets -- -D warnings / test
--workspace all green (960 passed, 0 failed, 9 ignored).
2026-05-01 06:04:34 +00:00