@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,396 @@
|
|
|
1
|
+
// test/owner/move-mechanism-facts.test.mjs
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import test from "node:test";
|
|
4
|
+
import { Chess as Chess3 } from "chess.js";
|
|
5
|
+
|
|
6
|
+
// src/chess-reasoning/move-mechanism-facts.ts
|
|
7
|
+
import { Chess as Chess2, SQUARES as SQUARES2 } from "chess.js";
|
|
8
|
+
|
|
9
|
+
// src/chess-reasoning/piece-identities.ts
|
|
10
|
+
import { Chess, SQUARES } from "chess.js";
|
|
11
|
+
|
|
12
|
+
// src/chess-reasoning/types.ts
|
|
13
|
+
var CHESS_REASONING_SCHEMA_VERSION = "0.2.0";
|
|
14
|
+
|
|
15
|
+
// src/chess-reasoning/piece-identities.ts
|
|
16
|
+
var PIECE_NAMES = {
|
|
17
|
+
p: "pawn",
|
|
18
|
+
n: "knight",
|
|
19
|
+
b: "bishop",
|
|
20
|
+
r: "rook",
|
|
21
|
+
q: "queen",
|
|
22
|
+
k: "king"
|
|
23
|
+
};
|
|
24
|
+
var COLOR_NAMES = { w: "white", b: "black" };
|
|
25
|
+
function pieceId(color, type, square, ordinal = 0) {
|
|
26
|
+
return `${COLOR_NAMES[color]}-${PIECE_NAMES[type]}:${square}:${ordinal}`;
|
|
27
|
+
}
|
|
28
|
+
function promotionId(color, type, square, ordinal) {
|
|
29
|
+
return `${COLOR_NAMES[color]}-${PIECE_NAMES[type]}:promotion:${square}:${ordinal}`;
|
|
30
|
+
}
|
|
31
|
+
function clonePieces(pieces) {
|
|
32
|
+
return Object.fromEntries(Object.entries(pieces).map(([id, piece]) => [id, { ...piece }]));
|
|
33
|
+
}
|
|
34
|
+
function createIdentityState(fen = new Chess().fen(), branchId = "main", ply = 0) {
|
|
35
|
+
const chess = new Chess(fen);
|
|
36
|
+
const pieces = {};
|
|
37
|
+
const squares = {};
|
|
38
|
+
const seen = /* @__PURE__ */ new Map();
|
|
39
|
+
for (const square of SQUARES) {
|
|
40
|
+
const piece = chess.get(square);
|
|
41
|
+
if (!piece) continue;
|
|
42
|
+
const key = `${piece.color}:${piece.type}:${square}`;
|
|
43
|
+
const ordinal = seen.get(key) || 0;
|
|
44
|
+
seen.set(key, ordinal + 1);
|
|
45
|
+
const id = pieceId(piece.color, piece.type, square, ordinal);
|
|
46
|
+
pieces[id] = {
|
|
47
|
+
id,
|
|
48
|
+
color: piece.color,
|
|
49
|
+
type: piece.type,
|
|
50
|
+
originSquare: square,
|
|
51
|
+
createdPly: ply,
|
|
52
|
+
promotedFrom: null,
|
|
53
|
+
capturedAtPly: null,
|
|
54
|
+
retiredAtPly: null
|
|
55
|
+
};
|
|
56
|
+
squares[square] = id;
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
schemaVersion: CHESS_REASONING_SCHEMA_VERSION,
|
|
60
|
+
branchId,
|
|
61
|
+
fen: chess.fen(),
|
|
62
|
+
ply,
|
|
63
|
+
pieces,
|
|
64
|
+
squares,
|
|
65
|
+
promotionSequence: 0
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
function applyIdentityMove(state, moveInput) {
|
|
69
|
+
const chess = new Chess(state.fen);
|
|
70
|
+
const beforeFen = chess.fen();
|
|
71
|
+
const move = chess.move(moveInput);
|
|
72
|
+
const nextPly = state.ply + 1;
|
|
73
|
+
const pieces = clonePieces(state.pieces);
|
|
74
|
+
const squares = { ...state.squares };
|
|
75
|
+
const mover = squares[move.from];
|
|
76
|
+
if (!mover) throw new Error(`No stable piece identity at ${move.from}`);
|
|
77
|
+
let captured = null;
|
|
78
|
+
const capturedSquare = move.isEnPassant() ? `${move.to[0]}${move.from[1]}` : move.to;
|
|
79
|
+
if (move.isCapture() || move.isEnPassant()) {
|
|
80
|
+
captured = squares[capturedSquare] || null;
|
|
81
|
+
if (!captured) throw new Error(`No captured piece identity at ${capturedSquare}`);
|
|
82
|
+
pieces[captured] = { ...pieces[captured], capturedAtPly: nextPly };
|
|
83
|
+
delete squares[capturedSquare];
|
|
84
|
+
}
|
|
85
|
+
delete squares[move.from];
|
|
86
|
+
let rookMoved = null;
|
|
87
|
+
if (move.isKingsideCastle() || move.isQueensideCastle()) {
|
|
88
|
+
const rank = move.from[1];
|
|
89
|
+
const rookFrom = `${move.isKingsideCastle() ? "h" : "a"}${rank}`;
|
|
90
|
+
const rookTo = `${move.isKingsideCastle() ? "f" : "d"}${rank}`;
|
|
91
|
+
rookMoved = squares[rookFrom] || null;
|
|
92
|
+
if (!rookMoved) throw new Error(`No stable rook identity at ${rookFrom}`);
|
|
93
|
+
delete squares[rookFrom];
|
|
94
|
+
squares[rookTo] = rookMoved;
|
|
95
|
+
}
|
|
96
|
+
let promoted = null;
|
|
97
|
+
let promotionSequence = state.promotionSequence;
|
|
98
|
+
if (move.isPromotion()) {
|
|
99
|
+
promotionSequence += 1;
|
|
100
|
+
pieces[mover] = { ...pieces[mover], retiredAtPly: nextPly };
|
|
101
|
+
promoted = promotionId(move.color, move.promotion, move.to, promotionSequence);
|
|
102
|
+
pieces[promoted] = {
|
|
103
|
+
id: promoted,
|
|
104
|
+
color: move.color,
|
|
105
|
+
type: move.promotion,
|
|
106
|
+
originSquare: move.to,
|
|
107
|
+
createdPly: nextPly,
|
|
108
|
+
promotedFrom: mover,
|
|
109
|
+
capturedAtPly: null,
|
|
110
|
+
retiredAtPly: null
|
|
111
|
+
};
|
|
112
|
+
squares[move.to] = promoted;
|
|
113
|
+
} else {
|
|
114
|
+
squares[move.to] = mover;
|
|
115
|
+
}
|
|
116
|
+
const nextState = {
|
|
117
|
+
schemaVersion: CHESS_REASONING_SCHEMA_VERSION,
|
|
118
|
+
branchId: state.branchId,
|
|
119
|
+
fen: chess.fen(),
|
|
120
|
+
ply: nextPly,
|
|
121
|
+
pieces,
|
|
122
|
+
squares,
|
|
123
|
+
promotionSequence
|
|
124
|
+
};
|
|
125
|
+
return {
|
|
126
|
+
state: nextState,
|
|
127
|
+
delta: {
|
|
128
|
+
branchId: state.branchId,
|
|
129
|
+
ply: nextPly,
|
|
130
|
+
uci: `${move.from}${move.to}${move.promotion || ""}`,
|
|
131
|
+
san: move.san,
|
|
132
|
+
mover,
|
|
133
|
+
captured,
|
|
134
|
+
promoted,
|
|
135
|
+
rookMoved,
|
|
136
|
+
beforeFen,
|
|
137
|
+
afterFen: chess.fen()
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// src/chess-reasoning/move-mechanism-facts.ts
|
|
143
|
+
var DIRECTIONS = [
|
|
144
|
+
["N", 0, 1],
|
|
145
|
+
["NE", 1, 1],
|
|
146
|
+
["E", 1, 0],
|
|
147
|
+
["SE", 1, -1],
|
|
148
|
+
["S", 0, -1],
|
|
149
|
+
["SW", -1, -1],
|
|
150
|
+
["W", -1, 0],
|
|
151
|
+
["NW", -1, 1]
|
|
152
|
+
];
|
|
153
|
+
function enemy(color) {
|
|
154
|
+
return color === "w" ? "b" : "w";
|
|
155
|
+
}
|
|
156
|
+
function attackers(board, square, color) {
|
|
157
|
+
return board.attackers(square, color).sort((a, b) => SQUARES2.indexOf(a) - SQUARES2.indexOf(b));
|
|
158
|
+
}
|
|
159
|
+
function ray(board, origin, fileStep, rankStep) {
|
|
160
|
+
const squares = [];
|
|
161
|
+
const file = origin.charCodeAt(0) - 97;
|
|
162
|
+
const rank = Number(origin[1]) - 1;
|
|
163
|
+
for (let distance = 1; distance < 8; distance += 1) {
|
|
164
|
+
const f = file + fileStep * distance;
|
|
165
|
+
const r = rank + rankStep * distance;
|
|
166
|
+
if (f < 0 || f > 7 || r < 0 || r > 7) break;
|
|
167
|
+
const square = `${String.fromCharCode(97 + f)}${r + 1}`;
|
|
168
|
+
squares.push(square);
|
|
169
|
+
if (board.get(square)) break;
|
|
170
|
+
}
|
|
171
|
+
return squares;
|
|
172
|
+
}
|
|
173
|
+
function sliderDirections(type) {
|
|
174
|
+
if (type === "q") return DIRECTIONS;
|
|
175
|
+
return DIRECTIONS.filter(([, dx, dy]) => type === "r" ? !dx || !dy : !!dx && !!dy);
|
|
176
|
+
}
|
|
177
|
+
function moveMechanismFacts(before, after, from, to, beforeIds, afterIds) {
|
|
178
|
+
const moverBefore = before.get(from);
|
|
179
|
+
const moverAfter = after.get(to);
|
|
180
|
+
if (!moverBefore || !moverAfter || moverBefore.color !== moverAfter.color) {
|
|
181
|
+
throw new Error(`Move mechanism has no corresponding mover at ${from}-${to}`);
|
|
182
|
+
}
|
|
183
|
+
const beforeState = beforeIds || createIdentityState(before.fen());
|
|
184
|
+
if (beforeState.fen !== before.fen() || afterIds && afterIds.fen !== after.fen()) {
|
|
185
|
+
throw new Error("Move mechanism identity position does not match the board");
|
|
186
|
+
}
|
|
187
|
+
const actualMove = before.moves({ verbose: true }).find((move) => {
|
|
188
|
+
if (move.from !== from || move.to !== to) return false;
|
|
189
|
+
const replay = new Chess2(before.fen());
|
|
190
|
+
replay.move({ from, to, promotion: move.promotion });
|
|
191
|
+
return replay.fen() === after.fen();
|
|
192
|
+
});
|
|
193
|
+
if (!actualMove) throw new Error(`Move mechanism after-position is not a legal ${from}-${to} result`);
|
|
194
|
+
const afterState = afterIds || applyIdentityMove(beforeState, {
|
|
195
|
+
from,
|
|
196
|
+
to,
|
|
197
|
+
promotion: actualMove.promotion
|
|
198
|
+
}).state;
|
|
199
|
+
const pieceId2 = beforeState.squares[from];
|
|
200
|
+
const pieceIdAfter = afterState.squares[to];
|
|
201
|
+
if (!pieceId2 || !pieceIdAfter || (actualMove.promotion ? afterState.pieces[pieceIdAfter]?.promotedFrom !== pieceId2 : pieceIdAfter !== pieceId2)) {
|
|
202
|
+
throw new Error("Move mechanism mover identities do not match");
|
|
203
|
+
}
|
|
204
|
+
const attackedUnmovedFriendlyPieces = [];
|
|
205
|
+
const stationarySliderRays = [];
|
|
206
|
+
const color = moverBefore.color;
|
|
207
|
+
for (const square of SQUARES2) {
|
|
208
|
+
const piece = before.get(square);
|
|
209
|
+
const samePiece = after.get(square);
|
|
210
|
+
const id = beforeState.squares[square];
|
|
211
|
+
if (!piece || !samePiece || !id || afterState.squares[square] !== id || piece.color !== samePiece.color || piece.type !== samePiece.type) continue;
|
|
212
|
+
if (piece.color === color) {
|
|
213
|
+
const beforeAttackers = attackers(before, square, enemy(color));
|
|
214
|
+
const afterAttackers = attackers(after, square, enemy(color));
|
|
215
|
+
if (beforeAttackers.length || afterAttackers.length) {
|
|
216
|
+
attackedUnmovedFriendlyPieces.push({
|
|
217
|
+
pieceId: id,
|
|
218
|
+
square,
|
|
219
|
+
pieceType: piece.type,
|
|
220
|
+
attackersBefore: beforeAttackers,
|
|
221
|
+
attackersAfter: afterAttackers
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
if (piece.type !== "r" && piece.type !== "b" && piece.type !== "q") continue;
|
|
226
|
+
for (const [direction, dx, dy] of sliderDirections(piece.type)) {
|
|
227
|
+
const beforeSquares = ray(before, square, dx, dy);
|
|
228
|
+
const afterSquares = ray(after, square, dx, dy);
|
|
229
|
+
const beforeSet = new Set(beforeSquares);
|
|
230
|
+
const afterSet = new Set(afterSquares);
|
|
231
|
+
const gainedSquares = afterSquares.filter((target) => !beforeSet.has(target));
|
|
232
|
+
const lostSquares = beforeSquares.filter((target) => !afterSet.has(target));
|
|
233
|
+
if (gainedSquares.length || lostSquares.length) {
|
|
234
|
+
stationarySliderRays.push({
|
|
235
|
+
pieceId: id,
|
|
236
|
+
fromSquare: square,
|
|
237
|
+
color: piece.color,
|
|
238
|
+
pieceType: piece.type,
|
|
239
|
+
direction,
|
|
240
|
+
beforeSquares,
|
|
241
|
+
afterSquares,
|
|
242
|
+
gainedSquares,
|
|
243
|
+
lostSquares
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
const controlDelta = [];
|
|
249
|
+
for (const square of SQUARES2) {
|
|
250
|
+
for (const attackingColor of ["w", "b"]) {
|
|
251
|
+
const attackersBefore = attackers(before, square, attackingColor);
|
|
252
|
+
const attackersAfter = attackers(after, square, attackingColor);
|
|
253
|
+
if (attackersBefore.length !== attackersAfter.length || attackersBefore.some((origin, index) => origin !== attackersAfter[index])) {
|
|
254
|
+
controlDelta.push({ square, color: attackingColor, attackersBefore, attackersAfter });
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
return {
|
|
259
|
+
mover: {
|
|
260
|
+
pieceId: pieceId2,
|
|
261
|
+
pieceIdAfter,
|
|
262
|
+
fromSquare: from,
|
|
263
|
+
toSquare: to,
|
|
264
|
+
color,
|
|
265
|
+
pieceTypeBefore: moverBefore.type,
|
|
266
|
+
pieceTypeAfter: moverAfter.type,
|
|
267
|
+
attackersBefore: attackers(before, from, enemy(color)),
|
|
268
|
+
attackersAfter: attackers(after, to, enemy(color))
|
|
269
|
+
},
|
|
270
|
+
attackedUnmovedFriendlyPieces,
|
|
271
|
+
stationarySliderRays,
|
|
272
|
+
controlDelta,
|
|
273
|
+
checksRun: [
|
|
274
|
+
"mover-geometric-attackers-before-after",
|
|
275
|
+
"unmoved-friendly-piece-geometric-attackers-before-after",
|
|
276
|
+
"stationary-slider-visible-rays-before-after",
|
|
277
|
+
"all-square-both-color-geometric-control-delta"
|
|
278
|
+
],
|
|
279
|
+
scope: "Complete board-local geometric measurements at this move. Attackers include pinned pieces; visible slider rays include the first occupied square. Empty lists mean measured none or no change. No claim of legal captures, safe retreat, exchange profit, forced purpose, or strategic importance."
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// test/owner/move-mechanism-facts.test.mjs
|
|
284
|
+
function facts(fen, uci, suppliedIdentities = true) {
|
|
285
|
+
const before = new Chess3(fen);
|
|
286
|
+
const beforeIds = createIdentityState(fen);
|
|
287
|
+
const { state: afterIds } = applyIdentityMove(beforeIds, {
|
|
288
|
+
from: uci.slice(0, 2),
|
|
289
|
+
to: uci.slice(2, 4),
|
|
290
|
+
promotion: uci.slice(4) || void 0
|
|
291
|
+
});
|
|
292
|
+
const after = new Chess3(afterIds.fen);
|
|
293
|
+
return moveMechanismFacts(
|
|
294
|
+
before,
|
|
295
|
+
after,
|
|
296
|
+
uci.slice(0, 2),
|
|
297
|
+
uci.slice(2, 4),
|
|
298
|
+
...suppliedIdentities ? [beforeIds, afterIds] : []
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
test("KE-REASON-016/021: Rb2+ mover is unattacked while the unmoved rook on d1 is attacked from a4", () => {
|
|
302
|
+
const observed = facts("1k1r3r/2p5/p2p1p2/2P2B2/b3PB1p/5P2/3R2PP/2KR4 w - - 1 25", "d2b2");
|
|
303
|
+
assert.equal(observed.mover.pieceId, "white-rook:d2:0");
|
|
304
|
+
assert.deepEqual(observed.mover.attackersBefore, []);
|
|
305
|
+
assert.deepEqual(observed.mover.attackersAfter, []);
|
|
306
|
+
assert.deepEqual(observed.attackedUnmovedFriendlyPieces.find((piece) => piece.square === "d1"), {
|
|
307
|
+
pieceId: "white-rook:d1:0",
|
|
308
|
+
square: "d1",
|
|
309
|
+
pieceType: "r",
|
|
310
|
+
attackersBefore: ["a4"],
|
|
311
|
+
attackersAfter: ["a4"]
|
|
312
|
+
});
|
|
313
|
+
assert.ok(observed.checksRun.includes("unmoved-friendly-piece-geometric-attackers-before-after"));
|
|
314
|
+
assert.match(observed.scope, /geometric measurements/i);
|
|
315
|
+
});
|
|
316
|
+
test("KE-REASON-016/021: after 7...b6, e5 frees the queen d4 horizontal ray through f4-h4, not a8", () => {
|
|
317
|
+
const observed = facts("r1bqkb1r/p1pp1pp1/1p3n1p/8/2BQP3/2N5/PPP2PPP/R1B1K2R w KQkq - 0 8", "e4e5");
|
|
318
|
+
const east = observed.stationarySliderRays.find((item) => item.fromSquare === "d4" && item.direction === "E");
|
|
319
|
+
assert.deepEqual(east?.beforeSquares, ["e4"]);
|
|
320
|
+
assert.deepEqual(east?.afterSquares, ["e4", "f4", "g4", "h4"]);
|
|
321
|
+
assert.deepEqual(east?.gainedSquares, ["f4", "g4", "h4"]);
|
|
322
|
+
assert.ok(!observed.stationarySliderRays.some((item) => item.fromSquare === "d4" && item.gainedSquares.includes("a8")));
|
|
323
|
+
assert.deepEqual(observed.controlDelta.find((item) => item.square === "g4" && item.color === "w")?.attackersAfter, ["d4"]);
|
|
324
|
+
});
|
|
325
|
+
test("KE-REASON-016/021: control loss occurs at the actual knight move, not the prior pawn push", () => {
|
|
326
|
+
const rootFen = "r1bqkb1r/p1pp1pp1/1p3n1p/8/2BQP3/2N5/PPP2PPP/R1B1K2R w KQkq - 0 8";
|
|
327
|
+
const pawnPush = facts(rootFen, "e4e5");
|
|
328
|
+
assert.equal(pawnPush.controlDelta.find((item) => item.square === "d5" && item.color === "b"), void 0);
|
|
329
|
+
const afterPawnPush = new Chess3(rootFen);
|
|
330
|
+
afterPawnPush.move({ from: "e4", to: "e5" });
|
|
331
|
+
const knightMove = facts(afterPawnPush.fen(), "f6h7");
|
|
332
|
+
assert.deepEqual(knightMove.controlDelta.find((item) => item.square === "d5" && item.color === "b"), {
|
|
333
|
+
square: "d5",
|
|
334
|
+
color: "b",
|
|
335
|
+
attackersBefore: ["f6"],
|
|
336
|
+
attackersAfter: []
|
|
337
|
+
});
|
|
338
|
+
assert.deepEqual(knightMove.mover.attackersBefore, ["e5"]);
|
|
339
|
+
});
|
|
340
|
+
test("KE-REASON-016/021: moving slider is not reported as stationary opening, even along same file", () => {
|
|
341
|
+
const rootFen = "k1B5/1R6/p2B1p2/8/4P2p/5P2/6PP/2K2b2 w - - 7 34";
|
|
342
|
+
const observed = facts(rootFen, "b7b2");
|
|
343
|
+
assert.ok(!observed.stationarySliderRays.some((item) => item.pieceId === observed.mover.pieceId));
|
|
344
|
+
assert.ok(!observed.stationarySliderRays.some((item) => item.fromSquare === "b7" || item.fromSquare === "b2"));
|
|
345
|
+
const afterRook = new Chess3(rootFen);
|
|
346
|
+
afterRook.move({ from: "b7", to: "b2" });
|
|
347
|
+
const response = facts(afterRook.fen(), "f1b5");
|
|
348
|
+
const blocked = response.stationarySliderRays.find((item) => item.fromSquare === "b2" && item.direction === "N");
|
|
349
|
+
assert.deepEqual(blocked?.beforeSquares, ["b3", "b4", "b5", "b6", "b7", "b8"]);
|
|
350
|
+
assert.deepEqual(blocked?.afterSquares, ["b3", "b4", "b5"]);
|
|
351
|
+
assert.deepEqual(blocked?.lostSquares, ["b6", "b7", "b8"]);
|
|
352
|
+
});
|
|
353
|
+
test("KE-REASON-016/021: Bf7+, queen promotion and bishop underpromotion have exact mover identities and controls", () => {
|
|
354
|
+
const bishop = facts("8/4P3/1R2B3/6Bk/7P/8/8/2K5 w - - 3 50", "e6f7");
|
|
355
|
+
assert.equal(bishop.mover.pieceId, bishop.mover.pieceIdAfter);
|
|
356
|
+
assert.equal(bishop.mover.pieceTypeBefore, "b");
|
|
357
|
+
assert.deepEqual(bishop.controlDelta.find((item) => item.square === "e8" && item.color === "w")?.attackersAfter, ["f7"]);
|
|
358
|
+
assert.deepEqual(bishop.controlDelta.find((item) => item.square === "g4" && item.color === "w"), {
|
|
359
|
+
square: "g4",
|
|
360
|
+
color: "w",
|
|
361
|
+
attackersBefore: ["e6"],
|
|
362
|
+
attackersAfter: []
|
|
363
|
+
});
|
|
364
|
+
const mateFen = "8/4P3/1R2B3/6Bk/7P/8/8/2K5 w - - 3 50";
|
|
365
|
+
for (const uci of ["e7e8q", "e7e8b"]) {
|
|
366
|
+
const promotionMate = new Chess3(mateFen);
|
|
367
|
+
promotionMate.move({ from: "e7", to: "e8", promotion: uci.slice(4) });
|
|
368
|
+
assert.equal(promotionMate.isCheckmate(), true, `${uci} is a legal mate at the observed board`);
|
|
369
|
+
assert.equal(facts(mateFen, uci).mover.pieceTypeAfter, uci.slice(4));
|
|
370
|
+
}
|
|
371
|
+
const promotionFen = "8/4P3/1R2B3/6B1/7P/5k2/8/2K5 w - - 7 52";
|
|
372
|
+
const queen = facts(promotionFen, "e7e8q");
|
|
373
|
+
const underpromotion = facts(promotionFen, "e7e8b", false);
|
|
374
|
+
assert.equal(queen.mover.pieceTypeAfter, "q");
|
|
375
|
+
assert.equal(underpromotion.mover.pieceTypeAfter, "b");
|
|
376
|
+
assert.notEqual(queen.mover.pieceId, queen.mover.pieceIdAfter);
|
|
377
|
+
assert.match(queen.mover.pieceIdAfter, /promotion:e8/);
|
|
378
|
+
assert.ok(queen.controlDelta.some((entry) => entry.square === "e7" && entry.color === "w"));
|
|
379
|
+
assert.ok(!underpromotion.controlDelta.some((entry) => entry.square === "e7" && entry.color === "w"));
|
|
380
|
+
});
|
|
381
|
+
test("KE-REASON-016/021: en passant removes the off-target blocker and castling excludes the moved rook", () => {
|
|
382
|
+
const ep = facts("7k/8/8/3pP3/8/8/8/K2R4 w - d6 0 1", "e5d6");
|
|
383
|
+
assert.deepEqual(ep.controlDelta.find((item) => item.square === "e4" && item.color === "b"), {
|
|
384
|
+
square: "e4",
|
|
385
|
+
color: "b",
|
|
386
|
+
attackersBefore: ["d5"],
|
|
387
|
+
attackersAfter: []
|
|
388
|
+
});
|
|
389
|
+
assert.deepEqual(
|
|
390
|
+
ep.stationarySliderRays.find((item) => item.fromSquare === "d1" && item.direction === "N")?.afterSquares,
|
|
391
|
+
["d2", "d3", "d4", "d5", "d6"]
|
|
392
|
+
);
|
|
393
|
+
const castle = facts("4k3/8/8/8/8/8/8/4K2R w K - 0 1", "e1g1");
|
|
394
|
+
assert.ok(!castle.stationarySliderRays.some((item) => item.pieceId === "white-rook:h1:0"));
|
|
395
|
+
assert.equal(castle.mover.toSquare, "g1");
|
|
396
|
+
});
|