feat: honest AI opponent in quick game
CI / changes (pull_request) Successful in 3s
CI / unit (pull_request) Successful in 9s
CI / integration (pull_request) Successful in 14s
CI / ui (pull_request) Successful in 49s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m12s
CI / changes (pull_request) Successful in 3s
CI / unit (pull_request) Successful in 9s
CI / integration (pull_request) Successful in 14s
CI / ui (pull_request) Successful in 49s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m12s
New Game's quick game gains an explicit opponent selector — 🤖 AI (default) or 👤 Random player. AI starts a game seated with a pooled robot that joins and moves at once: no per-move timeout (a 7-day inactivity loss reusing the turn-timeout sweeper), chat/nudge disabled, no statistics, the opponent shown as 🤖 everywhere. The random path (disguised robot) is unchanged. Driven by one game flag (games.vs_ai), set only on AI-started games so the disguised path is never revealed; Matchmaker.StartVsAI seats the robot directly (no open pool); the robot replies event-driven via the game service's after-commit/after-create hook. Wire: vs_ai on EnqueueRequest + GameView.
This commit is contained in:
@@ -31,6 +31,7 @@ the edge before prod. Each phase maps back to the owner's raw pre-release TODO l
|
||||
| OW | Open auto-match: enter the game at once and wait inside it (robot after 90–180 s) | owner ad-hoc | **done** |
|
||||
| DA | Dictionary admin: online release-archive upload → word-diff preview → install/activate; versioned dict volume; active version persisted in DB; resident label = release tag | owner ad-hoc | **done** |
|
||||
| AB | Manual account block (admin suspension): permanent/temporary with an editable en+ru reason picklist; a block forfeits the player's active games + cancels their open ones; a backend gate refuses a blocked account with **403 `account_blocked`**; the UI shows a terminal blocked screen and stops all push/poll; manual unblock; temporary blocks self-expire (migration `00003`) | owner ad-hoc | **done** |
|
||||
| AI | Honest AI opponent in quick game: an explicit 🤖 AI / 👤 random selector (AI default); the robot is seated and moves at once; 7-day inactivity loss (the per-turn timeout reused); chat/nudge disabled, no statistics; the opponent is shown as 🤖 everywhere | owner ad-hoc | **done** |
|
||||
| → | Stage 18 — prod contour deploy | — | see [`PLAN.md`](PLAN.md) |
|
||||
|
||||
## Key findings (these reshaped the raw list — read before starting a phase)
|
||||
@@ -466,3 +467,39 @@ Then Stage 18.
|
||||
`lobby.tournaments` / `lobby.soon`). No backend/wire/history/GCG change.
|
||||
- **No schema/wire change → no contour DB wipe.** Bake-back: `docs/UI_DESIGN.md`, `docs/FUNCTIONAL.md`
|
||||
(+`_ru`). Regression gate: UI `check` + unit + build + bundle budget + e2e (Chromium & WebKit).
|
||||
|
||||
- **AI — Honest AI opponent in quick game** (owner ad-hoc, not on the raw TODO list): a second quick-game
|
||||
opponent the player *knowingly* chooses, distinct from the disguised robot of the random/open path
|
||||
(which is kept as-is). New Game's quick-game mode replaces the "auto-match" subtitle with a two-button
|
||||
selector **🤖 AI / 👤 Random player** (the `.seg`/`.opt` segmented style, AI the default); for AI the
|
||||
move-clock line reads "Loss after 7 days of inactivity" and the "searching" hint is hidden.
|
||||
- **Locked decisions (interview):** AI move is **event-driven** (the robot replies the instant the
|
||||
player's move commits; the 30 s driver is the fallback); AI games **do not touch `account_stats`**
|
||||
(practice, like guests); the **Stage 5 strength logic is reused unchanged** (`playToWin` 40 % from the
|
||||
seed + margin band); **no per-move timeout — a 7-day inactivity loss** instead; the 7-day line lives on
|
||||
the New Game screen (the in-game screen has no move-clock line); chat + nudge **disabled**, word-check
|
||||
kept, add-friend never drawn, opponent shown as **🤖** everywhere.
|
||||
- **The 7-day rule reuses the existing per-turn timeout:** an AI game is created with
|
||||
`turn_timeout_secs = AIInactivityTimeout` (7 days) and the existing timeout sweeper resigns the overdue
|
||||
seat — since the robot moves at once, only the human is ever on the clock, so the per-turn timeout *is*
|
||||
the abandon rule (no new column, no new sweeper).
|
||||
- **One game flag drives everything:** `games.vs_ai` (edited into the R1 baseline — pre-release, so a
|
||||
contour DB wipe after merge). It is set **only** on AI-started games, so a robot-filled random game keeps
|
||||
`vs_ai=false` and the disguised opponent is never revealed; the UI derives 🤖 / the gates **from the flag,
|
||||
never from the opponent account**. New backend path `Matchmaker.StartVsAI` (picks a pooled robot via the
|
||||
existing `Pick`, creates an **active** seated game via `game.Service.Create`, random seat order) — the AI
|
||||
request never enters the open pool, so the open-game reaper never touches it. The robot driver gains a
|
||||
`vs_ai` branch (no sleep, no proactive nudge, zero delay) and a focused `DriveGame`/`TriggerMove` fast
|
||||
path wired from the game service's after-create/after-commit hook (`SetAITrigger`, a func value so the
|
||||
game package never imports the robot package). Chat/nudge gated by a new `social` `VsAI` check
|
||||
(`ErrGameVsAI` → 409 `ai_game`); statistics skipped in `commit` when `vs_ai`.
|
||||
- **Wire:** `EnqueueRequest` += `vs_ai`, `GameView` += `vs_ai` (trailing FB fields, regenerated Go + TS),
|
||||
threaded through the backend DTO, the gateway transcode and the `pkg/wire` + `notify` builders.
|
||||
- **Tests:** `lobby` unit (StartVsAI seats a robot + flags the game; empty pool leaves no game); backend
|
||||
integration (`ai_game_test.go`: active+seated+vs_ai+7-day clock, robot moves immediately, stats skipped,
|
||||
7-day timeout resigns the human, chat/nudge rejected); UI codec round-trip (`vs_ai` on enqueue + game
|
||||
view); e2e (an AI game shows 🤖, no "searching", chat disabled, the dictionary still works) + the
|
||||
existing quick-match e2e updated to pick **Random player** (the default is now AI).
|
||||
- **Schema/wire change → a contour DB wipe** after merge (`DROP SCHEMA backend CASCADE` + restart, the
|
||||
R1/R3 pattern). Bake-back: `docs/ARCHITECTURE.md`, `docs/FUNCTIONAL.md` (+`_ru`), `docs/UI_DESIGN.md`,
|
||||
`backend/README.md`, Go Doc comments.
|
||||
|
||||
Reference in New Issue
Block a user