Repository navigation
Improve Klondike Draw 1/3 progress handling and endgame search - #2
Merged
Merged
Conversation
Add ordering regression tests and document the draw-1 trial results for P1a and P1b.
Keep the proximity bonus at zero hidden cards and penalize lower cards buried beneath higher cards of the same suit. Add regression tests and record the P2a and P2c draw-1 trial.
Allow immediate inverses when the previous source may have revealed a hidden card, while keeping path-signature cycle rejection. Add regression coverage and record the P3 draw-1 trial.
Finish visible endgames directly, use one equivalent empty destination, and iterate unique safe foundation chains while preserving primitive accounting. Enforce depth limits on safe foundation moves, add regression tests, and record the P4 draw-1 trial.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Klondike could undervalue real reveals, discard useful immediate reversals, and spend search work on equivalent moves or deterministic endgames. This PR corrects those decisions for Draw 1 and Draw 3, reduces unnecessary endgame exploration, and preserves legal partial progress when a full finish exceeds the depth limit.
The latest user-run Draw 1 batch won 408/500 games (81.60%), compared with 396/500 (79.20%) in the original reference. Mean duration was 7.94 s in the latest CSV. These are descriptive results from separate batches, not a paired measurement proving a 2.40 percentage-point improvement. The detailed results and limitations are below.
Changes and expected benefits
P1 — Prioritize actual reveals and preserve foundation-move bonuses
Previously, reveal detection used the change in the total number of face-up tableau cards. That quantity confuses transfers with reveals: moving a visible card to a foundation while flipping the card beneath it can leave the total unchanged, while bringing a card from the waste into the tableau can increase it without revealing anything.
The solver now measures the decrease in face-down cards in the source column for column-to-column and column-to-foundation moves. Other move types do not receive a false reveal bonus. Safe and ordinary foundation moves also retain the reveal, thoughtful-reveal, and critical-path bonuses that their early priority returns previously discarded.
Expected benefit: explore useful reveals earlier and distinguish foundation moves that unlock further progress, using the existing search budget. This changes ordering rather than adding another search pass.
P2 — Correct checkpoint progress evaluation
The near-autoplay bonus now remains active at zero hidden cards. Previously, its contribution dropped from 4,900 with one hidden card to zero with none; it now increases to 6,400. This prevents the final reveal from losing a large progress bonus. Zero hidden cards still does not imply a win when stock or waste remains.
The same-suit dependency penalty now targets a lower hidden card buried beneath a higher hidden card of the same suit. It tracks the highest rank above the current card within each column, caps the rank-gap severity at three, and keeps the existing weight of 160. It is a progress-scoring signal, not proof that a position is unsolvable and not a new hard pruning rule. The alternative of disabling this penalty was not applied.
Expected benefit: rank checkpoint candidates more consistently, especially around the final reveal and blocked foundation dependencies. These corrections do not guarantee that every changed checkpoint choice is better.
P3 — Preserve immediate reversals that can retain a reveal
Moving a card or run away and immediately returning it can leave a newly revealed card face up. The previous inverse-move filter could reject that useful sequence based only on the two move descriptors.
The early filter now permits the relevant column-to-column and column-to-foundation-to-column reversals when the previous source has exactly one visible card and therefore may have revealed one. This is deliberately conservative: one visible card does not prove that a reveal occurred. The search's path signatures still reject actual cycles after applying the move. Definite inverses remain filtered, including cases with zero or multiple visible cards at the previous source.
Expected benefit: retain legal routes that make lasting progress despite moving a card back. The tradeoff is that some ambiguous candidates reach the existing cycle check; configured search limits remain unchanged.
P4 — Reduce endgame and forced-move search overhead
stock_len == 0, construct a finish from legal primitive foundation moves. Apply this at the root and within recursive search, with a bound based on the number of remaining foundation moves.The closure requires no stock and no waste; merely reaching the end of a stock pass is insufficient. It returns an actual replayable move sequence. Primitive depth, node and checkpoint accounting, time checks, and the budget check before recognizing victory are retained. The chain loop also preserves intermediate heuristic bounds, transposition-table checks, cycle detection, progress sampling, and path/signature restoration on backtracking.
The safe-foundation generation path now respects
DepthLimit; it previously bypassed that rule. This is a deliberate correctness change: a path that only succeeded by exceeding the declared depth may now stop at that limit.Follow-up correction — Preserve partial visible-endgame progress
Review identified a regression in the direct closure: if the complete finish exceeded
max_depth, it stopped before recording any partial moves, even withallow_partial=true.Concrete reproduction covered by tests:
solve(..., true)returnedNonefrom the root.The closure now builds only the permitted prefix and records its final progress even between the normal four-visit sampling points. Interrupted prefixes also preserve progress when the node quota is reached. It does not open additional search branches or raise a limit. With
allow_partial=false, an insufficient depth still causes immediate pruning without constructing a partial result.Expected benefit: preserve useful partial output and replayability at search limits. This fixes the API behavior; a better partial result alone does not establish a higher full-game win rate.
Search limits and scope
The configured core limits are unchanged:
Depth and undo accounting restart from an adopted checkpoint, as before; these are not global caps on the final concatenated path. The node quota is also per search segment, while the timeout is shared across the solve. Preserving these settings does not by itself prove unchanged wall-clock time.
The final implementation includes P1, P2a/P2c, P3, and P4 plus the partial-result correction. It does not include the P5 stock-navigation proposals or the P6 alternative-checkpoint budget split. The FFI signatures and TUI source are unchanged.
Automated validation
The branch adds 25 regression tests: five for P1, three for P2, four for P3, ten for P4, and three for the partial-result correction. Parameterized cases cover more scenarios than the test count alone indicates.
allow_partial=false; replay and 52-card conservation; an existing prefix; every sampling phase; node limits 1, 3, 7, and 8; expired deadlines.The new public-API partial-result regression test was run before the correction and failed because the solver returned no legal partial path. It passes after the correction.
Successful validation of the final implementation:
Lint status:
cargo clippy --all-targets -- -D warnings, run insidesolver-core-rs, is not clean: it reports 43 unique diagnostics. These include existing core/generated-code issues andtoo_many_argumentson the primitive-reference test helper introduced for the chain comparison. No new lint was introduced by the subsequent partial-result fix. This PR does not claim a passing standalone core Clippy check.Recorded Draw 1 simulations
All seven recorded batches have 500 games, eight parallel games, and zero reported timeouts. The original reference CSV already existed on
main; this PR adds the six later CSVs with explanatory Spanish comments, preserving their original data rows. Experiment labels below correspond to those comments.Durations below are recalculated from the per-game rows and rounded to three decimal places. Stalled P95 uses the nearest-rank definition: sorted sample index
ceil(0.95 × n)with one-based indexing. This can differ slightly from interpolated percentiles.What the measurements show
Compared with the original reference, the final batch records 12 more wins out of 500, a 2.40 percentage-point higher win rate, and 12 fewer stalled games. All-game mean duration is about 0.027 s higher; stalled mean is about 0.447 s lower. Stalled P95 falls from 13.460 s to 11.771 s, and the maximum observed stalled duration falls from 16.258 s to 14.028 s.
Compared with the first P4 batch, the final batch records six more wins and virtually the same overall mean, but its stalled mean rises from 9.126 s to 9.494 s and stalled P95 rises from 11.518 s to 11.771 s. The requirement that stalled times must not increase is therefore not yet established by these runs.
The intermediate totals also fluctuate; the data does not demonstrate an independent win-rate improvement for each implementation group.
Limits of the evidence
game_idvalues across files cannot be treated as matching deals. Implementation attribution comes from the experiment records/comments, with the 14:36:25 run explicitly left unconfirmed.max_depth=150,undos=-1, andtimeout_s=0are TUI configuration values. The current Klondike adapter constructsKlondikeEngine::new(draw_advance)and does not forward those settings; the effective core defaults are the ones listed above. Per-game elapsed time also includes replay/scoring in that adapter.The next performance acceptance step is a paired Draw 1 and Draw 3 comparison on the same recorded seeds/decks, at the same parallelism and effective limits. Measure recovered versus lost wins and duration distributions both for all games and for the fixed group that stalled in the reference, in addition to the final stalled group. That is the evidence needed to confirm the intended benefit without longer solve or stalled times.
Review map
Commits:
c3ada5f(P1),06b0cd2(P2),546c2ee(P3),0d47522(P4),ef495d4(additional result archive), andeb68d94(partial-result fix and latest result). The numerical weights/search limits are unchanged; theweights.rsdiff updates explanatory comments.