@knights-eye-chess/ai-analysis 0.0.0-stage → 0.1.0-dev.17
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/LICENSE +235 -0
- package/LICENSING.md +26 -0
- package/NOTICE +5 -0
- package/NPM-PUBLISHING.md +28 -0
- package/README.md +139 -2
- package/SOURCE-ORIGIN.json +16 -0
- package/THIRD_PARTY_NOTICES.md +18 -0
- package/build.mjs +22 -0
- package/dist/answer-pipeline.test.mjs +375 -0
- package/dist/chunk-PLKNJRPF.js +10662 -0
- package/dist/chunk-XQDZJOYD.js +2515 -0
- package/dist/index.js +146 -0
- package/dist/owner-tests/chess-reasoning-compact-search-preparation.test.mjs +963 -0
- package/dist/owner-tests/mechanism-encoding.test.mjs +529 -0
- package/dist/owner-tests/move-mechanism-facts.test.mjs +396 -0
- package/dist/owner-tests/pipeline-host-preparation.test.mjs +10556 -0
- package/dist/owner-tests/position-preparation.test.mjs +625 -0
- package/dist/owner-tests/preparation-consolidation.test.mjs +1052 -0
- package/dist/owner-tests/prose-reference-admission.test.mjs +1770 -0
- package/dist/owner-tests/score-comparison-notices.test.mjs +240 -0
- package/dist/owner-tests/simple-engine-presentation.test.mjs +233 -0
- package/dist/owner-tests/tool-input-loop-guard.test.mjs +78 -0
- package/dist/research.js +2274 -0
- package/dist/runtime-closure.json +103 -0
- package/dist/shared-runtime-closure.json +39 -0
- package/dist/shared.js +152 -0
- package/dist/types/ai-request-failure.d.ts +14 -0
- package/dist/types/ai-request-limits.d.ts +4 -0
- package/dist/types/ai-routing-diagnostics.d.ts +14 -0
- package/dist/types/chess-reasoning/ablation-adaptive-evidence-loop.d.ts +127 -0
- package/dist/types/chess-reasoning/ablation-adaptive-runtime.d.ts +178 -0
- package/dist/types/chess-reasoning/ablation-direct-synthesis.d.ts +1030 -0
- package/dist/types/chess-reasoning/ablation-evidence-bundle.d.ts +185 -0
- package/dist/types/chess-reasoning/ablation-evidence-capture.d.ts +37 -0
- package/dist/types/chess-reasoning/ablation-lean-direct-runtime.d.ts +193 -0
- package/dist/types/chess-reasoning/ablation-model-facing-evidence.d.ts +38 -0
- package/dist/types/chess-reasoning/ablation-transport-correction.d.ts +13 -0
- package/dist/types/chess-reasoning/ai-sdk-runtime-support.d.ts +93 -0
- package/dist/types/chess-reasoning/answer-host-contracts.d.ts +59 -0
- package/dist/types/chess-reasoning/answer-model-connector.d.ts +68 -0
- package/dist/types/chess-reasoning/answer-pipeline-exports.d.ts +98 -0
- package/dist/types/chess-reasoning/answer-pipeline.d.ts +41 -0
- package/dist/types/chess-reasoning/answer-reference-validation.d.ts +40 -0
- package/dist/types/chess-reasoning/answer-tool-host.d.ts +8 -0
- package/dist/types/chess-reasoning/branch-expansion-policy.d.ts +13 -0
- package/dist/types/chess-reasoning/branch-expansion.d.ts +39 -0
- package/dist/types/chess-reasoning/causal-claim-checks.d.ts +21 -0
- package/dist/types/chess-reasoning/chess-reference-dictionary.d.ts +13 -0
- package/dist/types/chess-reasoning/classification-question-target.d.ts +22 -0
- package/dist/types/chess-reasoning/classification-reassessment.d.ts +31 -0
- package/dist/types/chess-reasoning/compact-search-preparation.d.ts +7 -0
- package/dist/types/chess-reasoning/concept-registry.d.ts +99 -0
- package/dist/types/chess-reasoning/conversation-context.d.ts +12 -0
- package/dist/types/chess-reasoning/engine-search-failure.d.ts +7 -0
- package/dist/types/chess-reasoning/evidence-citation-normalization.d.ts +9 -0
- package/dist/types/chess-reasoning/evidence-licenses.d.ts +57 -0
- package/dist/types/chess-reasoning/evidence-selector-contract.d.ts +5 -0
- package/dist/types/chess-reasoning/existing-analysis-context.d.ts +104 -0
- package/dist/types/chess-reasoning/fact-observation-provider.d.ts +15 -0
- package/dist/types/chess-reasoning/fact-provider.d.ts +111 -0
- package/dist/types/chess-reasoning/fresh-engine-pv-witnesses.d.ts +67 -0
- package/dist/types/chess-reasoning/historical-prose.d.ts +23 -0
- package/dist/types/chess-reasoning/idea-graph.d.ts +34 -0
- package/dist/types/chess-reasoning/initial-search-plan.d.ts +37 -0
- package/dist/types/chess-reasoning/lossless-evidence-sharing.d.ts +18 -0
- package/dist/types/chess-reasoning/matched-comparison.d.ts +10 -0
- package/dist/types/chess-reasoning/material-accounting.d.ts +80 -0
- package/dist/types/chess-reasoning/material-format.d.ts +4 -0
- package/dist/types/chess-reasoning/mechanical-fact-provider.d.ts +33 -0
- package/dist/types/chess-reasoning/mechanical-ray-facts.d.ts +19 -0
- package/dist/types/chess-reasoning/mechanism-presentation.d.ts +9 -0
- package/dist/types/chess-reasoning/move-mechanism-facts.d.ts +45 -0
- package/dist/types/chess-reasoning/native-answer-connector.d.ts +4 -0
- package/dist/types/chess-reasoning/opening-source-contracts.d.ts +54 -0
- package/dist/types/chess-reasoning/opening-source-credits.d.ts +16 -0
- package/dist/types/chess-reasoning/opponent-threat-preparation.d.ts +7 -0
- package/dist/types/chess-reasoning/piece-identities.d.ts +11 -0
- package/dist/types/chess-reasoning/poc-contracts.d.ts +1026 -0
- package/dist/types/chess-reasoning/poc-hypotheses.d.ts +74 -0
- package/dist/types/chess-reasoning/poc-move-witnesses.d.ts +13 -0
- package/dist/types/chess-reasoning/poc-observations.d.ts +23 -0
- package/dist/types/chess-reasoning/poc-piece-refs.d.ts +12 -0
- package/dist/types/chess-reasoning/poc-plan-model.d.ts +45 -0
- package/dist/types/chess-reasoning/poc-semantic-text.d.ts +8 -0
- package/dist/types/chess-reasoning/poc-tools.d.ts +81 -0
- package/dist/types/chess-reasoning/portable-answer-runtime.d.ts +291 -0
- package/dist/types/chess-reasoning/position-analysis-contracts.d.ts +31 -0
- package/dist/types/chess-reasoning/position-analysis-scope.d.ts +3 -0
- package/dist/types/chess-reasoning/preparation-consolidation.d.ts +21 -0
- package/dist/types/chess-reasoning/production-observation-provider.d.ts +16 -0
- package/dist/types/chess-reasoning/question-routing.d.ts +326 -0
- package/dist/types/chess-reasoning/recorded-main-line.d.ts +37 -0
- package/dist/types/chess-reasoning/recorded-position-tools.d.ts +11 -0
- package/dist/types/chess-reasoning/recorded-reference-context.d.ts +13 -0
- package/dist/types/chess-reasoning/recorded-selected-classification.d.ts +36 -0
- package/dist/types/chess-reasoning/reference-limits.d.ts +2 -0
- package/dist/types/chess-reasoning/research-exports.d.ts +197 -0
- package/dist/types/chess-reasoning/saved-score-presentation.d.ts +14 -0
- package/dist/types/chess-reasoning/score-comparison-notices.d.ts +33 -0
- package/dist/types/chess-reasoning/server-simple-policy.d.ts +31 -0
- package/dist/types/chess-reasoning/simple-agent-config.d.ts +73 -0
- package/dist/types/chess-reasoning/simple-board-analysis.d.ts +85 -0
- package/dist/types/chess-reasoning/simple-compact-evidence.d.ts +7 -0
- package/dist/types/chess-reasoning/simple-completion.d.ts +23 -0
- package/dist/types/chess-reasoning/simple-engine-presentation.d.ts +3 -0
- package/dist/types/chess-reasoning/simple-evidence-preparation.d.ts +17 -0
- package/dist/types/chess-reasoning/simple-evidence-view.d.ts +17 -0
- package/dist/types/chess-reasoning/simple-explanation-coverage.d.ts +90 -0
- package/dist/types/chess-reasoning/simple-fact-delivery.d.ts +43 -0
- package/dist/types/chess-reasoning/simple-fallback-premise.d.ts +5 -0
- package/dist/types/chess-reasoning/simple-prose-references.d.ts +108 -0
- package/dist/types/chess-reasoning/simple-runtime-version.d.ts +1 -0
- package/dist/types/chess-reasoning/stable.d.ts +2 -0
- package/dist/types/chess-reasoning/tool-input-loop-guard.d.ts +5 -0
- package/dist/types/chess-reasoning/types.d.ts +180 -0
- package/dist/types/chess-reasoning/visible-support-lines.d.ts +12 -0
- package/dist/types/chess-reasoning/wikibooks-index.d.ts +30 -0
- package/dist/types/chess-reasoning/wikibooks-tool-context.d.ts +10 -0
- package/dist/types/player-perspective.d.ts +16 -0
- package/dist/types/shared-exports.d.ts +22 -0
- package/docs/API.md +82 -0
- package/docs/OWNERSHIP-ADOPTION.md +35 -0
- package/docs/OWNERSHIP-DRIFT.json +55 -0
- package/docs/RESEARCH-API.md +28 -0
- package/licenses/third-party/INVENTORY.json +202 -0
- package/licenses/third-party/ai-7.0.85/LICENSE +13 -0
- package/licenses/third-party/ai-sdk--gateway-4.0.69/LICENSE +13 -0
- package/licenses/third-party/ai-sdk--provider-4.0.9/LICENSE +13 -0
- package/licenses/third-party/ai-sdk--provider-utils-5.0.34/APACHE-2.0.txt +73 -0
- package/licenses/third-party/ai-sdk--provider-utils-5.0.34/src/to-json-schema/zod3-to-json-schema/LICENSE +16 -0
- package/licenses/third-party/chess.js-1.4.0/LICENSE +23 -0
- package/licenses/third-party/eventsource-parser-3.1.1/LICENSE +21 -0
- package/licenses/third-party/json-schema-0.4.0/LICENSE +195 -0
- package/licenses/third-party/standard-schema--spec-1.1.0/LICENSE +21 -0
- package/licenses/third-party/types--json-schema-7.0.15/LICENSE +21 -0
- package/licenses/third-party/types--node-22.19.19/LICENSE +21 -0
- package/licenses/third-party/undici-7.30.0/LICENSE +21 -0
- package/licenses/third-party/undici-7.30.0/lib/web/fetch/LICENSE +21 -0
- package/licenses/third-party/undici-types-6.21.0/LICENSE +21 -0
- package/licenses/third-party/vercel--oidc-3.2.0/LICENSE +202 -0
- package/licenses/third-party/workflow--serde-4.1.0/APACHE-2.0.txt +73 -0
- package/licenses/third-party/workflow--serde-4.1.0/LICENSE.md +3 -0
- package/licenses/third-party/zod-4.6.5/LICENSE +21 -0
- package/npm-release.json +5 -0
- package/package.json +72 -4
- package/scripts/check-release-tooling.mjs +22 -0
- package/scripts/pack.mjs +68 -0
- package/scripts/publish-reviewed.mjs +85 -0
- package/scripts/release-tooling.json +13 -0
- package/scripts/update-dependency.mjs +44 -0
- package/scripts/verify-release.mjs +52 -0
- package/src/ai-request-failure.ts +34 -0
- package/src/ai-request-limits.ts +4 -0
- package/src/ai-routing-diagnostics.ts +33 -0
- package/src/chess-reasoning/ablation-adaptive-evidence-loop.ts +515 -0
- package/src/chess-reasoning/ablation-adaptive-runtime.ts +924 -0
- package/src/chess-reasoning/ablation-direct-synthesis.ts +1128 -0
- package/src/chess-reasoning/ablation-evidence-bundle.ts +726 -0
- package/src/chess-reasoning/ablation-evidence-capture.ts +513 -0
- package/src/chess-reasoning/ablation-lean-direct-runtime.ts +670 -0
- package/src/chess-reasoning/ablation-model-facing-evidence.ts +212 -0
- package/src/chess-reasoning/ablation-transport-correction.ts +106 -0
- package/src/chess-reasoning/ai-sdk-runtime-support.ts +381 -0
- package/src/chess-reasoning/answer-host-contracts.ts +10 -0
- package/src/chess-reasoning/answer-model-connector.ts +83 -0
- package/src/chess-reasoning/answer-pipeline-exports.ts +98 -0
- package/src/chess-reasoning/answer-pipeline.ts +195 -0
- package/src/chess-reasoning/answer-reference-validation.ts +124 -0
- package/src/chess-reasoning/answer-tool-host.ts +10 -0
- package/src/chess-reasoning/branch-expansion-policy.ts +32 -0
- package/src/chess-reasoning/branch-expansion.ts +80 -0
- package/src/chess-reasoning/causal-claim-checks.ts +256 -0
- package/src/chess-reasoning/chess-reference-dictionary.ts +21 -0
- package/src/chess-reasoning/classification-question-target.ts +30 -0
- package/src/chess-reasoning/classification-reassessment.ts +34 -0
- package/src/chess-reasoning/compact-search-preparation.ts +125 -0
- package/src/chess-reasoning/concept-registry.ts +275 -0
- package/src/chess-reasoning/conversation-context.ts +29 -0
- package/src/chess-reasoning/engine-search-failure.ts +11 -0
- package/src/chess-reasoning/evidence-citation-normalization.ts +15 -0
- package/src/chess-reasoning/evidence-licenses.ts +166 -0
- package/src/chess-reasoning/evidence-selector-contract.ts +5 -0
- package/src/chess-reasoning/existing-analysis-context.ts +544 -0
- package/src/chess-reasoning/fact-observation-provider.ts +134 -0
- package/src/chess-reasoning/fact-provider.ts +250 -0
- package/src/chess-reasoning/fresh-engine-pv-witnesses.ts +128 -0
- package/src/chess-reasoning/historical-prose.ts +116 -0
- package/src/chess-reasoning/idea-graph.ts +224 -0
- package/src/chess-reasoning/initial-search-plan.ts +115 -0
- package/src/chess-reasoning/lossless-evidence-sharing.ts +135 -0
- package/src/chess-reasoning/matched-comparison.ts +40 -0
- package/src/chess-reasoning/material-accounting.ts +55 -0
- package/src/chess-reasoning/material-format.ts +47 -0
- package/src/chess-reasoning/mechanical-fact-provider.ts +1602 -0
- package/src/chess-reasoning/mechanical-ray-facts.ts +123 -0
- package/src/chess-reasoning/mechanism-presentation.ts +94 -0
- package/src/chess-reasoning/move-mechanism-facts.ts +185 -0
- package/src/chess-reasoning/native-answer-connector.ts +13 -0
- package/src/chess-reasoning/opening-source-contracts.ts +47 -0
- package/src/chess-reasoning/opening-source-credits.ts +53 -0
- package/src/chess-reasoning/opponent-threat-preparation.ts +98 -0
- package/src/chess-reasoning/piece-identities.ts +159 -0
- package/src/chess-reasoning/poc-contracts.ts +1097 -0
- package/src/chess-reasoning/poc-hypotheses.ts +759 -0
- package/src/chess-reasoning/poc-move-witnesses.ts +170 -0
- package/src/chess-reasoning/poc-observations.ts +611 -0
- package/src/chess-reasoning/poc-piece-refs.ts +62 -0
- package/src/chess-reasoning/poc-plan-model.ts +345 -0
- package/src/chess-reasoning/poc-semantic-text.ts +10 -0
- package/src/chess-reasoning/poc-tools.ts +978 -0
- package/src/chess-reasoning/portable-answer-runtime.ts +684 -0
- package/src/chess-reasoning/position-analysis-contracts.ts +88 -0
- package/src/chess-reasoning/position-analysis-scope.ts +20 -0
- package/src/chess-reasoning/preparation-consolidation.ts +91 -0
- package/src/chess-reasoning/production-observation-provider.ts +41 -0
- package/src/chess-reasoning/question-routing.ts +332 -0
- package/src/chess-reasoning/recorded-main-line.ts +55 -0
- package/src/chess-reasoning/recorded-position-tools.ts +78 -0
- package/src/chess-reasoning/recorded-reference-context.ts +44 -0
- package/src/chess-reasoning/recorded-selected-classification.ts +54 -0
- package/src/chess-reasoning/reference-limits.ts +2 -0
- package/src/chess-reasoning/research-exports.ts +197 -0
- package/src/chess-reasoning/saved-score-presentation.ts +6 -0
- package/src/chess-reasoning/score-comparison-notices.ts +115 -0
- package/src/chess-reasoning/server-simple-policy.ts +75 -0
- package/src/chess-reasoning/simple-agent-config.ts +58 -0
- package/src/chess-reasoning/simple-board-analysis.ts +57 -0
- package/src/chess-reasoning/simple-compact-evidence.ts +79 -0
- package/src/chess-reasoning/simple-completion.ts +37 -0
- package/src/chess-reasoning/simple-engine-presentation.ts +95 -0
- package/src/chess-reasoning/simple-evidence-preparation.ts +32 -0
- package/src/chess-reasoning/simple-evidence-view.ts +69 -0
- package/src/chess-reasoning/simple-explanation-coverage.ts +118 -0
- package/src/chess-reasoning/simple-fact-delivery.ts +52 -0
- package/src/chess-reasoning/simple-fallback-premise.ts +65 -0
- package/src/chess-reasoning/simple-prose-references.ts +528 -0
- package/src/chess-reasoning/simple-runtime-version.ts +2 -0
- package/src/chess-reasoning/stable.ts +18 -0
- package/src/chess-reasoning/tool-input-loop-guard.ts +38 -0
- package/src/chess-reasoning/types.ts +195 -0
- package/src/chess-reasoning/visible-support-lines.ts +44 -0
- package/src/chess-reasoning/wikibooks-index.ts +56 -0
- package/src/chess-reasoning/wikibooks-tool-context.ts +91 -0
- package/src/player-perspective.ts +41 -0
- package/src/shared-exports.ts +23 -0
- package/test/answer-pipeline.test.mjs +375 -0
- package/test/owner/chess-reasoning-compact-search-preparation.test.mjs +111 -0
- package/test/owner/mechanism-encoding.test.mjs +78 -0
- package/test/owner/move-mechanism-facts.test.mjs +105 -0
- package/test/owner/pipeline-host-preparation.test.mjs +115 -0
- package/test/owner/position-preparation.test.mjs +22 -0
- package/test/owner/preparation-consolidation.test.mjs +74 -0
- package/test/owner/prose-reference-admission.test.mjs +26 -0
- package/test/owner/score-comparison-notices.test.mjs +116 -0
- package/test/owner/simple-engine-presentation.test.mjs +50 -0
- package/test/owner/tool-input-loop-guard.test.mjs +25 -0
- package/test-types.mts +23 -0
- package/tsconfig.json +22 -0
|
@@ -0,0 +1,1052 @@
|
|
|
1
|
+
// test/owner/preparation-consolidation.test.mjs
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import assert from "node:assert/strict";
|
|
4
|
+
|
|
5
|
+
// src/chess-reasoning/mechanism-presentation.ts
|
|
6
|
+
var object = (v) => !!v && typeof v === "object" && !Array.isArray(v);
|
|
7
|
+
var COLUMNS = {
|
|
8
|
+
mover: ["pieceId", "pieceIdAfter", "fromSquare", "toSquare", "color", "pieceTypeBefore", "pieceTypeAfter", "attackersBefore", "attackersAfter"],
|
|
9
|
+
unmoved: ["pieceId", "square", "pieceType", "attackersBefore", "attackersAfter"],
|
|
10
|
+
rays: ["pieceId", "fromSquare", "color", "pieceType", "direction", "gainedSquares", "lostSquares"],
|
|
11
|
+
control: ["square", "color", "attackersBefore", "attackersAfter"]
|
|
12
|
+
};
|
|
13
|
+
function exactKeys(value, columns) {
|
|
14
|
+
return object(value) && Object.keys(value).length === columns.length && columns.every((column) => Object.hasOwn(value, column));
|
|
15
|
+
}
|
|
16
|
+
function encodeRows(value, columns) {
|
|
17
|
+
return columns.map((column) => value[column]);
|
|
18
|
+
}
|
|
19
|
+
function compactMechanism(value, targetSquares) {
|
|
20
|
+
if (!object(value) || !Array.isArray(value.controlDelta)) return value;
|
|
21
|
+
const targets = [...new Set(targetSquares)].filter((s) => /^[a-h][1-8]$/.test(s));
|
|
22
|
+
const controlDelta = value.controlDelta.filter((d) => object(d) && targets.includes(String(d.square)));
|
|
23
|
+
const rays = Array.isArray(value.stationarySliderRays) ? value.stationarySliderRays.map((ray) => {
|
|
24
|
+
if (!object(ray)) return ray;
|
|
25
|
+
const { beforeSquares: _before, afterSquares: _after, ...changes } = ray;
|
|
26
|
+
void _before;
|
|
27
|
+
void _after;
|
|
28
|
+
return changes;
|
|
29
|
+
}) : value.stationarySliderRays;
|
|
30
|
+
const projected = {
|
|
31
|
+
...value,
|
|
32
|
+
stationarySliderRays: rays,
|
|
33
|
+
controlDelta,
|
|
34
|
+
presentation: {
|
|
35
|
+
controlSquares: targets,
|
|
36
|
+
omittedControlDeltaCount: value.controlDelta.length - controlDelta.length,
|
|
37
|
+
scope: "All changed stationary rays and mover/unmoved attack measurements retained. Control deltas shown only for controlSquares; an omitted square is not checked-negative in this view. Full ray extents and all control deltas are retrievable."
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
if (!exactKeys(value, ["mover", "attackedUnmovedFriendlyPieces", "stationarySliderRays", "controlDelta", "checksRun", "scope"]) || !exactKeys(projected.mover, COLUMNS.mover) || !Array.isArray(projected.attackedUnmovedFriendlyPieces) || !projected.attackedUnmovedFriendlyPieces.every((item) => exactKeys(item, COLUMNS.unmoved)) || !Array.isArray(rays) || !rays.every((item) => exactKeys(item, COLUMNS.rays)) || !controlDelta.every((item) => exactKeys(item, COLUMNS.control))) return projected;
|
|
41
|
+
return {
|
|
42
|
+
encoding: "move-mechanism/1",
|
|
43
|
+
columns: COLUMNS,
|
|
44
|
+
rows: {
|
|
45
|
+
mover: encodeRows(projected.mover, COLUMNS.mover),
|
|
46
|
+
unmoved: projected.attackedUnmovedFriendlyPieces.map((item) => encodeRows(item, COLUMNS.unmoved)),
|
|
47
|
+
rays: rays.map((item) => encodeRows(item, COLUMNS.rays)),
|
|
48
|
+
control: controlDelta.map((item) => encodeRows(item, COLUMNS.control))
|
|
49
|
+
},
|
|
50
|
+
checksRun: projected.checksRun,
|
|
51
|
+
scope: projected.scope,
|
|
52
|
+
presentation: projected.presentation
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
function presentMoveMechanisms(moves, inspectPlies = [], inspectSquares = []) {
|
|
56
|
+
return moves.flatMap((fact, index) => {
|
|
57
|
+
if (!object(fact) || !object(fact.mechanism)) return [];
|
|
58
|
+
if (index >= 2 && !inspectPlies.includes(index + 1) && !inspectSquares.includes(String(fact.to))) return [];
|
|
59
|
+
const targets = [...inspectSquares, ...moves.slice(index, index + 3).flatMap((m) => object(m) && typeof m.to === "string" ? [m.to] : [])];
|
|
60
|
+
return [{ ply: index + 1, uci: fact.uci, san: fact.san, mechanism: compactMechanism(fact.mechanism, targets) }];
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// src/chess-reasoning/compact-search-preparation.ts
|
|
65
|
+
import { Chess as Chess9 } from "chess.js";
|
|
66
|
+
|
|
67
|
+
// src/chess-reasoning/simple-engine-presentation.ts
|
|
68
|
+
import { Chess } from "chess.js";
|
|
69
|
+
var object2 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
|
|
70
|
+
function projectEnginePresentation(value, fullGeometry = false) {
|
|
71
|
+
const visit = (v) => {
|
|
72
|
+
if (Array.isArray(v)) return v.map(visit);
|
|
73
|
+
if (!object2(v)) return v;
|
|
74
|
+
if (typeof v.id !== "string" || !object2(v.data)) {
|
|
75
|
+
return Object.fromEntries(Object.entries(v).map(([k, child]) => [k, visit(child)]));
|
|
76
|
+
}
|
|
77
|
+
const nested = object2(v.data.details) && (Array.isArray(v.data.details.lines) || Array.isArray(v.data.details.moveFacts));
|
|
78
|
+
const d = nested ? v.data.details : v.data;
|
|
79
|
+
if (Object.hasOwn(d, "modelPresentation")) return v;
|
|
80
|
+
if (object2(d.selectedMoveMechanism) && Array.isArray(d.selectedMoveMechanism.controlDelta) && !fullGeometry) {
|
|
81
|
+
const squares = d.selectedMoveMechanism.controlDelta.flatMap((delta) => object2(delta) && typeof delta.square === "string" ? [delta.square] : []);
|
|
82
|
+
const projected2 = { ...d, selectedMoveMechanism: compactMechanism(d.selectedMoveMechanism, squares) };
|
|
83
|
+
return { ...v, data: nested ? { ...v.data, details: projected2 } : projected2 };
|
|
84
|
+
}
|
|
85
|
+
if (Array.isArray(d.moveFacts) && !fullGeometry) {
|
|
86
|
+
const inspection = object2(d.mechanismInspection) ? d.mechanismInspection : {};
|
|
87
|
+
const { moveFacts, ...rest } = d;
|
|
88
|
+
const projected2 = {
|
|
89
|
+
...rest,
|
|
90
|
+
mechanismFacts: presentMoveMechanisms(
|
|
91
|
+
moveFacts,
|
|
92
|
+
Array.isArray(inspection.inspectPlies) ? inspection.inspectPlies : [],
|
|
93
|
+
Array.isArray(inspection.inspectSquares) ? inspection.inspectSquares : []
|
|
94
|
+
),
|
|
95
|
+
modelPresentation: {
|
|
96
|
+
mechanismThroughPly: 2,
|
|
97
|
+
explicitInspection: inspection,
|
|
98
|
+
retrieveFullEvidenceId: v.id,
|
|
99
|
+
geometryNotice: "Mechanisms shown for first two plies, explicit inspectPlies and moves landing on inspectSquares. Other plies omitted, not absent; retrieve full evidence for all replayed move facts."
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
return { ...v, data: nested ? { ...v.data, details: projected2 } : projected2 };
|
|
103
|
+
}
|
|
104
|
+
if (!Array.isArray(d.lines)) return v;
|
|
105
|
+
const rootFen = object2(d.searchScope) && typeof d.searchScope.fen === "string" ? d.searchScope.fen : void 0;
|
|
106
|
+
let omittedGeometry = 0, omittedRaw = false, referencedFen = false;
|
|
107
|
+
const lines = d.lines.map((line) => {
|
|
108
|
+
if (!object2(line) || !Array.isArray(line.pv)) return line;
|
|
109
|
+
const next = { ...line };
|
|
110
|
+
if (!Object.hasOwn(line, "rawPvSameAsPv") && Array.isArray(line.rawPv) && JSON.stringify(line.rawPv) === JSON.stringify(line.pv)) {
|
|
111
|
+
delete next.rawPv;
|
|
112
|
+
next.rawPvSameAsPv = true;
|
|
113
|
+
omittedRaw = true;
|
|
114
|
+
}
|
|
115
|
+
if (rootFen && object2(line.pvCompletion) && Array.isArray(line.pvCompletion.segments)) {
|
|
116
|
+
const fens = [];
|
|
117
|
+
try {
|
|
118
|
+
const chess = new Chess(rootFen);
|
|
119
|
+
fens.push(chess.fen());
|
|
120
|
+
for (const move of line.pv) {
|
|
121
|
+
if (typeof move !== "string" || !/^[a-h][1-8][a-h][1-8][qrbn]?$/.test(move)) break;
|
|
122
|
+
chess.move({ from: move.slice(0, 2), to: move.slice(2, 4), promotion: move.slice(4) || void 0 });
|
|
123
|
+
fens.push(chess.fen());
|
|
124
|
+
}
|
|
125
|
+
} catch {
|
|
126
|
+
}
|
|
127
|
+
next.pvCompletion = { ...line.pvCompletion, segments: line.pvCompletion.segments.map((segment) => {
|
|
128
|
+
if (!object2(segment) || !Number.isInteger(segment.startPly) || segment.startPly < 0 || typeof segment.fen !== "string" || fens[segment.startPly] !== segment.fen || Object.hasOwn(segment, "fenFromLinePvPly")) return segment;
|
|
129
|
+
const { fen: _fen, ...rest } = segment;
|
|
130
|
+
void _fen;
|
|
131
|
+
referencedFen = true;
|
|
132
|
+
return { ...rest, fenFromLinePvPly: segment.startPly };
|
|
133
|
+
}) };
|
|
134
|
+
}
|
|
135
|
+
return next;
|
|
136
|
+
});
|
|
137
|
+
const continuations = !fullGeometry && Array.isArray(d.continuationMoveFacts) ? d.continuationMoveFacts.map((c) => {
|
|
138
|
+
if (!object2(c) || !Array.isArray(c.moves)) return c;
|
|
139
|
+
const compact = presentMoveMechanisms(c.moves);
|
|
140
|
+
return { ...c, moves: c.moves.map((m, i) => {
|
|
141
|
+
if (!object2(m)) return m;
|
|
142
|
+
const next = { ...m };
|
|
143
|
+
if (i >= 2 && Object.hasOwn(next, "captureGeometry")) {
|
|
144
|
+
delete next.captureGeometry;
|
|
145
|
+
omittedGeometry++;
|
|
146
|
+
}
|
|
147
|
+
if (Object.hasOwn(next, "mechanism")) {
|
|
148
|
+
const delivered = compact.find((f) => object2(f) && f.ply === i + 1);
|
|
149
|
+
if (object2(delivered)) next.mechanism = delivered.mechanism;
|
|
150
|
+
else {
|
|
151
|
+
delete next.mechanism;
|
|
152
|
+
omittedGeometry++;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return next;
|
|
156
|
+
}) };
|
|
157
|
+
}) : d.continuationMoveFacts;
|
|
158
|
+
const roots = !fullGeometry && Array.isArray(d.rootMoveFacts) ? d.rootMoveFacts.map((f) => !object2(f) || !f.mechanism ? f : { ...f, mechanism: compactMechanism(f.mechanism, [String(f.to)]) }) : d.rootMoveFacts;
|
|
159
|
+
if (!omittedGeometry && !omittedRaw && !referencedFen && !Array.isArray(d.rootMoveFacts)) return v;
|
|
160
|
+
const projected = {
|
|
161
|
+
...d,
|
|
162
|
+
lines,
|
|
163
|
+
...roots !== void 0 ? { rootMoveFacts: roots } : {},
|
|
164
|
+
...d.continuationMoveFacts !== void 0 ? { continuationMoveFacts: continuations } : {},
|
|
165
|
+
modelPresentation: {
|
|
166
|
+
...omittedGeometry ? {
|
|
167
|
+
captureGeometryThroughPly: 2,
|
|
168
|
+
omittedCaptureGeometry: omittedGeometry,
|
|
169
|
+
retrieveFullEvidenceId: v.id,
|
|
170
|
+
geometryNotice: "Deeper capture and mechanism geometry omitted, not absent. Retrieve this evidence ID for full geometry; basic move and capture facts remain complete."
|
|
171
|
+
} : {},
|
|
172
|
+
...referencedFen ? { segmentFenReference: "fenFromLinePvPly=N is the exact position after N moves of this line.pv, legally replayed from this engine result\u2019s searchScope.fen. Segment scores, depth and search provenance still apply only to that segment." } : {}
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
return { ...v, data: nested ? { ...v.data, details: projected } : projected };
|
|
176
|
+
};
|
|
177
|
+
return visit(value);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// src/chess-reasoning/material-format.ts
|
|
181
|
+
var DEFAULT_MATERIAL_FORMAT = "hybrid";
|
|
182
|
+
var order = ["pawn", "knight", "bishop", "rook", "queen"];
|
|
183
|
+
var obj = (v) => !!v && typeof v === "object" && !Array.isArray(v);
|
|
184
|
+
var keys = (v, expected) => Object.keys(v).every((k) => expected.includes(k));
|
|
185
|
+
var counts = (v) => obj(v) && keys(v, [...order]) && order.every((k) => Number.isInteger(v[k]));
|
|
186
|
+
var snapshot = (v) => obj(v) && keys(v, ["white", "black", "whiteMinusBlack", "balance"]) && counts(v.white) && counts(v.black) && counts(v.whiteMinusBlack) && typeof v.balance === "string";
|
|
187
|
+
var row = (v) => order.map((k) => v[k]);
|
|
188
|
+
var inventory = (v) => order.map((k) => `${v[k]} ${k}${v[k] === 1 ? "" : "s"}`).join(", ");
|
|
189
|
+
var textSnapshot = (v) => `White: ${inventory(v.white)}. Black: ${inventory(v.black)}. ${v.balance}`;
|
|
190
|
+
var hybridSnapshot = (v) => ({ white: row(v.white), black: row(v.black), balance: v.balance });
|
|
191
|
+
function formattedMaterial(v, format) {
|
|
192
|
+
if (snapshot(v)) return format === "text" ? { encoding: "material-text/1", position: textSnapshot(v) } : { encoding: "material-table/1", pieceOrder: order.join(" "), ...hybridSnapshot(v) };
|
|
193
|
+
if (!obj(v) || v.authority !== "legal-replay-piece-counts" || !keys(v, ["authority", "startFen", "plies", "start", "changes", "endFen", "end", "balanceChangeFromStart", "scope"]) || typeof v.startFen !== "string" || typeof v.endFen !== "string" || !Number.isInteger(v.plies) || !snapshot(v.start) || !snapshot(v.end) || !counts(v.balanceChangeFromStart) || typeof v.scope !== "string" || !Array.isArray(v.changes) || v.changes.some((c) => !obj(c) || !keys(c, ["ply", "san", "whiteMinusBlack", "balance"]) || !Number.isInteger(c.ply) || typeof c.san !== "string" || !counts(c.whiteMinusBlack) || typeof c.balance !== "string")) return v;
|
|
194
|
+
const balanceChangeFromStart = v.balanceChangeFromStart;
|
|
195
|
+
const shared = { authority: v.authority, startFen: v.startFen, plies: v.plies, endFen: v.endFen, scope: v.scope };
|
|
196
|
+
if (format === "hybrid") return {
|
|
197
|
+
encoding: "material-table/1",
|
|
198
|
+
...shared,
|
|
199
|
+
pieceOrder: order.join(" "),
|
|
200
|
+
start: hybridSnapshot(v.start),
|
|
201
|
+
checkpointColumns: "ply san pawn knight bishop rook queen; absolute White minus Black",
|
|
202
|
+
checkpoints: v.changes.map((c) => [c.ply, c.san, ...row(c.whiteMinusBlack)]),
|
|
203
|
+
end: hybridSnapshot(v.end),
|
|
204
|
+
balanceChangeFromStart: row(v.balanceChangeFromStart)
|
|
205
|
+
};
|
|
206
|
+
return {
|
|
207
|
+
encoding: "material-text/1",
|
|
208
|
+
...shared,
|
|
209
|
+
convention: "Balances list all differences; unmentioned piece types have equal counts.",
|
|
210
|
+
start: textSnapshot(v.start),
|
|
211
|
+
checkpoints: Object.fromEntries(v.changes.map((c) => [`After ply ${c.ply} (${c.san})`, c.balance])),
|
|
212
|
+
end: textSnapshot(v.end),
|
|
213
|
+
balanceChangeFromStart: `Change only, White minus Black: ${order.map((k) => `${k} ${balanceChangeFromStart[k] >= 0 ? "+" : ""}${balanceChangeFromStart[k]}`).join(", ")}.`
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
function projectMaterial(value, format = DEFAULT_MATERIAL_FORMAT) {
|
|
217
|
+
if (!["raw", "hybrid", "text"].includes(format)) throw Error("invalid-material-format");
|
|
218
|
+
if (format === "raw") return value;
|
|
219
|
+
const visit = (v) => Array.isArray(v) ? v.map(visit) : obj(v) ? Object.fromEntries(Object.entries(v).map(([k, x]) => [k, k === "material" ? formattedMaterial(x, format) : visit(x)])) : v;
|
|
220
|
+
return visit(value);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// src/chess-reasoning/material-accounting.ts
|
|
224
|
+
import { Chess as Chess2 } from "chess.js";
|
|
225
|
+
|
|
226
|
+
// src/chess-reasoning/simple-evidence-view.ts
|
|
227
|
+
import { Chess as Chess8 } from "chess.js";
|
|
228
|
+
|
|
229
|
+
// src/chess-reasoning/poc-contracts.ts
|
|
230
|
+
import { Chess as Chess6 } from "chess.js";
|
|
231
|
+
|
|
232
|
+
// src/chess-reasoning/poc-piece-refs.ts
|
|
233
|
+
import { Chess as Chess4, SQUARES as SQUARES2 } from "chess.js";
|
|
234
|
+
|
|
235
|
+
// src/chess-reasoning/piece-identities.ts
|
|
236
|
+
import { Chess as Chess3, SQUARES } from "chess.js";
|
|
237
|
+
|
|
238
|
+
// src/chess-reasoning/move-mechanism-facts.ts
|
|
239
|
+
import { Chess as Chess5, SQUARES as SQUARES3 } from "chess.js";
|
|
240
|
+
|
|
241
|
+
// src/chess-reasoning/evidence-licenses.ts
|
|
242
|
+
var EVIDENCE_LICENSE = Object.freeze({
|
|
243
|
+
CHESS_LEGALITY: "chess.legality",
|
|
244
|
+
CHESS_IDENTITY: "chess.identity",
|
|
245
|
+
CHESS_POSITION_FACT: "chess.position-fact",
|
|
246
|
+
ENGINE_BOUNDED_CONTINUATION: "engine.bounded-continuation",
|
|
247
|
+
ENGINE_BOUNDED_COMPARISON: "engine.bounded-comparison",
|
|
248
|
+
ENGINE_BOUNDED_SCORE: "engine.bounded-score",
|
|
249
|
+
ENGINE_BOUNDED_RESPONSE_COVERAGE: "engine.bounded-response-coverage",
|
|
250
|
+
COVERAGE_EXHAUSTIVE: "coverage.exhaustive",
|
|
251
|
+
ATTENTION_AFFORDANCE: "attention.affordance",
|
|
252
|
+
SEMANTIC_REVIEWED_STATE: "semantic.reviewed-state",
|
|
253
|
+
WORKFLOW_AUDIT: "workflow.audit"
|
|
254
|
+
});
|
|
255
|
+
var LICENSES = new Set(Object.values(EVIDENCE_LICENSE));
|
|
256
|
+
|
|
257
|
+
// src/chess-reasoning/poc-contracts.ts
|
|
258
|
+
var DEFAULT_REASONING_RUN_CONTROLS = Object.freeze({
|
|
259
|
+
maxModelSteps: 15,
|
|
260
|
+
maxToolCalls: 16,
|
|
261
|
+
maxProbeCalls: 5,
|
|
262
|
+
maxEvidenceRecords: 48,
|
|
263
|
+
maxObservationRecords: 48,
|
|
264
|
+
maxObservationsPresented: 16,
|
|
265
|
+
maxHypotheses: 16,
|
|
266
|
+
maxOutputChars: 2400,
|
|
267
|
+
maxProviderOutputChars: 64e3,
|
|
268
|
+
maxWallTimeMs: 3e4
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
// src/chess-reasoning/ablation-adaptive-evidence-loop.ts
|
|
272
|
+
import { Output, generateText, jsonSchema } from "ai";
|
|
273
|
+
|
|
274
|
+
// src/chess-reasoning/concept-registry.ts
|
|
275
|
+
var ENTITY_TYPES = {
|
|
276
|
+
IDEA: "idea",
|
|
277
|
+
PLAN: "plan",
|
|
278
|
+
ROLE: "role-assignment",
|
|
279
|
+
CONDITION: "condition",
|
|
280
|
+
TRANSFORMATION: "transformation",
|
|
281
|
+
OUTCOME: "outcome"
|
|
282
|
+
};
|
|
283
|
+
var PARTICIPANT_ROLES = {
|
|
284
|
+
ACTOR: "actor",
|
|
285
|
+
SUBJECT: "subject",
|
|
286
|
+
TARGET: "target",
|
|
287
|
+
GOAL: "goal",
|
|
288
|
+
RESOURCE: "resource",
|
|
289
|
+
DEFENDER: "defender",
|
|
290
|
+
DESTINATION: "destination"
|
|
291
|
+
};
|
|
292
|
+
var CONCEPT_KEYS = {
|
|
293
|
+
OFFER: "material.offer",
|
|
294
|
+
MATERIAL_CLAIM: "material.claim",
|
|
295
|
+
FORK: "tactic.fork",
|
|
296
|
+
TRAPPED_TARGET: "tactic.trapped-target",
|
|
297
|
+
PROMOTION: "tactic.promotion",
|
|
298
|
+
MATING_ATTACK: "attack.mating",
|
|
299
|
+
PERPETUAL_CHECK: "attack.perpetual-check",
|
|
300
|
+
POSITIONAL_CLAIM: "position.claim",
|
|
301
|
+
KING_ATTACK_PLAN: "plan.king-attack",
|
|
302
|
+
PIECE_ROLE: "role.piece-function",
|
|
303
|
+
CHANGED_CONSTRAINT: "condition.changed-constraint",
|
|
304
|
+
POSITIONAL_TRANSFORMATION: "position.transformation"
|
|
305
|
+
};
|
|
306
|
+
var COMMON_TERMINAL_STATES = ["converted", "refuted", "abandoned", "expired"];
|
|
307
|
+
function claimLifecycle(capturedBy) {
|
|
308
|
+
return {
|
|
309
|
+
initialState: "created",
|
|
310
|
+
retainedState: "retained",
|
|
311
|
+
terminalStates: [...COMMON_TERMINAL_STATES],
|
|
312
|
+
trackedParticipantRole: PARTICIPANT_ROLES.TARGET,
|
|
313
|
+
expireWhenTrackedUnavailable: true,
|
|
314
|
+
onTrackedCapture: {
|
|
315
|
+
capturedBy,
|
|
316
|
+
matchingState: "converted",
|
|
317
|
+
matchingAnnotation: "converted",
|
|
318
|
+
otherState: "refuted",
|
|
319
|
+
otherAnnotation: "refuted"
|
|
320
|
+
}
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
function createConceptRegistry(definitions) {
|
|
324
|
+
const registry = {};
|
|
325
|
+
for (const definition of definitions) {
|
|
326
|
+
if (!definition.key.trim()) throw new Error("Concept keys must not be empty");
|
|
327
|
+
if (registry[definition.key]) throw new Error(`Duplicate concept key: ${definition.key}`);
|
|
328
|
+
registry[definition.key] = Object.freeze({
|
|
329
|
+
...definition,
|
|
330
|
+
lifecycle: definition.lifecycle ? Object.freeze({
|
|
331
|
+
...definition.lifecycle,
|
|
332
|
+
terminalStates: Object.freeze([...definition.lifecycle.terminalStates]),
|
|
333
|
+
onTrackedCapture: definition.lifecycle.onTrackedCapture ? Object.freeze({ ...definition.lifecycle.onTrackedCapture }) : void 0
|
|
334
|
+
}) : void 0
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
return Object.freeze(registry);
|
|
338
|
+
}
|
|
339
|
+
var DEFAULT_CONCEPT_REGISTRY = createConceptRegistry([
|
|
340
|
+
{
|
|
341
|
+
key: CONCEPT_KEYS.OFFER,
|
|
342
|
+
label: "Material offer",
|
|
343
|
+
category: "tactical",
|
|
344
|
+
description: "A material liability the opponent may choose to accept.",
|
|
345
|
+
defaultEntityType: ENTITY_TYPES.IDEA,
|
|
346
|
+
acceptancePolicy: "material-offer-liability/1",
|
|
347
|
+
lifecycle: claimLifecycle("opponent")
|
|
348
|
+
},
|
|
349
|
+
{
|
|
350
|
+
key: CONCEPT_KEYS.MATERIAL_CLAIM,
|
|
351
|
+
label: "Material claim",
|
|
352
|
+
category: "tactical",
|
|
353
|
+
description: "An evidence-backed entitlement to win material.",
|
|
354
|
+
defaultEntityType: ENTITY_TYPES.IDEA,
|
|
355
|
+
lifecycle: claimLifecycle("owner")
|
|
356
|
+
},
|
|
357
|
+
{
|
|
358
|
+
key: CONCEPT_KEYS.FORK,
|
|
359
|
+
label: "Fork",
|
|
360
|
+
category: "tactical",
|
|
361
|
+
description: "One resource attacks multiple targets.",
|
|
362
|
+
defaultEntityType: ENTITY_TYPES.IDEA,
|
|
363
|
+
lifecycle: claimLifecycle("owner")
|
|
364
|
+
},
|
|
365
|
+
{
|
|
366
|
+
key: CONCEPT_KEYS.TRAPPED_TARGET,
|
|
367
|
+
label: "Trapped target",
|
|
368
|
+
category: "tactical",
|
|
369
|
+
description: "A target lacks an adequate escape or defensive resource.",
|
|
370
|
+
defaultEntityType: ENTITY_TYPES.IDEA,
|
|
371
|
+
lifecycle: claimLifecycle("owner")
|
|
372
|
+
},
|
|
373
|
+
{
|
|
374
|
+
key: CONCEPT_KEYS.PROMOTION,
|
|
375
|
+
label: "Promotion",
|
|
376
|
+
category: "tactical",
|
|
377
|
+
description: "A promotion event or promotion claim.",
|
|
378
|
+
defaultEntityType: ENTITY_TYPES.IDEA
|
|
379
|
+
},
|
|
380
|
+
{
|
|
381
|
+
key: CONCEPT_KEYS.MATING_ATTACK,
|
|
382
|
+
label: "Mating attack",
|
|
383
|
+
category: "tactical",
|
|
384
|
+
description: "A coordinated plan whose concrete target is mate.",
|
|
385
|
+
defaultEntityType: ENTITY_TYPES.PLAN
|
|
386
|
+
},
|
|
387
|
+
{
|
|
388
|
+
key: CONCEPT_KEYS.PERPETUAL_CHECK,
|
|
389
|
+
label: "Perpetual check",
|
|
390
|
+
category: "tactical",
|
|
391
|
+
description: "A claim to a repeatable checking sequence.",
|
|
392
|
+
defaultEntityType: ENTITY_TYPES.IDEA
|
|
393
|
+
},
|
|
394
|
+
{
|
|
395
|
+
key: CONCEPT_KEYS.POSITIONAL_CLAIM,
|
|
396
|
+
label: "Positional claim",
|
|
397
|
+
category: "positional",
|
|
398
|
+
description: "A measured, evidence-bounded positional interpretation.",
|
|
399
|
+
defaultEntityType: ENTITY_TYPES.IDEA
|
|
400
|
+
},
|
|
401
|
+
{
|
|
402
|
+
key: CONCEPT_KEYS.KING_ATTACK_PLAN,
|
|
403
|
+
label: "King attack plan",
|
|
404
|
+
category: "plan",
|
|
405
|
+
description: "A goal-directed sequence intended to attack the king.",
|
|
406
|
+
defaultEntityType: ENTITY_TYPES.PLAN
|
|
407
|
+
},
|
|
408
|
+
{
|
|
409
|
+
key: CONCEPT_KEYS.PIECE_ROLE,
|
|
410
|
+
label: "Piece role",
|
|
411
|
+
category: "role",
|
|
412
|
+
description: "A context-dependent function performed by a piece.",
|
|
413
|
+
defaultEntityType: ENTITY_TYPES.ROLE
|
|
414
|
+
},
|
|
415
|
+
{
|
|
416
|
+
key: CONCEPT_KEYS.CHANGED_CONSTRAINT,
|
|
417
|
+
label: "Changed constraint",
|
|
418
|
+
category: "condition",
|
|
419
|
+
description: "A condition whose importance changed after a move or sequence.",
|
|
420
|
+
defaultEntityType: ENTITY_TYPES.CONDITION
|
|
421
|
+
},
|
|
422
|
+
{
|
|
423
|
+
key: CONCEPT_KEYS.POSITIONAL_TRANSFORMATION,
|
|
424
|
+
label: "Positional transformation",
|
|
425
|
+
category: "transformation",
|
|
426
|
+
description: "A causal change in the meaning of a positional feature.",
|
|
427
|
+
defaultEntityType: ENTITY_TYPES.TRANSFORMATION
|
|
428
|
+
}
|
|
429
|
+
]);
|
|
430
|
+
|
|
431
|
+
// src/chess-reasoning/poc-move-witnesses.ts
|
|
432
|
+
import { Chess as Chess7 } from "chess.js";
|
|
433
|
+
|
|
434
|
+
// src/chess-reasoning/ablation-evidence-bundle.ts
|
|
435
|
+
import { SQUARES as SQUARES4 } from "chess.js";
|
|
436
|
+
var PRECOMMITTED_PROBE_ATTRIBUTION_CATEGORIES = Object.freeze([
|
|
437
|
+
"fact-unavailable",
|
|
438
|
+
"fact-available-not-presented",
|
|
439
|
+
"fact-presented-ignored",
|
|
440
|
+
"fact-recognized-not-probed",
|
|
441
|
+
"hypothesis-formed-later-discarded",
|
|
442
|
+
"correct-graph-poor-rendering",
|
|
443
|
+
"unsupported-claim-admitted",
|
|
444
|
+
"safe-fail-closed-termination"
|
|
445
|
+
]);
|
|
446
|
+
|
|
447
|
+
// src/chess-reasoning/ai-sdk-runtime-support.ts
|
|
448
|
+
import { NoOutputGeneratedError, RetryError } from "ai";
|
|
449
|
+
import { APICallError, EmptyResponseBodyError, NoContentGeneratedError } from "@ai-sdk/provider";
|
|
450
|
+
var encoder = new TextEncoder();
|
|
451
|
+
var OBSERVABLE_TRACE_EXCLUSIONS = Object.freeze([
|
|
452
|
+
"provider credentials and authorization headers",
|
|
453
|
+
"raw HTTP request and response bodies",
|
|
454
|
+
"hidden chain-of-thought and provider reasoning details"
|
|
455
|
+
]);
|
|
456
|
+
|
|
457
|
+
// src/chess-reasoning/ablation-adaptive-evidence-loop.ts
|
|
458
|
+
var ABLATION_ADAPTIVE_EVIDENCE_LOOP_VERSION = "architecture-ablation-adaptive-evidence-loop/2";
|
|
459
|
+
var ADAPTIVE_EVIDENCE_DECISION_SCHEMA = Object.freeze({
|
|
460
|
+
type: "object",
|
|
461
|
+
additionalProperties: false,
|
|
462
|
+
required: ["schemaVersion", "decision"],
|
|
463
|
+
properties: {
|
|
464
|
+
schemaVersion: { const: ABLATION_ADAPTIVE_EVIDENCE_LOOP_VERSION },
|
|
465
|
+
decision: {
|
|
466
|
+
oneOf: [
|
|
467
|
+
{
|
|
468
|
+
type: "object",
|
|
469
|
+
additionalProperties: false,
|
|
470
|
+
required: ["type", "tool", "input", "selectionRationale", "modelStatedRationale"],
|
|
471
|
+
properties: {
|
|
472
|
+
type: { const: "tool" },
|
|
473
|
+
tool: { type: "string", minLength: 1, maxLength: 120 },
|
|
474
|
+
input: { type: "object" },
|
|
475
|
+
selectionRationale: { type: "string", minLength: 1, maxLength: 1e3 },
|
|
476
|
+
modelStatedRationale: { type: "string", minLength: 1, maxLength: 1e3 }
|
|
477
|
+
}
|
|
478
|
+
},
|
|
479
|
+
{
|
|
480
|
+
type: "object",
|
|
481
|
+
additionalProperties: false,
|
|
482
|
+
required: ["type", "rationale", "modelStatedRationale"],
|
|
483
|
+
properties: {
|
|
484
|
+
type: { const: "finish" },
|
|
485
|
+
rationale: { type: "string", minLength: 1, maxLength: 1e3 },
|
|
486
|
+
modelStatedRationale: { type: "string", minLength: 1, maxLength: 1e3 }
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
]
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
// src/chess-reasoning/simple-compact-evidence.ts
|
|
495
|
+
var COMPACT_ENGINE_VERSION = "simple-engine-evidence/1";
|
|
496
|
+
var object3 = (x) => !!x && typeof x === "object" && !Array.isArray(x);
|
|
497
|
+
var uci = /^[a-h][1-8][a-h][1-8][qrbn]?$/;
|
|
498
|
+
function projectCompactEngineEvidence(value) {
|
|
499
|
+
const pieces = {}, pieceIds = /* @__PURE__ */ new Map();
|
|
500
|
+
let changed = false;
|
|
501
|
+
const visit = (x) => {
|
|
502
|
+
if (Array.isArray(x)) {
|
|
503
|
+
const entries = x.map(visit);
|
|
504
|
+
if (entries.length >= 3 && entries.every(object3)) {
|
|
505
|
+
const columns = Object.keys(entries[0]);
|
|
506
|
+
if (columns.length >= 2 && entries.every((e) => JSON.stringify(Object.keys(e)) === JSON.stringify(columns) && columns.every((k) => e[k] !== void 0))) {
|
|
507
|
+
const table = { $table: { columns, rows: entries.map((e) => columns.map((k) => e[k])) } };
|
|
508
|
+
if (JSON.stringify(table).length < JSON.stringify(entries).length) {
|
|
509
|
+
changed = true;
|
|
510
|
+
return table;
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
return entries;
|
|
515
|
+
}
|
|
516
|
+
if (!object3(x)) return x;
|
|
517
|
+
if (typeof x.pieceId === "string" && typeof x.square === "string" && typeof x.color === "string" && typeof x.pieceType === "string") {
|
|
518
|
+
const { square, ...identity } = x;
|
|
519
|
+
const key = JSON.stringify(identity);
|
|
520
|
+
let id = pieceIds.get(key);
|
|
521
|
+
if (!id) {
|
|
522
|
+
id = `p${pieceIds.size}`;
|
|
523
|
+
pieceIds.set(key, id);
|
|
524
|
+
pieces[id] = identity;
|
|
525
|
+
}
|
|
526
|
+
changed = true;
|
|
527
|
+
return { $piece: id, square };
|
|
528
|
+
}
|
|
529
|
+
if (typeof x.uci === "string" && uci.test(x.uci) && typeof x.san === "string" && x.mover && x.from === x.uci.slice(0, 2) && x.to === x.uci.slice(2, 4) && x.capture !== void 0 && x.captureGeometry !== null && x.promotion === (x.uci.slice(4) || null) && x.givesCheck === /[+#]$/.test(x.san)) {
|
|
530
|
+
const { uci: move, san, mover, capture, captureGeometry, from: _from, to: _to, promotion: _promotion, givesCheck: _check, ...extra } = x;
|
|
531
|
+
void _from;
|
|
532
|
+
void _to;
|
|
533
|
+
void _promotion;
|
|
534
|
+
void _check;
|
|
535
|
+
const row2 = [move, san, visit(mover), visit(capture)];
|
|
536
|
+
if (captureGeometry !== void 0 || Object.keys(extra).length) row2.push(visit(captureGeometry ?? null));
|
|
537
|
+
if (Object.keys(extra).length) row2.push(visit(extra));
|
|
538
|
+
while (row2.length > 3 && row2.at(-1) === null) row2.pop();
|
|
539
|
+
changed = true;
|
|
540
|
+
return { $move: row2 };
|
|
541
|
+
}
|
|
542
|
+
return Object.fromEntries(Object.entries(x).map(([k, v]) => [k, visit(v)]));
|
|
543
|
+
};
|
|
544
|
+
const reserved = (x) => Array.isArray(x) ? x.some(reserved) : object3(x) ? Object.keys(x).some((k) => ["$piece", "$move", "$table"].includes(k)) || Object.values(x).some(reserved) : false;
|
|
545
|
+
if (reserved(value)) return value;
|
|
546
|
+
const content = visit(value);
|
|
547
|
+
return changed ? { encoding: COMPACT_ENGINE_VERSION, pieces, content } : value;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
// src/chess-reasoning/simple-evidence-view.ts
|
|
551
|
+
function compactSimpleEngineResult(value, format = DEFAULT_MATERIAL_FORMAT, fullGeometry = false) {
|
|
552
|
+
value = projectMaterial(projectEnginePresentation(value, fullGeometry), format);
|
|
553
|
+
const baseline = compactSimpleToolResult(value);
|
|
554
|
+
const candidate = compactSimpleToolResult(projectCompactEngineEvidence(value));
|
|
555
|
+
return JSON.stringify(candidate).length < JSON.stringify(baseline).length ? candidate : baseline;
|
|
556
|
+
}
|
|
557
|
+
function compactSimpleToolResult(value) {
|
|
558
|
+
const counts2 = /* @__PURE__ */ new Map();
|
|
559
|
+
let reservedKeyPresent = false;
|
|
560
|
+
const visit = (item, replace) => {
|
|
561
|
+
if (!item || typeof item !== "object") return item;
|
|
562
|
+
const encoded = JSON.stringify(item);
|
|
563
|
+
if (Object.hasOwn(item, "$metadata")) reservedKeyPresent = true;
|
|
564
|
+
if (encoded.length >= 80) {
|
|
565
|
+
if (!replace) counts2.set(encoded, (counts2.get(encoded) || 0) + 1);
|
|
566
|
+
else if ((counts2.get(encoded) || 0) > 1) {
|
|
567
|
+
let id = ids.get(encoded);
|
|
568
|
+
if (!id) {
|
|
569
|
+
id = `m${ids.size}`;
|
|
570
|
+
ids.set(encoded, id);
|
|
571
|
+
metadata[id] = item;
|
|
572
|
+
}
|
|
573
|
+
return { $metadata: id };
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
return Array.isArray(item) ? item.map((entry2) => visit(entry2, replace)) : Object.fromEntries(Object.entries(item).map(([childKey, entry2]) => [childKey, visit(entry2, replace)]));
|
|
577
|
+
};
|
|
578
|
+
const ids = /* @__PURE__ */ new Map(), metadata = {};
|
|
579
|
+
visit(value, false);
|
|
580
|
+
if (reservedKeyPresent) return value;
|
|
581
|
+
const content = visit(value, true);
|
|
582
|
+
const compact = { metadata, content };
|
|
583
|
+
return ids.size && JSON.stringify(compact).length < JSON.stringify(value).length ? compact : value;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
// src/chess-reasoning/compact-search-preparation.ts
|
|
587
|
+
var object4 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
588
|
+
var known = (value, keys2) => Object.keys(value).every((key) => keys2.includes(key));
|
|
589
|
+
var detailsKeys = [
|
|
590
|
+
"phase",
|
|
591
|
+
"rootMoves",
|
|
592
|
+
"rootSelection",
|
|
593
|
+
"bestMove",
|
|
594
|
+
"engine",
|
|
595
|
+
"engineBuild",
|
|
596
|
+
"searchScope",
|
|
597
|
+
"rootMoveFacts",
|
|
598
|
+
"continuationMoveFacts",
|
|
599
|
+
"lines",
|
|
600
|
+
"minimumPvPlies",
|
|
601
|
+
"requestedPvCoverage",
|
|
602
|
+
"authority",
|
|
603
|
+
"branchExpansion"
|
|
604
|
+
];
|
|
605
|
+
var lineKeys = [
|
|
606
|
+
"multipv",
|
|
607
|
+
"rootMove",
|
|
608
|
+
"pv",
|
|
609
|
+
"depth",
|
|
610
|
+
"nodes",
|
|
611
|
+
"scoreCp",
|
|
612
|
+
"matePly",
|
|
613
|
+
"scoreScope",
|
|
614
|
+
"scoreProvenance",
|
|
615
|
+
"rawPv",
|
|
616
|
+
"pvCompletion",
|
|
617
|
+
"material"
|
|
618
|
+
];
|
|
619
|
+
var completionKeys = ["requestedPlies", "actualPlies", "status", "conditional", "segments"];
|
|
620
|
+
var segmentKeys = [
|
|
621
|
+
"fen",
|
|
622
|
+
"startPly",
|
|
623
|
+
"depth",
|
|
624
|
+
"pv",
|
|
625
|
+
"scoreCp",
|
|
626
|
+
"matePly",
|
|
627
|
+
"scoreScope",
|
|
628
|
+
"scoreProvenance",
|
|
629
|
+
"command",
|
|
630
|
+
"engine",
|
|
631
|
+
"engineBuild",
|
|
632
|
+
"cacheProvenance"
|
|
633
|
+
];
|
|
634
|
+
function compactSearchRecord(record2, playedMove) {
|
|
635
|
+
const data = record2.data;
|
|
636
|
+
if (!known(data, ["recordedPosition", "details"]) || !object4(data.recordedPosition) || !object4(data.details)) return null;
|
|
637
|
+
const recordedPosition = data.recordedPosition;
|
|
638
|
+
const details = data.details;
|
|
639
|
+
if (!known(details, detailsKeys) || !object4(details.searchScope) || typeof details.searchScope.fen !== "string" || !Array.isArray(details.lines) || !details.lines.length || !Array.isArray(details.continuationMoveFacts)) return null;
|
|
640
|
+
const positionKeys = ["recordHash", "ply", "startFen", "fen", "historyUci", "rootContextHash", "nextRecordedMove", "scope"];
|
|
641
|
+
if (!known(recordedPosition, positionKeys) || !Number.isInteger(recordedPosition.ply) || typeof recordedPosition.fen !== "string") return null;
|
|
642
|
+
const strongestOther = details.lines.find((line) => object4(line) && line.rootMove !== playedMove);
|
|
643
|
+
const mechanismAlternative = details.bestMove && details.bestMove !== playedMove ? details.bestMove : object4(strongestOther) ? strongestOther.rootMove : details.bestMove;
|
|
644
|
+
const lines = [];
|
|
645
|
+
for (const line of details.lines) {
|
|
646
|
+
if (!object4(line) || !known(line, lineKeys) || !Array.isArray(line.pv) || !line.pv.every((move) => typeof move === "string") || typeof line.rootMove !== "string" || !object4(line.pvCompletion) || !known(line.pvCompletion, completionKeys) || !Array.isArray(line.pvCompletion.segments)) return null;
|
|
647
|
+
let pvSan;
|
|
648
|
+
try {
|
|
649
|
+
const board = new Chess9(details.searchScope.fen);
|
|
650
|
+
pvSan = line.pv.map((move) => {
|
|
651
|
+
if (!/^[a-h][1-8][a-h][1-8][qrbn]?$/.test(move)) throw Error("invalid PV move");
|
|
652
|
+
const replayed = board.move({ from: move.slice(0, 2), to: move.slice(2, 4), promotion: move.slice(4) || void 0 });
|
|
653
|
+
if (!replayed) throw Error("illegal PV move");
|
|
654
|
+
return replayed.san;
|
|
655
|
+
});
|
|
656
|
+
} catch {
|
|
657
|
+
return null;
|
|
658
|
+
}
|
|
659
|
+
const segments = [];
|
|
660
|
+
for (const segment of line.pvCompletion.segments) {
|
|
661
|
+
if (!object4(segment) || !known(segment, segmentKeys) || !Array.isArray(segment.pv) || !Number.isInteger(segment.startPly)) return null;
|
|
662
|
+
const start = segment.startPly;
|
|
663
|
+
const same = start >= 0 && JSON.stringify(line.pv.slice(start, start + segment.pv.length)) === JSON.stringify(segment.pv);
|
|
664
|
+
const { pv: _pv, ...remaining } = segment;
|
|
665
|
+
void _pv;
|
|
666
|
+
segments.push(same ? { ...remaining, pvFromLinePly: start, pvLength: segment.pv.length } : { ...remaining, pv: segment.pv });
|
|
667
|
+
}
|
|
668
|
+
const { segments: _segments, ...completion } = line.pvCompletion;
|
|
669
|
+
void _segments;
|
|
670
|
+
const material = object4(line.material) && object4(line.material.start) && object4(line.material.end) ? {
|
|
671
|
+
authority: line.material.authority,
|
|
672
|
+
startBalance: line.material.start.balance,
|
|
673
|
+
endBalance: line.material.end.balance,
|
|
674
|
+
balanceChangeFromStart: line.material.balanceChangeFromStart,
|
|
675
|
+
scope: line.material.scope
|
|
676
|
+
} : void 0;
|
|
677
|
+
const separateRawPv = Array.isArray(line.rawPv) && JSON.stringify(line.rawPv) !== JSON.stringify(line.pv) ? { rawPv: line.rawPv } : {};
|
|
678
|
+
const continuation = details.continuationMoveFacts.find((f) => object4(f) && f.rootMove === line.rootMove);
|
|
679
|
+
const mechanismRelevant = line.rootMove === playedMove || line.rootMove === mechanismAlternative || !mechanismAlternative && lines.length === 0;
|
|
680
|
+
const mechanismFacts = mechanismRelevant && object4(continuation) && Array.isArray(continuation.moves) ? presentMoveMechanisms(continuation.moves) : [];
|
|
681
|
+
lines.push({
|
|
682
|
+
...object4(continuation) && Array.isArray(continuation.moves) ? { mechanismAvailablePlies: continuation.moves.length } : {},
|
|
683
|
+
...mechanismFacts.length ? { mechanismFacts } : {},
|
|
684
|
+
...line.multipv !== void 0 ? { multipv: line.multipv } : {},
|
|
685
|
+
rootMove: line.rootMove,
|
|
686
|
+
...playedMove ? { playedMove: playedMove === line.rootMove } : {},
|
|
687
|
+
pv: line.pv,
|
|
688
|
+
pvSan,
|
|
689
|
+
depth: line.depth,
|
|
690
|
+
scoreCp: line.scoreCp,
|
|
691
|
+
matePly: line.matePly,
|
|
692
|
+
scoreScope: line.scoreScope,
|
|
693
|
+
...line.scoreProvenance ? { scoreProvenance: line.scoreProvenance } : {},
|
|
694
|
+
pvCompletion: { ...completion, segments },
|
|
695
|
+
...material ? { materialSummary: material } : {},
|
|
696
|
+
...separateRawPv
|
|
697
|
+
});
|
|
698
|
+
}
|
|
699
|
+
const { historyUci: _historyUci, ...root } = recordedPosition;
|
|
700
|
+
void _historyUci;
|
|
701
|
+
const { rootMoveFacts: _rootMoveFacts, continuationMoveFacts: _continuationMoveFacts, lines: _lines, ...search2 } = details;
|
|
702
|
+
void _rootMoveFacts;
|
|
703
|
+
void _continuationMoveFacts;
|
|
704
|
+
void _lines;
|
|
705
|
+
const projection = {
|
|
706
|
+
...record2,
|
|
707
|
+
data: { recordedPosition: root, details: {
|
|
708
|
+
...search2,
|
|
709
|
+
lines,
|
|
710
|
+
modelPresentation: {
|
|
711
|
+
scope: "compact initial preparation only; supplied PVs are complete, mechanismFacts cover first two plies for played and strongest roots with scoped control targets; deeper geometry remains retrievable",
|
|
712
|
+
mechanismRoots: lines.filter((line) => Array.isArray(line.mechanismFacts)).map((line) => line.rootMove),
|
|
713
|
+
omitted: ["otherRootMechanisms", "rootMoveFacts", "continuationMoveFacts", "captureGeometry", "materialCheckpoints", "duplicatePvSegmentMoves"],
|
|
714
|
+
retrieveFullEvidenceId: record2.id,
|
|
715
|
+
segmentPvReference: "A segment with pvFromLinePly=N and pvLength=L repeats exactly L moves of that line.pv starting at zero-based index N; its own depth, score, FEN, command and cache provenance remain specific to that segment.",
|
|
716
|
+
instruction: "For deeper or omitted geometry, other control squares, piece identities or material checkpoints, call retrieve_evidence with this evidence ID. This reuses the acquired record without another engine search. mechanismAvailablePlies states the retained geometry horizon; inspect later moves with test_game_line/inspectPlies."
|
|
717
|
+
}
|
|
718
|
+
} },
|
|
719
|
+
// replay duplicates the full PV arrays. The PVs above remain available for
|
|
720
|
+
// game links, while the immutable replay stays in the authoritative record.
|
|
721
|
+
replay: null
|
|
722
|
+
};
|
|
723
|
+
return projection;
|
|
724
|
+
}
|
|
725
|
+
function compactPreparedSearchResult(result, playedMove, format) {
|
|
726
|
+
const packet = {
|
|
727
|
+
contextHash: result.contextHash,
|
|
728
|
+
evidence: result.evidence.map((record2) => compactSearchRecord(record2, playedMove) ?? record2),
|
|
729
|
+
limitations: [
|
|
730
|
+
...result.limitations,
|
|
731
|
+
"Initial preparation shows complete supplied PV moves and bounded scores, with first-two-ply mechanism measurements for played/strongest roots, but omits deeper geometry and material checkpoints. retrieve_evidence(evidenceIds) returns the already acquired full records without a new search."
|
|
732
|
+
]
|
|
733
|
+
};
|
|
734
|
+
return compactSimpleEngineResult(packet, format);
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
// src/chess-reasoning/matched-comparison.ts
|
|
738
|
+
import { Chess as Chess10 } from "chess.js";
|
|
739
|
+
function matchedRootComparison(result, fen2, requiredRoots = []) {
|
|
740
|
+
const lines = Array.isArray(result.lines) ? result.lines : [];
|
|
741
|
+
const rootMoves = lines.flatMap((line) => line && typeof line.rootMove === "string" ? [line.rootMove] : []);
|
|
742
|
+
const unmatched = (reason) => ({ status: "unmatched", actualDepth: null, rootMoves, reason });
|
|
743
|
+
if ((result.sourceCacheProvenance ?? result.cacheProvenance) !== "engine") return unmatched("Common engine search identity is unavailable.");
|
|
744
|
+
if (rootMoves.length !== lines.length) return unmatched("A compared root is malformed.");
|
|
745
|
+
if (lines.length < 2 || new Set(rootMoves).size !== rootMoves.length || requiredRoots.some((root) => !rootMoves.includes(root))) return unmatched("The search does not cover all compared moves.");
|
|
746
|
+
try {
|
|
747
|
+
const legal = new Set(new Chess10(fen2).moves({ verbose: true }).map((move) => move.from + move.to + (move.promotion || "")));
|
|
748
|
+
if (rootMoves.some((root) => !legal.has(root))) return unmatched("A compared move is not legal at this root.");
|
|
749
|
+
} catch {
|
|
750
|
+
return unmatched("The comparison root is invalid.");
|
|
751
|
+
}
|
|
752
|
+
const depth = lines[0].depth, perspective = fen2.split(" ")[1] === "w" ? "white" : fen2.split(" ")[1] === "b" ? "black" : null;
|
|
753
|
+
if (!perspective || !Number.isSafeInteger(depth) || depth < 1 || lines.some((line) => line.depth !== depth)) return unmatched("The moves have no common achieved depth.");
|
|
754
|
+
for (const line of lines) {
|
|
755
|
+
if (line.scoreScope?.fen !== fen2 || line.scoreScope.perspective !== perspective || line.scoreScope.positiveMeans !== "advantage for perspective color") return unmatched("The score roots or perspectives differ.");
|
|
756
|
+
if (line.matePly == null ? !Number.isFinite(line.scoreCp) || Math.abs(line.scoreCp) >= 1e5 : !Number.isFinite(line.matePly)) return unmatched("A compared score is unavailable.");
|
|
757
|
+
if (line.matePly == null ? line.scoreScope.scoreCpUnit !== "centipawns" : line.scoreScope.matePlyUnit !== "signed mate-in-moves (legacy field name, not plies)") return unmatched("The score units are unknown.");
|
|
758
|
+
const origin = line.scoreProvenance;
|
|
759
|
+
if (origin && (origin.kind !== "root-search" || origin.fen !== fen2 || origin.originatingDepth !== depth)) return unmatched("A score is not a matching root-search measurement.");
|
|
760
|
+
if (origin?.engineBuild != null && origin.engineBuild !== result.engineBuild) return unmatched("The engine builds differ.");
|
|
761
|
+
if (origin?.command != null && result.command && origin.command !== result.command) return unmatched("The search commands differ.");
|
|
762
|
+
}
|
|
763
|
+
if (new Set(lines.flatMap((line) => typeof line.scoreProvenance?.command === "string" ? [line.scoreProvenance.command] : [])).size > 1) return unmatched("The search commands differ.");
|
|
764
|
+
return { status: "matched", actualDepth: depth, rootMoves, reason: "These root scores came from one engine search at a common achieved depth." };
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
// src/chess-reasoning/preparation-consolidation.ts
|
|
768
|
+
var object5 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
|
|
769
|
+
var strings = (v) => Array.isArray(v) && v.every((x) => typeof x === "string");
|
|
770
|
+
var finite = (v) => typeof v === "number" && Number.isFinite(v);
|
|
771
|
+
var equal = (a, b) => JSON.stringify(a) === JSON.stringify(b);
|
|
772
|
+
function search(record2) {
|
|
773
|
+
const data = record2.data;
|
|
774
|
+
if (!object5(data.recordedPosition) || !object5(data.details)) return;
|
|
775
|
+
const root = data.recordedPosition, details = data.details;
|
|
776
|
+
if (!object5(details.searchScope) || !Array.isArray(details.lines) || !details.lines.length || !details.lines.every(object5)) return;
|
|
777
|
+
const scope = details.searchScope, lines = details.lines;
|
|
778
|
+
const perspective = typeof root.fen === "string" && root.fen.split(" ")[1] === "w" ? "white" : "black";
|
|
779
|
+
if (typeof root.fen !== "string" || scope.fen !== root.fen || !Number.isInteger(root.ply) || !strings(scope.requestedRootMoves) || new Set(lines.map((l) => l.rootMove)).size !== lines.length) return;
|
|
780
|
+
for (const line of lines) {
|
|
781
|
+
if (typeof line.rootMove !== "string" || !strings(line.pv) || line.pv[0] !== line.rootMove || !finite(line.depth) || !object5(line.scoreScope) || line.scoreScope.fen !== root.fen || line.scoreScope.perspective !== perspective || line.scoreScope.positiveMeans !== "advantage for perspective color") return;
|
|
782
|
+
if (finite(line.matePly)) {
|
|
783
|
+
if (line.scoreScope.matePlyUnit !== "signed mate-in-moves (legacy field name, not plies)") return;
|
|
784
|
+
} else if (line.matePly != null || !finite(line.scoreCp) || Math.abs(line.scoreCp) >= 1e5 || line.scoreScope.scoreCpUnit !== "centipawns") return;
|
|
785
|
+
}
|
|
786
|
+
return { root, details, scope, lines, requestedRootMoves: scope.requestedRootMoves };
|
|
787
|
+
}
|
|
788
|
+
function consolidatePreparedSearches(entries) {
|
|
789
|
+
const replacements = /* @__PURE__ */ new Map(), omitted = /* @__PURE__ */ new Set();
|
|
790
|
+
const dependencies = [];
|
|
791
|
+
for (const primary of entries) {
|
|
792
|
+
if (!primary.discoveryCallId) continue;
|
|
793
|
+
const discovery = entries.find((e) => e.callId === primary.discoveryCallId);
|
|
794
|
+
const dependency = {
|
|
795
|
+
relation: "candidate-selection",
|
|
796
|
+
discoveryCallId: primary.discoveryCallId,
|
|
797
|
+
comparisonCallId: primary.callId,
|
|
798
|
+
discoveryEvidenceIds: discovery?.records.map((r) => r.id) ?? [],
|
|
799
|
+
comparisonEvidenceIds: primary.records.map((r) => r.id),
|
|
800
|
+
presentation: "separate",
|
|
801
|
+
reason: "Comparison is not a complete compatible replacement."
|
|
802
|
+
};
|
|
803
|
+
dependencies.push(dependency);
|
|
804
|
+
if (!discovery || discovery.modelRun.status !== "completed" || primary.modelRun.status !== "completed" || discovery.records.length !== 1 || primary.records.length !== 1 || omitted.has(discovery.callId)) continue;
|
|
805
|
+
const original = discovery.records[0], comparison = primary.records[0], a = search(original), b = search(comparison);
|
|
806
|
+
if (!a || !b || !original.source.endsWith("/explore-continuations") || !comparison.source.endsWith("/compare_moves") || original.contextHash !== comparison.contextHash || !equal(a.root, b.root) || a.requestedRootMoves.length || comparison.coverage?.complete !== true || !primary.playedMove || !b.lines.some((l) => l.rootMove === primary.playedMove) || new Set(b.lines.map((l) => l.depth)).size !== 1) continue;
|
|
807
|
+
if (matchedRootComparison({ ...b.details, command: b.scope.command, cacheProvenance: b.scope.cacheProvenance, sourceCacheProvenance: b.scope.sourceCacheProvenance }, String(b.root.fen), b.requestedRootMoves).status !== "matched") continue;
|
|
808
|
+
const roots = a.lines.map((l) => String(l.rootMove)), compared = b.requestedRootMoves;
|
|
809
|
+
if (![...roots, primary.playedMove, ...compared].every((move) => compared.includes(move) && comparison.coverage.covered.includes(move) && b.lines.some((l) => l.rootMove === move))) continue;
|
|
810
|
+
if (!compactSearchRecord(original, primary.playedMove) || !compactSearchRecord(comparison, primary.playedMove)) continue;
|
|
811
|
+
const commonComparisonOrder = b.lines.map((l) => String(l.rootMove)).filter((move) => roots.includes(move));
|
|
812
|
+
const differentContinuations = a.lines.filter((line) => {
|
|
813
|
+
const other = b.lines.find((l) => l.rootMove === line.rootMove);
|
|
814
|
+
return !equal(line.pv, other.pv) || !equal(line.pvCompletion, other.pvCompletion);
|
|
815
|
+
}).map((l) => l.rootMove);
|
|
816
|
+
const discoverySummary = {
|
|
817
|
+
callId: discovery.callId,
|
|
818
|
+
evidenceId: original.id,
|
|
819
|
+
source: original.source,
|
|
820
|
+
engine: a.details.engine,
|
|
821
|
+
engineBuild: a.details.engineBuild,
|
|
822
|
+
searchScope: a.scope,
|
|
823
|
+
coverage: original.coverage,
|
|
824
|
+
summary: original.summary,
|
|
825
|
+
limitations: discovery.limitations ?? [],
|
|
826
|
+
scores: a.lines.map((l) => ({
|
|
827
|
+
rootMove: l.rootMove,
|
|
828
|
+
depth: l.depth,
|
|
829
|
+
scoreCp: l.scoreCp,
|
|
830
|
+
matePly: l.matePly,
|
|
831
|
+
scoreScope: l.scoreScope,
|
|
832
|
+
...l.scoreProvenance ? { scoreProvenance: l.scoreProvenance } : {},
|
|
833
|
+
...object5(l.pvCompletion) ? { continuation: { status: l.pvCompletion.status, conditional: l.pvCompletion.conditional, actualPlies: l.pvCompletion.actualPlies, requestedPlies: l.pvCompletion.requestedPlies } } : {}
|
|
834
|
+
})),
|
|
835
|
+
discoveryOrder: roots,
|
|
836
|
+
comparisonOrderForDiscoveredMoves: commonComparisonOrder,
|
|
837
|
+
reportedOrderChanged: !equal(roots, commonComparisonOrder),
|
|
838
|
+
distinctContinuationRootMoves: differentContinuations,
|
|
839
|
+
retrieveFullEvidenceId: original.id,
|
|
840
|
+
interpretation: "Discovery supplied candidates for this comparison. Its scores and reported ordering belong to the discovery search, not the primary comparison. Reported order changes may include tied moves. Full discovery continuations and details remain retrievable; use retrieve_evidence before relying on an omitted discovery line. Score disagreements remain in scoreComparisonNotices. This dependency does not establish that the answering model used or cited either record."
|
|
841
|
+
};
|
|
842
|
+
const result = discovery.modelRun.result;
|
|
843
|
+
const discoveryOpeningSourceContext = object5(result) ? result.openingSourceContext : void 0;
|
|
844
|
+
replacements.set(primary.callId, {
|
|
845
|
+
...primary.modelRun,
|
|
846
|
+
discovery: discoverySummary,
|
|
847
|
+
...discoveryOpeningSourceContext ? { discoveryOpeningSourceContext } : {}
|
|
848
|
+
});
|
|
849
|
+
omitted.add(discovery.callId);
|
|
850
|
+
Object.assign(dependency, {
|
|
851
|
+
presentation: "comparison-with-discovery-summary",
|
|
852
|
+
reason: "Complete same-root comparison covers discovered alternatives and played move.",
|
|
853
|
+
primaryEvidenceId: comparison.id,
|
|
854
|
+
reportedOrderChanged: discoverySummary.reportedOrderChanged,
|
|
855
|
+
distinctContinuationRootMoves: differentContinuations
|
|
856
|
+
});
|
|
857
|
+
}
|
|
858
|
+
return {
|
|
859
|
+
results: entries.filter((e) => !omitted.has(e.callId)).map((e) => replacements.get(e.callId) ?? e.modelRun),
|
|
860
|
+
diagnostics: {
|
|
861
|
+
version: "preparation-consolidation/1",
|
|
862
|
+
searchCount: entries.length,
|
|
863
|
+
primaryEntryCount: entries.length - omitted.size,
|
|
864
|
+
summarizedDiscoveryCount: omitted.size,
|
|
865
|
+
dependencies,
|
|
866
|
+
interpretation: "Candidate-selection and presentation decisions are observable. They do not establish whether uncited records influenced the answer. All authoritative records remain available for retrieval."
|
|
867
|
+
}
|
|
868
|
+
};
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
// src/chess-reasoning/score-comparison-notices.ts
|
|
872
|
+
var object6 = (value) => value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
873
|
+
var finite2 = (value) => typeof value === "number" && Number.isFinite(value);
|
|
874
|
+
var strings2 = (value) => Array.isArray(value) ? value.filter((v) => typeof v === "string") : [];
|
|
875
|
+
function disagreementKind(observations) {
|
|
876
|
+
const mates = observations.filter((observation) => observation.score.unit === "mate-in-moves").map((observation) => observation.score.value);
|
|
877
|
+
if (mates.length !== observations.length) return mates.length ? "mate-status-disagreement" : "centipawn-variation";
|
|
878
|
+
if (mates.some((value) => value > 0) && mates.some((value) => value < 0)) return "opposite-mate-sign";
|
|
879
|
+
return "mate-distance-variation";
|
|
880
|
+
}
|
|
881
|
+
var sharedInterpretation = "Different search results for the same root move; equal requested depth does not make their search scope, actual depth, cache or search history equivalent. Preserve each scoped score. For relative move comparisons prefer alternatives covered together at the same actual depth in the identified comparison records; this supports a relative claim within that search, not independent confirmation or rejection of a mate result. Do not combine scores from separate searches into a move-loss estimate. This notice alone does not require another search.";
|
|
882
|
+
var interpretationFor = (kind) => {
|
|
883
|
+
const qualification = kind === "mate-status-disagreement" ? "Mate status differs: a centipawn score in another search does not refute its mating continuation, and mate distances cannot be converted to centipawns. " : kind === "opposite-mate-sign" ? "The mate signs oppose, indicating different reported mating sides; retain each search scope and inspect the underlying continuations. " : kind === "mate-distance-variation" ? "The searches report different mate distances; mate distances are not centipawns. " : "The searches report different centipawn evaluations. ";
|
|
884
|
+
return qualification + sharedInterpretation;
|
|
885
|
+
};
|
|
886
|
+
function originatingRootDepth(line, search2, fen2) {
|
|
887
|
+
const provenance = object6(line.scoreProvenance);
|
|
888
|
+
if (provenance) return provenance.kind === "root-search" && provenance.fen === fen2 && finite2(provenance.originatingDepth) && Number.isInteger(provenance.originatingDepth) && provenance.originatingDepth > 0 && provenance.originatingDepth === line.depth ? provenance.originatingDepth : null;
|
|
889
|
+
const source = search2.sourceCacheProvenance ?? search2.cacheProvenance;
|
|
890
|
+
return source === "engine" && finite2(line.depth) && Number.isInteger(line.depth) && line.depth > 0 ? line.depth : null;
|
|
891
|
+
}
|
|
892
|
+
function scoreComparisonNotices(records) {
|
|
893
|
+
const groups = /* @__PURE__ */ new Map();
|
|
894
|
+
for (const record2 of records) {
|
|
895
|
+
const wrapper = object6(record2.data);
|
|
896
|
+
const data = object6(wrapper?.details) ?? wrapper;
|
|
897
|
+
const search2 = object6(data?.searchScope);
|
|
898
|
+
if (!data || !search2 || typeof search2.fen !== "string" || !Array.isArray(data.lines)) continue;
|
|
899
|
+
const fen2 = search2.fen;
|
|
900
|
+
const perspective = fen2.split(" ")[1] === "w" ? "white" : fen2.split(" ")[1] === "b" ? "black" : null;
|
|
901
|
+
if (!perspective) continue;
|
|
902
|
+
const roots = strings2(search2.requestedRootMoves);
|
|
903
|
+
const lines = data.lines.map(object6).filter((line) => !!line);
|
|
904
|
+
const scoped = (line) => {
|
|
905
|
+
const scope = object6(line.scoreScope);
|
|
906
|
+
return scope?.fen === fen2 && scope.perspective === perspective && scope.positiveMeans === "advantage for perspective color";
|
|
907
|
+
};
|
|
908
|
+
const comparedRoots = roots.length ? roots : lines.map((line) => String(line.rootMove));
|
|
909
|
+
const matched = (!roots.length || record2.coverage?.complete === true && roots.every((root) => record2.coverage.covered.includes(root))) && matchedRootComparison({ ...data, command: search2.command, cacheProvenance: search2.cacheProvenance, sourceCacheProvenance: search2.sourceCacheProvenance }, fen2, comparedRoots).status === "matched";
|
|
910
|
+
for (const line of lines) {
|
|
911
|
+
if (typeof line.rootMove !== "string" || !scoped(line)) continue;
|
|
912
|
+
const scope = object6(line.scoreScope);
|
|
913
|
+
let score;
|
|
914
|
+
if (finite2(line.matePly) && scope.matePlyUnit === "signed mate-in-moves (legacy field name, not plies)") {
|
|
915
|
+
score = { unit: "mate-in-moves", value: line.matePly };
|
|
916
|
+
} else if (line.matePly == null && finite2(line.scoreCp) && Math.abs(line.scoreCp) < 1e5 && scope.scoreCpUnit === "centipawns") {
|
|
917
|
+
score = { unit: "centipawns", value: line.scoreCp };
|
|
918
|
+
} else continue;
|
|
919
|
+
const key = JSON.stringify([fen2, line.rootMove, perspective]);
|
|
920
|
+
let group = groups.get(key);
|
|
921
|
+
if (!group) {
|
|
922
|
+
group = { fen: fen2, rootMove: line.rootMove, perspective, observations: [], matched: /* @__PURE__ */ new Set() };
|
|
923
|
+
groups.set(key, group);
|
|
924
|
+
}
|
|
925
|
+
if (group.observations.some((observation) => observation.evidenceId === record2.id)) continue;
|
|
926
|
+
group.observations.push({
|
|
927
|
+
evidenceId: record2.id,
|
|
928
|
+
source: record2.source,
|
|
929
|
+
score,
|
|
930
|
+
reportedDepth: finite2(line.depth) ? line.depth : null,
|
|
931
|
+
actualDepth: originatingRootDepth(line, search2, fen2),
|
|
932
|
+
requestedDepth: finite2(search2.requestedDepth) ? search2.requestedDepth : null,
|
|
933
|
+
requestedMultiPv: finite2(search2.requestedMultiPv) ? search2.requestedMultiPv : null,
|
|
934
|
+
rootScope: roots.length ? "restricted" : "unrestricted",
|
|
935
|
+
requestedRootMoves: roots,
|
|
936
|
+
cacheProvenance: typeof search2.cacheProvenance === "string" ? search2.cacheProvenance : "unreported",
|
|
937
|
+
sourceCacheProvenance: typeof search2.sourceCacheProvenance === "string" ? search2.sourceCacheProvenance : "unreported",
|
|
938
|
+
engine: typeof data.engine === "string" ? data.engine : null,
|
|
939
|
+
engineBuild: typeof data.engineBuild === "string" ? data.engineBuild : null,
|
|
940
|
+
...object6(line.scoreProvenance) ? { scoreProvenance: line.scoreProvenance } : {}
|
|
941
|
+
});
|
|
942
|
+
if (matched && comparedRoots.includes(line.rootMove)) group.matched.add(record2.id);
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
return [...groups.values()].filter((group) => new Set(group.observations.map((observation) => JSON.stringify(observation.score))).size > 1).map((group) => {
|
|
946
|
+
const kind = disagreementKind(group.observations);
|
|
947
|
+
return {
|
|
948
|
+
fen: group.fen,
|
|
949
|
+
rootMove: group.rootMove,
|
|
950
|
+
perspective: group.perspective,
|
|
951
|
+
disagreementKind: kind,
|
|
952
|
+
observations: group.observations,
|
|
953
|
+
preferredWithinSearchComparisonEvidenceIds: [...group.matched],
|
|
954
|
+
interpretation: interpretationFor(kind)
|
|
955
|
+
};
|
|
956
|
+
});
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
// test/owner/preparation-consolidation.test.mjs
|
|
960
|
+
var fen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
|
|
961
|
+
var scoreScope = { fen, perspective: "white", positiveMeans: "advantage for perspective color", scoreCpUnit: "centipawns", matePlyUnit: "signed mate-in-moves (legacy field name, not plies)" };
|
|
962
|
+
function record(id, restricted = false) {
|
|
963
|
+
const roots = restricted ? ["d2d4", "e2e4", "b1c3"] : ["e2e4", "d2d4"];
|
|
964
|
+
return {
|
|
965
|
+
id,
|
|
966
|
+
contextHash: "game",
|
|
967
|
+
source: "Stockfish/" + (restricted ? "compare_moves" : "explore-continuations"),
|
|
968
|
+
data: { recordedPosition: { recordHash: "record", ply: 0, startFen: fen, fen, historyUci: [], rootContextHash: "root" }, details: { phase: "after", engine: "Stockfish", engineBuild: "test", searchScope: { fen, requestedDepth: 18, requestedMultiPv: restricted ? null : 2, requestedRootMoves: restricted ? roots : [], cacheProvenance: "engine" }, rootMoveFacts: [], continuationMoveFacts: [], lines: roots.map((rootMove, i) => ({ rootMove, pv: [rootMove], depth: 18, scoreCp: 50 - i * 10, matePly: null, scoreScope, pvCompletion: { requestedPlies: 1, actualPlies: 1, status: "complete", conditional: false, segments: [] } })) } },
|
|
969
|
+
coverage: { complete: restricted, covered: roots, unresolved: [] },
|
|
970
|
+
licenses: ["engine.bounded-comparison"],
|
|
971
|
+
replay: { fen, linesUci: roots.map((x) => [x]) }
|
|
972
|
+
};
|
|
973
|
+
}
|
|
974
|
+
function entry(callId, record2, discoveryCallId) {
|
|
975
|
+
const result = compactPreparedSearchResult({ contextHash: "game", evidence: [record2], limitations: [] }, "b1c3");
|
|
976
|
+
return { callId, records: [record2], playedMove: "b1c3", discoveryCallId, modelRun: { callId, status: "completed", result } };
|
|
977
|
+
}
|
|
978
|
+
var pair = () => [entry("discovery", record("evidence:discovery")), entry("comparison", record("evidence:comparison", true), "discovery")];
|
|
979
|
+
test("KE-REASON-016/021/026: one primary comparison keeps original scores, order changes, dependency and retrieval identity", () => {
|
|
980
|
+
const input = pair(), copy = structuredClone(input), notices = scoreComparisonNotices(input.flatMap((x) => x.records));
|
|
981
|
+
const out = consolidatePreparedSearches(input);
|
|
982
|
+
assert.equal(out.results.length, 1);
|
|
983
|
+
assert.deepEqual(out.results[0].result, input[1].modelRun.result);
|
|
984
|
+
assert.equal(out.results[0].discovery.evidenceId, "evidence:discovery");
|
|
985
|
+
assert.equal(out.results[0].discovery.retrieveFullEvidenceId, "evidence:discovery");
|
|
986
|
+
assert.deepEqual(out.results[0].discovery.scores.map((x) => [x.rootMove, x.scoreCp]), [["e2e4", 50], ["d2d4", 40]]);
|
|
987
|
+
assert.deepEqual(out.results[0].discovery.comparisonOrderForDiscoveredMoves, ["d2d4", "e2e4"]);
|
|
988
|
+
assert.equal(out.results[0].discovery.reportedOrderChanged, true);
|
|
989
|
+
assert.equal(out.diagnostics.dependencies[0].relation, "candidate-selection");
|
|
990
|
+
assert.equal(out.diagnostics.dependencies[0].primaryEvidenceId, "evidence:comparison");
|
|
991
|
+
assert.deepEqual(scoreComparisonNotices(input.flatMap((x) => x.records)), notices);
|
|
992
|
+
assert.equal(notices.length, 2);
|
|
993
|
+
assert.deepEqual(input, copy);
|
|
994
|
+
});
|
|
995
|
+
test("KE-REASON-016/021/026: distinct PVs and conditional segments remain explicitly discoverable by original ID", () => {
|
|
996
|
+
const input = pair();
|
|
997
|
+
const line = input[0].records[0].data.details.lines[0];
|
|
998
|
+
line.pv.push("e7e5");
|
|
999
|
+
line.pvCompletion.conditional = true;
|
|
1000
|
+
input[0].modelRun.result = compactPreparedSearchResult({ contextHash: "game", evidence: input[0].records, limitations: [] }, "b1c3");
|
|
1001
|
+
const out = consolidatePreparedSearches(input);
|
|
1002
|
+
assert.deepEqual(out.results[0].discovery.distinctContinuationRootMoves, ["e2e4"]);
|
|
1003
|
+
assert.deepEqual(input[0].records[0].data.details.lines[0].pv, ["e2e4", "e7e5"]);
|
|
1004
|
+
});
|
|
1005
|
+
test("KE-REASON-023/026: discovery-only delivered opening commentary survives consolidation", () => {
|
|
1006
|
+
const input = pair(), source = { pages: [{ title: "source", prose: "opening", matchedTargets: ["original"] }] };
|
|
1007
|
+
input[0].modelRun.result = { chessResult: input[0].modelRun.result, openingSourceContext: source };
|
|
1008
|
+
assert.deepEqual(consolidatePreparedSearches(input).results[0].discoveryOpeningSourceContext, source);
|
|
1009
|
+
});
|
|
1010
|
+
test("KE-REASON-016/026: no omission for incomplete, failed, foreign, unrecognized, mixed-depth or unrelated results", () => {
|
|
1011
|
+
const changes = [
|
|
1012
|
+
(a) => a[1].modelRun.status = "failed",
|
|
1013
|
+
(a) => a[1].records[0].coverage.complete = false,
|
|
1014
|
+
(a) => a[1].records[0].coverage.covered.pop(),
|
|
1015
|
+
(a) => a[1].records[0].data.details.lines.pop(),
|
|
1016
|
+
(a) => a[1].records[0].data.recordedPosition.ply = 1,
|
|
1017
|
+
(a) => a[1].records[0].contextHash = "foreign",
|
|
1018
|
+
(a) => a[1].records[0].data.details.lines[0].depth = 19,
|
|
1019
|
+
(a) => a[1].records[0].data.details.lines[0].scoreScope = { ...scoreScope, perspective: "black" },
|
|
1020
|
+
(a) => a[0].records[0].data.details.newProducerField = "retain",
|
|
1021
|
+
(a) => a[1].discoveryCallId = void 0,
|
|
1022
|
+
(a) => a[1].playedMove = void 0,
|
|
1023
|
+
(a) => a[0].records[0].data.details.lines[0].pv.push("a1a8")
|
|
1024
|
+
];
|
|
1025
|
+
for (const change of changes) {
|
|
1026
|
+
const input = pair();
|
|
1027
|
+
change(input);
|
|
1028
|
+
const out = consolidatePreparedSearches(input);
|
|
1029
|
+
assert.deepEqual(out.results, input.map((e) => e.modelRun));
|
|
1030
|
+
assert.equal(out.diagnostics.summarizedDiscoveryCount, 0);
|
|
1031
|
+
}
|
|
1032
|
+
const only = [pair()[0]];
|
|
1033
|
+
assert.deepEqual(consolidatePreparedSearches(only).results, only.map((e) => e.modelRun));
|
|
1034
|
+
});
|
|
1035
|
+
test("KE-REASON-016/021/026: unverified cached comparison depth cannot authorize consolidation", () => {
|
|
1036
|
+
const input = pair();
|
|
1037
|
+
input[1].records[0].data.details.searchScope.sourceCacheProvenance = "widget-analysis-cache";
|
|
1038
|
+
assert.equal(consolidatePreparedSearches(input).results.length, 2);
|
|
1039
|
+
for (const line of input[1].records[0].data.details.lines) line.scoreProvenance = { kind: "root-search", fen, originatingDepth: 18 };
|
|
1040
|
+
input[0].records[0].data.details.lines[0].scoreProvenance = { kind: "legacy-unknown", originatingDepth: null, reportedDepth: 18 };
|
|
1041
|
+
assert.equal(consolidatePreparedSearches(input).results.length, 2, "known origins alone do not establish one search");
|
|
1042
|
+
input[1].records[0].data.details.searchScope.sourceCacheProvenance = "engine";
|
|
1043
|
+
const out = consolidatePreparedSearches(input);
|
|
1044
|
+
assert.equal(out.results.length, 1);
|
|
1045
|
+
assert.equal(out.results[0].discovery.scores[0].scoreProvenance.kind, "legacy-unknown");
|
|
1046
|
+
});
|
|
1047
|
+
test("KE-REASON-026: the original command remains authoritative when origins share a different command", () => {
|
|
1048
|
+
const input = pair(), details = input[1].records[0].data.details;
|
|
1049
|
+
details.searchScope.command = "go depth 18 searchmoves d2d4 e2e4 b1c3";
|
|
1050
|
+
for (const line of details.lines) line.scoreProvenance = { kind: "root-search", fen, originatingDepth: 18, command: "go depth 26" };
|
|
1051
|
+
assert.equal(consolidatePreparedSearches(input).results.length, 2);
|
|
1052
|
+
});
|