Stage 17: fix the robot-nudge frequency + per-game push language
CI / changes (pull_request) Successful in 1s
CI / unit (pull_request) Successful in 9s
CI / integration (pull_request) Successful in 11s
CI / ui (pull_request) Has been skipped
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m6s
CI / changes (pull_request) Successful in 1s
CI / unit (pull_request) Successful in 9s
CI / integration (pull_request) Successful in 11s
CI / ui (pull_request) Has been skipped
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m6s
Two owner-reported defects from a live contour game. A. Frequency: the robot's proactive nudge fired hourly for 12h+ (a 12h idle threshold then the 1h cooldown, uncapped). Replaced with a lengthening, randomized schedule (proactiveNudgeGap): the first nudge ~60-90 min into the human's turn, each later gap growing toward 1-6h (uniform sample in [60min, ceil], ceil ramping 90min->6h over 12h of idle, measured from the previous nudge), so a long wait gets a handful of increasingly-spaced reminders instead of a stream. B. Language: out-of-app push routed by the recipient's GLOBAL service_language (last-login-wins), so after re-logging via the RU bot an English game's nudges came from the RU bot. Now a game push (your_turn, game_over, nudge, match_found) carries the game's own language (engine.Variant.Language) on push.Event, and the gateway routes by it (falling back to service_language for non-game pushes). The New-Game variant-gating guarantees the game's bot is one the player has started, so delivery is never blocked. Tests: proactiveNudgeGap unit + retimed TestRobotProactiveNudge; TestVariantLanguage; emit your_turn/game_over language; TestNudgeRoutedByGameLanguage integration. Docs: ARCHITECTURE (§7 nudge, §10/§13 routing), FUNCTIONAL (+ _ru), PLAN tracker.
This commit is contained in:
@@ -55,9 +55,16 @@ const (
|
||||
// sleep window relative to the opponent's timezone, in hours.
|
||||
sleepDriftHours = 3
|
||||
|
||||
// proactiveNudgeIdle is how long the robot waits on the human's turn before it
|
||||
// proactively nudges (subject to the social once-per-hour-per-game limit).
|
||||
proactiveNudgeIdle = 12 * time.Hour
|
||||
// The robot proactively nudges the idle human on a lengthening, randomized schedule rather
|
||||
// than an hourly stream: the first nudge lands ~60-90 min into the turn, and each subsequent
|
||||
// gap grows toward 1-6 h the longer the wait drags on, so a long idle turn gets only a handful
|
||||
// of increasingly-spaced reminders. The gap is a uniform sample in [nudgeGapFloorMinutes,
|
||||
// ceil] minutes, where ceil ramps from nudgeGapFirstCeilMinutes to nudgeGapCeilMinutes over
|
||||
// nudgeGapRamp of idle.
|
||||
nudgeGapFloorMinutes = 60.0
|
||||
nudgeGapFirstCeilMinutes = 90.0
|
||||
nudgeGapCeilMinutes = 360.0
|
||||
nudgeGapRamp = 12 * time.Hour
|
||||
)
|
||||
|
||||
// defaultBand is the target resulting score margin after the robot's move: when
|
||||
@@ -181,6 +188,23 @@ func nudgeReplyDelay(seed int64, moveCount int) time.Duration {
|
||||
return clampMinutes(lo + nudgeReplySpreadMinutes*u)
|
||||
}
|
||||
|
||||
// proactiveNudgeGap is the randomized wait before the next proactive nudge, given how long the
|
||||
// human had already been idle at the previous nudge (refIdle; 0 for the first nudge of the turn).
|
||||
// It is a uniform sample in [nudgeGapFloorMinutes, ceil] minutes, where ceil ramps from
|
||||
// nudgeGapFirstCeilMinutes (a ~60-90 min first gap) up to nudgeGapCeilMinutes (a 1-6 h gap) as
|
||||
// refIdle reaches nudgeGapRamp — so the reminders space out the longer the turn is neglected. It
|
||||
// is deterministic per (seed, refIdle), so the driver computes the same due time on every scan.
|
||||
func proactiveNudgeGap(refIdle time.Duration, seed int64) time.Duration {
|
||||
f := float64(refIdle) / float64(nudgeGapRamp)
|
||||
if f > 1 {
|
||||
f = 1
|
||||
}
|
||||
ceil := nudgeGapFirstCeilMinutes + (nudgeGapCeilMinutes-nudgeGapFirstCeilMinutes)*f
|
||||
u := unitFloat(mix(seed, "pnudge", int(refIdle/(30*time.Minute))))
|
||||
mins := nudgeGapFloorMinutes + (ceil-nudgeGapFloorMinutes)*u
|
||||
return time.Duration(mins * float64(time.Minute))
|
||||
}
|
||||
|
||||
// clampMinutes converts a minute count to a duration, clamping it to the hard delay
|
||||
// bounds so an out-of-range band can never produce an absurd think time.
|
||||
func clampMinutes(mins float64) time.Duration {
|
||||
|
||||
Reference in New Issue
Block a user