Stage 17 round 6 (#7): reset the nudge cooldown once the player acts
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 30s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m4s

The hourly nudge cooldown now clears as soon as the sender has moved or posted a chat
since their last nudge — engagement lifts the 'don't spam' limit. Backend: Nudge checks
game.LastMoveAt + the sender's last non-nudge chat against the last nudge time
(GameReader gains LastMoveAt). UI: nudgeOnCooldown mirrors it — a chat reset is read from
the message list, a move is tracked client-side (lastActedAt on commit/pass/exchange; the
backend stays authoritative across a reload). Integration test covers the reset.
This commit is contained in:
Ilia Denisov
2026-06-07 11:32:08 +02:00
parent 2cb2b57cdb
commit cdf616d6c4
6 changed files with 122 additions and 6 deletions
+7
View File
@@ -226,6 +226,13 @@ func (svc *Service) RobotSchedule(ctx context.Context, gameID uuid.UUID) (seed i
return svc.store.RobotSchedule(ctx, gameID)
}
// LastMoveAt returns the time of an account's most recent move in a game (and whether it
// has moved). The social service uses it to reset the nudge cooldown once a player has
// taken a turn (Stage 17).
func (svc *Service) LastMoveAt(ctx context.Context, gameID, accountID uuid.UUID) (time.Time, bool, error) {
return svc.store.LastMoveAt(ctx, gameID, accountID)
}
// transition validates the actor and turn, applies op under the per-game lock and
// commits the result.
func (svc *Service) transition(ctx context.Context, gameID, accountID uuid.UUID, op engineOp) (MoveResult, error) {
+19
View File
@@ -651,6 +651,25 @@ func (s *Store) GameSeed(ctx context.Context, id uuid.UUID) (int64, error) {
return row.Seed, nil
}
// LastMoveAt returns the time of the account's most recent move in the game and true, or
// the zero time and false when it has not moved. The social service uses it to reset the
// nudge cooldown once the player has taken a turn (Stage 17).
func (s *Store) LastMoveAt(ctx context.Context, gameID, accountID uuid.UUID) (time.Time, bool, error) {
var at sql.NullTime
err := s.db.QueryRowContext(ctx,
`SELECT MAX(m.created_at) FROM backend.game_moves m
JOIN backend.game_players p ON p.game_id = m.game_id AND p.seat = m.seat
WHERE m.game_id = $1 AND p.account_id = $2`,
gameID, accountID).Scan(&at)
if err != nil {
return time.Time{}, false, fmt.Errorf("game: last move at %s: %w", gameID, err)
}
if !at.Valid {
return time.Time{}, false, nil
}
return at.Time, true, nil
}
// RobotSchedule returns a game's bag seed and current turn-start time. The admin console
// combines them with the robot strategy to show a robot seat's play-to-win intent and its
// next-move ETA. Both are server-only state, never part of the public game view.