@chessceo/mcp 0.49.5 → 0.49.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/analysis/auto.js +4 -3
- package/dist/analysis/deep.js +3 -1
- package/dist/analysis/file_handle.js +11 -0
- package/dist/index.js +11 -4
- package/dist/tools.js +13 -12
- package/docs/engine-usage.md +11 -0
- package/docs/pgn-authoring.md +14 -0
- package/package.json +1 -1
package/dist/analysis/auto.js
CHANGED
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
// a mid-run crash / cancellation doesn't lose the whole walk.
|
|
25
25
|
import { authedRequest, fetchGame } from "../http.js";
|
|
26
26
|
import { analysisToStoredEval } from "./response.js";
|
|
27
|
+
import { analyseRouting } from "./file_handle.js";
|
|
27
28
|
import { parsePGN } from "../pgn/parser.js";
|
|
28
29
|
import { buildIdIndex, positionKey, resolveNodeId, ROOT_ID } from "../pgn/paths.js";
|
|
29
30
|
const evalJobs = new Map();
|
|
@@ -146,7 +147,7 @@ export async function autoEvaluate(args, applyBatchMutations) {
|
|
|
146
147
|
// Unawaited — runs concurrently with the tool response. Any thrown
|
|
147
148
|
// error gets recorded on the job so the LLM's status poll surfaces
|
|
148
149
|
// it instead of the process seeing an unhandled rejection.
|
|
149
|
-
void runEvalJob(job, id, targets, movetimeMs, applyBatchMutations).catch(err => {
|
|
150
|
+
void runEvalJob(job, id, targets, movetimeMs, applyBatchMutations, analyseRouting(args)).catch(err => {
|
|
150
151
|
job.status = "error";
|
|
151
152
|
job.error = err instanceof Error ? err.message : String(err);
|
|
152
153
|
job.finishedAt = Date.now();
|
|
@@ -167,7 +168,7 @@ export async function autoEvaluate(args, applyBatchMutations) {
|
|
|
167
168
|
// against the per-combo semaphore in the backend anyway), checkpoints
|
|
168
169
|
// every SAVE_EVERY_N successfully-evaluated nodes so partial progress
|
|
169
170
|
// is durable, and re-anchors the version after each save.
|
|
170
|
-
async function runEvalJob(job, fileId, targets, movetimeMs, applyBatchMutations) {
|
|
171
|
+
async function runEvalJob(job, fileId, targets, movetimeMs, applyBatchMutations, routing) {
|
|
171
172
|
const pending = [];
|
|
172
173
|
const flush = async () => {
|
|
173
174
|
if (pending.length === 0)
|
|
@@ -196,7 +197,7 @@ async function runEvalJob(job, fileId, targets, movetimeMs, applyBatchMutations)
|
|
|
196
197
|
if (job.cancelled || aborted)
|
|
197
198
|
break;
|
|
198
199
|
try {
|
|
199
|
-
const analysis = await authedRequest("POST", "/api/agent/cloud-engines/analyse", { fen: t.fen, movetime_ms: movetimeMs, multipv: 1 });
|
|
200
|
+
const analysis = await authedRequest("POST", "/api/agent/cloud-engines/analyse", { fen: t.fen, movetime_ms: movetimeMs, multipv: 1, ...routing });
|
|
200
201
|
const ev = analysisToStoredEval(analysis);
|
|
201
202
|
if (ev) {
|
|
202
203
|
pending.push({ op: "set_ceo_eval", node_id: t.nodeId, ceoEval: ev });
|
package/dist/analysis/deep.js
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
// Extracted from index.ts in v0.44 as part of the file split.
|
|
17
17
|
import { authedRequest } from "../http.js";
|
|
18
18
|
import { analysisToStoredEval, convertCloudSnapshotResponse } from "./response.js";
|
|
19
|
-
import { resolveFromNodeOrFen, storeEvalOnNode, } from "./file_handle.js";
|
|
19
|
+
import { analyseRouting, resolveFromNodeOrFen, storeEvalOnNode, } from "./file_handle.js";
|
|
20
20
|
const deepJobs = new Map();
|
|
21
21
|
const DEEP_JOB_TTL_MS = 15 * 60 * 1000;
|
|
22
22
|
function newDeepJobId() {
|
|
@@ -43,6 +43,7 @@ export async function deepAnalyseStart(args) {
|
|
|
43
43
|
const jobId = newDeepJobId();
|
|
44
44
|
const job = {
|
|
45
45
|
id: jobId,
|
|
46
|
+
routing: analyseRouting(args),
|
|
46
47
|
status: "running",
|
|
47
48
|
fileHandle: resolved.file,
|
|
48
49
|
fen,
|
|
@@ -73,6 +74,7 @@ async function runDeepJob(job) {
|
|
|
73
74
|
movetime_ms: job.movetimeMs,
|
|
74
75
|
stockfish_multipv: job.multipv,
|
|
75
76
|
engines: ["stockfish"],
|
|
77
|
+
...job.routing,
|
|
76
78
|
};
|
|
77
79
|
let raw;
|
|
78
80
|
try {
|
|
@@ -154,3 +154,14 @@ export async function storeEvalOnNode(handle, ev) {
|
|
|
154
154
|
return [];
|
|
155
155
|
}
|
|
156
156
|
}
|
|
157
|
+
// Routing fields for POST /api/agent/cloud-engines/analyse: which rental to
|
|
158
|
+
// run on and which engine legs to run. Omitted fields let the backend pick.
|
|
159
|
+
export function analyseRouting(args) {
|
|
160
|
+
const out = {};
|
|
161
|
+
if (typeof args.contract_id === "string" && args.contract_id.trim()) {
|
|
162
|
+
out.contract_id = args.contract_id.trim();
|
|
163
|
+
}
|
|
164
|
+
if (Array.isArray(args.engines))
|
|
165
|
+
out.engines = args.engines;
|
|
166
|
+
return out;
|
|
167
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -25,7 +25,7 @@ import { authContext, authedRequest, deleteGame, fetchGame, get, makeFileId, res
|
|
|
25
25
|
import { analysisToStoredEval, capPvsInResponse, convertCloudSnapshotResponse, fetchCompactEval, } from "./analysis/response.js";
|
|
26
26
|
import { autoEvaluate, autoEvaluateCancel, autoEvaluateStatus, } from "./analysis/auto.js";
|
|
27
27
|
import { deepAnalyseCancel, deepAnalyseStart, deepAnalyseStatus, } from "./analysis/deep.js";
|
|
28
|
-
import { getNodeByPath, resolveFromNodeOrFen, storeEvalOnNode, } from "./analysis/file_handle.js";
|
|
28
|
+
import { analyseRouting, getNodeByPath, resolveFromNodeOrFen, storeEvalOnNode, } from "./analysis/file_handle.js";
|
|
29
29
|
import { findPositionInCourses, readCourseAtPosition, runSfEval } from "./courses.js";
|
|
30
30
|
import { applyBatchMutations, applyMutation, argNodeId, } from "./prep/mutations.js";
|
|
31
31
|
import { listNodes, listTranspositions, readPrepFile, } from "./prep/read.js";
|
|
@@ -126,6 +126,9 @@ const ENGINE_GUIDE = {
|
|
|
126
126
|
lc0: "Neural-net engine trained on human-style play, roughly 3500-strength. Its evaluation approximates how a strong human judges the position (practical chances, initiative, structure) rather than pure objective truth — good for finding ideas and gauging which side is easier to play over the board. Has real weaknesses in unconventional/irregular positions outside its training distribution: evals there can be unstable or it can miss a deep concrete tactic. When it disagrees sharply with Stockfish, look for the tactical justification before trusting its read.",
|
|
127
127
|
combo: "Runs Stockfish and Lc0 together — use when you want both the objective read and the practical/human-feel read on the same position.",
|
|
128
128
|
};
|
|
129
|
+
// Modern engines give equality very often, so small numbers are not "equal".
|
|
130
|
+
// Rough guide, not exact thresholds. Shown alongside ENGINE_GUIDE above.
|
|
131
|
+
const ENGINE_EVAL_SCALE = "Small numbers are not equality. ±0.00 to ±0.10 is equal in practice (+0.10 is '=' or '+=' at most). +0.20 to +0.40 is real pressure, not 'a bit better'. +0.50 and up is a clear advantage. Stockfish 0.00 with a plus from Lc0 is a practical edge, not equality. Check both engines before calling a position equal.";
|
|
129
132
|
// v0.48: consolidated the five `read_*_guide` / `read_example_prep_files`
|
|
130
133
|
// tools into ONE `read_docs`. LLM lists what it wants; we return them
|
|
131
134
|
// in a single response. Also cleaner: enumerating available docs in one
|
|
@@ -382,7 +385,11 @@ async function callToolInner(name, args) {
|
|
|
382
385
|
case "list_cloud_machine_options": {
|
|
383
386
|
const raw = await authedRequest("GET", "/api/agent/cloud-engines/options");
|
|
384
387
|
const resp = raw;
|
|
385
|
-
return {
|
|
388
|
+
return {
|
|
389
|
+
...(resp && typeof resp === "object" ? resp : { options: [] }),
|
|
390
|
+
engine_guide: ENGINE_GUIDE,
|
|
391
|
+
eval_scale: ENGINE_EVAL_SCALE,
|
|
392
|
+
};
|
|
386
393
|
}
|
|
387
394
|
case "start_cloud_engine":
|
|
388
395
|
return authedRequest("POST", "/api/agent/cloud-engines", {
|
|
@@ -405,8 +412,7 @@ async function callToolInner(name, args) {
|
|
|
405
412
|
body.lc0_multipv = args.lc0_multipv;
|
|
406
413
|
if (typeof args.contempt === "number")
|
|
407
414
|
body.contempt = args.contempt;
|
|
408
|
-
|
|
409
|
-
body.engines = args.engines;
|
|
415
|
+
Object.assign(body, analyseRouting(args));
|
|
410
416
|
const raw = await authedRequest("POST", "/api/agent/cloud-engines/analyse", body);
|
|
411
417
|
const converted = convertCloudSnapshotResponse(raw, fen);
|
|
412
418
|
// PV cap: engine PVs beyond ~6 plies are speculative (the tail is
|
|
@@ -442,6 +448,7 @@ async function callToolInner(name, args) {
|
|
|
442
448
|
}
|
|
443
449
|
}
|
|
444
450
|
}
|
|
451
|
+
converted.eval_scale = ENGINE_EVAL_SCALE;
|
|
445
452
|
return converted;
|
|
446
453
|
}
|
|
447
454
|
case "deep_analyse":
|
package/dist/tools.js
CHANGED
|
@@ -75,7 +75,7 @@ export const TOOLS = [
|
|
|
75
75
|
name: "get_prep_position",
|
|
76
76
|
description: "Query one position within a prep session created by `prepare_opponent`. Returns move statistics (frequency + win rate + last-played date per move) plus the actual games played from that position, in one call.\n\n" +
|
|
77
77
|
"Position input: prefer `file_id`+`node_id` when inside a prep file (server derives FEN from the tree). Otherwise pass `fen`.\n\n" +
|
|
78
|
-
"AUTO-EVAL: if a cloud
|
|
78
|
+
"AUTO-EVAL: if a cloud engine instance is running, the response includes `.eval` (whichever engines that rental provides — Stockfish, Lc0, or both) so you don't need a separate cloud_analyse.\n\n" +
|
|
79
79
|
"Reading the response — CRITICAL:\n" +
|
|
80
80
|
"• Win % is one weight, not a verdict. Sample size matters (3 games at 66% is noise; 300 at 55% is signal).\n" +
|
|
81
81
|
"• Prep is symmetric information — both sides see the same history. Assume the opponent knows the weakness you spotted.\n" +
|
|
@@ -118,7 +118,7 @@ export const TOOLS = [
|
|
|
118
118
|
"- `gm-classical` — GM classical games (both players ≥2500, real thinking-time). BEST for opening prep — every move is signal, avgElo ~2600 across all listed moves.\n" +
|
|
119
119
|
"- `main` — the whole 11.7M-game DB. Widest coverage but noisiest (includes 1000-Elo blunder-fests in the move stats). Use as fallback when gm-classical's totalCount is too small to be informative.\n\n" +
|
|
120
120
|
"Game movetext is trimmed to the moves AFTER the queried position (using each game's plyNumber). Saves ~70% of the bytes vs full movetext.\n\n" +
|
|
121
|
-
"AUTO-EVAL: if a cloud
|
|
121
|
+
"AUTO-EVAL: if a cloud engine instance is running, the response includes `.eval` with a compact read from the engine(s) it provides and the corresponding NAG. Do NOT fire cloud_analyse separately for the same FEN. When called with `file_id`+`node_id`, the eval is also auto-stored on that node's `ceoEval` — later readable via quote_engine_eval.",
|
|
122
122
|
inputSchema: {
|
|
123
123
|
type: "object",
|
|
124
124
|
properties: {
|
|
@@ -284,7 +284,7 @@ export const TOOLS = [
|
|
|
284
284
|
},
|
|
285
285
|
{
|
|
286
286
|
name: "list_cloud_engines",
|
|
287
|
-
description: "List the user's currently running cloud engines. Use before starting a new one, or to find the contract_id for stop_cloud_engine. `cloud_analyse`
|
|
287
|
+
description: "List the user's currently running cloud engines. Use before starting a new one, or to find the contract_id for stop_cloud_engine. `cloud_analyse` uses the only running rental that can serve the request unless you pass `contract_id`, so listing is only necessary when there are several.",
|
|
288
288
|
inputSchema: { type: "object", properties: {} },
|
|
289
289
|
},
|
|
290
290
|
{
|
|
@@ -303,9 +303,9 @@ export const TOOLS = [
|
|
|
303
303
|
},
|
|
304
304
|
{
|
|
305
305
|
name: "cloud_analyse",
|
|
306
|
-
description: "Runs a synchronous ~2s analysis on the user's running
|
|
306
|
+
description: "Runs a synchronous ~2s analysis on one of the user's running engine instances and returns the requested engines' final read for the FEN — depth, top-N candidate moves with scores (**scoreCp is White-POV centipawns**: +20 = White is +0.20 pawns better regardless of whose turn it is; matches the sign convention used everywhere else in this MCP, including the stored ceoEval). Mate is White-POV plies-to-mate (+5 = White mates in 5). Also returns each engine's principal variation.\n\n" +
|
|
307
307
|
"GROUNDING: every claim you make about a position must trace back to actual engine output from a call in THIS session. Don't invent evaluations, don't name 'best moves' you haven't seen the engine list, don't fabricate variations that 'look plausible.' Compute is cheap — call this 5-10 times while walking a tree rather than pattern-matching from your training data. When you don't have data for the position, either run the tool or say so; don't fill the gap with chess prose the user can't distinguish from measured output.\n\n" +
|
|
308
|
-
"
|
|
308
|
+
"Runs on any of the caller's running rentals — combo, stockfish-only, or lc0-only. Pass `contract_id` to choose one; without it the only rental that can serve the requested engines is used, and the error lists the candidates if there are several.\n\n" +
|
|
309
309
|
"How to read the response:\n" +
|
|
310
310
|
"• Stockfish is objective truth — trust it for 'does this line hold?' 'is there a tactic?' 'is this endgame drawn?' A Stockfish 0.00 means 'objectively equal', NOT 'trivial draw' — one side can still be much harder to play in practice.\n" +
|
|
311
311
|
"• Lc0 is practical eval — trust it for 'which side is easier?' 'which candidate is best when Stockfish shows several as equal?' Lc0 sees long-term positional factors Stockfish's fixed search can miss.\n" +
|
|
@@ -350,10 +350,11 @@ export const TOOLS = [
|
|
|
350
350
|
maximum: 100,
|
|
351
351
|
description: "Lc0 contempt bias. Signed 0-100 strength (same scale as the web UI's ContemptStrength slider — server multiplies by 8 to get the internal cp bias). 0 = objective (default). Positive favours White, negative favours Black. Typical: ±15 light nudge, ±30-60 real fighting play, ±80-100 maximum steer. Not applied to Stockfish. See engine_usage_primer for when to use.",
|
|
352
352
|
},
|
|
353
|
+
contract_id: { type: "string", description: "Which of your running rentals to use, from list_cloud_engines. Any engine shape works (combo, stockfish-only, lc0-only). Omit it when only one rental can serve the request; if several can, the error lists their contract ids." },
|
|
353
354
|
engines: {
|
|
354
355
|
type: "array",
|
|
355
356
|
items: { type: "string", enum: ["stockfish", "lc0"] },
|
|
356
|
-
description: "Which engines to run. Default = both. Use `[\"lc0\"]` to skip Stockfish (e.g. while a deep_analyse job is holding the SF slot on the same
|
|
357
|
+
description: "Which engines to run. Default = both. Use `[\"lc0\"]` to skip Stockfish (e.g. while a deep_analyse job is holding the SF slot on the same rental). Use `[\"stockfish\"]` when only the objective read matters. The skipped engine's field is omitted from the response.",
|
|
357
358
|
},
|
|
358
359
|
pv_max_plies: {
|
|
359
360
|
type: "integer",
|
|
@@ -721,14 +722,14 @@ export const TOOLS = [
|
|
|
721
722
|
},
|
|
722
723
|
{
|
|
723
724
|
name: "auto_evaluate",
|
|
724
|
-
description: "Walk the tree from `node_id` (default `'r'` = whole file) and populate the persistent `ceoEval` on every descendant via cloud_analyse. Requires a running cloud
|
|
725
|
+
description: "Walk the tree from `node_id` (default `'r'` = whole file) and populate the persistent `ceoEval` on every descendant via cloud_analyse. Requires a running cloud engine instance (any shape; pass contract_id to choose).\n\n" +
|
|
725
726
|
"**Async job — returns immediately.** Response: `{ job_id, target_count, status: 'running', estimated_seconds }`. Then poll `auto_evaluate_status(job_id)` until `done: true`. Cancel a run with `auto_evaluate_cancel(job_id)` — partial progress is preserved. Do useful other work between polls (write more of the tree, walk the opponent's repertoire) — the engine runs in the background.\n\n" +
|
|
726
727
|
"Progress is checkpointed to the prep file every 8 successfully-evaluated nodes, so a cancel / crash / MCP restart mid-run leaves the tree partially populated rather than losing everything. On MCP restart the job record disappears; re-run auto_evaluate and `only_missing=true` naturally skips what was already saved.\n\n" +
|
|
727
728
|
"**Does NOT set visible NAGs.** NAG placement is your call, not the engine's — an opening tree full of 0.00 positions doesn't need a `$10` (=) glyph on every move. Use quote_engine_eval on individual nodes before writing prose that references engine numbers.\n\n" +
|
|
728
|
-
"Costs real money — one cloud_analyse per node. A 200-node walk at default movetime is ~5 min of engine time (calls serialise on the per-
|
|
729
|
+
"Costs real money — one cloud_analyse per node. A 200-node walk at default movetime is ~5 min of engine time (calls serialise on the per-engine semaphore in the backend).",
|
|
729
730
|
inputSchema: {
|
|
730
731
|
type: "object",
|
|
731
|
-
properties: {
|
|
732
|
+
properties: { contract_id: { type: "string", description: "Which of your running rentals to use, from list_cloud_engines. Any engine shape works (combo, stockfish-only, lc0-only). Omit it when only one rental can serve the request; if several can, the error lists their contract ids." },
|
|
732
733
|
id: { type: "string" },
|
|
733
734
|
node_id: { type: "string", description: "Subtree root (default 'r' = whole file)." },
|
|
734
735
|
only_missing: { type: "boolean", description: "Skip nodes that already carry a stored ceoEval (default true)." },
|
|
@@ -762,12 +763,12 @@ export const TOOLS = [
|
|
|
762
763
|
},
|
|
763
764
|
{
|
|
764
765
|
name: "deep_analyse",
|
|
765
|
-
description: "Start a long Stockfish think on a single position (up to 5 min movetime). Returns a `job_id` immediately; poll `deep_analyse_status(job_id)` for the result, cancel with `deep_analyse_cancel(job_id)`. Runs SF only — Lc0 doesn't benefit from long thinks past a handful of seconds — and **holds only the SF engine slot on the
|
|
766
|
+
description: "Start a long Stockfish think on a single position (up to 5 min movetime). Returns a `job_id` immediately; poll `deep_analyse_status(job_id)` for the result, cancel with `deep_analyse_cancel(job_id)`. Runs SF only — Lc0 doesn't benefit from long thinks past a handful of seconds — and **holds only the SF engine slot on the rental, so `cloud_analyse(..., engines: [\"lc0\"])` stays available for other work in parallel**.\n\n" +
|
|
766
767
|
"Use this when a specific critical position deserves depth — a novelty candidate, a hairy tactical shot, a difficult endgame — and you want Stockfish at depth 35+ rather than the ~depth 22 you get from a 2s cloud_analyse. Movetime is in ms; typical: 30_000-60_000 for 'careful check', 120_000-300_000 for 'find the truth'.\n\n" +
|
|
767
768
|
"Result shape when done matches cloud_analyse's Stockfish leg (depth, top-N candidates with scoreCp/mate, best move, PV). Auto-stores the eval on `file_id`+`node_id` when both are supplied, same as cloud_analyse.",
|
|
768
769
|
inputSchema: {
|
|
769
770
|
type: "object",
|
|
770
|
-
properties: {
|
|
771
|
+
properties: { contract_id: { type: "string", description: "Which of your running rentals to use, from list_cloud_engines. Any engine shape works (combo, stockfish-only, lc0-only). Omit it when only one rental can serve the request; if several can, the error lists their contract ids." },
|
|
771
772
|
file_id: { type: "string", description: "Prep file id. Combine with `node_id` to derive FEN from the tree AND persist the result on the node's ceoEval." },
|
|
772
773
|
node_id: { type: "string", description: "Node id inside `file_id`. Root is 'r'. When set, overrides `fen`/`moves`." },
|
|
773
774
|
fen: { type: "string", description: "Position as FEN. Only used if `node_id` is not set." },
|
|
@@ -926,7 +927,7 @@ export const TOOLS = [
|
|
|
926
927
|
{
|
|
927
928
|
name: "prep_snapshot",
|
|
928
929
|
description: "One call, three parallel fetches at the same position: opponent's stats on their side, your stats on your side, and the 11.7M-game general database at that position. Use this while walking the opening tree — one round trip instead of three separate calls, and you can compare the three views directly (e.g. opponent has 2 games here but the general DB has 8k → prep candidate).\n\n" +
|
|
929
|
-
"AUTO-EVAL: if a cloud
|
|
930
|
+
"AUTO-EVAL: if a cloud engine instance is running, the response includes a top-level `.eval` (the engines that rental provides, read at the shared position) so you get four signals in one call. Do NOT fire cloud_analyse separately for the same FEN.",
|
|
930
931
|
inputSchema: {
|
|
931
932
|
type: "object",
|
|
932
933
|
properties: {
|
package/docs/engine-usage.md
CHANGED
|
@@ -89,6 +89,17 @@ If you actually want to estimate PRACTICAL drawing chance, ask the tools that an
|
|
|
89
89
|
|
|
90
90
|
**Never conflate the objective eval with the practical outcome.** A 0.00 middlegame with a piece imbalance, opposite-side attacks, or a rating gap is a fighting game; the number just told you nobody has a forced win.
|
|
91
91
|
|
|
92
|
+
## Eval scale: small numbers are not equality
|
|
93
|
+
|
|
94
|
+
Modern engines give equality very often, so the scale is compressed. A number that looks small can still be a real edge. Rough guide (approximate, not exact thresholds):
|
|
95
|
+
|
|
96
|
+
- **±0.00 to ±0.10**: equal in practice. A Stockfish +0.10 is "=" or "+=" at most.
|
|
97
|
+
- **+0.20 to +0.40**: real pressure, not "a bit better". The side with the plus has a clearly easier game; the other side has to find accurate moves for a long time.
|
|
98
|
+
- **+0.50 and up**: clear advantage in engine terms, and usually a real practical winning chance.
|
|
99
|
+
- **Stockfish 0.00 with a plus from Lc0**: objectively balanced, but Lc0 expects the side with the plus to play more easily. Report it as "practical edge", not "equal" and not "winning".
|
|
100
|
+
|
|
101
|
+
Never describe a position as equal from a 0.00 or ±0.10 reading alone when the other engine shows a plus. Check both numbers first.
|
|
102
|
+
|
|
92
103
|
## Lc0 contempt
|
|
93
104
|
|
|
94
105
|
Contempt is an Lc0 option that skews its evaluation and move choice toward one side. Passing `contempt` to `cloud_analyse` sets it on the Lc0 leg only; Stockfish always analyzes objectively.
|
package/docs/pgn-authoring.md
CHANGED
|
@@ -326,6 +326,20 @@ If you only quote the Lc0 number, disclose the bias in the same sentence — `{L
|
|
|
326
326
|
|
|
327
327
|
Contempt scale is signed 0-100 (same as the web UI's ContemptStrength slider). Typical: `±10-20` a light nudge, `±30-60` real fighting play, `±80-100` maximum steer.
|
|
328
328
|
|
|
329
|
+
### Claims to verify before you write them
|
|
330
|
+
|
|
331
|
+
Prose errors that reached a real study file (2026-10, London starter, all caught by the owner, not by the model):
|
|
332
|
+
|
|
333
|
+
- **"Wins back a pawn" / "pawn up".** Count the captures on the line before you claim material. A pawn push that the other side simply captures (e.g. `...c3` answered by `bxc3`) does not win anything back. Say what the move is for: activity, a file, a threat.
|
|
334
|
+
- **"Protected" / "defends".** Only say a piece is protected or defended if the recapture or the defending move is actually legal in that position. Check the square, not the idea.
|
|
335
|
+
- **"Blocks" / "stops".** Name the exact line or piece that is blocked. A knight on d2 does not block a queen on b6 from b2 (that is a different file/diagonal). If you can't name the blocked line, don't write it.
|
|
336
|
+
- **"Undefended" / "hangs".** Say which piece left which defending square, and check the other pieces that could still reach that square.
|
|
337
|
+
- **"Gains space".** Only for a move that actually advances or controls more squares. If the pawn is already on the square, say "has more space" or "controls the centre", not "after c4 gains space".
|
|
338
|
+
- **"Forcing" / "only move".** Only if each move in the PV is a check, a capture, or a direct threat. Otherwise say "natural" or "principled".
|
|
339
|
+
- **Engine words.** A number is not equality. Read the eval scale in `engine-usage.md` before writing "equal", "balanced", or "slight plus".
|
|
340
|
+
|
|
341
|
+
If a claim needs a board check, `describe_position` first (below), and drop the claim if the check doesn't support it.
|
|
342
|
+
|
|
329
343
|
### Before you write any commentary: describe_position
|
|
330
344
|
|
|
331
345
|
**This is the biggest lever for prose quality in the whole system.** Live audit of a recent session — 13 `describe_position` calls versus 50+ `set_comment` ops. The nodes where `describe_position` was called first produced comments that grounded specifically in the position (correct piece squares, real pawn structure, actual weak squares). The nodes where it wasn't produced generic prose that pattern-matched to similar-*looking* positions and confidently named pieces on wrong squares. This gap is why `set_comment` now emits a warning whenever a substantive comment (≥40 chars) lands on a node whose position was never grounded via `describe_position` this session.
|
package/package.json
CHANGED