From 5acd8e4cd069ed8cc991cf0cb38a319396cd8d14 Mon Sep 17 00:00:00 2001 From: funman300 Date: Mon, 6 Jul 2026 15:26:06 -0700 Subject: [PATCH] docs(architecture): bring source-of-truth docs to post-migration reality ARCHITECTURE.md predated the card_game migration (PR #88, 2026-06-22) and still documented the pre-migration core: local Card with face_up, PileType, stored score/undo/recycle, and a snapshot undo stack. Rewrites the Core Game Models section around the upstream card_game/klondike types and the derived-stats/replay-undo model, updates the solitaire_core crate section (deps + ownership), adds solitaire_web and solitaire_assetgen to the workspace tree, and corrects the solitaire_app and Settings entries. Version bumped to 1.4. Also adds the two missing crates to the CLAUDE.md crate map (AGENTS.md, its gitignored local twin, was regenerated in place) and records the full device checklist passing on the Fold 7 in SESSION_HANDOFF. Co-Authored-By: Claude Fable 5 --- ARCHITECTURE.md | 110 +++++++++++++++++++++++++++++---------------- CLAUDE.md | 4 +- SESSION_HANDOFF.md | 17 +++---- 3 files changed, 81 insertions(+), 50 deletions(-) diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 22b6989..9c274c5 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -1,9 +1,11 @@ # Ferrous Solitaire — Architecture Document -> **Version:** 1.3 +> **Version:** 1.4 > **Language:** Rust (Edition 2024) > **Engine:** Bevy (latest stable) -> **Last Updated:** 2026-05-12 +> **Last Updated:** 2026-07-06 — post card_game/klondike migration (PR #88): +> core card/pile types come from the upstream `card_game` workspace and +> score/undo/recycle are derived from the upstream session, not stored. --- @@ -82,13 +84,15 @@ ferrous_solitaire/ │ ├── win_fanfare.wav │ └── ambient_loop.wav │ -├── solitaire_core/ # Pure Rust game logic — zero external deps beyond rand/serde +├── solitaire_core/ # Pure Rust game rules — wraps upstream card_game/klondike (serde + thiserror only otherwise) ├── solitaire_sync/ # Shared API types — used by client and server ├── solitaire_data/ # Persistence, sync client, settings ├── solitaire_engine/ # Bevy ECS systems, components, plugins -├── solitaire_server/ # Self-hosted sync server (Axum + SQLite) -├── solitaire_wasm/ # WebAssembly bindings — browser-side replay player -└── solitaire_app/ # Main binary entry point +├── solitaire_server/ # Self-hosted sync server (Axum + SQLite) + web frontend +├── solitaire_wasm/ # WebAssembly bindings — browser-side logic/replay + debug bridge +├── solitaire_web/ # Bevy WASM canvas build for the browser /play route +├── solitaire_assetgen/ # One-shot generator for card/background PNG assets +└── solitaire_app/ # Main binary entry point (desktop + Android cdylib) ``` --- @@ -96,18 +100,30 @@ ferrous_solitaire/ ## 3. Crate Responsibilities ### `solitaire_core` -**Dependencies:** `rand`, `serde`, `chrono` only. +**Dependencies:** `serde`, `thiserror`, plus the upstream `card_game` and +`klondike` crates (pinned via the Quaternions registry — never edit upstream). -The entire game rules engine. No Bevy, no network, no file I/O. Designed to be tested in isolation with `cargo test -p solitaire_core`. +The game rules layer. No Bevy, no network, no file I/O. Designed to be tested +in isolation with `cargo test -p solitaire_core`. + +Since the card_game migration (2026-06-22, PR #88) the primitive types are +**upstream**: `Card`, `Deck`, `Suit`, `Rank`, `Session` come from `card_game`; +`Klondike`, `KlondikePile`, `KlondikeInstruction`, `DrawStockConfig`, +`Foundation`, `Tableau` come from `klondike`. `solitaire_core` re-exports them +so downstream crates import from one place and never depend on the upstream +crates directly. Owns: -- All game data models (`Card`, `Suit`, `Rank`, `Pile`, `GameState`) -- Move validation logic -- Scoring engine -- Undo stack +- `GameState` — a wrapper around the upstream `Session`; the session + is the single source of truth for board state and stats +- `MoveError` and the `Result`-based mutation API +- `KlondikeConfig` adaptation (`klondike_adapter`) — draw mode, scoring, + take-from-foundation +- `GameMode` (Classic / Zen / Challenge / TimeAttack) and mode-aware scoring +- Solvability check API (`SolveOutcome`, delegating to `Session::solve`) - Win / auto-complete detection - Achievement unlock condition evaluation -- Seeded RNG for reproducible deals +- Seeded deals (same seed ⇒ same layout, via the upstream dealer) **Rules decisions:** - **Stock recycling is unlimited in every draw mode — by design.** Extra @@ -189,9 +205,13 @@ Owns: Because `ReplayPlayer` uses the same `solitaire_core::GameState` as the desktop client, the two implementations cannot drift: the same seed + move list produces identical pile state at every step on both platforms. ### `solitaire_app` -**Dependencies:** `bevy`, `solitaire_engine`. +**Dependencies:** `bevy`, `solitaire_engine`, `solitaire_data` (+ `jni` on Android). -Thin binary entry point. Registers all Bevy plugins and sets initial window properties. +Thin entry point (desktop binary + Android `cdylib`). Registers all Bevy +plugins and sets initial window properties. The one crate in the workspace +allowed `unsafe`: the Android entry point reconstructs the raw JNI handles and +hands them to the safe `solitaire_data::android_jni` bridge; everything else +is `forbid(unsafe_code)`. --- @@ -559,26 +579,32 @@ This ensures all players worldwide get the same challenge for a given date, rega ### Core Game Models (`solitaire_core`) +Since the card_game migration, the primitives are upstream types re-exported +through `solitaire_core`: + ```rust -pub enum Suit { Clubs, Diamonds, Hearts, Spades } -pub enum Rank { Ace, Two, Three, Four, Five, Six, Seven, Eight, Nine, Ten, Jack, Queen, King } +// From `card_game` (upstream — never edit): +pub enum Suit { /* Clubs, Diamonds, Hearts, Spades */ } +pub enum Rank { /* Ace ..= King */ } +pub struct Card { /* deck + suit + rank; identity type, no face_up flag — + facing is positional, tracked by the Klondike board */ } +pub struct Session { /* replayable instruction log + derived stats */ } -pub struct Card { - pub id: u32, - pub suit: Suit, - pub rank: Rank, - pub face_up: bool, -} - -pub enum PileType { +// From `klondike` (upstream — never edit): +pub enum KlondikePile { Stock, Waste, - Foundation(Suit), - Tableau(usize), // 0–6 + Foundation(Foundation), // 4 slots, any suit may claim any slot + Tableau(Tableau), // 7 columns } +pub enum DrawStockConfig { DrawOne, DrawThree } +pub enum KlondikeInstruction { /* RotateStock, DstFoundation, ... — the + serialized move format (schema v4+) */ } +``` -pub enum DrawMode { DrawOne, DrawThree } +Owned by `solitaire_core`: +```rust /// Active game mode. Classic is the default; others unlock at level 5. pub enum GameMode { Classic, Zen, Challenge, TimeAttack } @@ -589,24 +615,30 @@ pub enum MoveError { RuleViolation(String), UndoStackEmpty, GameAlreadyWon, + StockEmpty, } pub struct GameState { - pub piles: HashMap>, - pub draw_mode: DrawMode, pub mode: GameMode, - pub score: i32, - pub move_count: u32, - pub undo_count: u32, // number of undos used in this game - pub recycle_count: u32, // number of stock recycles pub elapsed_seconds: u64, - pub seed: u64, - pub is_won: bool, - pub is_auto_completable: bool, - undo_stack: VecDeque, // private, max 64 (VecDeque for O(1) pop_front) + pub seed: u64, // same seed ⇒ same deal + pub take_from_foundation: bool, + session: Session, // private — the single source of truth } ``` +**Derived, not stored:** `score()`, `move_count()`, `undo_count()`, +`recycle_count()`, `is_won()`, `is_auto_completable()`, and all pile +accessors read through the session. Undo replays the instruction log +(no snapshot stack); the −15 undo penalty is applied by the upstream +score formula via the session config. Persistence (schema v5) saves +`saved_moves` as upstream `KlondikeInstruction`s and rebuilds the +session by replay on load — older files carrying `score`/`undo_count`/ +`recycle_count` keys load fine, the extra fields are ignored. + +**Rules decision:** stock recycling is unlimited in every draw mode +(see the "Rules decisions" note in §3 `solitaire_core`). + ### Persistence Models (`solitaire_data`) ```rust @@ -644,7 +676,7 @@ pub struct AchievementRecord { } pub struct Settings { - pub draw_mode: DrawMode, + pub draw_mode: DrawStockConfig, pub sfx_volume: f32, // 0.0–1.0 pub music_volume: f32, pub animation_speed: AnimSpeed, diff --git a/CLAUDE.md b/CLAUDE.md index 79323a8..71cb9ab 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -30,7 +30,9 @@ solitaire_data/ # Persistence + sync client solitaire_engine/ # Bevy ECS + UI + gameplay orchestration solitaire_server/ # Axum backend (optional sync layer) solitaire_wasm/ # WASM bindings for browser-side replay player -solitaire_app/ # Entry binary +solitaire_web/ # Bevy WASM canvas build for the browser /play route +solitaire_assetgen/ # One-shot card/background PNG asset generator +solitaire_app/ # Entry binary (desktop + Android cdylib) assets/ # Runtime assets (except audio + default theme) ``` diff --git a/SESSION_HANDOFF.md b/SESSION_HANDOFF.md index 6b19795..6998a98 100644 --- a/SESSION_HANDOFF.md +++ b/SESSION_HANDOFF.md @@ -168,18 +168,15 @@ Three bugs fixed: ## Open punch list -### 1. Physical-device smoke test — PARTIALLY DONE (2026-07-06, Galaxy Fold 7) +### 1. Physical-device smoke test — DONE (2026-07-06, Galaxy Fold 7) -v0.41.1 was installed and launched on a physical Fold 7 via adb. Verified: +v0.41.1 installed via adb and the full device checklist passed on hardware: fold/unfold layout on both screens (incl. the #116 resume path and the -pile-marker fix), safe-area inset resolution, and app launch/restore basics. - -**Still unexercised** from the device checklist: Draw-Three waste fan tap -accuracy (the #106 fix — switch to Draw-Three, drag the visible top waste -card ~10×, confirm it plays *that* card), modal centring on both screens, -drag-and-drop across all pile types, kill-and-restore, and the sync token -flow. AVD is not a substitute — `adb shell input tap` doesn't deliver real -touch events. +pile-marker fix), safe-area inset resolution, Draw-Three waste fan tap +accuracy (#106), modal centring on both screens, drag-and-drop across all +pile types, text rendering, kill-and-restore, and the sync token flow. +Reminder for future gates: AVD is not a substitute — `adb shell input tap` +doesn't deliver real touch events. ### 2. Matomo analytics live validation (independent — NOT a release blocker) -- 2.47.3