Skip to content

Show fifty-move rule in analysis UI - #21647

Draft
TVSuchty wants to merge 6 commits into
lichess-org:masterfrom
TVSuchty:analysis-fifty-move-status
Draft

TVSuchty wants to merge 6 commits into
lichess-org:masterfrom
TVSuchty:analysis-fifty-move-status

Conversation

@TVSuchty

Copy link
Copy Markdown

Fixes #15036

When analysing a position that has already hit the fifty-move rule (FEN halfmove clock >= 100), the UI gave no reason for the draw. Threefold already shows in the engine panel; fifty-move did not.

Changes

  • Shared isFiftyMoves(fen) helper (100+ halfmoves, not only exactly 100)
  • Engine panel: pearl - and "Fifty moves without progress", same as threefold
  • Move-list result on unfinished/synthetic analysis: 1/2-1/2 Fifty moves without progress
  • Skip local engine eval and hide the eval gauge on those positions
  • Practice with computer / study practice treat fifty-move as a draw, including clocks past 100

Testing

AI disclosure

This work was produced by Grok 4.6 (xAI Grok Build CLI), an AI coding agent, on behalf of this GitHub account.

Prompts used:

  1. Look up a Lichess todo that can be implemented, implement it, and open a pull request. Limit work to one important but not too big feature.
  2. Say that you are AI. Change workspace and GitHub (work in the Lichess repo, not the previous local chess-database project).

I reviewed the existing threefold-repetition handling and mirrored that path for fifty-move. I did not generate tests without reading the analysis/ceval code first.

When a position's FEN halfmove clock reaches 100, analysis now shows the fifty-move draw in the engine panel and move-list result, matching threefold handling.

Fixes lichess-org#15036
@TVSuchty
TVSuchty marked this pull request as ready for review September 14, 2026 08:10

@Aybavs Aybavs left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The helper needs the variant. Crazyhouse has no fifty-move rule, Crazyhouse.scala overrides it:

override def fiftyMoves(history: History): Boolean = false

So isFiftyMoves(fen) calls a draw there that the server doesn't: ceval gets reset, the gauge hides, and the engine panel says "Fifty moves without progress" on a position that isn't drawn. Left a suggestion on the helper. Every call site has a variant handy, d.variant in statusOf, ceval.opts.variant.key in renderCeval, root.variantKey in the analyse and practice files.

Two other things I noticed:

renderResult: with a study this only fires when a relay has "show results" off, so it shows a result to someone who asked not to see one. !ctrl.study && covers it.

ctrl.ts imports isFiftyMoves from lib/game and plyColor from lib/game/chess, same file.

All of it is one commit on top of your branch if you want to take it: Aybavs@5fbaa20. Call sites updated, and the tests moved to ui/lib/tests/chess.test.ts with the variant cases added. The crazyhouse one fails on your current version and passes after.

Comment thread ui/lib/src/game/chess.ts Outdated
Comment on lines +26 to +30
// FIDE fifty-move rule: 100 halfmoves in the FEN clock.
export function isFiftyMoves(fen: FEN): boolean {
const halfmoves = Number(fen.split(' ')[4]);
return Number.isFinite(halfmoves) && halfmoves >= 100;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
// FIDE fifty-move rule: 100 halfmoves in the FEN clock.
export function isFiftyMoves(fen: FEN): boolean {
const halfmoves = Number(fen.split(' ')[4]);
return Number.isFinite(halfmoves) && halfmoves >= 100;
}
// crazyhouse has no fifty-move rule, as in scalachess
export const isFiftyMoves = (variant: VariantKey, fen: FEN): boolean =>
variant !== 'crazyhouse' && Number(fen.split(/\s/)[4]) >= 100;

Number.isFinite isn't needed, a FEN with no halfmove clock already gives NaN and NaN >= 100 is false.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Applied this. isFiftyMoves now takes the variant and returns false for crazyhouse, matching scalachess Crazyhouse.fiftyMoves = false. Number.isFinite is gone.

Also on the same follow-up: renderResult is gated with !ctrl.study, and the ctrl.ts imports are consolidated onto lib/game/chess.

Crazyhouse overrides fiftyMoves to false in scalachess, so a FEN-only check was calling a draw the server does not. Studies with results hidden also fell through to the synthetic 1/2-1/2 line.

Tests live in ui/lib/tests/chess.test.ts.
A mate with halfmove clock 100 was shown as a fifty-move draw in the move list and practice box. isFiftyMoveDraw requires no board outcome first, matching scalachess status order.
@TVSuchty

Copy link
Copy Markdown
Author

Ran a local Codex review with GPT-5.6-Sol.

Sol agreed with @Aybavs (crazyhouse, study results, imports) and found one extra bug: a checkmate (or stalemate) with halfmove clock 100 was still shown as a fifty-move draw in renderResult and the practice box. Engine panel already checked outcome() first.

Fixed in c521758: isFiftyMoveDraw(variant, fen, outcome) requires no board outcome. Regression cases in ui/lib/tests/chess.test.ts (7k/6Q1/5K2/8/8/8/8/8 b - - 100 50).

@TVSuchty

Copy link
Copy Markdown
Author

GPT-5.6-Sol re-reviewed c521758 vs master.

No remaining bugs. Checkmate/stalemate at clock 100 now wins over the fifty-move label; crazyhouse stays excluded; studies still do not get a synthetic result.

@TVSuchty
TVSuchty requested a review from Aybavs September 15, 2026 10:46

@Aybavs Aybavs left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Looks right to me.

Checked the mate case: for 7k/6Q1/5K2/8/8/8/8/8 b - - 100 50 the predicate was true before c521758 and the practice box said "Draw / Fifty moves", now it says "Checkmate / white wins". The call sites still on the raw isFiftyMoves all test outcome() first, so nothing left there.

Minor: chessops exports Outcome, so outcome: Outcome | undefined would do instead of the inline shape, and | null never comes out of node.outcome().

I only ran the tests and tsc on your branch though, no local lila, so none of this has been seen in a browser yet.

@TVSuchty
TVSuchty force-pushed the analysis-fifty-move-status branch from 8f6e873 to b7598c4 Compare September 15, 2026 14:10
@ornicar

ornicar commented Sep 17, 2026

Copy link
Copy Markdown
Collaborator

You removed the draft status. Have you tried the changes in a browser?

Have you reproduced the original issue?

@TVSuchty

Copy link
Copy Markdown
Author

You removed the draft status. Have you tried the changes in a browser?

Have you reproduced the original issue?

When did I do that? I will try to set it up. Still had some troubles on how to exactly do that. Any best practices? I will write you if I set it up; might take a week and then I will show you screenshot.

@ornicar
ornicar marked this pull request as draft September 17, 2026 10:13
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.

The UI when analyzing should show when 50 the move rule is hit

4 participants