Stage 4: lobby & social (matchmaking, friends, blocks, chat+nudge, invitations, profile, email, multi-player drop-out)
Tests · Go / test (push) Successful in 6s
Tests · Integration / integration (push) Successful in 9s
Tests · Go / test (pull_request) Successful in 6s
Tests · Integration / integration (pull_request) Successful in 9s

Engine: multi-player drop-out-and-continue with a per-game tile disposition (remove default / return), resigned seats skipped and excluded from the win, leaver rack never revealed; 2-player behaviour unchanged.

New domains (service/store, no HTTP yet): internal/social (friend request/accept graph, per-user blocks, per-game chat with nudge as a message kind, content filter via mvdan.cc/xurls/v2 + leet/separator normaliser + phone heuristic) and internal/lobby (in-memory variant-keyed matchmaking pool, friend-game invitations invite->accept with lazy 7-day expiry). account gains profile editing and the email confirm-code flow (Mailer seam: SMTP or log mailer).

Migration 00003_social.sql + regenerated jet. main wires the new services into the server (accessors for the Stage 6 handlers); robot substitution stays in Stage 5, REST/stream/push in Stage 6/8. Docs (PLAN, ARCHITECTURE, FUNCTIONAL+ru, TESTING, README) updated.
This commit is contained in:
Ilia Denisov
2026-06-02 19:29:30 +02:00
parent 571bc8c9f2
commit bfa8797f8c
54 changed files with 4270 additions and 81 deletions
+26 -10
View File
@@ -23,9 +23,13 @@ linking an identity that already has history merges it into the current
account (stats summed, games/friends transferred).
### Lobby & matchmaking *(Stage 4)*
Bottom tab menu: **my games**, **profile**. Auto-match (always 2 players) joins
a `(variant, language)` pool; after 10 s with no human, the robot substitutes.
Friend games (24) are formed by friend list, internal ID, or deep-link.
Bottom tab menu: **my games**, **profile**. 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 (24) are
formed by inviting players from the friend list or by internal ID (deep-link
invites arrive with the platform integration): the inviter chooses the settings
and the game starts once every invitee has accepted — any decline cancels it, and
an unanswered invitation expires after seven days.
### Playing a game *(Stage 3)*
Place tiles, pass, exchange, or resign. A play is validated against the game's
@@ -37,9 +41,12 @@ personal hint wallet once the per-game allowance is spent. The game ends when th
bag empties and a player clears their rack, after 6 consecutive scoreless turns,
by resignation, or by the per-game move timeout (5 minutes to 24 hours, default
24 hours): a missed turn auto-resigns, except while the player is inside their
daily away window. A resignation or timeout gives the win to the other player and
the leaver keeps their score (two-player games; multi-player drop-out-and-continue
arrives with the lobby in Stage 4).
daily away window. In a two-player game a resignation or timeout gives the win to
the other player and the leaver keeps their score. In a game with three or four
players the leaver's seat is dropped and the others play on, the game ending when a
single active player remains; the disposition of the leaver's tiles (returned to
the bag or removed from play) is chosen when the game is created, and the leaver's
rack is never shown to the others.
### Robot opponent *(Stage 5)*
Indistinguishable-from-human substitute in auto-match. Decides once whether to
@@ -47,12 +54,21 @@ play to win (~40%), targets a small score margin, plays with human-like timing
and a night sleep window, and nudges/answers nudges like a person.
### Social: friends, block, chat, nudge *(Stage 4)*
Add friends; block chat and/or friend requests independently; per-game chat;
nudge the awaited opponent at most once per hour (platform-native push).
Send a friend request and have it accepted (decline or cancel withdraws it,
unfriending removes the friendship). Block globally — switch off incoming chat
and/or friend requests — and block individual players (a per-user block hides that
person's chat and stops requests and game invitations both ways; it also ends any
existing friendship). Per-game chat is for quick reactions: messages are short
(up to 60 characters) and may not contain links, email addresses or phone numbers,
even disguised. Nudge the player whose turn is awaited at most once per hour (the
nudge is part of the game chat); the out-of-app push is delivered via the platform.
### Profile & settings *(Stage 4)*
Language (en/ru), display name, linked accounts, email binding, timezone, block
toggles.
Edit language (en/ru), display name, timezone, the daily away window and the block
toggles, and bind an email by confirm-code: the backend emails a short code that,
once entered, attaches the email to the account (an email already confirmed by
another account cannot be taken — that is a merge, a later stage). Linked platform
accounts and merge arrive in Stage 10.
### History & statistics *(Stage 3)*
Finished games are archived in a dictionary-independent form and exportable to