Files
Ferrous-Solitaire/SESSION_HANDOFF.md
T
funman300 73ac67d76b docs(handoff): record splash pulse + scanline; mark Option B closed
Bookkeeping pass after 29136d8 (cursor pulse) and a27cf5a (scanline
overlay) shipped — both halves of the splash polish arc deferred in
cacb19c are now done.

Changes:
- "Since the v0.20.0 cut": added per-commit narratives for 29136d8
  and a27cf5a with the implementation notes worth preserving past
  the commit log:
  - The "multiply, don't override" pattern that resolved the
    cursor-pulse / fade-timeline conflict (and generalises to any
    two ECS systems writing the same per-frame component).
  - The texture-α × tint-α GPU-composite trick that integrates the
    scanline with the fade without a new "multiplicative fadable"
    abstraction.
  - Two Bevy 0.18 API surprises (RenderAssetUsages module move;
    pixel_size() returning Result) — pinned for next time we touch
    runtime-generated images.
  - The defensive period <= 0.0 guard on cursor_pulse_factor — a
    cheap NaN-prevention pattern worth mirroring on every trig
    helper.
- "Open punch list" → "Visual-identity follow-ups": collapsed the
  two splash-polish bullets into closed pointers.
- Resume prompt → Option B: marked closed with "no further splash
  work pending unless a new mockup detail surfaces" so a future
  session knows it's a finished arc, not an in-flight one.

Three options now closed (A, B, C); D / E / F remain — all three
have a real blocker (D = multi-session, E = artwork PNGs missing,
F = Android hardware/AVD) so the next session starts with a
genuine commitment-vs-blocker decision rather than picking the
smallest piece.
2026-05-07 22:45:46 -07:00

27 KiB
Raw Blame History

Solitaire Quest — Session Handoff

Last updated: 2026-05-07 — v0.20.0 cut and tagged at 41a009a, several post-cut commits sit on top of the tag (see git log --oneline origin/master..HEAD for the live list), working tree is 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, four more pieces landed: the rules-based desktop-adaptation spec (closes the spec gap exposed when we noticed 23 of 24 mockups were mobile-only), the splash boot-screen port (full mockup-spec splash with header, boot log, progress bar, palette swatches, version footer + the SplashFadable scaffold refactor), the replay-overlay scrub bar (1 px cyan fill at the bottom of the banner, mirroring cursor / total), and the replay banner label port to the ▌ replay cursor-block treatment that aligns it with the splash boot-screen idiom.

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: 41a009a (the v0.20.0 cut). Local master is ahead of origin by the post-cut commits enumerated under "Since the v0.20.0 cut" below; run git log --oneline origin/master..HEAD for the live count. Decide whether to roll these into v0.20.1 / v0.21.0-candidates before pushing.
  • Working tree: clean. No WIP outstanding.
  • artwork/ directory: still untracked. Intentional.
  • Build: cargo clippy --workspace --all-targets -- -D warnings clean.
  • Tests: 1180 passing / 0 failing across the workspace. Up from 1176 at the v0.20.0 cut: the splash boot-screen port added two (splash_renders_terminal_boot_screen_content, fadables_start_transparent_and_reach_full_alpha) and the replay scrub-bar finish added two more (scrub_pct_covers_state_corners, overlay_scrub_fill_tracks_cursor).
  • 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.

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 tokensui_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_COLOURSTATE_WARNING.
  • card_plugin chrome (d752870). Drag-elevation shadow routes through CARD_SHADOW_* tokens. RIGHT_CLICK_HIGHLIGHT_COLOURSTATE_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. The Terminal spec calls for dark #1a1a1a cards with light suit pips (pink for hearts/diamonds, foreground gray for spades/ clubs); the runtime path still renders the legacy white-card PNG artwork. The fallback constants in card_plugin (CARD_FACE_COLOUR, RED_SUIT_COLOUR, BLACK_SUIT_COLOUR, CARD_FACE_COLOUR_RED_CBM, card_back_colour palette) are intentionally unmigrated and should swap in lockstep with the artwork. Largest visible payoff remaining in the visual- identity arc.
  • 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_DEFAULTPILE_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; several post-cut
commits sit on top locally and have NOT been pushed yet — run
`git log --oneline origin/master..HEAD` for the live list (current
substantives: 39b8496 desktop-adaptation spec, cacb19c splash
boot-screen port, c84d9f4 replay scrub-bar finish, 6204db8 replay
banner ▌ cursor-block label, plus any handoff edits since).

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. Card-face artwork regeneration. Generate Terminal-aesthetic
     card PNGs (dark face, light suit pips), then migrate
     CARD_FACE_COLOUR / RED_SUIT_COLOUR / BLACK_SUIT_COLOUR /
     CARD_FACE_COLOUR_RED_CBM in lockstep. Largest visible
     payoff remaining in the visual-identity arc. Multi-session.
  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.