docs: catch up handoff and changelog
Build and Deploy / build-and-push (push) Successful in 5m23s

This commit is contained in:
funman300
2026-06-08 19:03:40 -07:00
parent 6193d31497
commit 7fe6ac6c1c
5 changed files with 442 additions and 52 deletions
+29 -14
View File
@@ -2,7 +2,10 @@
**Context:** A collaborator ([Quaternions](https://git.aleshym.co/Quaternions/card_game)) is building a pure-logic Klondike library in Rust. This document maps what that library currently provides against what Ferrous Solitaire's `solitaire_core` crate requires.
**Approach:** Most gaps are closed in Ferrous Solitaire's own `solitaire_core` crate via a wrapper/adapter layer. Gaps 1, 3, and 4 have been addressed upstream. Integration is ready to begin.
**Approach:** Integration is complete. Upstream `card_game` / `klondike` now owns
authoritative Klondike rules, session history, undo snapshots, and solving.
Ferrous keeps product-specific scoring, persistence, rendering DTOs, game modes,
and typed UI errors in `solitaire_core`.
---
@@ -42,10 +45,12 @@
---
## What Ferrous Solitaire's `solitaire_core` Needs (Gaps)
## What Ferrous Solitaire's `solitaire_core` Still Owns
### 1. Scoring — remaining adapter responsibilities
Ferrous uses **Windows XP Standard** scoring. The exact table already implemented in `solitaire_core/src/scoring.rs`:
Ferrous uses **Windows XP Standard** scoring. The upstream library handles the
per-move counters and configurable deltas; Ferrous adds the product-specific
parts in `GameState` / `KlondikeAdapter`.
| Event | Delta | Handled by |
|---|---|---|
@@ -61,11 +66,13 @@ Ferrous uses **Windows XP Standard** scoring. The exact table already implemente
Reference: <https://www.solitaireparadise.com/games_list/klondike_solitaire_scoring.html>
**Undo penalty:** `SessionState::score()` = `KlondikeStats.score(&scoring) + undos × undo_penalty`. The 15 undo penalty is built into `SessionConfig` (default). Once `GameState` fully delegates to `Session`, our `KlondikeAdapter::score_for_undo()` helper becomes redundant.
**Undo penalty:** `SessionState::score()` = `KlondikeStats.score(&scoring) + undos × undo_penalty`. Ferrous still owns the exact user-visible score because it must restore the pre-move score when undoing recycle penalties and then apply the product's undo penalty.
**Recycle penalty note:** `ScoringConfig::recycle` is a flat delta (default 0 = always free). WXP allows a fixed number of free recycles before charging a penalty, which the upstream library cannot express with a single delta. Our adapter tracks `recycle_count` from `KlondikeStats` and applies the penalty only beyond the free allowance.
**In our wrapper:** Configure `ScoringConfig` with the WXP deltas for the five events upstream handles (including undo via `SessionConfig`). Implement recycle-with-free-allowance, score floor, and time bonus in the adapter.
**In our wrapper:** `KlondikeAdapter::config_for` configures the upstream rules
and scoring deltas. `GameState` applies recycle-with-free-allowance, score floor,
time bonus, game-mode suppression, and undo score restoration.
### 2. Game Modes
Ferrous has three modes that alter scoring and undo behaviour:
@@ -78,7 +85,9 @@ Ferrous has three modes that alter scoring and undo behaviour:
Zen is intended for relaxed play where the score does not matter. Challenge is a timed daily puzzle where the no-undo constraint is the difficulty mechanic.
**In our wrapper:** Add `GameMode` to `solitaire_core::GameState`; intercept undo calls and scoring deltas in the adapter before delegating to `KlondikeState`.
**In our wrapper:** `GameMode` lives on `solitaire_core::GameState`; undo and
scoring behavior are applied before/after delegating legal moves to the upstream
session.
### 3. Solvability Solver *(upstream merged — card_game v0.4.0)*
`card_game v0.4.0` ships `Session::solve()` — a budget-bounded DFS that returns `Result<Option<Solution<G>>, SolveError>`. `SolveError` has two variants:
@@ -87,9 +96,13 @@ Zen is intended for relaxed play where the score does not matter. Challenge is a
`Solution<G>` contains the winning move sequence as `Vec<StateSnapshot<G>>`; `clean_solution()` removes cycles. `Session::solve()` uses `SessionConfig::solve_moves_budget` and `SessionConfig::solve_states_budget` (defaults: 100 000 each).
Our 767-line `solitaire_core::solver` reimplements the full game rules to run the DFS; `session.solve()` replaces it entirely. The solver will be removed once the `Session<Klondike>` is wired into `GameState`.
The old local DFS has been replaced. `solitaire_core::solver` is now a small
adapter around `Session::solve()` that preserves the engine-facing
`SolverResult`, `SolverConfig`, and first-move payload contract.
**In our wrapper:** Replace `solitaire_core::solver` with `session.solve()`. Map `Ok(Some(_))` → Winnable, `Ok(None)` → Unwinnable, `Err(_)` → Inconclusive.
**In our wrapper:** `solve_game_state` calls `session.solve()` with the requested
budgets. It maps `Ok(Some(_))` → Winnable, `Ok(None)` → Unwinnable, and budget
errors → Inconclusive.
### 4. `take_from_foundation` House Rule *(upstream merged — v0.3.0)*
`MoveFromFoundationConfig` is now part of `KlondikeConfig`. When set to `Disallowed`, `is_instruction_valid` blocks foundation → tableau instructions.
@@ -135,7 +148,9 @@ Ferrous tracks `PileType::Waste` as a distinct pile. `klondike` folds waste into
### 8. Undo Stack Approach *(resolved — not an issue)*
`card_game v0.4.0` `Session` uses snapshot-based undo: `SessionState` stores `Vec<StateSnapshot<G>>` where each entry holds the pre-move game state and the instruction. Undo pops the last snapshot and restores state directly — O(1), matching our existing `GameState.undo_stack`.
**Resolution:** Use `Session`'s built-in snapshot history. Our `GameState.undo_stack: VecDeque<StateSnapshot>` will be removed once `GameState` is fully migrated to delegate to `Session`.
**Resolution:** `GameState` uses `Session`'s built-in snapshot history. Ferrous
keeps parallel score/recycle metadata so undo can restore product-specific score
state that upstream snapshots do not own.
---
@@ -144,12 +159,12 @@ Ferrous tracks `PileType::Waste` as a distinct pile. `klondike` folds waste into
Steps in dependency order. Upstream issues #10, #11, and the solver are all merged.
1.**Add `klondike = "0.3.0"` / `card_game = "0.4.0"` as dependencies** of `solitaire_core`; `KlondikeAdapter` wraps `KlondikeConfig` and exposes scoring helpers.
2. **Map pile types** — project `klondike`'s stock face-up half as `PileType::Waste`; expose the same `HashMap<PileType, Pile>` the engine already reads. Wire `Session<Klondike>` into `KlondikeAdapter` (gap 7).
3.**Configure `KlondikeConfig`** — set `move_from_foundation: MoveFromFoundationConfig::Disallowed` by default; wire the user's house-rule toggle to `Allowed` (gap 4, upstream).
2. **Map pile types** — project `klondike`'s stock face-up half as the engine's waste pile and expose renderer-facing pile snapshots.
3.**Configure `KlondikeConfig`** — set `move_from_foundation: MoveFromFoundationConfig::Allowed` by default; wire the user's settings toggle to `Disallowed` when foundation returns are disabled (gap 4, upstream).
4.**Port scoring** — pass WXP deltas into `ScoringConfig`; `SessionConfig::undo_penalty` handles undo; implement recycle-with-free-allowance, score floor, and time bonus in the adapter (gap 1).
5.**Port `GameMode`** — intercept undo + scoring in the adapter based on mode (gap 2).
6. **Replace solver** — call `session.solve()` with budgets from our `SolverConfig`; map `Ok(Some)` → Winnable, `Ok(None)` → Unwinnable, `Err` → Inconclusive (gap 3, upstream).
7. **Implement `serde`**define `SavedInstruction` + `SavedStateSnapshot` newtypes; serialise session history; migrate save-file schema (gap 5).
6. **Replace solver** — call `session.solve()` with budgets from `SolverConfig`; map `Ok(Some)` → Winnable, `Ok(None)` → Unwinnable, `Err` → Inconclusive (gap 3, upstream).
7. **Implement `serde`**serialise schema v4 with upstream `KlondikeInstruction`; auto-migrate schema v3 via `SavedInstruction` compatibility types.
---
@@ -192,5 +207,5 @@ The script enforces:
- Upstream scoring + config PRs: #12 (closes #11), #13 (closes #10)
- Upstream solver PR: #14
- `solitaire_core` source: `solitaire_core/src/`
- Scoring spec: `solitaire_core/src/scoring.rs`
- Scoring implementation: `solitaire_core/src/game_state.rs`, `solitaire_core/src/klondike_adapter.rs`
- Architecture overview: `ARCHITECTURE.md`