* docs(Endgames): fix typo in "Recovery" definition for Stockfish evaluation
* docs: capture exploration — per-span ΔES endgame metric (SEED-016)
Plant SEED-016 proposing per-span expected-score delta as a continuous
performance metric per endgame type, complementing Conversion/Recovery.
Companion note captures the Lichess sigmoid endgame calibration caveat
and a refit option using the benchmark DB.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(SEED-016): add prod eval-coverage check and span-definition open question
Confirms eval availability is essentially 100% for ≥6-ply sequences under
both the codebase span definition and the strict contiguous-run definition,
removing it as a blocker. Surfaces the codebase-vs-strict span definition
choice as an open design call for V1.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(roadmap): insert Phase 87.1 (per-span ΔES endgame metric) from SEED-016
Slots in after Phase 87 (per-type cards) so the new ΔES gauge can land on
the per-type card shell shipped by Phase 87. Context block flags milestone
scope creep — v1.17 is otherwise a frontend refactor — so plan-phase can
decide whether to keep it in v1.17 or defer to v1.18. SEED-016 status
flipped from dormant to scheduled.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(v1.17): correct stale 'frontend-only refactor' framing
v1.17 has expanded well past its original frontend-refactor scope: Phase
85.1 added new backend math helpers + CIs, Phase 88 reworks Time Pressure
with new /benchmarks metrics and backend service code, and Phase 87.1 adds
a new per-endgame-type metric. Reframe the milestone summary as a
statistical-rigor pass and drop the now-incorrect scope-creep risk from
Phase 87.1's Context block.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(87.1): capture phase context
Per-span ΔES metric for endgame types — context gathered.
User direction at discuss: reuse the existing Achievable Score Gap
visual (MiniBulletChart / ScoreGapRow) and label; internal name
"Endgame Type Achievable Score Gap" to disambiguate from the
page-level metric. Sign flipped from SEED-016 to match
`higher_is_better`. Stat helper locked to
compute_paired_difference_test (Phase 85.1 parity). Repo query
strategy (extend vs sibling) deferred to planner.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(87.1): amend CONTEXT for dual-label naming scheme
User-facing labels updated to disambiguate the new per-type metric from
the page-level Achievable Score Gap (Phase 85.1) and Endgame Score Gap
(Phase 85):
- Concepts section: "Endgame Type Score Gap" (full qualifier)
- Per-type card row: "Score Gap" (short form; card title supplies context)
- Internal identifier: unchanged ('endgame_type_achievable_score_gap')
to preserve math-family grep with page-level achievable_score_gap
Amendments in §Phase Boundary, D-01, D-02, D-09 (popover copy), D-10 (LLM
payload terminology), and §Specifics. Frontend constant name flipped to
ENDGAME_TYPE_SCORE_GAP_NEUTRAL_MIN/MAX to match user-facing form.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(87.1): research phase domain
* docs(87.1): add validation strategy
* docs(87.1): add pattern map
* docs(87.1): create phase plan (4 plans, 2 waves)
* docs(87.1): update STATE for phase start
* feat(87.1-02): extend query_endgame_entry_rows with LEAD-based next-span eval
Phase 87.1 (SEED-016 D-06): per-span gap math requires the next span's entry
eval to compute exit_score for transitory spans. Add func.min(GamePosition.ply)
to the existing span_subq to expose span_min_ply as a window-function ORDER BY
key, then wrap span_subq with a span_with_next subquery applying
LEAD(entry_eval_cp/mate) OVER (PARTITION BY game_id ORDER BY span_min_ply ASC).
Row shape grows from 6 to 8 columns:
+ next_entry_eval_cp (NULL for terminal span)
+ next_entry_eval_mate (NULL for terminal span)
Existing callers (_aggregate_endgame_stats, _compute_score_gap_material) use
attribute access on Row objects, so the extra columns are ignored — no break.
The min(ply) aggregate is free against ix_gp_user_endgame_game (ply is the
sort key); the LEAD runs on the per-span result set (<= 6 rows per game).
Tests: legacy attribute access preserved; LEAD ordering matches span_min_ply
ASC across a 2-span game; terminal spans return NULL on the new columns.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(87.1-02): per-class Score Gap math, _compute_span_gap + 5 schema fields
Phase 87.1 (SEED-016 D-03/D-05/D-07): wire the per-class per-span gap metric
into _aggregate_endgame_stats. Adds:
- _compute_span_gap helper (service): gap_span = exit_score - ES_entry, sign
matches the page-level Achievable Score Gap (higher_is_better; positive =
user outperformed Stockfish baseline).
* Transitory span: exit_score = ES_sigmoid(next_entry_eval, user_color).
* Terminal span: exit_score = game-result score via _GAME_RESULT_TO_SCORE
(1.0/0.5/0.0). _GAME_RESULT_TO_SCORE is a module-level named constant
per CLAUDE.md "no magic numbers".
* NULL-eval span (both entry_eval_cp + _mate NULL) -> returns None;
excluded from the per-class cohort (D-07).
Reuses eval_cp_to_expected_score / eval_mate_to_expected_score from
eval_utils.py (no new sigmoid math, mate saturation conventions preserved).
- _aggregate_endgame_stats: accumulate per-class gap vectors and call
compute_paired_difference_test (the helper Phase 85.1 SEC1-10 uses for the
page-level metric). Loop body now handles 8-column SA Rows (prod shape post
Plan 02 Task 1) and legacy 6-tuple test fixtures (next-eval padded NULL).
- EndgameCategoryStats: 5 new fields populated from the helper output:
type_achievable_score_gap_mean (None when n=0 — wire-clean sparse signal)
type_achievable_score_gap_n (cohort size after NULL-eval exclusion)
type_achievable_score_gap_p_value (None when n < CONFIDENCE_MIN_N=10)
type_achievable_score_gap_ci_low (None when n < 2)
type_achievable_score_gap_ci_high (None when n < 2)
Dual-label per CONTEXT D-02: user-facing label is "Score Gap" (card row) /
"Endgame Type Score Gap" (concepts). Internal name keeps "achievable" so
developers grep the math-family with achievable_score_gap (Phase 85.1).
Tests: 18 new tests — 9 unit tests on _compute_span_gap covering all the
D-11 boundary cases (NULL-eval, pure transitory, pure terminal, mate at
entry/exit, mate precedence, sign convention), and 9 integration tests on
_aggregate_endgame_stats verifying field presence, NULL-eval exclusion,
n-gates (p_value at n>=10, CI at n>=2), per-class independence, mate at
endpoints, hand-computed reference matching to 1e-6, and a regression
guard for legacy 6-tuple test fixtures.
Gates: ruff/ty clean across app/ tests/; full pytest suite 1514 passed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(87.1-02): complete Plan 02 — backend math+repo+service+schema
SUMMARY.md records the two implementation commits ( 884589d repo LEAD,
be0150b service math + schema), the math contract honored, the verification
gates (ruff/ty/pytest all green; 342 plan-scope tests, 1514 full suite), and
the dual-label naming rule for downstream Plan 03/04.
STATE.md: Plan position advanced to "Plan 02 COMPLETE"; appended a
last-activity entry covering both commits + the 18 new tests + the next-up
plan (Plan 01 benchmarks/codegen for Wave 1; Plans 03+04 unblocked once
Wave 1 merges).
ROADMAP.md: Plan 02 checkbox ticked with the two commit hashes; phase
progress bumped to 1/4. ROADMAP-87.1 success criteria 1, 2, 3 satisfied.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(87.1-01): add endgame_type_achievable_score_gap to zones registry
- Add "endgame_type_achievable_score_gap" to MetricId Literal with dual-label naming comment
- Add ZoneSpec entry to ZONE_REGISTRY with placeholder band (-0.05, 0.05), higher_is_better
- Extend PerClassBands dataclass with achievable_score_gap: tuple[float, float] field
- Populate placeholder bands for all 6 PER_CLASS_GAUGE_ZONES entries
- Note BucketedMetricId deferral (per-class only, not per-material-axis)
Publishes the registry contract Plan 03 (frontend card) and Plan 04 (LLM payload)
consume. Bands are placeholders mirroring the global achievable_score_gap until
benchmarks SKILL.md §3.4.2 calibration runs.
Refs: SEED-016 D-02/D-04, CONTEXT.md (dual-label naming, amended 2026-05-15).
* feat(87.1-01): codegen ENDGAME_TYPE_SCORE_GAP constants + per-class band
- Extend gen_endgame_zones_ts.py to emit ENDGAME_TYPE_SCORE_GAP_NEUTRAL_MIN/MAX
from ZONE_REGISTRY["endgame_type_achievable_score_gap"]
- Extend _format_per_class_gauge_zones to emit achievable_score_gap: [lo, hi]
alongside conversion/recovery per class
- Regenerate frontend/src/generated/endgameZones.ts (codegen drift gate green)
- Update inline doc comments to reflect the new field
Plan 03 (frontend card) imports ENDGAME_TYPE_SCORE_GAP_NEUTRAL_MIN/MAX
from this generated file. Frontend builds clean (Vite tsc + npm run lint).
Refs: SEED-016 D-04, PATTERNS.md scripts/gen_endgame_zones_ts.py Deltas 1-4.
* docs(87.1-01): add §3.4.2 Per-span Score Gap by Endgame Type to benchmarks SKILL
- Insert §3.4.2 subchapter immediately after §3.4.1 with question, per-user
metric definition (gap_span = exit_score − ES_entry), sample floor (≥20
qualifying spans per user per class per cell), Cohen's-d collapse verdict
per axis, and decision rule for updating PER_CLASS_GAUGE_ZONES vs the
global ZONE_REGISTRY entry
- Embed full SQL template (selected_users → spans → spans_with_next via LEAD →
gap_rows → per_user_class) preserving the universal equal-footing opponent
filter abs(opp_rating - user_rating) <= 100
- Document dual-label terminology (Endgame Type Score Gap / Score Gap) per
CONTEXT.md D-02 amended 2026-05-15
- Record sigmoid-bias caveat (Lichess winning-chances scale-compression) so
Plan 03 / Plan 04 popover + LLM payload copy can reference it
- Add §3.4.2 row to Live-threshold grep table and per-subchapter overview
- Add "Per-class per-span Score Gap" row to Top-axis collapse summary table
Calibration run itself is OUT OF SCOPE for this plan — placeholder bands
(-0.05, 0.05) shipped in Task 1 remain in effect until §3.4.2 produces real
per-class values.
Refs: SEED-016 D-04, CONTEXT.md, RESEARCH.md "SKILL.md addition §3.4.2".
* test(87.1-01): extend test_all_scalar_metrics_have_entries with new metric id
The new "endgame_type_achievable_score_gap" entry breaks the registry pin
test. Add the literal to the expected set and document its provenance
(SEED-016 D-02/D-04) in the docstring.
* docs(87.1-01): complete zones registry + codegen + SKILL §3.4.2 plan
Plan 01 ships:
- "endgame_type_achievable_score_gap" MetricId + ZoneSpec in app/services/endgame_zones.py
- PerClassBands.achievable_score_gap field on all 6 PER_CLASS_GAUGE_ZONES entries (placeholder bands)
- ENDGAME_TYPE_SCORE_GAP_NEUTRAL_MIN/MAX + per-class achievable_score_gap codegen in
scripts/gen_endgame_zones_ts.py + regenerated frontend/src/generated/endgameZones.ts
- §3.4.2 Per-span Score Gap by Endgame Type subchapter in .claude/skills/benchmarks/SKILL.md
- Updated registry-pin test for the new metric id
Refs: SEED-016 D-02/D-04, CONTEXT.md (dual-label naming), ROADMAP §Phase 87.1 §4.
* feat(87.1-03): mirror 5 type_achievable_score_gap_* fields on EndgameCategoryStats
- Add type_achievable_score_gap_{mean,n,p_value,ci_low,ci_high} to TS interface
- Mirrors app/schemas/endgames.py post-Plan-02 wire shape
- Dual-label rationale per CONTEXT D-02 documented inline
* test(87.1-03): add failing tests for per-type ScoreGapRow on EndgameTypeCard
- Asg-bullet / asg-value / asg-info testids assertions
- DOM-ordering assertion (gauges -> asg row -> WDL bar)
- Zone tinting (ZONE_SUCCESS positive out-of-band, ZONE_DANGER negative, neutral inside band)
- Sigmoid-bias caveat one-liner present in popover content
- Row hidden when type_achievable_score_gap_n === 0
* feat(87.1-03): add per-type ScoreGapRow row to EndgameTypeCard between gauges and WDL bar
- New ScoreGapRow positioned between Conv|Recov gauge row and WDL bar (D-01, D-08)
- Card row label 'Score Gap' (short form, D-02) — card title supplies type context
- Popover (MetricStatPopover) name='Score Gap' with full dual-label explanation
including sigmoid-bias caveat one-liner (D-09)
- Zone-only tinting (Phase 85.1 D-04): ZONE_SUCCESS above neutralMax,
ZONE_DANGER below neutralMin, neutral inside band
- Reuses global ENDGAME_TYPE_SCORE_GAP_NEUTRAL_MIN/MAX from generated/endgameZones.ts
- Hidden at type_achievable_score_gap_n === 0
- New testids: ${tileTestId}-asg-bullet, -asg-value, -asg-info
- Tests cover render, hide-at-n0, DOM ordering, tinting (both signs + neutral),
sigmoid caveat presence — jsdom oklch normalization handled via decimal-strip
* test(87.1-04): add failing tests for v29 bump + per-class Score Gap payload
RED phase for Phase 87.1 Plan 04. Tests will go green when:
- _PROMPT_VERSION is bumped endgame_v28 -> endgame_v29
- A v29 changelog block is added to the insights_llm.py constant comment
mentioning Endgame Type Score Gap and the Lichess sigmoid caveat
- The endgame_insights.md glossary defines "Endgame Type Score Gap"
with `type_achievable_score_gap` field-name reference
- The renderer emits per-class summary blocks for the new metric
Tests added:
- test_prompt_version_is_v29 (replaces _is_v28)
- test_prompt_version_bumped_to_v29_for_phase_87_1
- test_prompt_glossary_defines_endgame_type_score_gap
- TestEndgameTypeAchievableScoreGapPayload (5 cases): full + sparse cohort,
no verdict/p_value field, internal identifier preserved, per-class band
Per CONTEXT.md D-10: dual-label naming scheme (glossary uses
"Endgame Type Score Gap", card-context narration uses "Score Gap").
No parallel verdict / p_value field per feedback_llm_significance_signal.md.
* feat(87.1-04): bump _PROMPT_VERSION endgame_v28 -> endgame_v29 + per-class Score Gap band dispatch
GREEN phase Part 1 for Phase 87.1 Plan 04. The version bump invalidates
cached LLM reports so users get fresh narration that references the new
per-class Score Gap metric added to the payload.
Changes:
- _PROMPT_VERSION bumped endgame_v28 -> endgame_v29 with a v29 changelog
block describing the new metric, the dual-label terminology rule
(Endgame Type Score Gap / Score Gap), and the Lichess sigmoid-bias
caveat (per CONTEXT.md D-10 + memory feedback_llm_significance_signal.md).
- _format_zone_bounds extends the per-class dispatch (previously only
conversion_win_pct / recovery_save_pct) to cover the new
endgame_type_achievable_score_gap metric so its inline
`(typical LO to UP)` band reads from
PER_CLASS_GAUGE_ZONES[<class>].achievable_score_gap. Both global and
per-class registries currently hold the ±5% placeholder, but routing
through the per-class table now means future §3.4.2 benchmark
recalibration takes effect without renderer changes.
The SubsectionFinding pipeline already renders any MetricId enumerated in
endgame_zones.py — the new metric was added in Plan 01 — so no service-
layer changes are needed in this commit. Phase 87.1 Plan 02 populates the
EndgameCategoryStats.type_achievable_score_gap_* fields; insights_service
will emit one SubsectionFinding per category in a follow-up if/when the
metric needs to flow into the LLM. For now the renderer is ready and the
contract tests cover the wire shape.
Tests now passing (TestEndgameTypeAchievableScoreGapPayload, 5 cases;
test_prompt_version_is_v29; test_prompt_version_bumped_to_v29_for_phase_87_1).
The glossary test stays red — Task 2 lands the prompt-file glossary entry.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(87.1-03): complete Plan 03 — frontend per-type Score Gap row
- 87.1-03-SUMMARY: gate output, commits, deviations (jsdom oklch normalization), self-check PASSED
- 87.1-HUMAN-UAT: 3 manual checks (375px parity, sigmoid-caveat copy review, sign-convention visual)
- STATE.md: last-activity entry summarizing 3 commits + gate output + deviation
* feat(87.1-04): synthesize per-class Score Gap findings inside _assemble_user_prompt
Wires the Plan 02 schema fields (EndgameCategoryStats.type_achievable_score_gap_mean / _n)
into the LLM prompt assembly so the new per-span Score Gap metric reaches
the model alongside the existing Conv/Recov findings under
`conversion_recovery_by_type`. Without this, the v29 bump invalidates
caches but the LLM still sees no new data.
Approach: keep the change inside `insights_llm` (per the plan's
`files_modified` constraint — `insights_service` is intentionally not
modified for Plan 04). The synthesizer reads `findings.type_categories`,
emits one SubsectionFinding per non-pawnless class with a populated mean,
and the existing renderer pipeline picks them up via the standard
`groups[subsection_id]` path. Zone is assigned via
`assign_zone("endgame_type_achievable_score_gap", mean)` against the
global ZONE_REGISTRY entry, which currently mirrors the per-class
PER_CLASS_GAUGE_ZONES band — once §3.4.2 benchmark recalibration
diverges them per class, this dispatcher should switch to a per-class lookup.
No verdict / p_value field per memory feedback_llm_significance_signal.md.
The cohort band is the significance signal; future tightening lives in the
benchmark calibration, not in the prompt payload.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(87.1-04): glossary entry + dual-label rule for Endgame Type Score Gap
Adds the user-facing concept definition + LLM narration rule for the new
per-class Score Gap metric (Phase 87.1 D-10).
app/prompts/endgame_insights.md
- Concepts section: new bulleted entry defining "Endgame Type Score Gap"
alongside Conversion / Recovery, with the narration rule that the LLM
uses "Score Gap" in card-context prose and the full form when
introducing the metric or comparing to the page-level Achievable Score
Gap (CONTEXT.md D-10 dual-label scheme). Forbidden coinages ("ΔES",
"delta_es", "dES") listed explicitly so the LLM does not invent them.
- Metric glossary: new entry under `endgame_type_achievable_score_gap`
covering math derivation, scale, per-class typical band dispatch,
the Lichess sigmoid-bias caveat (memory feedback_llm_prompt_design.md +
.planning/notes/lichess-sigmoid-endgame-calibration.md), the dual-label
narration rule, the no-significance-field policy (memory
feedback_llm_significance_signal.md), and the relation to the page-level
Achievable Score Gap family.
- UI vocabulary table: new row mapping the internal identifier to the
dual-label form ("Score Gap" / "Endgame Type Score Gap") with an
example rendering. No em-dashes in any new prompt copy per CLAUDE.md.
CHANGELOG.md
- New `### Added` bullet under `## [Unreleased]` for Phase 87.1: per-type
Score Gap row on each card + LLM Insights prompt version bump
endgame_v28 -> endgame_v29.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(87.1-04): complete Plan 04 — LLM payload + v29 prompt-version bump
Wraps Phase 87.1 Plan 04 with:
- 87.1-04-SUMMARY.md documenting the in-renderer SubsectionFinding
synthesis, the v29 bump rationale, the dual-label glossary entry, and
the two Rule-2 auto-fixes (synthesizer + per-class band dispatch) that
were necessary to satisfy ROADMAP §6 with the plan's files_modified
constraint.
- 87.1-HUMAN-UAT.md: new "Verify LLM narration uses dual-label rule"
section + sign-off checkbox for the manual narration review.
- STATE.md: phase 87.1 marked READY FOR VERIFICATION with all four plans
complete.
Phase 87.1 is now ready for verification + HUMAN-UAT sign-off.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(87.1): add verification report — human_needed (4 UAT items)
All 22 plan must-have truths verified against codebase. 6/6 ROADMAP success
criteria confirmed. Outstanding: real-device 375px parity, sigmoid-bias copy
tone, sign-convention visual review, LLM dual-label adherence on real
generation. Phase technically complete; ship pending HUMAN-UAT sign-off.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(87.1): mark phase complete in STATE + ROADMAP
4/4 plans landed; verifier returned human_needed pending 4 UAT items
(375px parity, sigmoid-caveat tone, sign-convention visual, LLM dual-label
adherence on real generation). Per CLAUDE.md release process, CHANGELOG
[Unreleased] entry added by Plan 04.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs: tighten Achievable Score copy + correct sigmoid name
Achievable Score is now defined as "what a 2300+ rated player would
score from your endgame-entry positions against a peer of similar
rating" — the Lichess sigmoid was fit on 2300+ rapid game outcomes
(peer-vs-peer), so the implicit opponent is another 2300+ player.
Also renamed "Lichess winning-chances sigmoid" → "Lichess
expected-score sigmoid" in user-facing prose: lichess-org/lila#11148
fit the curve by curve_fit against {-1, 0, +1} game outcomes, which
is mathematically expected score on [-1, +1], not win probability.
At cp=0 the curve returns 50%, which is correct as expected score
(by symmetry) but wildly wrong as P(win) since draws dominate at
2300+ balanced positions.
Both fixes are prose only — payload shape, zone bands, and the
sigmoid coefficient (-0.00368208) are unchanged. _PROMPT_VERSION
bumped v29 -> v30 to invalidate cached LLM reports so users see
the corrected terminology. Internal helper names (eval_utils.py
LICHESS_K docstring, scalachess-mirroring code comments) keep
"winning-chances" to match Lichess's own function name.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(benchmarks): add §3.4.3 redundancy analysis (IQR-zone calibrated)
Adds a new SKILL.md subchapter and report section comparing Endgame Score
vs Score Gap on per-class cohorts to inform the EndgameTypeCard chart
inventory. Zones are self-derived from cohort IQR (p25/p75 per class
per metric), so the analysis is independent of placeholder bands in
endgame_zones.py / scoreBulletConfig.ts. Verdict: keep all three signals
(Score + Score Gap + WDL + Conv/Recov). Pooled r=0.48; strong-disagreement
uniformly below the 12.5%% independence baseline.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(87.1): note — keep all five charts on EndgameTypeCard
Captures the /gsd-explore verdict on the per-type card chart inventory.
Cross-references §3.4.3 of reports/benchmarks-latest.md (pooled r=0.48,
strict zone-agreement 53%, strong-disagreement 3.8%, queen r=0.23) so the
Score vs Score Gap redundancy question doesn't get re-litigated cold.
Records WDL's slot as visual-anchor (not statistical), and flags the WDL
drop as the natural lever if a future UAT pass flags the 5-chart card as
too dense.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(87.1): move Score Gap row last + Cpu icon + shorten copy
- EndgameTypeCard: Score Gap row now renders below the Endgame Score row
instead of between gauges and WDL bar — eval-based metric reads as a
follow-on signal, not a peer to the gauge cluster.
- Cpu icon prepended to the "Score Gap" label to flag the metric as
eval-based (matches the Cpu convention on EndgameOverallEntryCard and
the Openings → Stats eval bullet rows).
- Explanation copy halved (~750 → ~340 chars); the sigmoid-bias caveat
stays as a one-liner so the rely-on-zones guidance survives.
- ScoreGapRow `label` widened to ReactNode so callers can prepend icons
without a new prop; existing string callers in EndgameOverallPerformanceSection
remain valid.
- Test updated: positioning assertion now expects Score Gap as the last
row in the card body.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(87.1): calibrate endgame_type_achievable_score_gap bands from §3.4.2
Replaces the Phase 87.1 Plan 01 placeholder (-0.05, 0.05) with per-class
bands derived from the benchmark §3.4.2 calibration (n=5,727 users across
5 visible classes, equal-footing filter, sparse cell excluded).
Global ZONE_REGISTRY band → (-0.04, +0.04). Per-class PER_CLASS_GAUGE_ZONES
bands range from mixed (-0.03, +0.04) (narrowest IQR — multi-class spans
average out) to minor_piece (-0.04, +0.06). Mixed crosses the ELO keep-
separate threshold (d=0.57) but per-ELO stratification is deferred.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(87.1): rephrase Score Gap explanation, drop "sigmoid" jargon
Per-class Score Gap popover explanation now uses "Lichess expected-score
formula" (matches the glossary) instead of "sigmoid", and explicitly notes
that the final sequence in a game uses the game result rather than a
Stockfish end eval — the previous "start and end" framing was technically
wrong for terminal sequences. Sigmoid-bias caveat retained as a one-liner;
test grep updated to match the new wording.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(87.1): drop em-dash from Score Gap explanation
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(87.1): drop sigmoid-bias caveat from Score Gap explanation
User-facing copy keeps to the mechanics; the bias caveat is too technical
for the popover and the zone bands already encode the "use zones, not raw
magnitude" guidance visually. Dropped the matching test assertion.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(87.1): match per-class Score Gap bullet blue-zone width to Endgame Score
Adds ENDGAME_TYPE_SCORE_GAP_DOMAIN=0.12 and a `domain` prop on ScoreGapRow
so the per-card Score Gap bullet's neutral band (±0.04, width 0.08) fills
1/3 of the axis — matching the Endgame Score bullet directly above it
(band 0.10 / axis 0.30). Shared SCORE_GAP_DOMAIN is unchanged for the
Overall Performance rows.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(87.1): ship phase 87.1 — PR #97
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* style(87.1): apply ruff format to satisfy CI
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test(87.1): update rook band assertions to calibrated (-5, +4)
The §3.4.2 calibration commit ( f48513f ) tightened the rook
achievable_score_gap band from the (-0.05, +0.05) placeholder to
(-0.05, +0.04), but two LLM-payload tests were still pinned to the
placeholder and failed in CI.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>