Skip to content

fix: enforce must-use-larger-die at turn start instead of after execution#131

Merged
nodots merged 1 commit into
mainfrom
fix/larger-die-stuck-game
Jun 7, 2026
Merged

fix: enforce must-use-larger-die at turn start instead of after execution#131
nodots merged 1 commit into
mainfrom
fix/larger-die-stuck-game

Conversation

@nodots

@nodots nodots commented Jun 7, 2026

Copy link
Copy Markdown
Owner

Problem

Production game 2d98cf14-e262-433c-baf5-95a2f878311d froze in stateKind: 'moving' on the final play. The robot had one checker on its 2-point, 14 off, rolled [3,5]. Either die bears off the last checker, so maxDiceUsable = 1 and both dice were playable at turn start.

PR #129 added the must-use-larger-die validation in Play.pureMove, which rejects the smaller die after execution — but Play.initialize still offered the smaller-die move as ready. GNU's bearoff hint carries no die value, the AI matcher took the first ready move (die 3, roll order), and MustUseLargerDieError fired on every retry. RobotActionProcessor swallows robot-turn errors, so the game was never persisted and stayed stuck. Humans hit the same path: planMoveExecution uses readyMoves[0].dieValue.

Fix

  1. Play.initialize: when maxDiceUsable === 1 and ≥2 distinct die values were playable at start, convert smaller-die ready moves to completed no-moves — the option the rules forbid is never offered.
  2. planMoveExecution: when no explicit die was requested and the larger die is mandatory, prefer the largest ready die — heals stale plays persisted before this fix.
  3. pureMove validation kept as defensive backstop.

Tests

  • New describe block reproducing the production position (clockwise, both roll orders, counterclockwise mirror): smaller die becomes a no-move, the move bears off with the 5, 15 checkers off, no throw.
  • Existing "smaller die rejected" test updated: the attempt now plays the larger die instead of throwing.
  • Full suite: 68 suites / 448 tests green.

…tion

When only one die can be played and both dice had moves from the
turn-start board, the rules require the larger die. pureMove rejected
the smaller die after execution, but Play.initialize still offered it
as a ready move. A robot (or human) picking the smaller die had every
turn attempt rejected, freezing the game in 'moving'.

Production game 2d98cf14-e262-433c-baf5-95a2f878311d: one checker on
the 2-point, 14 off, roll [3,5]. Either die bears off. GNU's bearoff
hint carries no die; the AI matcher took the first ready move (die 3)
and MustUseLargerDieError fired on every retry.

- Play.initialize: convert smaller-die ready moves to completed
  no-moves when maxDiceUsable === 1 and both dice were playable
- planMoveExecution: prefer the larger ready die for plays persisted
  before this fix (stale plays loaded from the database)
- pureMove validation kept as backstop

Bump to 1.0.2.
@nodots
nodots merged commit 99abd51 into main Jun 7, 2026
1 check passed
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.

1 participant