feat: sparser robot nudges, typed unread badge, lobby unread bump
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 53s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m26s
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 53s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m26s
Three owner-requested polish changes: - robot: replace the lengthening 60-90 min -> 6 h proactive-nudge ramp with a flat uniform 9-12 h wait before every nudge; the existing sleep-window gate still skips and defers a nudge that would land in the robot's night. - ui: colour the lobby/in-game unread dot by type -- the regular danger colour when a chat message is unread, a softer amber (--warn) when only nudges are. Adds a per-viewer unread_messages flag (chat_messages.kind='message') across the backend DTO, FlatBuffers wire, gateway transcode and the UI store. - ui: float games with any unread notification to the top of the lobby's your-turn and opponent-turn sections (finished keeps its order), reusing the existing unread_chat flag. Docs (ARCHITECTURE 7, FUNCTIONAL + _ru) updated. No DB migration; the new wire field is backward-compatible.
This commit is contained in:
@@ -79,16 +79,14 @@ const (
|
||||
// sleep window relative to the opponent's timezone, in hours.
|
||||
sleepDriftHours = 3
|
||||
|
||||
// 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
|
||||
// The robot proactively nudges the idle human on a sparse, randomized schedule rather than an
|
||||
// hourly stream: every nudge waits a uniform random 9-12 h after its reference point (the turn
|
||||
// start for the first nudge, the previous nudge thereafter), so even a long-neglected turn
|
||||
// collects only a few widely-spaced reminders. The 3 h window width is the random spread; the
|
||||
// gap does not lengthen with idle time. The driver still skips a nudge that would land in the
|
||||
// robot's sleep window, deferring it to the first scan after wake.
|
||||
nudgeGapLoHours = 9.0
|
||||
nudgeGapHiHours = 12.0
|
||||
)
|
||||
|
||||
// defaultBand is the target resulting score margin after the robot's move: when
|
||||
@@ -263,19 +261,13 @@ func nudgeReplyDelay(seed int64, moveCount int) time.Duration {
|
||||
|
||||
// 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.
|
||||
// It is a uniform sample in [nudgeGapLoHours, nudgeGapHiHours] hours, deterministic per
|
||||
// (seed, refIdle) so the driver computes the same due time on every scan. refIdle only salts the
|
||||
// draw, so each successive nudge of a still-idle turn waits a fresh 9-12 h rather than lengthening.
|
||||
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))
|
||||
hours := nudgeGapLoHours + (nudgeGapHiHours-nudgeGapLoHours)*u
|
||||
return time.Duration(hours * float64(time.Hour))
|
||||
}
|
||||
|
||||
// clampMinutes converts a minute count to a duration, clamping it to the hard delay
|
||||
|
||||
Reference in New Issue
Block a user