feat(stats): per-game hints used + lifetime moves and hint share
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 10s
CI / integration (pull_request) Successful in 15s
CI / ui (pull_request) Successful in 51s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m24s

Store the hints a player used in each game, and add two lifetime tiles —
Moves and Hint share — to the statistics screen.

- per-game: game_players.hints_used now counts EVERY hint (allowance + wallet),
  not just the free allowance, so it is the seat's true total hints used this
  game. The allowance decision (used < HintsPerPlayer) and the hint badge values
  (hints_remaining / wallet_balance) are unchanged — the lobby hint-count fix does
  NOT regress; only the admin "hints used" column, which silently under-counted
  wallet hints, becomes accurate.
- account_stats gains two summed counters: moves (the player's plays — passes and
  exchanges excluded) and hints_used (every hint). Computed at game finish in
  buildStats over the same non-guest, non-honest-AI games as the rest of the stats.
- wire: StatsView gains moves + hints_used (trailing); gateway + UI codec + model;
  regen.
- ui: two tiles (Moves, Hint share = hints_used/moves, one-decimal % in the active
  locale); card order games·wins·draws·losses·moves·hint-share·best game·win-rate.
- docs: ARCHITECTURE §9 + baseline comment, FUNCTIONAL (+ru), UI_DESIGN.

Tests: TestHintPolicy (hints_used counts the wallet hint), TestGameLifecycleAndStats
(moves>0, hints=0), gateway stats round-trip, UI hintShare unit + codec + e2e.
This commit is contained in:
Ilia Denisov
2026-06-17 23:44:53 +02:00
parent 91c4efc8a8
commit 6d1d8030e3
30 changed files with 224 additions and 30 deletions
+4 -1
View File
@@ -603,7 +603,10 @@ disguised robot stays indistinguishable from a person.
non-guest accounts only — the finish-time recompute skips any `is_guest`
seat): wins, losses, **draws**, max points in a game, and
max points for a single **move** (which already folds in every word the move
formed plus the all-tiles bonus). A tie increments draws only; a resignation or
formed plus the all-tiles bonus); plus two summed counters — `moves` (the player's
plays, i.e. tile placements; passes and exchanges do not count) and `hints_used`
(every hint taken, allowance + wallet) — from which the screen derives the **hint
share** = hints_used / moves. A tie increments draws only; a resignation or
timeout is a loss for the acting player. A companion table **`account_best_move`**
(keyed by account **and variant**) keeps the highest-scoring single play **per
variant** with the word itself: its main word as an ordered JSON array of tiles
+3 -1
View File
@@ -229,7 +229,9 @@ honest-AI practice game (a live game's export would leak the move journal; an AI
game is throwaway). The client shares the `.gcg` file where the platform supports
it, otherwise downloads it. Statistics (durable accounts only):
wins, losses, draws, max points in a game, and max points for a single move (the
best play, which already includes every word it formed plus the all-tiles bonus).
best play, which already includes every word it formed plus the all-tiles bonus). It
also shows the player's **move count** (their plays — passes and exchanges do not
count) and their **hint share** (the percentage of those plays where a hint was taken).
The best move is also broken down **per game variant**, showing the **word itself**
drawn as game tiles (a wildcard shows its letter but no value) — one row per variant
the player has played, omitting variants with no plays.
+3 -1
View File
@@ -233,7 +233,9 @@ UTC), суточного окна отсутствия (away; сетка по 10
с ИИ одноразовая). Клиент делится файлом `.gcg` там, где платформа это поддерживает,
иначе скачивает его. Статистика (только у постоянных аккаунтов):
победы, поражения, ничьи, макс. очков за партию и макс. очков за один ход (лучший
ход, уже включающий все образованные им слова и бонус за все фишки).
ход, уже включающий все образованные им слова и бонус за все фишки). Также
показываются **число ходов** игрока (его выкладки — пасы и обмены не считаются) и
**доля подсказок** (процент таких ходов, на которых бралась подсказка).
Лучший ход также даётся **с разбивкой по вариантам игры****само слово**,
нарисованное игровыми фишками (wildcard показывает свою букву без очков), по одной
строке на каждый сыгранный вариант; варианты без ходов не выводятся.
+4 -2
View File
@@ -280,8 +280,10 @@ enabled on the first, uncached load) and flip in place when an event refreshes t
everywhere (score card, lobby row, turn line), the add-friend 🤝 is never drawn, and the chat's
send field and 🛎️ nudge stay **disabled** while the 🔎 dictionary word-check keeps working.
- **Statistics** (`screens/Stats.svelte`, the lobby ✏️ tab): a 2-column grid of stat
cards (wins / losses / draws / games / win-rate / best game) — pure numbers, no
charts — followed by a **full-width "best move" card** that breaks the best move
cards (games / wins / draws / losses / moves / hint-share / best game / win-rate) —
pure numbers, no charts; hint-share is a one-decimal percentage in the active locale's
notation (e.g. "4.8%" / "4,8%") — followed by a **full-width "best move" card** that
breaks the best move
down per variant: one row per played variant (catalogue order, empty variants
omitted) with the variant name on the left, the **word drawn as game tiles**
(`components/WordTiles.svelte`, the board's placed-tile look at a small fixed size;