jev-chess 1.0.0
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/ARCHITECTURE.md +148 -0
- package/README.md +109 -0
- package/dist/chessEngine.d.ts +36 -0
- package/dist/chessEngine.d.ts.map +1 -0
- package/dist/chessEngine.js +173 -0
- package/dist/chessEngine.js.map +1 -0
- package/dist/classicMatches.d.ts +50 -0
- package/dist/classicMatches.d.ts.map +1 -0
- package/dist/classicMatches.js +223 -0
- package/dist/classicMatches.js.map +1 -0
- package/dist/demo.d.ts +2 -0
- package/dist/demo.d.ts.map +1 -0
- package/dist/demo.js +142 -0
- package/dist/demo.js.map +1 -0
- package/dist/gameReviewer.d.ts +11 -0
- package/dist/gameReviewer.d.ts.map +1 -0
- package/dist/gameReviewer.js +89 -0
- package/dist/gameReviewer.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/moveEvaluator.d.ts +11 -0
- package/dist/moveEvaluator.d.ts.map +1 -0
- package/dist/moveEvaluator.js +103 -0
- package/dist/moveEvaluator.js.map +1 -0
- package/dist/moveResolver.d.ts +15 -0
- package/dist/moveResolver.d.ts.map +1 -0
- package/dist/moveResolver.js +97 -0
- package/dist/moveResolver.js.map +1 -0
- package/dist/personaEngine.d.ts +15 -0
- package/dist/personaEngine.d.ts.map +1 -0
- package/dist/personaEngine.js +151 -0
- package/dist/personaEngine.js.map +1 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +1490 -0
- package/dist/server.js.map +1 -0
- package/dist/typeSafeClient.d.ts +13 -0
- package/dist/typeSafeClient.d.ts.map +1 -0
- package/dist/typeSafeClient.js +336 -0
- package/dist/typeSafeClient.js.map +1 -0
- package/dist/types.d.ts +100 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/dist/web/main.d.ts +2 -0
- package/dist/web/main.d.ts.map +1 -0
- package/dist/web/main.js +620 -0
- package/dist/web/main.js.map +1 -0
- package/package.json +35 -0
- package/public/app.js +45 -0
- package/public/index.html +790 -0
- package/src/chessEngine.ts +191 -0
- package/src/classicMatches.ts +291 -0
- package/src/demo.ts +160 -0
- package/src/gameReviewer.ts +113 -0
- package/src/index.ts +8 -0
- package/src/moveEvaluator.ts +122 -0
- package/src/moveResolver.ts +120 -0
- package/src/personaEngine.ts +193 -0
- package/src/server.ts +1509 -0
- package/src/typeSafeClient.ts +321 -0
- package/src/types.ts +118 -0
- package/src/web/main.ts +615 -0
- package/tests/chess.test.ts +206 -0
- package/tsconfig.json +21 -0
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
import {
|
|
2
|
+
TypeSafeClient,
|
|
3
|
+
choice,
|
|
4
|
+
score,
|
|
5
|
+
noul,
|
|
6
|
+
type Questions,
|
|
7
|
+
type SystemOneRequest,
|
|
8
|
+
type SystemOneResult,
|
|
9
|
+
} from "@typesafe-ai/sdk";
|
|
10
|
+
|
|
11
|
+
export { choice, score, noul };
|
|
12
|
+
|
|
13
|
+
export interface ITypeSafeClient {
|
|
14
|
+
systemOne<const Q extends Questions>(
|
|
15
|
+
request: SystemOneRequest<Q>
|
|
16
|
+
): Promise<SystemOneResult<Q>>;
|
|
17
|
+
readonly isLive: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Intelligent simulation engine for when TYPESAFE_API_KEY is not set.
|
|
22
|
+
* Produces calibrated, deterministic System One distributions matching Jev's output schema.
|
|
23
|
+
*/
|
|
24
|
+
class SimulatedTypeSafeClient implements ITypeSafeClient {
|
|
25
|
+
readonly isLive = false;
|
|
26
|
+
|
|
27
|
+
public async systemOne<const Q extends Questions>(
|
|
28
|
+
request: SystemOneRequest<Q>
|
|
29
|
+
): Promise<SystemOneResult<Q>> {
|
|
30
|
+
const answers: Record<string, any> = {};
|
|
31
|
+
const stateStr =
|
|
32
|
+
typeof request.state === "string"
|
|
33
|
+
? request.state
|
|
34
|
+
: JSON.stringify(request.state);
|
|
35
|
+
|
|
36
|
+
for (const [id, q] of Object.entries(request.questions)) {
|
|
37
|
+
if (!q) continue;
|
|
38
|
+
|
|
39
|
+
if (q.type === "noul") {
|
|
40
|
+
let prob = 0.5;
|
|
41
|
+
const text = `${stateStr} ${JSON.stringify(q.instructions)}`.toLowerCase();
|
|
42
|
+
const evalMove = (request.state as any)?.evaluated_move;
|
|
43
|
+
const san = evalMove?.san ?? (text.includes("san") ? text : "");
|
|
44
|
+
|
|
45
|
+
if (text.includes("king") || text.includes("attack")) {
|
|
46
|
+
if (san.includes("#")) prob = 0.99;
|
|
47
|
+
else if (san.includes("+") || evalMove?.is_check) prob = 0.91;
|
|
48
|
+
else if (san.includes("f7") || san.includes("h7") || san.includes("g7") || san.includes("f2")) prob = 0.84;
|
|
49
|
+
else if (evalMove?.is_capture || san.includes("x")) prob = 0.68;
|
|
50
|
+
else if (san.includes("o-o") || san.includes("castle")) prob = 0.05;
|
|
51
|
+
else prob = 0.28;
|
|
52
|
+
} else if (text.includes("psychological") || text.includes("pressure") || text.includes("provocative")) {
|
|
53
|
+
if (san.includes("#")) prob = 0.99;
|
|
54
|
+
else if (san.includes("+") || evalMove?.is_check) prob = 0.88;
|
|
55
|
+
else if (evalMove?.is_capture || san.includes("x")) prob = 0.81;
|
|
56
|
+
else if (san.includes("d4") || san.includes("e4") || san.includes("c4") || san.includes("f4")) prob = 0.65;
|
|
57
|
+
else if (san.includes("o-o")) prob = 0.22;
|
|
58
|
+
else prob = 0.35;
|
|
59
|
+
} else if (text.includes("decisive_sacrifice") || text.includes("sacrifice")) {
|
|
60
|
+
prob = 0.94;
|
|
61
|
+
} else if (text.includes("blunder") || text.includes("risk")) {
|
|
62
|
+
prob = text.includes("??") ? 0.92 : 0.24;
|
|
63
|
+
} else if (text.includes("material")) {
|
|
64
|
+
prob = evalMove?.is_capture || san.includes("x") ? 0.88 : 0.22;
|
|
65
|
+
}
|
|
66
|
+
answers[id] = {
|
|
67
|
+
type: "noul",
|
|
68
|
+
noul: Math.round(prob * 100) / 100,
|
|
69
|
+
};
|
|
70
|
+
} else if (q.type === "choice") {
|
|
71
|
+
const criteria = (q.criteria ?? {}) as Record<string, string | null>;
|
|
72
|
+
const keys = Object.keys(criteria);
|
|
73
|
+
let selected = "none";
|
|
74
|
+
const probabilities: Record<string, number> = {};
|
|
75
|
+
|
|
76
|
+
const userIntent =
|
|
77
|
+
typeof request.state === "object" && request.state !== null && "user_intent" in request.state
|
|
78
|
+
? String((request.state as any).user_intent).toLowerCase()
|
|
79
|
+
: stateStr.toLowerCase();
|
|
80
|
+
|
|
81
|
+
const evalMove = (request.state as any)?.evaluated_move;
|
|
82
|
+
const san = evalMove?.san ?? "";
|
|
83
|
+
const isCapture = evalMove?.is_capture ?? san.includes("x");
|
|
84
|
+
const isCheck = evalMove?.is_check ?? san.includes("+");
|
|
85
|
+
const isCastling = evalMove?.is_castling ?? (san.includes("O-O") || san.includes("o-o"));
|
|
86
|
+
|
|
87
|
+
let bestScore = 0;
|
|
88
|
+
|
|
89
|
+
for (const k of keys) {
|
|
90
|
+
if (k === "none") continue;
|
|
91
|
+
let s = 0;
|
|
92
|
+
const desc = (criteria[k] ?? "").toLowerCase();
|
|
93
|
+
const kLower = k.toLowerCase();
|
|
94
|
+
|
|
95
|
+
// Intent resolution matching
|
|
96
|
+
if (userIntent.includes("knight") && (desc.includes("knight") || kLower.startsWith("n"))) s += 10;
|
|
97
|
+
if (userIntent.includes("bishop") && (desc.includes("bishop") || kLower.startsWith("b"))) s += 10;
|
|
98
|
+
if (userIntent.includes("queen's pawn") || userIntent.includes("d-pawn")) {
|
|
99
|
+
if (userIntent.includes("two square") && (kLower === "d4" || desc.includes("d2 to d4"))) s += 20;
|
|
100
|
+
else if (kLower === "d3" || desc.includes("d2 to d3")) s += 15;
|
|
101
|
+
}
|
|
102
|
+
if (userIntent.includes("king's pawn") || userIntent.includes("e-pawn")) {
|
|
103
|
+
if (userIntent.includes("two square") && (kLower === "e4" || desc.includes("e2 to e4"))) s += 20;
|
|
104
|
+
else if (kLower === "e3" || desc.includes("e2 to e3")) s += 15;
|
|
105
|
+
}
|
|
106
|
+
if (userIntent.includes("castle") && (desc.includes("castle") || kLower.includes("o-o"))) s += 25;
|
|
107
|
+
if (userIntent.includes("center") && (kLower === "d4" || kLower === "e4" || kLower === "nf3" || kLower === "nc3")) s += 6;
|
|
108
|
+
if (userIntent.includes("attack") && (desc.includes("captures") || kLower === "nf3")) s += 5;
|
|
109
|
+
|
|
110
|
+
// Strategic theme matching for any move
|
|
111
|
+
if (stateStr.includes("strategic_theme") || desc.includes("pawn") || desc.includes("develop")) {
|
|
112
|
+
if (isCastling && kLower === "prophylaxis") s += 40;
|
|
113
|
+
else if (isCheck && (kLower === "tactical_strike" || kLower === "king_hunt")) s += 38;
|
|
114
|
+
else if (isCapture && (kLower === "tactical_strike" || kLower === "simplification")) s += 36;
|
|
115
|
+
else if (san.startsWith("d") || san.startsWith("e") || san.startsWith("c") || san.startsWith("f")) {
|
|
116
|
+
if (kLower === "pawn_break") s += 35;
|
|
117
|
+
} else if (san.startsWith("N") || san.startsWith("B") || san.startsWith("R") || san.startsWith("Q")) {
|
|
118
|
+
if (kLower === "piece_activation") s += 35;
|
|
119
|
+
} else if (kLower === "prophylaxis") {
|
|
120
|
+
s += 20;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Historical match archetype classification
|
|
125
|
+
if (stateStr.includes("match_meta") || stateStr.includes("immortal") || stateStr.includes("century")) {
|
|
126
|
+
if ((stateStr.includes("immortal-game") || stateStr.includes("anderssen")) && kLower === "romantic_swashbuckler") s += 60;
|
|
127
|
+
else if ((stateStr.includes("opera") || stateStr.includes("morphy")) && kLower === "dynamic_initiative") s += 60;
|
|
128
|
+
else if ((stateStr.includes("century") || stateStr.includes("fischer")) && kLower === "dynamic_initiative") s += 60;
|
|
129
|
+
else if (stateStr.includes("kasparov") && kLower === "tactical_firestorm") s += 60;
|
|
130
|
+
else if (stateStr.includes("tal") && kLower === "tactical_firestorm") s += 60;
|
|
131
|
+
else if (kLower === "romantic_swashbuckler" || kLower === "tactical_firestorm") s += 20;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Mistake archetype diagnostics
|
|
135
|
+
if (stateStr.includes("??") || stateStr.includes("blunder")) {
|
|
136
|
+
if (kLower === "king_safety_negligence") s += 40;
|
|
137
|
+
if (kLower === "tactical_blindness") s += 25;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (s > bestScore) {
|
|
141
|
+
bestScore = s;
|
|
142
|
+
selected = k;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// If no match found or unrecognized command
|
|
147
|
+
if (bestScore === 0) {
|
|
148
|
+
selected = keys.includes("none") ? "none" : (keys[0] ?? "none");
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const isNone = selected === "none";
|
|
152
|
+
const baseConf = isNone ? 0.25 : 0.91;
|
|
153
|
+
|
|
154
|
+
for (const k of keys) {
|
|
155
|
+
const weight = k === selected ? baseConf : (1 - baseConf) / Math.max(1, keys.length - 1);
|
|
156
|
+
probabilities[k] = Math.round(weight * 100) / 100;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
answers[id] = {
|
|
160
|
+
type: "choice",
|
|
161
|
+
choice: selected,
|
|
162
|
+
confidence: baseConf,
|
|
163
|
+
probabilities,
|
|
164
|
+
};
|
|
165
|
+
} else if (q.type === "score") {
|
|
166
|
+
const criteria = (q.criteria ?? []) as readonly unknown[];
|
|
167
|
+
const numLevels = criteria.length;
|
|
168
|
+
const text = `${stateStr} ${JSON.stringify(q.instructions)}`.toLowerCase();
|
|
169
|
+
|
|
170
|
+
// Target score based on evaluated move
|
|
171
|
+
let targetScore = 1.0;
|
|
172
|
+
const evalMove = (request.state as any)?.evaluated_move;
|
|
173
|
+
const san = (evalMove?.san ?? (text.includes("san") ? text : "")).toLowerCase();
|
|
174
|
+
const isCapture = evalMove?.is_capture ?? san.includes("x");
|
|
175
|
+
const isCheck = evalMove?.is_check ?? san.includes("+");
|
|
176
|
+
const isCastling = evalMove?.is_castling ?? (san.includes("o-o") || san.includes("castle"));
|
|
177
|
+
|
|
178
|
+
if (text.includes("brilliance")) {
|
|
179
|
+
targetScore = 2.9; // Classic immortal matches are top-tier brilliance
|
|
180
|
+
} else if (text.includes("overall sharpness") || text.includes("volatility")) {
|
|
181
|
+
targetScore = 2.8;
|
|
182
|
+
} else if (text.includes("aggress") || text.includes("sharp")) {
|
|
183
|
+
if (san.includes("#") || san.includes("bxf7") || san.includes("rxd4") || san.includes("be6")) {
|
|
184
|
+
targetScore = 2.9;
|
|
185
|
+
} else if (isCheck) {
|
|
186
|
+
targetScore = 2.6;
|
|
187
|
+
} else if (isCapture) {
|
|
188
|
+
targetScore = 2.2;
|
|
189
|
+
} else if (san.startsWith("d4") || san.startsWith("e4") || san.startsWith("c4") || san.startsWith("f4")) {
|
|
190
|
+
targetScore = 1.7;
|
|
191
|
+
} else if (isCastling) {
|
|
192
|
+
targetScore = 0.4;
|
|
193
|
+
} else if (san.startsWith("n") || san.startsWith("b")) {
|
|
194
|
+
targetScore = 0.9;
|
|
195
|
+
} else {
|
|
196
|
+
targetScore = 0.6;
|
|
197
|
+
}
|
|
198
|
+
} else if (text.includes("prophyl")) {
|
|
199
|
+
if (isCastling) targetScore = 2.9;
|
|
200
|
+
else if (san.startsWith("a3") || san.startsWith("h3") || san.startsWith("c3") || san.startsWith("d3")) targetScore = 2.3;
|
|
201
|
+
else if (isCheck || isCapture) targetScore = 0.4;
|
|
202
|
+
else targetScore = 1.2;
|
|
203
|
+
} else if (text.includes("complex") || text.includes("tension")) {
|
|
204
|
+
if (san.includes("#") || san.includes("bxf7") || san.includes("??")) targetScore = 2.9;
|
|
205
|
+
else if (isCheck || isCapture) targetScore = 2.2;
|
|
206
|
+
else if (isCastling) targetScore = 0.5;
|
|
207
|
+
else if (san.startsWith("d") || san.startsWith("e")) targetScore = 1.5;
|
|
208
|
+
else targetScore = 1.0;
|
|
209
|
+
} else if (text.includes("difficult")) {
|
|
210
|
+
targetScore = text.includes("??") ? 2.8 : 0.6;
|
|
211
|
+
} else {
|
|
212
|
+
targetScore = 1.2;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
targetScore = Math.max(0, Math.min(numLevels - 1, targetScore));
|
|
216
|
+
|
|
217
|
+
const probabilities: Record<string, number> = {};
|
|
218
|
+
for (let i = 0; i < numLevels; i++) {
|
|
219
|
+
const dist = Math.abs(i - targetScore);
|
|
220
|
+
probabilities[i.toString()] = Math.round(Math.exp(-dist * 1.5) * 100) / 100;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
answers[id] = {
|
|
224
|
+
type: "score",
|
|
225
|
+
score: Math.round(targetScore * 10) / 10,
|
|
226
|
+
confidence: 0.91,
|
|
227
|
+
legend: Object.fromEntries(criteria.map((c, i) => [i.toString(), c])),
|
|
228
|
+
probabilities,
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return {
|
|
234
|
+
model: "jev-latest (offline-simulated)",
|
|
235
|
+
answers: answers as any,
|
|
236
|
+
usage: {
|
|
237
|
+
input_tokens: 280,
|
|
238
|
+
output_tokens: 45,
|
|
239
|
+
},
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
class LiveTypeSafeClientWrapper implements ITypeSafeClient {
|
|
245
|
+
readonly isLive = true;
|
|
246
|
+
private client: TypeSafeClient;
|
|
247
|
+
|
|
248
|
+
constructor(apiKey?: string) {
|
|
249
|
+
this.client = new TypeSafeClient({ apiKey });
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
public async systemOne<const Q extends Questions>(
|
|
253
|
+
request: SystemOneRequest<Q>
|
|
254
|
+
): Promise<SystemOneResult<Q>> {
|
|
255
|
+
return this.client.systemOne(request);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
let clientInstance: ITypeSafeClient | null = null;
|
|
260
|
+
|
|
261
|
+
function getStoredApiKey(): string | null {
|
|
262
|
+
if (typeof process !== "undefined" && process?.env?.["TYPESAFE_API_KEY"]) {
|
|
263
|
+
return process.env["TYPESAFE_API_KEY"];
|
|
264
|
+
}
|
|
265
|
+
if (typeof window !== "undefined" && typeof window.localStorage !== "undefined") {
|
|
266
|
+
return window.localStorage.getItem("TYPESAFE_API_KEY");
|
|
267
|
+
}
|
|
268
|
+
return null;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function setStoredApiKey(key: string | null) {
|
|
272
|
+
if (typeof process !== "undefined" && process?.env) {
|
|
273
|
+
if (key) {
|
|
274
|
+
process.env["TYPESAFE_API_KEY"] = key;
|
|
275
|
+
} else {
|
|
276
|
+
delete process.env["TYPESAFE_API_KEY"];
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
if (typeof window !== "undefined" && typeof window.localStorage !== "undefined") {
|
|
280
|
+
if (key) {
|
|
281
|
+
window.localStorage.setItem("TYPESAFE_API_KEY", key);
|
|
282
|
+
} else {
|
|
283
|
+
window.localStorage.removeItem("TYPESAFE_API_KEY");
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
export function setApiKey(key: string | null | undefined): boolean {
|
|
289
|
+
if (key && key.trim().length > 0) {
|
|
290
|
+
const cleanKey = key.trim();
|
|
291
|
+
setStoredApiKey(cleanKey);
|
|
292
|
+
clientInstance = new LiveTypeSafeClientWrapper(cleanKey);
|
|
293
|
+
return true;
|
|
294
|
+
} else {
|
|
295
|
+
setStoredApiKey(null);
|
|
296
|
+
clientInstance = new SimulatedTypeSafeClient();
|
|
297
|
+
return false;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
export function getApiKeyStatus(): { isLive: boolean; maskedKey?: string } {
|
|
302
|
+
const current = getTypeSafeClient();
|
|
303
|
+
const key = getStoredApiKey();
|
|
304
|
+
if (current.isLive && key) {
|
|
305
|
+
const visible = key.length > 8 ? `${key.slice(0, 4)}...${key.slice(-4)}` : "ts_••••••••";
|
|
306
|
+
return { isLive: true, maskedKey: visible };
|
|
307
|
+
}
|
|
308
|
+
return { isLive: false };
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
export function getTypeSafeClient(): ITypeSafeClient {
|
|
312
|
+
if (clientInstance) return clientInstance;
|
|
313
|
+
|
|
314
|
+
const key = getStoredApiKey();
|
|
315
|
+
if (key && key.trim().length > 0) {
|
|
316
|
+
clientInstance = new LiveTypeSafeClientWrapper(key);
|
|
317
|
+
} else {
|
|
318
|
+
clientInstance = new SimulatedTypeSafeClient();
|
|
319
|
+
}
|
|
320
|
+
return clientInstance;
|
|
321
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Domain types for TypeSafe-powered Chess Moves and Games.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export type GamePhase = "opening" | "middlegame" | "endgame";
|
|
6
|
+
|
|
7
|
+
export interface AnnotatedMove {
|
|
8
|
+
san: string;
|
|
9
|
+
from: string;
|
|
10
|
+
to: string;
|
|
11
|
+
piece: string;
|
|
12
|
+
color: "white" | "black";
|
|
13
|
+
isCapture: boolean;
|
|
14
|
+
capturedPiece?: string;
|
|
15
|
+
isCheck: boolean;
|
|
16
|
+
isCastling: boolean;
|
|
17
|
+
description: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface MaterialBalance {
|
|
21
|
+
whiteScore: number;
|
|
22
|
+
blackScore: number;
|
|
23
|
+
diff: number;
|
|
24
|
+
description: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface BoardContext {
|
|
28
|
+
fen: string;
|
|
29
|
+
turn: "white" | "black";
|
|
30
|
+
moveNumber: number;
|
|
31
|
+
inCheck: boolean;
|
|
32
|
+
materialBalance: MaterialBalance;
|
|
33
|
+
gamePhase: GamePhase;
|
|
34
|
+
recentHistory: string[];
|
|
35
|
+
candidateMoves: AnnotatedMove[];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface MoveIntentResult {
|
|
39
|
+
query: string;
|
|
40
|
+
matchedMove: AnnotatedMove | null;
|
|
41
|
+
san: string;
|
|
42
|
+
confidence: number;
|
|
43
|
+
probabilities: Record<string, number>;
|
|
44
|
+
clarificationNeeded: boolean;
|
|
45
|
+
alternativeCandidates: string[];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface MoveEvaluation {
|
|
49
|
+
san: string;
|
|
50
|
+
tacticalSharpness: {
|
|
51
|
+
score: number;
|
|
52
|
+
level: string;
|
|
53
|
+
confidence: number;
|
|
54
|
+
};
|
|
55
|
+
strategicTheme: {
|
|
56
|
+
theme: string;
|
|
57
|
+
confidence: number;
|
|
58
|
+
};
|
|
59
|
+
kingAttackRisk: {
|
|
60
|
+
probability: number;
|
|
61
|
+
};
|
|
62
|
+
psychologicalPressure: {
|
|
63
|
+
probability: number;
|
|
64
|
+
};
|
|
65
|
+
commentaryBadge: string;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export type PersonaId = "tal" | "petrosian" | "capablanca" | "coffeehouse";
|
|
69
|
+
|
|
70
|
+
export interface PersonaProfile {
|
|
71
|
+
id: PersonaId;
|
|
72
|
+
name: string;
|
|
73
|
+
title: string;
|
|
74
|
+
quote: string;
|
|
75
|
+
weights: {
|
|
76
|
+
aggression: number;
|
|
77
|
+
prophylaxis: number;
|
|
78
|
+
complexity: number;
|
|
79
|
+
materialGreed: number;
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface DimensionScores {
|
|
84
|
+
aggression: number;
|
|
85
|
+
prophylaxis: number;
|
|
86
|
+
complexity: number;
|
|
87
|
+
materialGreed: number;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export interface PersonaScoredMove {
|
|
91
|
+
move: AnnotatedMove;
|
|
92
|
+
compositeScore: number;
|
|
93
|
+
dimensionScores: DimensionScores;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export interface PersonaDecision {
|
|
97
|
+
persona: PersonaProfile;
|
|
98
|
+
selectedMove: AnnotatedMove;
|
|
99
|
+
rankedCandidates: PersonaScoredMove[];
|
|
100
|
+
rationale: string;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export type MistakeArchetype =
|
|
104
|
+
| "tactical_blindness"
|
|
105
|
+
| "poisoned_pawn_greed"
|
|
106
|
+
| "over_extension"
|
|
107
|
+
| "passive_concession"
|
|
108
|
+
| "king_safety_negligence"
|
|
109
|
+
| "sound_move";
|
|
110
|
+
|
|
111
|
+
export interface BlunderDiagnosis {
|
|
112
|
+
moveNumber: number;
|
|
113
|
+
playedMove: string;
|
|
114
|
+
mistakeArchetype: MistakeArchetype;
|
|
115
|
+
defensiveDifficulty: number; // 0 (trivial) to 3 (hopeless)
|
|
116
|
+
tacticalTension: number;
|
|
117
|
+
coachAdvice: string;
|
|
118
|
+
}
|