feat(robot): occasional off-strategy deviation, strict in the endgame
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 9s
CI / integration (pull_request) Successful in 13s
CI / ui (pull_request) Has been skipped
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m9s

The robot followed its per-game playToWin/lose intent on every move, which made
the outcome too predictable. It now flips that intent for a single move on ~20%
of opening/midgame turns (a winning robot eases off, a losing one surges ahead),
so the chosen strategy may not pan out — which favours the human. The chance
tapers linearly to 0 over the last 14 tiles in the bag and is 0 once the bag is
empty, so the endgame follows the chosen strategy strictly.

The decision is deterministic from the seed (mix(seed,"deviate",moveCount)) and
applies to both robot paths via the shared selectMove; the per-game play-to-win
intent the admin card shows is unchanged. Adds deviateProb/deviates helpers and
unit tests (taper bounds + monotonicity, never-in-endgame, determinism, ~20%
distribution); bakes the behaviour into ARCHITECTURE §7, FUNCTIONAL (+_ru),
backend/README, PRERELEASE and PLAN Stage 5.
This commit is contained in:
Ilia Denisov
2026-06-15 21:37:23 +02:00
parent 9e6899bb7d
commit 3bceafbc12
9 changed files with 156 additions and 16 deletions
+8 -1
View File
@@ -407,7 +407,14 @@ English game the Latin pool.
(`engine.Candidates`) the move whose resulting lead (playing to win) or deficit
(playing to lose) is closest to a small band (**130 points**), rather than
always the maximum; with no legal play it exchanges a full rack when the bag can
refill it, else passes.
refill it, else passes. On **≈20%** of moves through the opening and midgame it
**deviates** — playing that single move toward the *opposite* band (a winning
robot eases off, a losing one surges ahead), so the chosen strategy may not pan
out, which favours the human; the deviation chance tapers linearly to **0 over
the last 14 tiles in the bag** and is **0 once the bag is empty**, so the endgame
follows the per-game intent strictly. It is **deterministic from the seed**
(`mix(seed,"deviate",moveCount)`), a per-move wobble that leaves the per-game
play-to-win intent (and the admin card) unchanged.
- **Timing**: the per-move delay is **move-number-aware** — a right-skewed sample
(exponent k=4, short delays frequent) from a band that interpolates from
**[3, 10] min** at the first move to **[10, 90] min** by ~28 moves, so openings are
+7 -4
View File
@@ -141,8 +141,10 @@ When auto-match finds no human within the wait window (1.53 minutes), a robot
takes the empty seat of the game you are already waiting in. It is meant to feel like a person: it
decides once per game whether to play to win (about 40% of the time, so the human
wins most games), aims for a close score rather than crushing or throwing the game,
and plays at a human pace — short thinking times for most moves, the occasional long
one, and a night-time pause that tracks the player's own day. It answers a nudge
now and then plays a single move against that plan — a surprise lead or a slack move —
yet holds to the plan once the bag empties, and plays at a human pace — short thinking
times for most moves, the occasional long one, and a night-time pause that tracks the
player's own day. It answers a nudge
within a few minutes and nudges back when the player has been away a long time. It
carries a human-like, language-appropriate name (a Russian game draws mostly Russian
names); it does not chat, and **silently ignores friend requests** — a request to a
@@ -150,8 +152,9 @@ robot stays pending and expires, exactly like a human who never responds.
The same robot also backs the **honest-AI quick game** the player chooses directly (above). There
it makes no pretence: it is shown as **🤖** everywhere, joins and moves at once (no thinking time
or night pause), keeps the same strength (it still plays to win only about 40% of the time), and
chat, nudge and add-friend are off. AI games are **practice** — they never count toward a player's
or night pause), keeps the same strength (it still plays to win only about 40% of the time, with
the same occasional move against its plan that fades out by the endgame), and chat, nudge and
add-friend are off. AI games are **practice** — they never count toward a player's
statistics.
### Social: friends, block, chat, nudge
+6 -4
View File
@@ -146,8 +146,10 @@ nudge) приходят от бота **этой партии** — по язы
в которой вы уже ждёте, занимает робот-соперник. Он задуман неотличимым от человека:
один раз за партию решает, играть ли на победу (примерно в 40% случаев, так что
человек выигрывает большинство партий), целится в близкий счёт, а не в разгром или
поддавки, и ходит с человеческим темпом — чаще короткие раздумья, изредка долгие, и
ночная пауза, подстроенная под день игрока. На nudge отвечает за несколько минут и
поддавки, время от времени делает один ход вопреки этому плану — неожиданный отрыв или
слабый ход, — но к концу партии (когда мешок пуст) держится плана, и ходит с
человеческим темпом — чаще короткие раздумья, изредка долгие, и ночная пауза,
подстроенная под день игрока. На nudge отвечает за несколько минут и
сам шлёт nudge, когда игрок надолго пропал. Носит человекоподобное имя, подходящее
языку партии (в русской партии — в основном русские имена); не общается в чате и
**молча игнорирует заявки в друзья** — заявка роботу остаётся в ожидании и истекает,
@@ -155,8 +157,8 @@ nudge) приходят от бота **этой партии** — по язы
Тот же робот стоит и за **честной игрой против ИИ**, которую игрок выбирает напрямую (выше). Там он
не притворяется: везде показан как **🤖**, присоединяется и ходит сразу (без раздумий и ночной
паузы), сохраняет ту же силу (по-прежнему играет на победу лишь примерно в 40% партий), а чат, nudge
и «добавить в друзья» выключены. Партии с ИИ — это **тренировка**: они не идут в статистику игрока.
паузы), сохраняет ту же силу (по-прежнему играет на победу лишь примерно в 40% партий, с теми же
редкими ходами вопреки плану, затухающими к эндшпилю), а чат, nudge и «добавить в друзья» выключены. Партии с ИИ — это **тренировка**: они не идут в статистику игрока.
### Социальное: друзья, блок, чат, nudge
Подружиться можно двумя способами: погасить **одноразовый код**, который выпускает