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:
+24
-4
@@ -361,8 +361,24 @@ Key points:
|
||||
Substitutes for a human in 2-player auto-match: the matchmaking reaper seats it in an
|
||||
open game's empty opponent slot when no human has joined within the wait window (§8).
|
||||
It lives in `internal/robot` and plays as an ordinary seated account through the game
|
||||
service, so only `internal/engine` imports the solver. It is designed to be
|
||||
indistinguishable from a person.
|
||||
service, so only `internal/engine` imports the solver. In the random/auto-match path it is
|
||||
designed to be indistinguishable from a person.
|
||||
|
||||
The same robot serves **two quick-game modes**, chosen by the player on New Game and recorded
|
||||
on the game as **`games.vs_ai`**: the **random** path above (disguised as a person) and an
|
||||
**honest-AI** path the player knowingly picks (shown as **🤖**). The mode is a per-game flag,
|
||||
never derived from the opponent account, so the disguised path is never revealed. In an
|
||||
honest-AI game the robot keeps its per-game strength (`playToWin`) and margin band but **moves
|
||||
at once** — no sampled delay, no sleep window, no proactive nudge; chat and nudge are disabled,
|
||||
the opponent is shown as 🤖 everywhere, and the game records **no statistics** for either seat
|
||||
(practice, like a guest game). The fast reply is **event-driven**: committing a move (or
|
||||
creating the game) triggers `robot.DriveGame` immediately via the game service's after-commit /
|
||||
after-create hook (`game.Service.SetAITrigger`, a func value so the game package never imports
|
||||
the robot package), with the periodic driver as the fallback. There is **no short move
|
||||
timeout**; instead the game is created with `turn_timeout_secs = AIInactivityTimeout` (**7
|
||||
days**) and the existing turn-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 doubles as the
|
||||
"abandoned after 7 days of inactivity → loss" rule with no new column or sweeper.
|
||||
|
||||
The robot keeps **no per-game state**: every choice is derived deterministically
|
||||
from the game's bag `seed` (a restart-stable FNV-1a mix), so a background driver
|
||||
@@ -406,8 +422,12 @@ English game the Latin pool.
|
||||
|
||||
## 8. Lobby & social
|
||||
|
||||
- **Matchmaking**: auto-match drops the player **straight into a real game and lets
|
||||
them wait inside it**. `Enqueue` (`POST /lobby/enqueue`) opens a game seating the
|
||||
- **Matchmaking**: a quick game offers **two opponents** on New Game — an **honest AI** (the
|
||||
default) or a **random** human (§7). The AI choice (`vs_ai` on `POST /lobby/enqueue`) takes the
|
||||
`Matchmaker.StartVsAI` path, which picks a pooled robot and creates a game **already seated and
|
||||
active** (`vs_ai`, random seat order); it never enters the open pool, so the reaper below never
|
||||
touches it. The random choice drops the player **straight into a real game and lets
|
||||
them wait inside it**: `Enqueue` (`POST /lobby/enqueue`) opens a game seating the
|
||||
caller with an **empty opponent seat** (status `open`, §9), or — when another player
|
||||
is already waiting for the same `variant` and per-turn rule — seats the caller into
|
||||
that open game and starts it; which seat the caller takes is randomised for
|
||||
|
||||
Reference in New Issue
Block a user