Files
Ferrous-Solitaire/SESSION_HANDOFF.md
T
funman300 d109c32b75 docs(handoff): record Option D closure + 9-commit card-face migration arc
Updates SESSION_HANDOFF.md to reflect the post-2026-05-08 state:

- "Last updated" + status header rewritten — origin caught up
  to local through dd101b3, 1184 tests passing (net +4 from
  the 1180 baseline: splash polish +2, card-face pin +1, CBM
  test consolidation -2 then +1).
- New narrative entry under "Since the v0.20.0 cut" walks
  through the 9-commit Option D arc: plan + tooling
  (5623368/3a4bb63/babe5cc/48b28d2), lockstep step 4+5
  (e8bf9d7), the three sign-off follow-ups (a14200a default-
  theme SVG override, 8719f77 backgrounds flattened, ae84dc1
  top-bar overlap), the path-glyph fix (af414b6), and the
  glyph-orientation tweak (dd101b3).
- "Visual-identity follow-ups" punch-list: card-face item
  marked closed with the same commit chain referenced from
  the narrative.
- Resume prompt header rewritten — Options B/C/D all closed,
  the post-tag work is fully on origin. Option D's bullet
  expanded to record the closure rather than describe pending
  work.
- The "fallback path the migration walked past" pattern is
  documented explicitly so a future session can pattern-match
  on it (token migrations need a checklist of every concrete
  artifact downstream of the tokens, not just the tokens
  themselves).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-08 10:12:45 -07:00

669 lines
33 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Solitaire Quest — Session Handoff
**Last updated:** 2026-05-08 — v0.20.0 cut and tagged at `41a009a`,
all post-cut commits pushed to origin (HEAD = `dd101b3`), working
tree clean.
The cut itself shipped two through-lines: a full **Terminal visual-
identity port** (token system, modal scaffold, gameplay-feedback,
toasts, table / card chrome, splash cursor) and the **Android
persistence shim** that closes the `dirs::data_dir() = None` pitfall
flagged in CLAUDE.md §10. Since the cut, the post-tag work split
into two arcs: (1) splash boot-screen port + replay-overlay
banner enrichments + desktop-adaptation spec — closing Resume-prompt
Options B and C (see "Since the v0.20.0 cut" entries below); and
(2) **the card-face artwork regeneration arc — Option D, closed
2026-05-08** — full Terminal cards rendering on every face, plus
three follow-up fixes that surfaced during sign-off (default-theme
SVG override, table backgrounds, top-bar overlap), plus a
glyph-orientation tweak (no 180° inverted-corner rotation).
## Status at pause
- **HEAD locally:** see `git rev-parse HEAD`. Most recent narrative
entry below names the latest substantive commit; this status line
intentionally avoids hard-coding the SHA so a docs-only edit
doesn't immediately stale the handoff.
- **HEAD on origin:** matches local. All post-cut commits pushed
through `dd101b3`. Decide whether to roll the post-tag work
into v0.20.1 / v0.21.0-candidates the next time a release is cut.
- **Working tree:** clean. No WIP outstanding.
- **`artwork/` directory:** still untracked. Intentional.
- **Build:** `cargo clippy --workspace --all-targets -- -D warnings`
clean.
- **Tests:** **1184 passing / 0 failing** across the workspace.
Net delta from the 1180 baseline: splash polish added two
(`build_scanline_image_has_expected_2x2_rgba_bytes`,
`scanline_overlay_spawns_and_fades_with_splash`); the
card-face migration added one (`card_face_svg_pin` integration
test) and consolidated two (`face_colour` CBM tests folded
into `text_colour` CBM tests, net 2 then +1 from pin);
call it +4 net.
- **Tags on origin:** `v0.9.0` through `v0.20.0`. v0.20.0 is on
`41a009a`.
## Since the v0.20.0 cut (un-pushed)
### `39b8496` `docs(ui): add Terminal desktop-adaptation spec`
`docs/ui-mockups/desktop-adaptation.md` — 283 lines covering
viewport assumptions, seven universal adaptation rules, and per-
screen geometry rules for the priority surfaces (Game Table, Win
Summary, Settings, Help, Pause, Home, Splash, Stats, and the
modal-pattern screens Profile / Achievements / Theme Picker /
Daily Challenge). Closes the spec gap — 23 of 24 mockups were
mobile-only, but the v0.20.0 token-port pass was already layout-
agnostic so nothing shipped broken. The spec matters for *next*
ports.
**Why rules > visual mockups for this gap:** Stitch's
`generate_variants` API timed out on the layout-only adaptation
prompt (server-side flake, not a prompt-shape issue — confirmed
by polling `list_screens` with no new variant landing). A markdown
rules file applies to every screen including the 9 missing-plugin
surfaces (splash, challenge, time-attack, weekly-goals,
leaderboard, sync, level-up, replay-overlay, radial-menu) that
aren't in the Stitch project at all. It's also referenceable from
code comments and commit messages without loading an image.
### `cacb19c` `feat(engine): port the splash to the Terminal boot-screen treatment`
Implements the full mockup-spec splash from
`docs/ui-mockups/splash-mobile.html` plus the desktop adaptation
rules:
- **Header**: cursor block (96 px `▌`), wordmark ("Solitaire
Quest"), 192 px divider, "TERMINAL EDITION" subtitle.
- **Boot log**: three ✓ check rows (`assets loaded`,
`theme: terminal`, `progress restored`) + a `▌ ready_` line.
Capped at 480 px width on desktop (else 70 % viewport).
- **Progress bar**: 1 px track (`BORDER_SUBTLE`) with a 100 %-
width cyan (`ACCENT_PRIMARY`) fill + `DONE · 247 ASSETS`
caption. Capped at 720 px on desktop (else 80 %).
- **Footer**: `BASE16-EIGHTIES` label, eight palette swatches
(12 × 12 px each — one per named token in the design system),
version line.
**Refactored the alpha-fade scaffold** from per-marker queries
(`SplashTitle` / `SplashSubtitle` / `SplashCursor`) to a single
`SplashFadable { base_color: Color }` + `SplashFadableBg`
variant. ~15 fadable elements share one global query each;
adding more is one component-attach, not three new query types.
**Skipped, with rationale captured in the commit:**
- Scanline overlay (needs a tiled-pattern asset or custom shader).
*Open in "Visual-identity follow-ups" below.*
- Pulsing cursor on the "ready_" line (would fight the global
fade timeline). *Open in "Visual-identity follow-ups" below.*
- "RUSTY SOLITAIRE" wordmark from the mockup (the actual product
is "Solitaire Quest"; the mockup leaked the repo name). *Closed
— the in-engine wordmark stays "Solitaire Quest".*
### `c84d9f4` `feat(engine): scrub fill bar + per-frame updater for replay overlay`
Closes the WIP described in the prior handoff. Adds the 1 px cyan
scrub bar called for in `docs/ui-mockups/replay-overlay-mobile.html`:
a track in `BORDER_SUBTLE` spans the bottom edge of the banner and
the cyan `ACCENT_PRIMARY` fill mirrors `cursor / total` via a new
`ReplayOverlayScrubFill` component + `update_scrub_fill` system.
The pure `scrub_pct` helper is shared between the spawn path
(initial fill width) and the per-frame updater so the first paint
already reflects state instead of popping `0 → cursor` on the
first tick — same shape as the existing `format_progress` /
`update_progress_text` split. Two new tests cover the four corners
of `scrub_pct` and an end-to-end drive of `ReplayPlaybackState`
asserting `Node.width` on the unique scrub-fill entity. Same
change-detection guard as the text updaters, so an idle replay
leaves the node untouched.
Header text treatment (closed by `6204db8` immediately below),
move-log scroll, MOVE chip, and WIN MOVE callout from the same
mockup are still open — separate commits.
### `6204db8` `feat(engine): port replay banner label to ▌ cursor-block treatment`
Aligns the replay overlay's headline with the splash boot-screen
idiom landed in `cacb19c`: `Replay``▌ replay` and
`Replay complete``▌ replay complete`. The cursor block (`▌`,
U+258C) prefixed to a lowercased label reads as a Terminal output
line rather than a generic UI title, tightening the family
resemblance between the two top-level overlay surfaces. Pure
text-content change; no behavioural shift, no new components, no
new systems.
**Mockup deviation (intentional):** the source mockup string in
`docs/ui-mockups/replay-overlay-mobile.html` is `▌replay.tsx`. The
`.tsx` is a prototyping leak — Stitch renders in React, so the
mockup author reached for a familiar filename — and was dropped
for the in-engine version since the codebase is Rust. The `▌` +
lowercase pattern is what reads as a Terminal-output-line; the
extension is incidental. (Same shape as the "RUSTY SOLITAIRE"
wordmark deviation noted under `cacb19c` — the mockup leaked the
repo name; the actual product is "Solitaire Quest".)
### `54005d5` `feat(engine): add GAME #YYYY-DDD caption beneath the replay headline`
Adds the right-anchored game-identifier piece of the replay-overlay
mockup, adapted to live *under* the existing "▌ replay" headline as
a `TYPE_CAPTION` (11 px) / `TEXT_SECONDARY` subtitle. Format is
`GAME #{year}-{ordinal:03}` (e.g. `GAME #2026-122` for a replay
recorded 2026-05-02) — year + chrono ordinal gives a compact,
monotonically-increasing identifier matching the mockup's
`GAME #2024-127` motif. New `ReplayOverlayGameCaption` marker, new
pure helper `format_game_caption(state) -> Option<String>` (None
for Inactive / Completed since the replay is consumed in those
branches; spawn-time fall-through to empty string).
**Layout impact:** `BANNER_HEIGHT` bumped 48 → 60 px so the new
left column (headline + 2 px gap + caption ≈ 39 px content) fits
under the scrub bar with room to spare. +12 px banner mass is the
deliberate cost of the new content; no other plugin observes
`BANNER_HEIGHT` so the change is local.
Two new tests (1180 → 1182): `format_game_caption_covers_state_corners`
pins the three branches plus the zero-pad-to-3-digits invariant
for early-January ordinals; `overlay_game_caption_shows_replay_date`
drives `ReplayPlaybackState` end-to-end.
### `e080b49` `feat(engine): restyle replay progress text as Terminal MOVE chip`
Closes the centre-text half of the replay-overlay enrichments. The
plain "Move N of M" text becomes a 1px `ACCENT_PRIMARY`-bordered
chip containing "MOVE N/M" — uppercase + slash separator reads as
a Terminal output line and matches the floating-chip motif in
`docs/ui-mockups/replay-overlay-mobile.html`. The chip lives
in-banner rather than floating above the focused card (the
screen-takeover treatment that requires plumbing cursor → card
identity remains deferred).
**Implementation note:** `BorderColor` in Bevy 0.18 is a per-side
struct, not a tuple — `BorderColor::all(ACCENT_PRIMARY)` is the
correct constructor. Worth pinning for next time we touch a
border-painted UI surface. The `ReplayOverlayProgressText` marker
stays on the inner Text rather than the new chip Node so
`update_progress_text` keeps repainting unchanged — a deliberate
"markers belong on the entity that updates change" choice.
Test count unchanged (1182); `overlay_progress_text_reflects_cursor`
swapped its assertion from "Move 5 of 10" to "MOVE 5/10".
This pair (`54005d5` + `e080b49`) closes Option C from the
SESSION_HANDOFF Resume prompt's banner-local enrichments. Floating-
chip-above-focused-card and the full screen-takeover redesign
remain — both data-layer or cross-plugin and intentionally still
open.
### `29136d8` `feat(engine): add pulsing trailing cursor to splash "▌ ready_" line`
Closes the cursor-pulse half of the splash polish arc deferred in
`cacb19c`. The "▌ ready_" line now ends with a 6×12 px cyan Node
that pulses on a 1 s sine cadence, multiplied with the global
splash fade timeline so the cursor never reaches full alpha while
the rest of the splash is still fading in.
**The "multiply, don't override" pattern.** Two systems write the
same `BackgroundColor` per frame: `advance_splash` writes the
global-fade alpha, `pulse_splash_cursor` overwrites with
`global_alpha × pulse_factor`. Both derive from `SplashAge` on the
root, so the writes are commensurate — the second one isn't
"fighting" the first, just refining it. This is the cleanest fix
for the "fight the global fade timeline" warning the original
`cacb19c` skip note flagged.
**Defensive division guard.** `cursor_pulse_factor(age, period, min)`
short-circuits to `1.0` when `period <= 0.0` so a future
misconfiguration produces a steady cursor rather than NaN
propagation (NaN in alpha = invisible UI, hard to debug). Worth
mirroring on every trig/division helper, not just this one.
One new test (1182 → 1183): `cursor_pulse_factor_corners` pins the
peak (factor = 1 at age = period / 4), trough (factor = min at age =
period × 3 / 4), and the zero/negative-period guard.
### `a27cf5a` `feat(engine): add tiled scanline overlay to splash`
Closes the scanline half of the splash polish arc. A fullscreen
`ImageNode` tiles a runtime-generated 2×2 RGBA8 texture over the
splash content — top row transparent, bottom row `#1a1a1a` at
~30 % alpha — producing the 1 px-pitch horizontal scanline pattern
called for in `docs/ui-mockups/splash-mobile.html`.
**Texture-α × tint-α composite for fade integration.** The 30 %
alpha is baked into the texture pixels, not the `ImageNode.color`
tint. `advance_splash`'s new third query writes
`(1, 1, 1, global_alpha)` into the tint each tick; the GPU
multiplies texture-α by tint-α, so the visible composite is
`0.3 × global_alpha`. Cleaner than building a "multiplicative
fadable" abstraction in the ECS — the GPU already does this
multiplication for free.
**Bevy 0.18 API surprises (worth pinning):**
- `RenderAssetUsages` re-exports under `bevy::asset::`, not
`bevy::render::render_asset::`. Type name unchanged; module
path moved.
- `TextureFormat::pixel_size()` returns `Result<usize, _>` rather
than the bare `usize` you'd expect for a static format query.
Annoying enough that the `debug_assert_eq!` against the buffer
length just hard-codes the `2 × 2 × 4 = 16` literal.
Headless test fixture now also `init_resource::<Assets<Image>>()`
since `MinimalPlugins` doesn't pull `AssetPlugin` — same pattern
`settings_plugin::tests` already used. Without it, the
`Option<ResMut<Assets<Image>>>` parameter on `spawn_splash` would
fall through and the scanline overlay would silently skip,
defeating the new tests.
Two new tests (1183 → 1185):
`build_scanline_image_has_expected_2x2_rgba_bytes` locks the
texture pixels literally so a future tweak can't drift the
appearance silently; `scanline_overlay_spawns_and_fades_with_splash`
asserts spawn placement under `SplashRoot` and the new
fade-images branch's correctness end-to-end.
This pair (`29136d8` + `a27cf5a`) closes Option B from the
SESSION_HANDOFF Resume prompt — both splash polish pieces now
shipped.
### `5623368`…`dd101b3` — Option D card-face migration arc
Closed 2026-05-08 across nine commits. The full Terminal card
artwork now renders end-to-end. Detail breakdown lives in the
"Visual-identity follow-ups" punch-list entry below; the short
version:
- Migration plan + pipeline tooling: `5623368` (plan doc),
`3a4bb63` (single-card PoC proving the `usvg`/`resvg` pipeline
at per-card grain), `babe5cc` (full
`solitaire_engine/examples/card_face_generator.rs` example
emitting 52 faces + 5 backs into `assets/cards/`), `48b28d2`
(the `card_face_svg_pin` integration test pinning rasteriser
output via inline FNV-1a hashing of raw RGBA8 bytes — the
pin's bootstrap pattern, "empty `EXPECTED` → run → paste",
is the maintenance interface for future intentional changes).
- Lockstep step 4+5: `e8bf9d7`. New PNG bytes + the 5
`card_plugin` constants (`CARD_FACE_COLOUR`,
`RED_SUIT_COLOUR`, `BLACK_SUIT_COLOUR`,
`CARD_FACE_COLOUR_RED_CBM``RED_SUIT_COLOUR_CBM`,
`card_back_colour`) + signature shifts in one commit.
`face_colour` deleted — Terminal face is uniformly
`CARD_FACE_COLOUR` regardless of CBM, so the function
collapsed to a constant. `text_colour` gained a
`color_blind: bool` parameter (red→cyan suit-glyph swap when
CBM is on). Four `face_colour` CBM tests folded into two
`text_colour` CBM tests in lockstep.
- Three follow-ups that surfaced during sign-off, all from the
same "fallback path the migration walked past" pattern:
`a14200a` regenerated the embedded **default-theme SVGs** at
`solitaire_engine/assets/themes/default/*.svg`; those bytes
`include_bytes!()`-embed into the binary and override
`assets/cards/*.png` at startup, so the PNG migration alone
didn't change what production rendered. `8719f77`
regenerated `assets/backgrounds/bg_*.png` to flat Terminal
near-black (5 solid-colour PNGs via a new
`solitaire_engine/examples/background_generator.rs` example).
`ae84dc1` cleared the **top-bar overlap** at portrait/narrow
window widths by swapping the action-button row's hardcoded
`font_size: 16.0` to `TYPE_BODY` (a typography-migration
miss) and stepping horizontal padding from `VAL_SPACE_3`
to `VAL_SPACE_2`.
- Glyph-rendering fix: `af414b6`. The bundled `FiraMono`
doesn't carry usable U+2660-2666 glyphs at the requested
size — `usvg` was silently substituting tiny "tofu" marks.
Switched suit glyphs from `<text>` elements to inline SVG
`<path>` elements via a new `suit_path_d` helper. 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).
- Glyph-orientation tweak: `dd101b3`. Removed the 180° rotation
from the bottom-right large suit glyph at user request. Both
glyphs now render upright. `design-system.md` § Game Cards
line 220 updated in lockstep — the deliberate deviation from
the traditional inverted-corner-indicator convention is
documented in the spec, not just the code.
The pin test fired exactly twice during this arc (once for the
text→path switch, once for the unrotation) and rebaselined
cleanly each time via the empty-then-paste pattern. The 5
`back_*` hashes stayed identical across both rebaselines —
secondary signal that the FNV-1a fingerprinting is purely
deterministic on rasteriser output.
This arc closes Option D from the SESSION_HANDOFF Resume prompt
and effectively completes the Terminal visual-identity port —
only the toast warning/error variant slots remain wired-but-
unused.
## What shipped in v0.20.0 (frozen at `41a009a`)
### Terminal visual-identity port
Top-down stack — every commit downstream of the token system
reads from it, so swapping the palette is now a one-file edit:
- **`ui_theme` token system** (`0d477ac`). base16-eighties
palette, 5-rung type scale, 7-rung 4-multiple spacing scale,
3-step radius, 14-rung z-index hierarchy, full motion budget,
4 invariant-pinning unit tests. Card-shadow alphas pinned to 0
(Terminal achieves depth via 1px borders + tonal layering).
- **Modal scaffold already on tokens** — `ui_modal` was ported
in the same commit's wake; three stale "loud yellow" /
"magenta secondary" doc comments fixed.
- **Gameplay feedback → semantic state tokens** (`ceec4fc`).
Selection / valid-drop tints route through `ACCENT_PRIMARY` /
`STATE_WARNING` / `STATE_SUCCESS`.
- **Toasts** (`a137607`). New `ToastVariant` enum
(Info / Warning / Error / Celebration); opaque `BG_ELEVATED`
+ 1px accent border + bottom-anchor. All ten call sites pass
their semantic variant.
- **`table_plugin` chrome** (`651f406`).
`PILE_MARKER_DEFAULT_COLOUR` promoted; `cursor_plugin` imports
it, replacing a "kept in sync" doc comment with a compile-
enforced invariant. `HINT_PILE_HIGHLIGHT_COLOUR`
`STATE_WARNING`.
- **`card_plugin` chrome** (`d752870`). Drag-elevation shadow
routes through `CARD_SHADOW_*` tokens. `RIGHT_CLICK_HIGHLIGHT_COLOUR`
`STATE_SUCCESS`. Stock recycle "↺" text → `TEXT_PRIMARY @ 0.7α`.
Card-face / suit / card-back palette intentionally NOT migrated
(artwork dependency — see open-list item below).
- **Splash cursor** (`cdcadda`). The signature `▌` cyan glyph
(96 px) added above the wordmark, matching the spec.
*Subsequently expanded post-cut by `cacb19c` into the full
boot-screen treatment.*
- **Hint-source / dest pairing** (`9891ae4`). `input_plugin`'s
source-card tint now matches the destination pile's
`STATE_WARNING`.
- **Design system + 24-mockup library** (`fa7f98a`).
`docs/ui-mockups/design-system.md` + 24 Stitch mockups (HTML +
PNG) covering every screen plus 9 missing-plugin surfaces.
- **`card_shadow_params` test aligned** (`1d1543e`). Drag-vs-
idle shadow assertion loosened to `>=` to accept the Terminal
"no shadow" intent without losing the regression-guard.
### Android persistence
- **`solitaire_data::data_dir` shim** (`4b51e50`). New
`solitaire_data::platform::data_dir()` falls through to
`dirs::data_dir()` on desktop and returns the per-app sandbox
at `/data/data/com.solitairequest.app/files` on Android — no
JNI needed (package id pinned in `[package.metadata.android]`).
Six `solitaire_data` callsites + `solitaire_engine/assets/user_dir.rs`
migrated. Settings, stats, achievements, replays, game-state,
time-attack sessions, and user themes now persist on Android.
### Inherited from earlier in the cycle (pre-session)
- Android build target + APK (`fb8b2ac`), runbook (`59424a3`),
F3 FPS overlay (`690e1d2`), Smart Window Size opt-out
(`e1b8766`), Shareable badge (`9b065e5`), Help cheat-sheet
M/P/Enter rows (`35516d3`), `pull_failure_sets_error_status`
flake fix (`67c150b`).
## Open punch list
### Phase Android (build + persistence shipped; runtime gaps remain)
- **APK launch verification on AVD / device.** `adb install` then
`adb logcat` against the `bevy_test` AVD or an x86_64 device.
The build works and persistence is wired, but no end-to-end
device run has been logged. Shakes out runtime bugs the build +
unit tests can't catch.
- **JNI ClipboardManager bridge.** Replaces the Android stub for
the Stats "Copy share link" toast. `arboard` doesn't ship an
Android backend; small custom JNI call.
- **Android Keystore for credentials.** `keyring` is target-gated
to a stub returning `KeychainUnavailable`; replace with Android
Keystore via JNI when sync auth ships on mobile.
- **Google Play Games (gpgs) integration.** Listed as a
Phase-Android target since Phase 1; now unblocked by the build
target.
- **Cosmetic `cargo apk build --lib` workaround.** Post-sign
panic doesn't affect the APK on disk but produces noisy stderr.
Either upstream a cargo-apk fix or document `--lib` as
canonical in the runbook.
### Visual-identity follow-ups (opened by v0.20.0's port)
- *Card-face / suit / card-back artwork regeneration — closed
2026-05-08 by the commit chain `5623368``dd101b3`.* The
Terminal spec called for dark `#1a1a1a` cards with light suit
pips (pink for hearts/diamonds, foreground gray for spades/
clubs). Closed across nine commits over two arcs:
- **Plan + tooling (`5623368``48b28d2`):** migration plan
doc, single-card PoC, full `card_face_generator` example
(52 faces + 5 backs into `assets/cards/`), and the
`card_face_svg_pin` integration test pinning rasteriser
output via FNV-1a so future `usvg`/`resvg` upgrades surface
as test failures rather than silent visual drift.
- **Lockstep step 4+5 (`e8bf9d7`):** PNGs + the 5 `card_plugin`
constants + signature shifts in one commit.
`CARD_FACE_COLOUR_RED_CBM` renamed to `RED_SUIT_COLOUR_CBM`
and repurposed from a face-tint to a suit-glyph swap (the
Terminal face is uniform `CARD_FACE_COLOUR` regardless of
CBM; CBM only swaps red suits to cyan in the glyph itself).
`face_colour` deleted, `text_colour` gained a `color_blind`
parameter.
- **Three follow-ups that surfaced during sign-off:**
`a14200a` regenerated the **default-theme SVGs** at
`solitaire_engine/assets/themes/default/*.svg` — those
`include_bytes!()`-embed into the binary and override
`assets/cards/*.png` at runtime, so the PNG migration alone
didn't change what production rendered. `8719f77`
regenerated `assets/backgrounds/bg_*.png` to flat Terminal
near-black (5 solid-colour PNGs via a new
`background_generator` example). `ae84dc1` cleared the
**top-bar overlap** at portrait/narrow window widths by
swapping the action-button row's hardcoded `font_size: 16.0`
to `TYPE_BODY` and stepping horizontal padding from
`VAL_SPACE_3` to `VAL_SPACE_2`.
- **Glyph-rendering fix (`af414b6`):** suit glyphs render as
inline SVG paths (not `<text>`) because the bundled
`FiraMono` doesn't carry usable U+2660-2666 at the
requested size — `usvg` was silently substituting tiny
"tofu" marks. Path-based rendering bypasses the font system
entirely; same bytes on every machine. The pin test
rebaselined cleanly via the empty-then-paste pattern.
- **Glyph-orientation tweak (`dd101b3`):** removed the 180°
rotation from the bottom-right large suit glyph at user
request — both glyphs now render in the same upright
orientation. `design-system.md` § Game Cards line 220
updated in lockstep to document the deliberate deviation
from the traditional inverted-corner-indicator convention.
- *Splash boot-loader scanline overlay — closed by `a27cf5a`.*
Runtime-generated 2 × 2 RGBA8 texture tiled via
`NodeImageMode::Tiled`; per-pixel alpha × tint alpha gives
multiplicative fade integration without new abstractions.
- *Splash cursor pulse — closed by `29136d8`.* Trailing 6 × 12 px
cyan Node, sine-pulsed, multiplied with the global splash fade
(the "multiply, don't override" pattern that resolves the
original `cacb19c` skip-rationale).
- **Replay-overlay enrichments beyond the scrub bar.** Banner-local
pieces of the mockup (`docs/ui-mockups/replay-overlay-mobile.html`)
all shipped: scrub bar (`c84d9f4`), `▌ replay` cursor-block label
(`6204db8`), `GAME #YYYY-DDD` caption (`54005d5`), `MOVE N/M`
chip restyle (`e080b49`). What's still open are the cross-plugin
/ data-layer pieces: a `MOVE N/M` chip *floating above the
focused card* during playback (would need to thread the cursor
through to the card layer — `update_progress_text` writes the
banner chip but the card-position lookup belongs in `card_plugin`).
The full mockup's screen-takeover treatment — mini-tableau
preview, playback controls, move-log scroll, WIN MOVE marker on
the scrub bar — is a multi-session redesign with
data-layer impact (move-log scroller; the WIN MOVE marker
needs a `win_move_index` field on `Replay` that doesn't yet
exist). Banner-overlay behaviour is intentionally preserved
for now.
- **Toast Warning / Error variants.** The `ToastVariant` enum
has slots for `Warning` (gold) and `Error` (pink) but no
in-engine event uses them yet. Wire when a warning- or error-
flavoured toast event materialises.
### Carried forward from v0.19.0
- **App icon round.** `Window::icon` not yet wired; no
`.icns` / `.ico` / Linux hicolor PNG hierarchy. The 11-size
icon export the v0.19 handoff referenced is *not* currently
in `artwork/` (current `artwork/` holds the reverted Rusty
Pixel card PNGs and is intentionally untracked); icon-export
needs to be re-run before this item can be picked up.
Half-day task once the PNGs are back in place. No cert
dependency.
### Other small candidates
- **Prev/Next selector chips spawn site.** v0.19.0's `9b065e5`
noted Prev/Next markers exist in `stats_plugin` but no spawn
site renders them today — the Shareable badge therefore lands
on the single-replay caption. If/when Prev/Next is plumbed,
the badge will need to follow.
- **Toast queue / immediate unification.** The two toast paths
(`spawn_queued_toast` for `InfoToastEvent` queue; `spawn_toast`
for fire-and-forget) now share visual treatment but remain
separate functions because they serve different temporal
needs (sequential vs. parallel). If overlap becomes a UX
issue, merge into one queue with priority lanes.
### Process notes
- **The desktop-adaptation spec is the canonical reference for
geometry decisions** when porting any future plugin. Read
`docs/ui-mockups/desktop-adaptation.md` first; apply the
universal rules to every surface; consult the per-screen
table for the priority surfaces. The 9 missing-plugin screens
(splash now ported; eight remaining) inherit the universal
rules without dedicated guidance.
- **Stitch `generate_variants` is unreliable for layout-only
adaptation prompts** as of 2026-05-07. The first call timed
out and no variant ever landed in `list_screens`. If a future
session wants visual desktop mockups, prefer
`generate_screen_from_text` with a fresh narrow prompt per
screen rather than `generate_variants` against existing
mobile screens.
- **Token-port pattern.** v0.20.0's chrome-migration commits
set a reusable shape for "centralised design system applied
across N plugins":
1. Constants module (`ui_theme.rs`) is the source of truth.
2. Const sites that can't call `Alpha::with_alpha` (not yet
`const` on stable) use a literal RGB matching the token,
with a unit test pinning the RGB to the token (e.g.
`MARKER_VALID`, `HINT_PILE_HIGHLIGHT_COLOUR`,
`RIGHT_CLICK_HIGHLIGHT_COLOUR`).
3. Cross-plugin duplication (e.g. `MARKER_DEFAULT`
`PILE_MARKER_DEFAULT_COLOUR`) collapses to a single
promoted const re-exported from one plugin and imported
by the other — replaces "kept in sync" doc comments with a
compile-time invariant.
4. Domain colours (suit pips, card faces, lerp helpers) stay
as literals with a comment naming the rationale; only UI
chrome routes through tokens.
- **`SplashFadable` scaffolding pattern** (introduced in
`cacb19c`). Any future overlay that needs to fade `N >> 3`
elements together should follow the same shape: one tiny
marker carrying the full-alpha base colour, one global query
that lerps every marker's alpha each frame, no per-element
query plumbing. Cleanly outscales the `Without<X>, Without<Y>`
query exclusion pattern that the old splash was hitting at
three siblings.
### Canonical remote
`github.com/funman300/Rusty_Solitaire` is the canonical repo.
Always push there. **Local master has unpushed post-cut commits**
— run `git log --oneline origin/master..HEAD` for the live list;
`git push` is the next durability step (or roll the post-cut
commits into v0.20.1).
### Design direction (Terminal — base16-eighties)
- **Tone:** retro-terminal / synthwave — flat depth (no box-shadows),
monospaced-forward typography (JetBrains Mono / FiraMono), tight
16 px edge margins, 8 px card radius.
- **Palette:** near-black surface ramp (`#151515` / `#202020` /
`#2a2a2a` / `#353535`), cyan primary CTA (`#6fc2ef`), lime
success (`#acc267`), gold warning (`#ddb26f`), pink error /
suit-red (`#fb9fb1`), lavender celebration (`#e1a3ee`), teal
info (`#12cfc0`).
- **Two-color suits.** Red = `#fb9fb1`, black = `#d0d0d0`.
Outlined glyphs for diamonds & clubs are *always on*; the
Settings "color-blind mode" toggle only swaps red → cyan.
## Resume prompt
```
You are a senior Rust + Bevy developer working on Solitaire Quest.
Working directory: <Rusty_Solitaire clone path on this machine>.
Branch: master. v0.20.0 is tagged at 41a009a; the post-cut work
through dd101b3 is pushed to origin (Options B, C, D all closed).
Run `git log --oneline 41a009a..HEAD` to see what landed since the
tag — substantives: desktop-adaptation spec, splash boot-screen
port, replay-overlay banner enrichments, and the full card-face
artwork arc (52 faces + 5 backs as Terminal SVG-rasterised PNGs,
default-theme SVGs in lockstep, table backgrounds flattened,
top-bar layout fix, glyph orientation upright).
State: HEAD locally — see `git rev-parse HEAD`. Working tree is
clean. All workspace tests pass (~1180+; check with
`cargo test --workspace`), clippy clean.
READ FIRST (in order, before doing anything):
1. SESSION_HANDOFF.md — this file
2. CHANGELOG.md — [0.20.0] section is the most recent cut
3. CLAUDE.md — unified-3.0 rule set
4. CLAUDE_SPEC.md — formal architecture spec
5. ARCHITECTURE.md — crate responsibilities + data flow
6. docs/ui-mockups/ — design system + 24-mockup library +
desktop-adaptation.md (the rules-based
companion to the mockups; read this
before any plugin port)
7. docs/android/* — Android setup + build runbook
8. ~/.claude/projects/<this-project>/memory/MEMORY.md
— saved feedback / project context
(machine-local; may be missing on a
fresh machine)
DECISION TO ASK THE PLAYER FIRST:
A. Push the post-cut commits to origin. Either as-is on master
or rolled into a v0.20.1 cut (CHANGELOG entry + tag).
Mechanical, but local master diverges from origin until done.
B. *Closed by `29136d8` + `a27cf5a`.* Both splash polish
pieces shipped (cursor pulse + scanline overlay). No further
splash work pending unless a new mockup detail surfaces.
C. *Closed by `54005d5` + `e080b49`.* Banner-local replay-overlay
pieces all shipped (scrub bar, ▌ label, GAME caption, MOVE
chip). Remaining are cross-plugin (floating MOVE chip above
the focused card — needs cursor → card-position plumbing) or
multi-session (full screen-takeover redesign — move-log
scroll, mini tableau, WIN MOVE marker, data-layer impact).
Either belongs in its own decision tree the next time replay
work surfaces.
D. *Closed 2026-05-08 by `5623368`…`dd101b3`.* The full
card-face / suit / card-back / default-theme / table-
background / top-bar / glyph-orientation arc landed across
nine commits. Terminal cards rendering on every face (dark
`#1a1a1a` background, pink/gray suit glyphs as inline SVG
paths, scanline-pattern cyan-accent backs); both rendering
paths (`assets/cards/*.png` and the bundled-default theme
SVGs at `solitaire_engine/assets/themes/default/*.svg`) in
lockstep; pin test (`card_face_svg_pin`) guards against
future rasteriser drift. Visual-identity arc effectively
complete — only the toast warning/error variant slots
remain wired-but-unused.
E. App icon round — re-run artwork/Icon Export.html (the
export PNGs are not currently in `artwork/`), then wire
Window::icon + generate .icns / .ico. Half-day task. No
cert dependency.
F. APK launch verification on AVD / device + the JNI bridges
it would shake out (ClipboardManager, Keystore).
WORKFLOW NOTES:
- Use the system git config (already correct).
- When attributing playtester feedback in commits/docs, use
"Quat" not "Rhys" (saved feedback memory).
- Sub-agents stage + verify only; orchestrator commits.
- Every commit must pass build / clippy / test before pushing.
- Push to GitHub (origin) — gh auth setup-git wired on
primary dev box; verify on laptop before first push.
OPEN AT THE START: ask which of AF. Don't pick unilaterally.
```