Skip to content

feat: add a move legend and rebuild the difficulty ladder - #52

Merged
Amayyas merged 3 commits into
mainfrom
feat/quality-legend
Aug 20, 2026
Merged

feat: add a move legend and rebuild the difficulty ladder#52
Amayyas merged 3 commits into
mainfrom
feat/quality-legend

Conversation

@Amayyas

@Amayyas Amayyas commented Aug 20, 2026

Copy link
Copy Markdown
Owner

Two requests in one branch: a legend for the move marks, and a rework of the
battle difficulty levels.

Legend

Seven tiers now sit beside the move list in the coach, collapsed by default
since it is reference material rather than something to read every game. Four
of them are standard chess annotation a player may already know; !!, ✓✓ and
are this app's own, and colour alone is not an explanation.

The order is its own exported list rather than object key order, so reordering
the table cannot silently reorder the legend. A test holds the two together: a
tier added to MOVE_QUALITY and forgotten in the list would otherwise ship
unexplained without anything failing.

Difficulty levels

The first level searched two plies and played well above a beginner. Measuring
the ladder by self-play showed the problem was not only the bottom rung.

The old ladder, measured. Sixteen games per pairing, alternating colours:

Pairing Score Measured gap Claimed gap
Novice vs a random mover 97% ~600
Débutant vs Novice 94% ~470 200
Intermédiaire vs Débutant 78% ~220 300
Avancé vs Intermédiaire 100% unmeasurable 400
Maître vs Avancé 100% unmeasurable 500

A ladder with a huge first step, a small second one, and then two walls the
weaker level never once crossed. The advertised ratings described none of it.

The new ladder. Starts at depth 1, which cannot see a recapture coming and
so hangs pieces the way a novice does, and narrows the top steps until every
rung is measurable. Fourteen games per pairing:

Pairing Score
Novice vs a random mover 96%
Débutant vs Novice 61%
Intermédiaire vs Débutant 93%
Avancé vs Intermédiaire 96%
Maître vs Avancé 96%

Every level beats the one below it, none of them wins everything.

The Elo figures are gone rather than corrected. This Stockfish build exposes
no UCI_Elo — I asked it for its options to confirm, rather than trusting the
comment that claimed it — so there is no calibrated opponent to anchor against
and any number would be invented like the last ones. Each level now says what it
does, which is more use to someone choosing one.

On the confidence of these numbers

They carry hundreds of Elo of uncertainty at this sample size. Two runs of the
same pairing gave 76 and 338. They establish the ordering and the absence of a
wall, and nothing finer — the file says so rather than implying a precision the
method does not support.

The one claim that is robust is the wall: Avancé and Maître won 16/16 in
their pairings, reproduced across two separate runs.

Verified against broken code

Mutation Result
a tier dropped from the legend order 1 test fails
Novice returned to depth 2 2 tests fail

Seven tiers now sit beside the move list. Four of them are standard
chess annotation a player may already know; !!, ✓✓ and ✓ are this app's
own, and colour alone is not an explanation.

Collapsed by default, since it is reference material rather than
something to read every game.

The order lives next to the table as its own exported list. Deriving it
from object key order would have worked until someone reordered the
table, and a tier missing from the list would ship unexplained without
anything failing — so a test holds the two together.
The first level searched two plies and played well above a beginner,
which is the complaint that started this. Measuring the ladder by
self-play showed the problem was not only the bottom rung.

Against a player choosing moves at random, the old Novice scored 97%.
The claimed ratings described gaps of 200, 300, 400 and 500 Elo; the
measured gaps were roughly 470, then 220, then two so wide that the
stronger level won all sixteen games. The numbers described nothing.

The new ladder starts at depth 1, which cannot see a recapture coming
and so hangs pieces the way a novice does, and narrows the top steps
until every rung is measurable. Fourteen games per pairing, alternating
colours: each level now beats the one below it between 61% and 96% of
the time, and none of them wins everything.

The Elo figures are gone rather than corrected. Without UCI_Elo — which
this build does not expose — there is no calibrated opponent to anchor
against, so any number would be invented like the last ones. Levels say
what they do instead, which is more use to someone picking one.

Those percentages carry hundreds of Elo of uncertainty at that sample
size: two runs of one pairing gave 76 and 338. They establish the
ordering and the absence of a wall, and the file says so rather than
implying more precision than the method supports.
@netlify

netlify Bot commented Aug 20, 2026

Copy link
Copy Markdown

Deploy Preview for chesstrainer-ai ready!

Name Link
🔨 Latest commit 31e4818
🔍 Latest deploy log https://app.netlify.com/projects/chesstrainer-ai/deploys/6a86e2989dffe20008561e6f
😎 Deploy Preview https://deploy-preview-52--chesstrainer-ai.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@Amayyas Amayyas self-assigned this Aug 20, 2026
@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Explain move quality and recalibrate battle difficulty

✨ Enhancement 🐞 Bug fix 🧪 Tests 🕐 20-40 Minutes

Grey Divider

AI Description

• Adds a collapsed coach legend explaining all seven move-quality symbols.
• Recalibrates five battle levels from measured self-play strength and lowers novice depth.
• Replaces unsupported Elo claims with behavioral descriptions and regression coverage.
Diagram

graph TD
  A["Move Metadata"] --> B["Quality Order"] --> C["Quality Legend"] --> D["Coach Page"]
  E["Engine Levels"] --> F["Battle UI"]
  T["Invariant Tests"] -. validates .-> B
  T -. validates .-> E
Loading
High-Level Assessment

The current approach fits the available engine and UI constraints. Calibrated UCI Elo settings are unavailable in the shipped Stockfish build, while deriving legend order from object keys would couple presentation to declaration order; explicit presets, behavioral descriptions, an ordered tier list, and invariant tests are therefore the most reliable strategy.

Files changed (8) +138 / -39

Enhancement (5) +53 / -2
BattlePage.tsxDescribe the active opponent behavior +1/-1

Describe the active opponent behavior

• Replaces the approximate Elo subtitle with the selected level's behavioral description during battle.

src/features/battle/BattlePage.tsx

BattleSetup.tsxShow behavioral guidance when selecting difficulty +1/-1

Show behavioral guidance when selecting difficulty

• Displays each engine level's concrete playing characteristics instead of an unsupported approximate Elo rating.

src/features/battle/BattleSetup.tsx

CoachPage.tsxAdd the move-quality legend to coach analysis +4/-0

Add the move-quality legend to coach analysis

• Places the new collapsible quality legend directly beneath analyzed move history.

src/features/coach/CoachPage.tsx

QualityLegend.tsxIntroduce a collapsible move-quality legend +30/-0

Introduce a collapsible move-quality legend

• Adds a collapsed-by-default reference listing all seven annotation symbols with their labels and existing quality colors.

src/features/coach/QualityLegend.tsx

evaluation.tsDefine explicit move-quality display order +17/-0

Define explicit move-quality display order

• Exports a typed strongest-to-weakest quality sequence independently from the metadata object's key order. This gives legend presentation a deliberate and testable ordering.

src/utils/evaluation.ts

Bug fix (1) +44 / -32
levels.tsRecalibrate battle engine difficulty presets +44/-32

Recalibrate battle engine difficulty presets

• Replaces unsupported Elo values with French descriptions of each opponent's behavior. Rebalances Stockfish skill, error, probability, and depth parameters based on self-play measurements, including a depth-one novice.

src/engine/levels.ts

Tests (2) +41 / -5
levels.test.tsValidate the rebuilt difficulty ladder +26/-5

Validate the rebuilt difficulty ladder

• Updates level tests to remove fabricated Elo expectations and verify monotonic strength across skill, depth, error magnitude, and error probability. Adds regression coverage for the depth-one novice and behavioral descriptions.

src/engine/levels.test.ts

evaluation.test.tsProtect move-quality legend completeness and ordering +15/-0

Protect move-quality legend completeness and ordering

• Verifies that the explicit legend order contains every quality tier exactly once and runs from best move to blunder.

src/utils/evaluation.test.ts

@qodo-code-review

qodo-code-review Bot commented Aug 20, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📜 Skill insights (0)

Grey Divider


Remediation recommended

1. Top tiers bypass weakening ✗ Dismissed 🐞 Bug ≡ Correctness
Description
Avancé stops at depth 5 before Stockfish's weakened-move selection at 1 + Skill Level (=7), and
Maître similarly stops at depth 8 before 13, so their maximum-error and probability settings never
affect the selected move. These tiers therefore play the top search candidate at their capped depth
rather than the configured weakened policy represented by the ladder.
Code

src/engine/levels.ts[R84-87]

+    skill: 6,
+    maxError: 400,
+    errorProbability: 80,
+    depth: 5,
Relevance

●●● Strong

Direct correctness issue undermines the PR’s stated difficulty-ladder intent; closely related engine
calibration changes were accepted in PR #5.

PR-#5

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The level table configures Avancé as skill 6/depth 5 and Maître as skill 12/depth 8. The engine
sends those skill/error values unchanged and searches only to each configured depth, while Stockfish
documents that weakened-move selection occurs at depth 1 + Skill Level; the stockfish.js
maintainer also confirms that the extra maximum-error and probability options operate through that
suboptimal-move selection.

src/engine/levels.ts[81-98]
src/engine/stockfishEngine.ts[56-60]
src/engine/stockfishEngine.ts[125-127]
🌐 Stockfish documents that the weakened move is selected at depth 1 + int(Skill Level).
🌐 The stockfish.js maintainer states that Maximum Error and Probability affect the suboptimal move chosen by pick_best.

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The Avancé and Maître searches end before Stockfish reaches the depth at which `Skill Level` selects a deliberately weaker move. Their error parameters are consequently inactive.

## Issue Context
Stockfish performs weakened-move selection at depth `1 + Skill Level`. Recalibrate these tiers so their configured skill-selection depth is reachable under the search cap, and add a test enforcing that invariant.

## Fix Focus Areas
- src/engine/levels.ts[81-98]
- src/engine/levels.test.ts[16-29]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Context sources
✅ Web pages:
  +4 more
Review mode: ⚖️ Balanced: This behavioral change spans engine strength configuration, battle UI, and coach legend rendering with multiple independent logic and contract updates, but it is not dense enough to justify redundant review passes.

Grey Divider

Tip of the day
💡 Did you know, you can copy the agent prompt from any finding and feed it to your IDE agent

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread src/engine/levels.ts
Review read Stockfish's skill handling as only picking a weakened move
when the search reaches 1 + Skill Level, which would leave the two
strongest levels playing the top candidate at their capped depth and
make their error settings decorative.

Measured instead of argued: on one middlegame position, 30 searches at
depth 5 with Skill Level 20 returned the same move every time, while the
same depth with Avancé's settings returned five different moves and the
engine's own choice only 3 times in 30. Depth 8 behaves the same way
with Maître's settings.

Stockfish 11 also picks a weakened move at the end of the search, so the
cap does not bypass it. Written down so the settings are not removed
later on the theory that they do nothing.
@Amayyas
Amayyas merged commit fb8eb56 into main Aug 20, 2026
13 checks passed
@Amayyas
Amayyas deleted the feat/quality-legend branch August 20, 2026 11:48
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