package domain import ( "fmt" "sort" "time" ) const ( MaxPlacementRTT = 100.0 MinRatingTolerance = 100.0 MaxRatingTolerance = 400.0 RatingWidenStep = 25.0 RatingWidenPeriod = 30.0 ) // Candidate is the server-side projection of a verified, live queue ticket. // RTT values come from backend probes, never from the client request body. type Candidate struct { TicketID string PlayerID string Rating float64 EnqueuedAt time.Time PredictedRTT map[string]float64 } type Selection struct { Players []Candidate Region string WorstRTT float64 TotalRTT float64 RatingRange float64 TotalWaitSeconds float64 } func RatingTolerance(waitSeconds float64) float64 { if waitSeconds < 0 { waitSeconds = 0 } value := MinRatingTolerance + RatingWidenStep*float64(int(waitSeconds/RatingWidenPeriod)) if value > MaxRatingTolerance { return MaxRatingTolerance } return value } func SelectCandidates(anchor Candidate, candidates []Candidate, size int, now time.Time) (Selection, error) { if size < 1 { return Selection{}, fmt.Errorf("candidate size must be positive") } pool := make([]Candidate, 0, len(candidates)+1) seen := map[string]bool{} add := func(candidate Candidate) { if candidate.TicketID != "" && !seen[candidate.TicketID] { seen[candidate.TicketID] = true pool = append(pool, candidate) } } add(anchor) for _, candidate := range candidates { add(candidate) } if len(pool) < size { return Selection{}, fmt.Errorf("only %d compatible candidates available for size %d", len(pool), size) } best := Selection{} found := false chosen := make([]Candidate, 0, size) var visit func(int) visit = func(start int) { if len(chosen) == size { if !containsTicket(chosen, anchor.TicketID) || !compatibleSet(chosen, now) { return } selection, ok := scoreSelection(chosen, now) if !ok { return } if !found || betterSelection(selection, best) { best = selection found = true } return } for i := start; i < len(pool); i++ { chosen = append(chosen, pool[i]) visit(i + 1) chosen = chosen[:len(chosen)-1] } } visit(0) if !found { return Selection{}, fmt.Errorf("no candidate set satisfies latency and mutual rating limits") } sort.Slice(best.Players, func(i, j int) bool { return best.Players[i].TicketID < best.Players[j].TicketID }) return best, nil } func compatibleSet(players []Candidate, now time.Time) bool { regions := commonRegions(players) if len(regions) == 0 { return false } for i := range players { for j := i + 1; j < len(players); j++ { waitI := now.Sub(players[i].EnqueuedAt).Seconds() waitJ := now.Sub(players[j].EnqueuedAt).Seconds() delta := abs(players[i].Rating - players[j].Rating) if delta > RatingTolerance(waitI) || delta > RatingTolerance(waitJ) { return false } } } return true } func commonRegions(players []Candidate) []string { if len(players) == 0 { return nil } regions := make(map[string]bool) for region, rtt := range players[0].PredictedRTT { if rtt <= MaxPlacementRTT { regions[region] = true } } for _, player := range players[1:] { for region := range regions { rtt, ok := player.PredictedRTT[region] if !ok || rtt > MaxPlacementRTT { delete(regions, region) } } } out := make([]string, 0, len(regions)) for region := range regions { out = append(out, region) } sort.Strings(out) return out } func scoreSelection(players []Candidate, now time.Time) (Selection, bool) { regions := commonRegions(players) if len(regions) == 0 { return Selection{}, false } best := Selection{} for _, region := range regions { worst, total := 0.0, 0.0 minRating, maxRating := players[0].Rating, players[0].Rating wait := 0.0 for _, player := range players { rtt := player.PredictedRTT[region] if rtt > worst { worst = rtt } total += rtt if player.Rating < minRating { minRating = player.Rating } if player.Rating > maxRating { maxRating = player.Rating } if seconds := now.Sub(player.EnqueuedAt).Seconds(); seconds > 0 { wait += seconds } } candidate := Selection{Players: append([]Candidate(nil), players...), Region: region, WorstRTT: worst, TotalRTT: total, RatingRange: maxRating - minRating, TotalWaitSeconds: wait} if best.Players == nil || betterSelection(candidate, best) { best = candidate } } return best, true } func betterSelection(a, b Selection) bool { if a.WorstRTT != b.WorstRTT { return a.WorstRTT < b.WorstRTT } if a.TotalRTT != b.TotalRTT { return a.TotalRTT < b.TotalRTT } if a.RatingRange != b.RatingRange { return a.RatingRange < b.RatingRange } if a.TotalWaitSeconds != b.TotalWaitSeconds { return a.TotalWaitSeconds > b.TotalWaitSeconds } return ticketIDs(a.Players) < ticketIDs(b.Players) } func containsTicket(players []Candidate, ticketID string) bool { for _, player := range players { if player.TicketID == ticketID { return true } } return false } func ticketIDs(players []Candidate) string { ids := make([]string, 0, len(players)) for _, player := range players { ids = append(ids, player.TicketID) } sort.Strings(ids) result := "" for _, id := range ids { result += id + "\x00" } return result } func abs(value float64) float64 { if value < 0 { return -value } return value }