refactor and simplify analysis ceval annotations

This commit is contained in:
Thibault Duplessis
2026-05-20 10:59:54 +02:00
parent d7d7367190
commit 3e7c43bc58
4 changed files with 38 additions and 37 deletions
+1 -1
View File
@@ -177,7 +177,7 @@ export function compute(ctrl: AnalyseCtrl): DrawShape[] {
});
}
if (ctrl.showMoveAnnotationsOnBoard()) {
const liveGlyph = ctrl.liveGlyphs.get(ctrl.path);
const liveGlyph = ctrl.liveAnnotate.get(ctrl.path);
shapes = shapes.concat(
// Override server analysis glyphs as local eval also overrides the eval score
annotationShapes(liveGlyph ? { ...ctrl.node, glyphs: [liveGlyph] } : ctrl.node),
+5 -24
View File
@@ -39,7 +39,7 @@ import { pubsub } from 'lib/pubsub';
import { storedBooleanProp, storedBooleanPropWithEffect } from 'lib/storage';
import { makeTree, treePath, treeOps, type TreeWrapper } from 'lib/tree';
import { completeNode } from 'lib/tree/node';
import type { ClientEval, Glyph, LocalEval, ServerEval, TreeNode, TreePath } from 'lib/tree/types';
import type { ClientEval, LocalEval, ServerEval, TreeNode, TreePath } from 'lib/tree/types';
import { confirm } from 'lib/view';
import { Autoplay, type AutoplayDelay } from './autoplay';
@@ -52,7 +52,7 @@ import { ForkCtrl } from './fork';
import { IdbTree } from './idbTree';
import type { AnalyseOpts, AnalyseData, ServerEvalData, JustCaptured, NvuiPlugin } from './interfaces';
import * as keyboard from './keyboard';
import { liveNodeGlyph } from './liveAnnotate';
import LiveAnnotate from './liveAnnotate';
import MotifCtrl from './motif/motifCtrl';
import Navigate from './navigate';
import { nextGlyphSymbol, add3or5FoldGlyphs } from './nodeFinder';
@@ -76,6 +76,7 @@ export default class AnalyseCtrl implements CevalHandler {
chessground: ChessgroundApi;
ceval: CevalCtrl;
evalCache: EvalCache;
liveAnnotate: LiveAnnotate;
navigate: Navigate;
idbTree: IdbTree = new IdbTree(this);
actionMenu: Toggle = toggle(false);
@@ -123,7 +124,6 @@ export default class AnalyseCtrl implements CevalHandler {
);
showFishnetAnalysis = storedBooleanProp('analyse.show-computer', true);
possiblyShowMoveAnnotationsOnBoard = storedBooleanProp('analyse.show-move-annotation', true);
liveGlyphs = new Map<TreePath, Glyph | undefined>();
keyboardHelp: boolean = location.hash === '#keyboard';
threatMode: Prop<boolean> = prop(false);
disclosureMode = storedBooleanProp('analyse.disclosure.enabled', false);
@@ -179,6 +179,7 @@ export default class AnalyseCtrl implements CevalHandler {
});
this.instanciateEvalCache();
this.liveAnnotate = new LiveAnnotate();
if (opts.inlinePgn) this.data = this.changePgn(opts.inlinePgn, false) || this.data;
@@ -740,10 +741,7 @@ export default class AnalyseCtrl implements CevalHandler {
if (node.ceval?.cloud && this.ceval.isDeeper()) node.ceval = ev;
}
if (!isThreat && ev) {
this.annotateLivePath(path);
this.annotateLiveChildren(path, node);
}
if (!isThreat) this.liveAnnotate.onNewCeval(path, node, this.tree);
if (path === this.path) {
this.setAutoShapes();
@@ -759,23 +757,6 @@ export default class AnalyseCtrl implements CevalHandler {
});
};
private annotateLivePath(path: TreePath): void {
if (path.length)
this.liveGlyphs.set(path, liveNodeGlyph(this.tree.nodeAtPath(path), this.tree.parentNode(path)));
}
private annotateLiveChildren(path: TreePath, node: TreeNode): void {
node.children.forEach(child => this.annotateLivePath(path + child.id));
}
private rebuildLiveGlyphs(node = this.tree.root, path: TreePath = treePath.root): void {
node.children.forEach(child => {
const childPath = path + child.id;
this.annotateLivePath(childPath);
this.rebuildLiveGlyphs(child, childPath);
});
}
private initCeval(): void {
const opts: CevalOpts = {
variant: this.data.game.variant,
+31 -11
View File
@@ -1,5 +1,6 @@
import { povChances } from 'lib/ceval/winningChances';
import type { Glyph, TreeNode } from 'lib/tree/types';
import type { TreeWrapper } from 'lib/tree';
import type { Glyph, TreeNode, TreePath } from 'lib/tree/types';
const glyphs = {
inaccuracy: { id: 6, symbol: '?!', name: 'Inaccuracy' } as Glyph,
@@ -7,15 +8,34 @@ const glyphs = {
blunder: { id: 4, symbol: '??', name: 'Blunder' } as Glyph,
};
export function liveGlyph(parentEval: EvalScore, currentEval: EvalScore, ply: Ply): Glyph | undefined {
const color: Color = ply % 2 === 1 ? 'white' : 'black';
const loss = povChances(color, parentEval) - povChances(color, currentEval);
if (loss > 0.3) return glyphs.blunder;
if (loss > 0.2) return glyphs.mistake;
if (loss > 0.1) return glyphs.inaccuracy;
return undefined;
}
export default class LiveAnnotate {
private readonly glyphs = new Map<TreePath, Glyph>();
export function liveNodeGlyph(node: TreeNode, parentNode: TreeNode): Glyph | undefined {
return parentNode.ceval && node.ceval && liveGlyph(parentNode.ceval, node.ceval, node.ply);
readonly get = this.glyphs.get.bind(this.glyphs);
readonly onNewCeval = (path: TreePath, node: TreeNode, tree: TreeWrapper): void => {
const parent = tree.parentNode(path);
this.update(path, node, parent);
node.children.forEach(child => this.update(path + child.id, child, node));
};
private readonly liveGlyph = (
parentEval: EvalScore,
currentEval: EvalScore,
ply: Ply,
): Glyph | undefined => {
const color: Color = ply % 2 === 1 ? 'white' : 'black';
const loss = povChances(color, parentEval) - povChances(color, currentEval);
if (loss > 0.3) return glyphs.blunder;
if (loss > 0.2) return glyphs.mistake;
if (loss > 0.1) return glyphs.inaccuracy;
return undefined;
};
private readonly update = (path: TreePath, node: TreeNode, parent: TreeNode): void => {
if (!path.length) return;
const glyph = parent.ceval && node.ceval && this.liveGlyph(parent.ceval, node.ceval, node.ply);
if (glyph) this.glyphs.set(path, glyph);
else this.glyphs.delete(path);
};
}
+1 -1
View File
@@ -164,7 +164,7 @@ export class InlineView {
'pending-deletion': path.startsWith(ctrl.pendingDeletionPath() || ' '),
'pending-copy': !!ctrl.pendingCopyPath()?.startsWith(path),
};
const liveGlyph = ctrl.liveGlyphs.get(path);
const liveGlyph = ctrl.liveAnnotate.get(path);
const glyphs = liveGlyph ? [liveGlyph] : node.glyphs;
if (ctrl.showMoveGlyphs()) {
glyphs