Чтение ответа Nardex
Эндпоинт /api/v1/positions/analyze возвращает один PositionAnalysis на каждую отправленную позицию. Эта страница — пополевой
справочник: что значит каждое свойство, как оно связано с остальными и как сложить
пер-ходовой выход в пер-партийный PR. Если ещё не видел форму запроса — начни с 5-минутного quickstart.
Форма ответа
Каждый вызов возвращает один объект. Сигнатура TypeScript (зеркало Rust-исходника):
interface PositionAnalysis {
alternatives: Alt[]; // ранжированные кандидаты, лучший первый
chosen?: Alt; // твой ход; отсутствует, если ты его не передал
equity_loss: number; // chosen.equity vs alternatives[0].equity
grade: 'ok' | 'doubtful' | 'error' | 'blunder';
equity_kind: 'money' | 'match';
forced: boolean; // true, если реального выбора не было
analysis_depth: 'ply_zero' | 'ply_two' | 'ply_three';
}
interface Alt {
play?: MoveDetail[]; // для checker-play решений
cube_action?: 'take' | 'drop' | 'double' | 'no_double';
equity: number;
probabilities: ProbVector; // форма зависит от варианта игры
} Массив alternatives
alternatives — это ранжирование движком легальных ходов-кандидатов (или
cube-действий). Сортировка по убыванию equity — то есть alternatives[0] и есть лучший ход. Длина зависит от уровня глубины и
настроек запроса; для типичных позиций — 5–20 элементов.
У каждого Alt есть либо массив play (решение
checker-play), либо строка cube_action (решение по кубу) — никогда
оба сразу. Что именно — определяет поле decision.type запроса.
chosen vs best
Если в запросе ты передал сыгранный ход, движок ищет его в alternatives и возвращает как chosen. Два производных числа:
equity_loss = alternatives[0].equity - chosen.equity. Всегда ≥ 0. Ноль значит, что ты нашёл лучший ход.grade= бакет пороговequity_loss. Дефолтные пороги: ok < 0.02, doubtful 0.02–0.08, error 0.08–0.20, blunder ≥ 0.20. Конфигурируется пер-деплою.
Если ход не передан (decision.played пустой), chosen отсутствует, equity_loss равен 0, а grade — 'ok' по конвенции. Используй chosen === undefined, чтобы поймать этот случай.
Probabilities — длинные нарды vs короткие
В Alt.probabilities лежит разбиение сети на win/lose. Форма различается по
варианту, потому что системы призов различаются:
// длинные нарды — вложенные ординальные вероятности
{
win: 0.62, // P(X выигрывает, включая марс и кокс)
win_m: 0.18, // P(победа >= марс)
win_k: 0.02, // P(победа >= кокс)
lose: 0.38,
lose_m: 0.05,
lose_k: 0.0
}
// короткие нарды (backgammon) — money-game призовые бакеты
{
win: 0.62,
win_g: 0.14, // P(выигрыш гаммоном)
win_bg: 0.01, // P(выигрыш бэкгаммоном)
lose: 0.38,
lose_g: 0.04,
lose_bg: 0.0
} Важно для длинных нард: поля win_m/win_k — вложенные ординальные,
а не 6-классовый softmax. win_m = P(победа >= марс) включает кокс, win_k — самое строгое. Не пытайся складывать их как независимые бакеты.
Уровни analysis_depth
Доступны три настройки глубины:
ply_zero— прямая оценка нейросетью, доли миллисекунды на GPU. Подходит для массового скоринга или low-latency UI.ply_two— 2-ply lookahead с NN на листьях. Дефолт для analyze-эндпоинта. Десятки мс.ply_three— более глубокий поиск, сотни мс. Самый точный, но квоты и rate-limit бюджеты на этом уровне жёстче.
Значения equity с разных глубин не строго сравнимы — не агрегируй equity_loss из ply_zero и ply_two в один PR без нормализации.
forced и другие крайние случаи
forced: true означает, что у ходящей стороны не было реального выбора —
обычно один легальный ход (или позиция куба, где решение недоступно). Когда считаешь PR
по партии, отфильтруй forced-ходы и из числителя, и из знаменателя.
Их включение искусственно занижает PR.
Другие крайние случаи:
- Game-over позиции (ходы невозможны) возвращают
alternatives: []иforced: true. - Bear-off позиции в позднем race могут иметь
alternativesдлиной 1 — единственный легальный ход. Считается forced. - Cube-решения в Crawford-партии возвращают
alternativesтолько с'no_double'. Куб недоступен.
Агрегация пер-ходового в PR
Стандартный рецепт (конвенция gnubg) — JavaScript:
function gamePR(positions) {
const meaningful = positions.filter(p => !p.forced);
if (meaningful.length === 0) return 0;
const totalLoss = meaningful.reduce((acc, p) => acc + p.equity_loss, 0);
return (totalLoss / meaningful.length) * 500;
} Если хочешь PR-позиционный и PR-кубовый отдельно, группируй по chosen.cube_action !== undefined:
function splitPR(positions) {
const positional = positions.filter(p => !p.forced && !p.chosen?.cube_action);
const cubeActions = positions.filter(p => !p.forced && p.chosen?.cube_action);
const pr = arr => arr.length === 0 ? 0
: (arr.reduce((a, p) => a + p.equity_loss, 0) / arr.length) * 500;
return { positional: pr(positional), cube: pr(cubeActions) };
} И на Python:
def game_pr(positions):
meaningful = [p for p in positions if not p["forced"]]
if not meaningful:
return 0.0
total = sum(p["equity_loss"] for p in meaningful)
return (total / len(meaningful)) * 500 Money equity vs match equity
Поле equity_kind на каждом PositionAnalysis говорит, в каких единицах даны
значения equity — в money или match-winning-chance. Шкалы разные:
'money'— equity в очках money-game: победа = +1, проигрыш = −1, выигрыш гаммоном = +2 и так далее. Диапазон обычно [−3, +3].'match'— equity как шанс выиграть матч с текущего счёта. Диапазон [0, 1]. Считается по таблице матч-equity (по умолчанию: post-Crawford safe).
Если смешать money и match equity в одном PR-агрегате, цифры будут бессмысленны.
Фильтруй по equity_kind, когда оцениваешь целый матч.
Дальше
- 5-минутный quickstart — минимально-жизнеспособный запрос.
- Nardex vs gnubg vs XG — отличия движков.
- Разработчикам — запросить доступ к бете.
- How to Analyze Backgammon Games — человеческая версия этих метрик.
- «Как анализировать партию в нардах» — то же для длинных нард (на русском).
FAQ
- Что именно означает
equity_loss? - Это equity лучшего хода движка минус equity сделанного (chosen) хода, выраженное в единицах equity money-game. Всегда неотрицательное; 0 значит, что был сыгран лучший ход.
- Почему
gradeотделён отequity_loss? grade— это дискретизацияequity_lossпо фиксированным порогам (ok / doubtful / error / blunder). Пороги зашиты в движке, но конфигурируемы пер-деплою. Воспринимайgradeкак UI-подсказку, аequity_loss— как источник правды.- Что лежит в поле
probabilities? - Для длинных нард: вложенные ординальные — { win, win_m, win_k, lose, lose_m, lose_k }. Для коротких нард (backgammon): { win, win_g, win_bg, lose, lose_g, lose_bg }. Названия разные, потому что системы призов разные. Точная форма — в выходах
crates/engine. - Как агрегировать пер-ходовой
equity_lossв PR? - PR (конвенция gnubg) = (сумма equity_loss по не-forced ходам / число не-forced ходов) × 500. Ходы с
forced: trueисключаются и из числителя, и из знаменателя. - Анализируются ли также cube-действия?
- Да — когда
decision.type === "cube_decision",chosenиalternatives— это объектыcube_action(а неplay). Считай их отдельно, когда строишь PR-позиционный vs PR-кубовый. - Что говорит
analysis_depth? - Уровень глубины поиска.
ply_zero= прямая оценка нейросети (самый быстрый).ply_two= 2-ply lookahead с сетью на листьях (по умолчанию).ply_three= более глубокий поиск, медленнее. Значения equity с разных глубин не строго сравнимы.