Skip to content

Disable puzzles with too few alternative moves so they're no longer served - #786

Merged
benckx merged 76 commits into
mainfrom
copilot/add-puzzle-check-loop-validation
Oct 18, 2026
Merged

benckx merged 76 commits into
mainfrom
copilot/add-puzzle-check-loop-validation

Conversation

Copilot AI commented Jun 9, 2026 •

Copy link
Copy Markdown
Contributor

Some puzzles start in check with only forced replies, letting players get stuck in a perpetual-check loop when they deviate from the recorded solution (e.g. 3oY3UwhN, ohT0nTBS). This adds a way to detect such puzzles, flag them, and exclude them from assignment.

Schema

  • Liquibase changeset 0108 adds a nullable disabled_at timestamp to puzzle (a puzzle is disabled when disabled_at is set); regenerated the codegen changelog.

Detection

  • New PuzzleSolvabilityValidator.hasEnoughMovesAtEachPlayerStep(...) (located in the scripts/puzzles package, not the service layer): replays setup + solution moves and requires ≥ MIN_LEGAL_MOVES_PER_STEP (3) legal moves at every player turn. The player is the side to move after setup; opponent turns are skipped.
  • New DisablePuzzlesWithoutEnoughMoves script (in the scripts/puzzles package) iterates all puzzles and sets disabled_at = now() for those that fail the check.

Serving

  • PuzzleCache keeps a boolean enabled per entry, derived from disabledAt == null. PuzzleCache.randomId now selects only from enabled entries. The cache still loads disabled puzzles, since they're needed for by-id lookups and played-puzzle history.
val enabledEntries = entries.filter { entry -> entry.enabled }
var filtered = enabledEntries
// ...apply exclude / rating range / category filters...
return if (filtered.isEmpty()) enabledEntries.random().puzzleId else filtered.random().puzzleId

UI

  • The puzzle's enabled flag is now surfaced through PuzzleResponse and the client-side PuzzleDto. The puzzles page shows a warning icon next to the puzzle id when a disabled puzzle is loaded (e.g. via a by-id link), with a tooltip explaining it is no longer served.

Rating

  • Playing a disabled puzzle no longer transfers any ELO: processOutcome checks the puzzle's enabled flag from the cache and applies a no-op transfer (ratings left unchanged) when the puzzle is disabled.

Notes

  • If every puzzle were disabled, randomId throws NoSuchElementException, already handled upstream as "Puzzle cache not ready yet".
  • Added a changelog entry.

Copilot AI linked an issue Jun 9, 2026 that may be closed by this pull request
Copilot AI changed the title [WIP] Add validation for puzzle moves to prevent check loops Disable puzzles with too few alternative moves so they're no longer served Jun 9, 2026
Copilot finished work on behalf of benckx June 9, 2026 20:10
Copilot AI requested a review from benckx June 9, 2026 20:10

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot drop the test

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dropped the test in d5dd623.

@benckx
benckx marked this pull request as ready for review June 11, 2026 05:55
@benckx

benckx commented Jun 15, 2026

Copy link
Copy Markdown
Owner

@copilot resolve the merge conflicts in this pull request

@benckx

benckx commented Oct 10, 2026

Copy link
Copy Markdown
Owner

@copilot Add a column in puzzle table for date of insertion. For existing puzzles, check in changelog.html for an appropriate date, that would be around 3 years ago.

Co-authored-by: benckx <8626080+benckx@users.noreply.github.com>

Copilot AI commented Oct 10, 2026

Copy link
Copy Markdown
Contributor Author

@copilot Add a column in puzzle table for date of insertion. For existing puzzles, check in changelog.html for an appropriate date, that would be around 3 years ago.

Added changeSet 0116 with a nullable created_at column on puzzle. Existing puzzles are backfilled with 2023-03-25 — the day the initial batch of ~18,000 puzzles was uploaded (per changelog.html). New puzzles created by CreatePuzzlesFromCsv set created_at = now(). Regenerated the codegen changelog.

@benckx

benckx commented Oct 18, 2026

Copy link
Copy Markdown
Owner

@copilot resolve the merge conflicts in this pull request

…eck-loop-validation

# Conflicts:
#	webapp-dao-migration/src/main/resources/liquibase-changelog-generation.xml
#	webapp-dao-migration/src/main/resources/liquibase-changelog.xml
#	webapp/src/main/resources/public/css/puzzles.css

Co-authored-by: benckx <8626080+benckx@users.noreply.github.com>

Copilot AI commented Oct 18, 2026

Copy link
Copy Markdown
Contributor Author

@copilot resolve the merge conflicts in this pull request

Merged main and resolved the conflicts in 7fc3ba9:

  • liquibase changelogs: main had taken changeSet id 0115 (content_section_vote.notified), so the puzzle changeSets were renumbered — disabled_at → 0116, created_at → 0117. Verified: liquibase applied cleanly through 0117.
  • puzzles.css: kept main's #puzzle-feedback-info-box → #puzzle-state-info-box rename (and the mobile flex order rules) alongside this branch's .puzzle-disabled-icon styles.

@benckx
benckx merged commit 728956a into main Oct 18, 2026
1 check passed
@benckx
benckx deleted the copilot/add-puzzle-check-loop-validation branch October 18, 2026 10:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Puzzles that don't make sense

2 participants