Skip to content

Fix/Opening Explorer not recognize right move book off FEN#3084

Merged
veloce merged 4 commits into
lichess-org:mainfrom
MaartenD:fix/Opening-Explorer-not-recognize-right-move-book-off-FEN-when-order-doesnt-match-100
May 6, 2026
Merged

Fix/Opening Explorer not recognize right move book off FEN#3084
veloce merged 4 commits into
lichess-org:mainfrom
MaartenD:fix/Opening-Explorer-not-recognize-right-move-book-off-FEN-when-order-doesnt-match-100

Conversation

@MaartenD

@MaartenD MaartenD commented May 1, 2026

Copy link
Copy Markdown
Contributor

Issue
Previously, when opening a completed game and jumping directly to a move that is past the known opening theory the opening name would not be displayed in the Opening Explorer. The name only appeared correctly when navigating move by move from the start.

Root cause
The opening name for each position is fetched and stored on the corresponding node in the game tree. When navigating step by step, each intermediate node gets its opening fetched along the way. When jumping directly to a later move, the intermediate nodes have no opening data yet, so _nodeOpeningAt, which walks the path to find the last known opening, returns null.

Fix (analysis_controller.dart)

Two changes were made:

  • Added _fetchOpeningsForPath: when navigating to a position with no known branch opening, openings are now fetched for all ancestor nodes along the path.
  • Updated _updateOpening: whenever an ancestor opening is fetched and set, _nodeOpeningAt is re-run on the current path to always determine the most recent (highest ply) ancestor opening. This ensures that even if an earlier ancestor's opening arrives first, it will be replaced by a more recent one once that fetch completes.
    Improvement: local development support (opening_explorer_repository.dart)

Additional change
Added a _explorerUri helper that uses Uri.http instead of Uri.https when kLichessOpeningExplorerHost points to a local address (localhost, 10.x, 192.168.x). This allows developers to run the Opening Explorer locally without SSL errors, matching the existing behavior of lichessUri used for the main lichess host. This was one of my struggles to set-up a good development environment.

Fixes #1480

@veloce veloce 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.

Thanks for tackling this.

I need tests before accepting the PR (Widget tests). That cover the bug we want to fix, but also the other cases (updating opening, etc).

}
});
}

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.

We already have some code that is supposed to deal with that:

if (isMainline && opening == null && branch.position.ply <= 10) {
openingFutures.add(_fetchOpening(branch.position.fen, path));
}
},
),
};
// wait for the opening to be fetched to recompute the branch opening
Future.wait(openingFutures)
.then((list) {
bool hasOpening = false;
for (final updated in list) {
if (updated != null) {
hasOpening = true;
final (path, opening) = updated;
_root.updateAt(path, (node) => node.opening = opening);
}
}
return hasOpening;
})
.then((hasOpening) {
if (hasOpening) {
scheduleMicrotask(() {
_setPath(state.requireValue.currentPath);
});
}
});

Probably this code is not working completely, since we have an issue mentioned in this PR.

But instead of adding a new method that loops over the nodes again, I'd rather fix the existing code.

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.

Root cause
In build(), the onVisitNode callback accumulated the path variable only when options.initialMoveCursor != null. In the common case where no cursor is set, path stayed UciPath.empty throughout the loop. As a result, _fetchOpening(branch.position.fen, path) was called with an empty path for every node, storing all openings at the root instead of at the correct node path. Additionally, the opening == null guard meant that for archived games which have a game-level opening set from server metadata per-node opening fetches were skipped entirely. Both issues caused _nodeOpeningAt to find nothing when navigating, leaving currentBranchOpening null.

What was changed
lib/src/model/analysis/analysis_controller.dart:

Added a separate UciPath mainlinePath = UciPath.empty variable that accumulates independently for every mainline node visited, regardless of initialMoveCursor. Replaced the broken if (isMainline && opening == null && branch.position.ply <= 10) block with if (isMainline), which always accumulates mainlinePath and fetches the opening using mainlinePath (the correct path) for positions up to ply 30.
The ply limit is aligned from <= 10 to <= 30, consistent with the existing check already present in _setPath.
Added an else branch in _updateOpening so that when an async opening fetch completes for a node that is an ancestor of the current position, currentBranchOpening is updated in state immediately.

Tests
test/model/analysis/fake_opening_service.dart:

Extended FakeOpeningService to accept a configurable openings map (keyed on EPD the first four fields of a FEN) so tests can return specific openings for specific positions.

test/view/analysis/analysis_opening_test.dart (new file, 3 tests):

  • Opening is set when navigating to a mainline position: verifies that after loading a PGN analysis, navigating to a mainline position correctly sets currentBranchOpening.
  • Ancestor opening is used when current node has no direct opening: verifies the core bug fix: when the current position has no opening, _nodeOpeningAt walks up the path and returns the nearest ancestor's opening, which is only possible if openings were stored at the correct node paths during build().
  • Opening updates correctly when navigating forward and backward: verifies that currentBranchOpening reflects the right opening at each position as the user navigates through the move tree.

https://github.com/user-attachments/assets/7bf27393-f6d3-483d-85e9-83f572caa0a5
The bug from production.

https://github.com/user-attachments/assets/1c9b3f23-ef58-45e1-ab42-75f53348d234
The fix from local development environment.

@veloce veloce 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.

lgtm!

@veloce veloce merged commit 61a8398 into lichess-org:main May 6, 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.

Opening Explorer not recognize right move book off FEN when order doesnt match 100%

2 participants