Commit Graph

7 Commits

Author SHA1 Message Date
Ilia Denisov aa765a0c06 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
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.
2026-06-15 20:14:24 +02:00
Ilia Denisov ac62d29ef7 feat(ui): name the previous player in the your-turn toast
CI / changes (pull_request) Successful in 1s
CI / unit (pull_request) Has been skipped
CI / integration (pull_request) Has been skipped
CI / ui (pull_request) Successful in 46s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 58s
The in-app "your turn" toast (shown when it becomes your turn in another
game) now reads "<Opponent>: Your turn!", naming the player who moved just
before this one — the previous seat in turn order, so it reads the same in
games with any number of players. Falls back to the bare "Your turn" label
when no name is present (an older peer, or a gap event without one).

YourTurnEvent already carries opponent_name end to end: the backend sets it
(game.displayName of the last mover) and the gateway forwards the payload
opaquely, so this is a client-only change — decode the field, thread it
through PushEvent, and pick the localized string.
2026-06-14 22:52:15 +02:00
Ilia Denisov 56dbf86472 feat(lobby): keep lobby/game caches fresh from any screen + invitation delta channel
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 9s
CI / integration (pull_request) Successful in 14s
CI / ui (pull_request) Successful in 48s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m7s
Builds on the cross-screen cache work: the global stream handler now keeps both
caches current no matter which screen is mounted, and invitations become a live
delta channel so the lobby's invitations list is fresh from any screen too.

Client (boundary already started):
- advanceCached now also folds opponent_joined into a not-currently-viewed game's
  cache via a new pure reducer applyOpponentJoined (extracted and reused by the
  mounted game board), so opening an open game that filled while you were elsewhere
  is flash-free.
- patchLobbyInvitation upserts a still-pending invitation and removes a terminal
  one (started/declined/cancelled/expired); the global notify handler calls it on
  the invitation / invitation_update sub-kinds.

Invitations delta channel (no wire/gateway/connector change — the notification
already carries the full invitation with id/status/game_id end to end):
- notify: a new in-app-only NotifyInvitationUpdate sub + NotificationInvitationUpdate
  constructor (shares encoding with NotificationInvitation). The Telegram connector
  renders no message for it, so a decline/cancel never becomes an out-of-app push.
- lobby: emit the changed invitation to every participant on respond (accept/decline),
  on the final accept's game start, and on cancel — so each participant's lobby patches
  its list in place. The authoritative list holds only pending invitations, so the
  client's pending-vs-terminal rule matches it exactly.

Tests: applyOpponentJoined + patchLobbyInvitation unit tests (TDD), the
NotificationInvitationUpdate encoding unit test, and integration assertions that
decline/cancel/accept publish invitation_update to every participant. Full local
suite green (backend unit+integration, UI check/unit/build/bundle/e2e). Docs:
ARCHITECTURE §10 (notify catalog + in-app-only note) and UI_DESIGN updated.
2026-06-14 11:22:47 +02:00
Ilia Denisov cf70e6b1fc fix(ui): keep lobby/game caches fresh across screens (no stale-status flash)
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Has been skipped
CI / integration (pull_request) Has been skipped
CI / ui (pull_request) Successful in 48s
CI / gate (pull_request) Successful in 1s
CI / deploy (pull_request) Successful in 58s
The per-screen in-memory caches (lobbycache, gamecache) were refreshed only by
the screen that owns them while it was mounted, so a state change that crossed
screens left the other screen's cache stale and it visibly redrew on the next
navigation:

- game -> lobby: the player's own move advanced the game cache but not the lobby
  snapshot, and an own move carries no self-directed push event, so returning to
  the lobby painted the pre-move status until the background refresh corrected it.
- lobby -> game (and from any other screen): an opponent's move / game-over
  refreshed the lobby (while mounted) but never the per-game cache, so opening
  that game flashed the pre-move board.

Make cache freshness cross-screen, owned by the single global stream handler
that runs for every live event regardless of the mounted screen:

- patchLobbyGame upserts the affected game's GameView into the lobby snapshot;
  the global handler calls it on opponent_moved / game_over / opponent_joined and
  on a match_found / game_started seed (so a game started elsewhere is present
  too). The game board still mirrors the player's own move and its own load() —
  the two updates no live event carries.
- advanceCached (a pure wrapper over the existing delta reducers) advances a
  not-currently-viewed game's cache from opponent_moved / game_over; the game in
  view is skipped so its mounted board stays the sole owner (no double apply).

End-state behaviour is unchanged (the background refresh always reconciled);
this removes the transient stale frame. Unit-tested patchLobbyGame and
advanceCached; docs/UI_DESIGN.md updated.
2026-06-14 10:42:43 +02:00
Ilia Denisov 0b57400c6f feat(ui): single-word rule indicators + auto-match select redesign
CI / changes (pull_request) Successful in 1s
CI / unit (pull_request) Successful in 9s
CI / integration (pull_request) Successful in 13s
CI / ui (pull_request) Successful in 46s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m9s
Surface the per-game "single word" rule to the client and refine the
random-opponent New Game screen.

- Wire: thread multiple_words_per_turn into the GameView and Invitation
  FlatBuffers tables (Go + TS regenerated), through pkg/wire builders and both
  the backend push-event and gateway REST paths.
- In-game indicators (single-word games only): a small 1 in the status bar's
  score-preview slot (yields to the live preview) and a centred "One word per
  turn" label in the history-drawer header. Standard games show neither.
- Invitation card gains a "One word per turn" line for single-word invitations.
- Auto-match redesign: variant plaques are mutually-exclusive selects (highlight
  on tap, no longer enqueue); a lone offered variant is pre-selected; a bottom
  "Start game" button (disabled until a variant is chosen) confirms. The rule
  toggle appears once a Russian variant is selected.
- Tests: e2e for the new auto flow and the in-game indicator (mock g3 is a
  single-word game); mock/data + fixtures carry the new field. Docs: UI_DESIGN.
2026-06-12 10:28:29 +02:00
Ilia Denisov 8881214213 R6(a): de-stage code, docs, READMEs; split stage6_test
Mechanical, behaviour-preserving removal of Stage N / TODO-N / phase (RN)
references from comments, doc-comments, service READMEs, the current-state docs
(ARCHITECTURE, FUNCTIONAL+_ru, TESTING, UI_DESIGN), config-file comments, and the
.fbs/.proto schema comments. PLAN.md / PRERELEASE.md / CLAUDE.md keep the stage
history.

- Rename the only stage-named identifiers: registerStage8 -> registerSocialOps,
  registerStage11 -> registerLinkOps (gateway transcode).
- Split stage6_test.go: TestEmailLoginFlow -> email_test.go,
  TestGuestAutoMatchLeavesNoStats (+ provisionGuest) -> account_test.go.
- Regenerated proto bindings (push.pb.go, telegram_grpc.pb.go) from the de-staged
  .proto comments; FB Go/TS bindings unchanged (flatc strips schema comments).

go build/vet/gofmt clean across modules; integration typecheck and pnpm check green.
2026-06-10 16:56:03 +02:00
Ilia Denisov 41a642ef97 R4: push enrichment — events carry a state delta, kill the last poll
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 9s
CI / integration (pull_request) Successful in 13s
CI / ui (pull_request) Successful in 37s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m8s
Enrich the in-app live stream into a delta channel so the UI renders a move from the event without a follow-up game.state, and make the matchmaking poll a stream-down fallback.

- pkg/fbs: trailing fields on opponent_moved (move+game+bag_len), your_turn (move_count), match_found (state), game_over (game), notify (account/invitation/state), MoveResult (rack+bag_len); regenerate Go + TS.
- backend: notify owns the FB encoding (encode.go + payload.go input structs); game/lobby/social map their domain types in. emitMove builds the move delta; game.Service.InitialState feeds match_found/game_started the recipient's initial StateView; friends/invitations notify carry their account/invitation. The move-commit response (submit_play/pass/exchange/resign) returns the actor's refilled rack + bag size.
- gateway: MoveResult transcode carries rack+bag_len.
- ui: pure lib/gamedelta.ts reducer advances the per-game cache keyed on move_count (idempotent + gap-safe); app.svelte seeds the cache on match_found/game_started; Game.svelte applies the delta (commit/pass/exchange/resign drop their load()); NewGame polls only while app.streamAlive is false.
- docs: ARCHITECTURE §10, FUNCTIONAL(+ru), backend/gateway/ui READMEs; PRERELEASE R4 marked done + Refinements.
2026-06-10 08:01:50 +02:00