Stage 17 round 5 docs: bake the bug fixes + UI polish + L2 into live docs
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 8s
CI / integration (pull_request) Successful in 11s
CI / ui (pull_request) Successful in 29s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 52s
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 8s
CI / integration (pull_request) Successful in 11s
CI / ui (pull_request) Successful in 29s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 52s
- ARCHITECTURE: resign on the opponent's turn (ResignSeat + turn-check bypass); robots block chat but accept-and-ignore friend requests; quick-match /lobby/cancel; the admin robot play-to-win intent + next-move ETA panel. - UI_DESIGN: even A->B zoom (recentre only on zoom-in), pinch, drop-target highlight, shuffle ≤0.3s + reduce-motion, borderless make-move disabled on illegal, variant title. - FUNCTIONAL (+ru): variant display names (Scrabble/Erudite); robot ignores friend requests. - PLAN: round-5 refinements bullet (+ the bilingual two-Scrabble open edge).
This commit is contained in:
+13
-4
@@ -235,7 +235,10 @@ Key points:
|
||||
applying the end-game rack-value adjustment, or a resignation. On a
|
||||
**resignation the resigner keeps their accumulated score (no rack adjustment)
|
||||
and never wins**: the win goes to the highest score among the remaining seats,
|
||||
unconditionally the other player in a two-player game. The engine exposes a
|
||||
unconditionally the other player in a two-player game. A player may resign **on the
|
||||
opponent's turn** (a forfeit is not a turn-scoped move): `engine.ResignSeat(seat)`
|
||||
resigns that player's own seat whoever is to move, and the game domain skips the turn
|
||||
check for resign (Stage 17). The engine exposes a
|
||||
decoded, solver-free API (`SubmitPlay`/`SubmitExchange`/`EvaluatePlay`/
|
||||
`HintView`/`Hand`) so `internal/game` drives it without importing the solver.
|
||||
- The **game domain** (`internal/game`) owns everything the engine does not —
|
||||
@@ -301,9 +304,10 @@ from the game's bag `seed` (a restart-stable FNV-1a mix), so a background driver
|
||||
(`robot.Service.Run`, mirroring the turn-timeout sweeper) recomputes the same
|
||||
behaviour on every scan and after a restart — the same philosophy as journal
|
||||
replay. A pool of durable accounts — each a `kind='robot'` identity (§4), keyed
|
||||
`robot-<lang>-<index>` and provisioned at startup with chat and friend requests
|
||||
blocked — backs the human-like names; those two profile toggles are all the
|
||||
friend/DM blocking requires (there is no DM surface; chat is per-game). Names are
|
||||
`robot-<lang>-<index>` and provisioned at startup with **chat blocked but friend
|
||||
requests open** — a request to a robot is accepted as pending and expires unanswered
|
||||
(the robot never responds), mirroring a human who ignores it (Stage 17); the chat
|
||||
block backs the human-like names (there is no DM surface; chat is per-game). Names are
|
||||
**composed per language** from a first-name pool (32 full + 32 colloquial forms) and
|
||||
a surname pool (gender-agreed for Russian) in one of three forms (first only /
|
||||
first + surname initial / first + full surname), deterministically per pool slot so
|
||||
@@ -331,6 +335,8 @@ English game the Latin pool.
|
||||
- **Observability**: robot accounts accrue ordinary statistics (§9) — the
|
||||
authoritative balance metric (target ≈ 40% robot wins) — and a
|
||||
`robot_games_finished_total` OTel counter plus a per-finish log give a live view.
|
||||
The **admin game card** surfaces each robot seat's per-game play-to-win intent (from
|
||||
the seed) and, on the robot's turn, its deterministic **next-move ETA** (Stage 17).
|
||||
|
||||
## 8. Lobby & social
|
||||
|
||||
@@ -342,6 +348,9 @@ English game the Latin pool.
|
||||
robot (§7) and starts the game. On a pairing or substitution the matchmaker
|
||||
emits a **match-found** notification (§10), delivered over the live stream;
|
||||
`Poll` remains as a fallback for a client that is not currently streaming.
|
||||
**Cancel** (`POST /lobby/cancel`) removes the player from the pool and drops any
|
||||
pending matched result, so a cancelled quick-match is dequeued rather than left for
|
||||
the reaper to robot-substitute (Stage 17).
|
||||
- **Friends** (Stage 8): two add paths over one `friendships` table. A **one-time
|
||||
code** the to-be-added player issues (a `friend_codes` row: 6-digit numeric,
|
||||
SHA-256-hashed, **12 h** TTL, one live code per issuer, single-use, redeem
|
||||
|
||||
+7
-4
@@ -55,9 +55,11 @@ two accounts share a game still in progress.
|
||||
|
||||
### Lobby & matchmaking *(Stage 4 / 15)*
|
||||
Bottom tab menu: **my games**, **profile**. The game types offered on **New Game** are
|
||||
limited to the languages the player's sign-in service supports (English → English;
|
||||
Russian → Russian + Эрудит; a bilingual service shows all three, and the web client is
|
||||
unrestricted). This gates only **starting** a new game — both auto-match and a friend
|
||||
limited to the languages the player's sign-in service supports (English → Scrabble;
|
||||
Russian → Scrabble + Erudite; a bilingual service shows all three, and the web client is
|
||||
unrestricted). Variants are shown by their **display name** — both Scrabble variants read
|
||||
"Scrabble"/"Скрэббл" and Erudit reads "Erudite"/"Эрудит" (by the interface language), and
|
||||
the same name titles the in-game screen. This gates only **starting** a new game — both auto-match and a friend
|
||||
invitation — so a player still sees and plays existing games of any language. Auto-match
|
||||
(always 2 players) joins a per-variant pool and is paired with the next waiting human;
|
||||
after 10 s with no human the robot substitutes (the robot arrives in Stage 5). Friend games (2–4) are
|
||||
@@ -92,7 +94,8 @@ and plays at a human pace — short thinking times for most moves, the occasiona
|
||||
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) and neither chats nor accepts friend requests.
|
||||
names); it does not chat, and **silently ignores friend requests** — a request to a
|
||||
robot stays pending and expires, exactly like a human who never responds.
|
||||
|
||||
### Social: friends, block, chat, nudge *(Stage 4 / 8)*
|
||||
Become friends in two ways: redeem a **one-time code** the other player issues (six
|
||||
|
||||
@@ -56,9 +56,11 @@ Mini App** авторизует по подписанным `initData` плат
|
||||
|
||||
### Лобби и подбор *(Stage 4 / 15)*
|
||||
Нижнее tab-меню: **мои игры**, **профиль**. Типы партий на экране **Новая игра**
|
||||
ограничены языками, которые поддерживает сервис входа игрока (английский → English;
|
||||
русский → Russian + Эрудит; двуязычный сервис показывает все три, а веб-клиент не
|
||||
ограничен). Это ограничивает только **старт** новой игры — и авто-подбор, и
|
||||
ограничены языками, которые поддерживает сервис входа игрока (английский → Scrabble;
|
||||
русский → Scrabble + Erudite; двуязычный сервис показывает все три, а веб-клиент не
|
||||
ограничен). Варианты показываются под **отображаемым именем** — оба варианта Scrabble
|
||||
читаются как «Scrabble»/«Скрэббл», а Erudit — «Erudite»/«Эрудит» (по языку интерфейса),
|
||||
и это же имя выносится в заголовок экрана игры. Это ограничивает только **старт** новой игры — и авто-подбор, и
|
||||
приглашение друга, — поэтому игрок по-прежнему видит и играет существующие игры на
|
||||
любом языке. Авто-подбор (всегда 2 игрока)
|
||||
встаёт в пул по варианту и сводится со следующим ожидающим человеком; через 10 с
|
||||
@@ -93,8 +95,9 @@ Mini App** авторизует по подписанным `initData` плат
|
||||
поддавки, и ходит с человеческим темпом — чаще короткие раздумья, изредка долгие, и
|
||||
ночная пауза, подстроенная под день игрока. На nudge отвечает за несколько минут и
|
||||
сам шлёт nudge, когда игрок надолго пропал. Носит человекоподобное имя, подходящее
|
||||
языку партии (в русской партии — в основном русские имена), не общается в чате и не
|
||||
принимает заявки в друзья.
|
||||
языку партии (в русской партии — в основном русские имена); не общается в чате и
|
||||
**молча игнорирует заявки в друзья** — заявка роботу остаётся в ожидании и истекает,
|
||||
ровно как у человека, который не отвечает.
|
||||
|
||||
### Социальное: друзья, блок, чат, nudge *(Stage 4 / 8)*
|
||||
Подружиться можно двумя способами: погасить **одноразовый код**, который выпускает
|
||||
|
||||
+21
-12
@@ -63,16 +63,22 @@ Login uses `Screen`.
|
||||
differently in Safari/Chrome). Labels are sized in `cqw` against the fixed viewport, so
|
||||
they stay a constant size as the cells grow (relatively smaller at higher zoom).
|
||||
**Double-tap** an empty/filled cell toggles zoom centred on it; double-tap a **pending**
|
||||
tile recalls it. On touch, placing a tile auto-zooms in centred on the target, and
|
||||
**holding a dragged tile over a cell for ~1 s** auto-zooms there (Stage 17). The custom
|
||||
pinch and swipe-to-open-history gestures stay dropped — they fight both native scroll and
|
||||
the one-finger drag-back gesture; history opens from the menu or a tap on the players
|
||||
plaque (below). A **hint** auto-zooms centred on the hint's placement, not the top-left.
|
||||
tile recalls it. **Pinch** zooms toward the pinch midpoint (a two-finger gesture;
|
||||
preventDefault fires only for two touches, so one-finger scroll stays native, and a second
|
||||
finger aborts an in-progress drag). The pan is **interpolated toward a pre-clamped target**
|
||||
as the real width grows/shrinks, so it magnifies evenly A→B instead of lurching and snapping
|
||||
back near the edges (Stage 17). It **recentres only on a zoom-in** — placing a 2nd+ tile or
|
||||
hovering a dragged tile never jumps the board. On touch the first tile placement auto-zooms
|
||||
in centred on the target, and **holding a dragged tile over a cell ~1 s** auto-zooms there
|
||||
the first time. The swipe-to-open-history gesture stays dropped (it fought native scroll);
|
||||
history opens from the menu or a tap on the players plaque (below). A **hint** auto-zooms
|
||||
centred on the hint's placement, not the top-left.
|
||||
- **Placing & recall** (`Game.svelte`): a rack tile is placed by tap-then-tap or by
|
||||
dragging it onto a cell; a pending tile is taken back by a **double-tap** or by **dragging
|
||||
it back onto the rack** (unzoomed board only — when zoomed the one-finger gesture scrolls).
|
||||
A single tap no longer recalls (too easy to trigger); a recalled tile returns to its
|
||||
original rack slot (Stage 17).
|
||||
dragging it onto a cell; while a dragged tile is carried over the board, the aimed-at empty
|
||||
cell is **highlighted as a drop target** (an accent ring). A pending tile is taken back by a
|
||||
**double-tap** or by **dragging it back onto the rack** (unzoomed board only — when zoomed
|
||||
the one-finger gesture scrolls). A single tap no longer recalls (too easy to trigger); a
|
||||
recalled tile returns to its original rack slot (Stage 17).
|
||||
- **Players plaque & history** (`Game.svelte`, Stage 17): the seats above the board share
|
||||
the width evenly; the seat whose turn it is is **raised** (a drop shadow on its sides)
|
||||
while the others read **sunk in** (an inset shadow). A tap anywhere on the plaque toggles
|
||||
@@ -105,12 +111,15 @@ Login uses `Screen`.
|
||||
short tap opens a small popover above the button; a ~0.7 s hold runs the primary action
|
||||
immediately. Used by the **Skip** and **Hint** tabs (each confirmed by an **Ok ✅** popover).
|
||||
- **MakeMove / Reset** (Stage 17): when ≥1 tile is pending the rack collapses its used slots
|
||||
and shifts left, a direct **✅** button beside the rack commits the move (no popover), and
|
||||
the 🔀 Shuffle tab is replaced by a **↩️ Reset** tab.
|
||||
and shifts left, a **borderless ✅ icon button** (styled like a tab, not a filled accent
|
||||
button) beside the rack commits the move — no popover, and disabled while the pending word
|
||||
is known illegal; the 🔀 Shuffle tab is replaced by a **↩️ Reset** tab.
|
||||
- **Game tab bar**: 🔄 Draw (disabled when the bag is empty), 🥺 Skip, 🛟 Hint (with a
|
||||
remaining-count badge, disabled at zero); 🔀 Shuffle (no label, no confirm), which
|
||||
**animates** — tiles hop along a low parabola to their new slots (duration scaled by the
|
||||
distance) with a short haptic shake. The under-board slot shows the **Scores: N** preview.
|
||||
distance, the longest ≤ 0.3 s; off under reduce-motion) with a short haptic shake. The
|
||||
under-board slot shows the **Scores: N** preview. The screen **title** is the variant's
|
||||
display name (Scrabble / Скрэббл / Erudite / Эрудит), not a constant "Scrabble".
|
||||
|
||||
## Announcement banner (`components/AdBanner.svelte`, `lib/banner.ts`)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user