OOKNET                             [ /  search the index  ]  
──────────────────────────────────────────────────────────────────────────────────────
══════════════════════════════════════════════════════════════════════════════════════
OOKNET   [ /  search  ]  
────────────────────────────────────────────────
════════════════════════════════════════════════
 
HASH      62976dc11fc4
DATE      2026-07-16
SUBJECT   ookstats + web: home top players use account rankings
FILES     2 CHANGED
HASH      62976dc11fc4
DATE      2026-07-16
SUBJECT   ookstats + web: home top players use
          account rankings
FILES     2 CHANGED
 

diff --git a/nix/pkgs/ookstats/src/internal/generator/home.go b/nix/pkgs/ookstats/
src/internal/generator/home.go
index 9bee76f..a08436a 100644
--- a/nix/pkgs/ookstats/src/internal/generator/home.go
+++ b/nix/pkgs/ookstats/src/internal/generator/home.go
@@ -4,9 +4,11 @@ import (
 	"database/sql"
 	"fmt"
 	"path/filepath"
+	"sort"
 	"strings"
 	"time"
 
+	"ookstats/internal/loader"
 	"ookstats/internal/wow"
 	"ookstats/internal/writer"
 )
@@ -91,14 +93,16 @@ type HomePlayerEntry struct {
 	AvatarURL              string `json:"avatar_url,omitempty"`
 }
 
-// Seasons we surface on the home page. Mirrors run_rankings.season_id values.
-var homeSeasons = []int{1, 2}
-
 // regions for the regional top-player lists
 var homeRegions = []string{"us", "eu", "kr", "tw"}
 
 // GenerateHome writes outDir/api/home.json with the home page payload.
 func GenerateHome(db *sql.DB, outDir string) error {
+	homeSeasons, err := loadHomeSeasons(db)
+	if err != nil {
+		return fmt.Errorf("load home seasons: %w", err)
+	}
+
 	home := HomeJSON{
 		GeneratedAt: time.Now().UnixMilli(),
 		Seasons:     make(map[string]HomeSeasonJSON),
@@ -141,6 +145,23 @@ func GenerateHome(db *sql.DB, outDir string) error {
 	return nil
 }
 
+func loadHomeSeasons(db *sql.DB) ([]int, error) {
+	rows, err := db.Query(`SELECT DISTINCT season_number FROM seasons ORDER BY seaso
n_number ASC`)
+	if err != nil {
+		return nil, err
+	}
+	defer rows.Close()
+	var out []int
+	for rows.Next() {
+		var n int
+		if err := rows.Scan(&n); err != nil {
+			return nil, err
+		}
+		out = append(out, n)
+	}
+	return out, rows.Err()
+}
+
 // loadTopRunsPerDungeon returns up to topN team-filtered global runs per dungeon
 for the season,
 // keyed by dungeon slug.
 func loadTopRunsPerDungeon(db *sql.DB, seasonID, topN int) (map[string][]HomeRunE
ntry, error) {
@@ -308,19 +329,95 @@ func loadTeamMembersForRuns(db *sql.DB, runIDs []int64) (map
[int64][]HomeRunMemb
 	return out, rows.Err()
 }
 
-// loadTopPlayers returns the global top-N plus per-region top-N player lists for
 the season.
+// loadTopPlayers returns the global top-N plus per-region top-N lists for the se
ason,
+// ranked over accounts like the players leaderboard pages, shown via each accoun
t's main.
 func loadTopPlayers(db *sql.DB, seasonID, topN int) (HomePlayerLists, error) {
 	var lists HomePlayerLists
-	global, err := loadTopGlobalPlayers(db, seasonID, topN)
+	aggs, err := loader.LoadAccountAggregates(db, seasonID)
 	if err != nil {
-		return lists, fmt.Errorf("global: %w", err)
+		return lists, err
+	}
+
+	type accountRow struct {
+		accountID int64
+		combined  int64
+		main      *loader.AccountChar
+		region    string
+	}
+	eligible := make([]accountRow, 0, len(aggs))
+	for _, agg := range aggs {
+		if len(agg.BestPerDungeon) < loader.DungeonsForFullCoverage {
+			continue
+		}
+		main := loader.PickMainCharacter(agg)
+		if main == nil {
+			continue
+		}
+		var sum int64
+		for _, br := range agg.BestPerDungeon {
+			sum += br.Duration
+		}
+		eligible = append(eligible, accountRow{accountID: agg.AccountID, combined: sum,
 main: main, region: agg.Region})
+	}
+	sort.Slice(eligible, func(i, j int) bool {
+		if eligible[i].combined != eligible[j].combined {
+			return eligible[i].combined < eligible[j].combined
+		}
+		return eligible[i].accountID < eligible[j].accountID
+	})
+
+	toEntry := func(row accountRow, rank, total int, regional bool) HomePlayerEntry 
{
+		e := HomePlayerEntry{
+			Rank:               rank,
+			PlayerID:           row.main.PlayerID,
+			Name:               row.main.Name,
+			RealmSlug:          row.main.RealmSlug,
+			RealmName:          row.main.RealmName,
+			Region:             row.main.Region,
+			ClassName:          row.main.ClassName,
+			CombinedBestTimeMs: row.combined,
+		}
+		if row.main.MainSpecID.Valid {
+			specID := int(row.main.MainSpecID.Int64)
+			e.ActiveSpecID = specID
+			e.ClassName, e.ActiveSpecName = wow.FallbackClassAndSpec(row.main.ClassName, r
ow.main.ActiveSpecName, &specID)
+		}
+		bracket := loader.PercentileBracket(rank, total)
+		if regional {
+			e.RegionalRanking = rank
+			e.RegionalRankingBracket = bracket
+		} else {
+			e.GlobalRanking = rank
+			e.GlobalRankingBracket = bracket
+		}
+		return e
+	}
+
+	total := len(eligible)
+	for i, row := range eligible {
+		if i >= topN {
+			break
+		}
+		lists.Global = append(lists.Global, toEntry(row, i+1, total, false))
 	}
-	lists.Global = global
 
 	for _, region := range homeRegions {
-		regional, err := loadTopRegionalPlayers(db, seasonID, region, topN)
-		if err != nil {
-			return lists, fmt.Errorf("region %s: %w", region, err)
+		regionTotal := 0
+		for _, row := range eligible {
+			if strings.EqualFold(row.region, region) {
+				regionTotal++
+			}
+		}
+		var regional []HomePlayerEntry
+		for _, row := range eligible {
+			if !strings.EqualFold(row.region, region) {
+				continue
+			}
+			rank := len(regional) + 1
+			if rank > topN {
+				break
+			}
+			regional = append(regional, toEntry(row, rank, regionTotal, true))
 		}
 		switch region {
 		case "us":
@@ -333,133 +430,59 @@ func loadTopPlayers(db *sql.DB, seasonID, topN int) (HomePl
ayerLists, error) {
 			lists.TW = regional
 		}
 	}
-	return lists, nil
-}
-
-func loadTopGlobalPlayers(db *sql.DB, seasonID, topN int) ([]HomePlayerEntry, err
or) {
-	return queryTopPlayers(db, `
-		SELECT
-			pp.player_id, pp.name, pp.class_name, pp.main_spec_id,
-			pp.global_ranking, pp.global_ranking_bracket,
-			pp.regional_ranking, pp.regional_ranking_bracket,
-			pp.combined_best_time,
-			r.slug, r.name, r.region,
-			pd.avatar_url
-		FROM player_profiles pp
-		JOIN realms r ON pp.realm_id = r.id
-		LEFT JOIN player_details pd ON pp.player_id = pd.player_id
-		WHERE pp.season_id = ?
-		  AND pp.has_complete_coverage = 1
-		  AND pp.global_ranking IS NOT NULL
-		  AND pp.global_ranking <= ?
-		ORDER BY pp.global_ranking
-	`, seasonID, topN)
-}
 
-func loadTopRegionalPlayers(db *sql.DB, seasonID int, region string, topN int) ([
]HomePlayerEntry, error) {
-	return queryTopPlayers(db, `
-		SELECT
-			pp.player_id, pp.name, pp.class_name, pp.main_spec_id,
-			pp.global_ranking, pp.global_ranking_bracket,
-			pp.regional_ranking, pp.regional_ranking_bracket,
-			pp.combined_best_time,
-			r.slug, r.name, r.region,
-			pd.avatar_url
-		FROM player_profiles pp
-		JOIN realms r ON pp.realm_id = r.id
-		LEFT JOIN player_details pd ON pp.player_id = pd.player_id
-		WHERE pp.season_id = ?
-		  AND pp.has_complete_coverage = 1
-		  AND r.region = ?
-		  AND pp.regional_ranking IS NOT NULL
-		  AND pp.regional_ranking <= ?
-		ORDER BY pp.regional_ranking
-	`, seasonID, region, topN)
+	if err := fillAvatars(db, &lists); err != nil {
+		return lists, err
+	}
+	return lists, nil
 }
 
-// queryTopPlayers executes a player-list query whose columns match the SELECT li
st above
-// and returns enriched HomePlayerEntry rows. Last positional arg is interpreted 
as the rank
-// to populate Rank from (global_ranking for the global list, regional_ranking fo
r regional).
-func queryTopPlayers(db *sql.DB, q string, args ...any) ([]HomePlayerEntry, error
) {
-	rows, err := db.Query(q, args...)
+// account aggregates carry no avatar; look up the handful of displayed mains
+func fillAvatars(db *sql.DB, lists *HomePlayerLists) error {
+	all := [][]HomePlayerEntry{lists.Global, lists.US, lists.EU, lists.KR, lists.TW}
+	idSet := map[int64]struct{}{}
+	for _, l := range all {
+		for _, e := range l {
+			idSet[e.PlayerID] = struct{}{}
+		}
+	}
+	if len(idSet) == 0 {
+		return nil
+	}
+	ids := make([]any, 0, len(idSet))
+	ph := make([]string, 0, len(idSet))
+	for id := range idSet {
+		ids = append(ids, id)
+		ph = append(ph, "?")
+	}
+	rows, err := db.Query(`
+		SELECT player_id, avatar_url
+		FROM player_details
+		WHERE player_id IN (`+strings.Join(ph, ",")+`)
+		  AND avatar_url IS NOT NULL
+	`, ids...)
 	if err != nil {
-		return nil, err
+		return err
 	}
 	defer rows.Close()
-
-	var out []HomePlayerEntry
+	avatars := map[int64]string{}
 	for rows.Next() {
-		var (
-			e               HomePlayerEntry
-			className       sql.NullString
-			mainSpecID      sql.NullInt64
-			globalRank      sql.NullInt64
-			globalBracket   sql.NullString
-			regionalRank    sql.NullInt64
-			regionalBracket sql.NullString
-			combinedBest    sql.NullInt64
-			realmName       sql.NullString
-			avatarURL       sql.NullString
-		)
-		if err := rows.Scan(
-			&e.PlayerID, &e.Name, &className, &mainSpecID,
-			&globalRank, &globalBracket,
-			&regionalRank, &regionalBracket,
-			&combinedBest,
-			&e.RealmSlug, &realmName, &e.Region,
-			&avatarURL,
-		); err != nil {
-			return nil, err
-		}
-		if className.Valid {
-			e.ClassName = className.String
-		}
-		if mainSpecID.Valid {
-			e.ActiveSpecID = int(mainSpecID.Int64)
+		var id int64
+		var url string
+		if err := rows.Scan(&id, &url); err != nil {
+			return err
 		}
-		if globalRank.Valid {
-			e.GlobalRanking = int(globalRank.Int64)
-		}
-		if globalBracket.Valid {
-			e.GlobalRankingBracket = globalBracket.String
-		}
-		if regionalRank.Valid {
-			e.RegionalRanking = int(regionalRank.Int64)
-		}
-		if regionalBracket.Valid {
-			e.RegionalRankingBracket = regionalBracket.String
-		}
-		if combinedBest.Valid {
-			e.CombinedBestTimeMs = combinedBest.Int64
-		}
-		if realmName.Valid {
-			e.RealmName = realmName.String
-		}
-		if avatarURL.Valid {
-			e.AvatarURL = avatarURL.String
-		}
-		// Resolve spec name; class_name comes from player_profiles directly
-		if e.ActiveSpecID > 0 {
-			specPtr := e.ActiveSpecID
-			cls, spec := wow.FallbackClassAndSpec(e.ClassName, "", &specPtr)
-			e.ClassName, e.ActiveSpecName = cls, spec
-		}
-		// Rank for the entry: regional ranking takes precedence (the per-region lists
-		// query for it); the global list will populate from GlobalRanking via the
-		// fallback below.
-		if e.RegionalRanking > 0 && isRegionalQuery(q) {
-			e.Rank = e.RegionalRanking
-		} else {
-			e.Rank = e.GlobalRanking
+		avatars[id] = url
+	}
+	if err := rows.Err(); err != nil {
+		return err
+	}
+	for _, l := range all {
+		for i := range l {
+			l[i].AvatarURL = avatars[l[i].PlayerID]
 		}
-		out = append(out, e)
 	}
-	return out, rows.Err()
-}
-
-// heuristic on ORDER text so queryTopPlayers can pick global vs regional rank
-func isRegionalQuery(q string) bool {
-	return strings.Contains(q, "ORDER BY pp.regional_ranking")
+	return nil
 }
 
 // loadSeasonName picks a representative season_name for the season number.

diff --git a/nix/pkgs/ookstats/src/internal/
generator/home.go b/nix/pkgs/ookstats/src/in
ternal/generator/home.go
index 9bee76f..a08436a 100644
--- a/nix/pkgs/ookstats/src/internal/generat
or/home.go
+++ b/nix/pkgs/ookstats/src/internal/generat
or/home.go
@@ -4,9 +4,11 @@ import (
 	"database/sql"
 	"fmt"
 	"path/filepath"
+	"sort"
 	"strings"
 	"time"
 
+	"ookstats/internal/loader"
 	"ookstats/internal/wow"
 	"ookstats/internal/writer"
 )
@@ -91,14 +93,16 @@ type HomePlayerEntry str
uct {
 	AvatarURL              string `json:"avata
r_url,omitempty"`
 }
 
-// Seasons we surface on the home page. Mir
rors run_rankings.season_id values.
-var homeSeasons = []int{1, 2}
-
 // regions for the regional top-player list
s
 var homeRegions = []string{"us", "eu", "kr"
, "tw"}
 
 // GenerateHome writes outDir/api/home.json
 with the home page payload.
 func GenerateHome(db *sql.DB, outDir string
) error {
+	homeSeasons, err := loadHomeSeasons(db)
+	if err != nil {
+		return fmt.Errorf("load home seasons: %w"
, err)
+	}
+
 	home := HomeJSON{
 		GeneratedAt: time.Now().UnixMilli(),
 		Seasons:     make(map[string]HomeSeasonJS
ON),
@@ -141,6 +145,23 @@ func GenerateHome(db *s
ql.DB, outDir string) error {
 	return nil
 }
 
+func loadHomeSeasons(db *sql.DB) ([]int, er
ror) {
+	rows, err := db.Query(`SELECT DISTINCT sea
son_number FROM seasons ORDER BY season_numb
er ASC`)
+	if err != nil {
+		return nil, err
+	}
+	defer rows.Close()
+	var out []int
+	for rows.Next() {
+		var n int
+		if err := rows.Scan(&n); err != nil {
+			return nil, err
+		}
+		out = append(out, n)
+	}
+	return out, rows.Err()
+}
+
 // loadTopRunsPerDungeon returns up to topN
 team-filtered global runs per dungeon for t
he season,
 // keyed by dungeon slug.
 func loadTopRunsPerDungeon(db *sql.DB, seas
onID, topN int) (map[string][]HomeRunEntry, 
error) {
@@ -308,19 +329,95 @@ func loadTeamMembersFo
rRuns(db *sql.DB, runIDs []int64) (map[int64
][]HomeRunMemb
 	return out, rows.Err()
 }
 
-// loadTopPlayers returns the global top-N 
plus per-region top-N player lists for the s
eason.
+// loadTopPlayers returns the global top-N 
plus per-region top-N lists for the season,
+// ranked over accounts like the players le
aderboard pages, shown via each account's ma
in.
 func loadTopPlayers(db *sql.DB, seasonID, t
opN int) (HomePlayerLists, error) {
 	var lists HomePlayerLists
-	global, err := loadTopGlobalPlayers(db, se
asonID, topN)
+	aggs, err := loader.LoadAccountAggregates(
db, seasonID)
 	if err != nil {
-		return lists, fmt.Errorf("global: %w", er
r)
+		return lists, err
+	}
+
+	type accountRow struct {
+		accountID int64
+		combined  int64
+		main      *loader.AccountChar
+		region    string
+	}
+	eligible := make([]accountRow, 0, len(aggs
))
+	for _, agg := range aggs {
+		if len(agg.BestPerDungeon) < loader.Dunge
onsForFullCoverage {
+			continue
+		}
+		main := loader.PickMainCharacter(agg)
+		if main == nil {
+			continue
+		}
+		var sum int64
+		for _, br := range agg.BestPerDungeon {
+			sum += br.Duration
+		}
+		eligible = append(eligible, accountRow{ac
countID: agg.AccountID, combined: sum, main:
 main, region: agg.Region})
+	}
+	sort.Slice(eligible, func(i, j int) bool {
+		if eligible[i].combined != eligible[j].co
mbined {
+			return eligible[i].combined < eligible[j
].combined
+		}
+		return eligible[i].accountID < eligible[j
].accountID
+	})
+
+	toEntry := func(row accountRow, rank, tota
l int, regional bool) HomePlayerEntry {
+		e := HomePlayerEntry{
+			Rank:               rank,
+			PlayerID:           row.main.PlayerID,
+			Name:               row.main.Name,
+			RealmSlug:          row.main.RealmSlug,
+			RealmName:          row.main.RealmName,
+			Region:             row.main.Region,
+			ClassName:          row.main.ClassName,
+			CombinedBestTimeMs: row.combined,
+		}
+		if row.main.MainSpecID.Valid {
+			specID := int(row.main.MainSpecID.Int64)
+			e.ActiveSpecID = specID
+			e.ClassName, e.ActiveSpecName = wow.Fall
backClassAndSpec(row.main.ClassName, row.mai
n.ActiveSpecName, &specID)
+		}
+		bracket := loader.PercentileBracket(rank,
 total)
+		if regional {
+			e.RegionalRanking = rank
+			e.RegionalRankingBracket = bracket
+		} else {
+			e.GlobalRanking = rank
+			e.GlobalRankingBracket = bracket
+		}
+		return e
+	}
+
+	total := len(eligible)
+	for i, row := range eligible {
+		if i >= topN {
+			break
+		}
+		lists.Global = append(lists.Global, toEnt
ry(row, i+1, total, false))
 	}
-	lists.Global = global
 
 	for _, region := range homeRegions {
-		regional, err := loadTopRegionalPlayers(d
b, seasonID, region, topN)
-		if err != nil {
-			return lists, fmt.Errorf("region %s: %w"
, region, err)
+		regionTotal := 0
+		for _, row := range eligible {
+			if strings.EqualFold(row.region, region)
 {
+				regionTotal++
+			}
+		}
+		var regional []HomePlayerEntry
+		for _, row := range eligible {
+			if !strings.EqualFold(row.region, region
) {
+				continue
+			}
+			rank := len(regional) + 1
+			if rank > topN {
+				break
+			}
+			regional = append(regional, toEntry(row,
 rank, regionTotal, true))
 		}
 		switch region {
 		case "us":
@@ -333,133 +430,59 @@ func loadTopPlayers(d
b *sql.DB, seasonID, topN int) (HomePlayerLi
sts, error) {
 			lists.TW = regional
 		}
 	}
-	return lists, nil
-}
-
-func loadTopGlobalPlayers(db *sql.DB, seaso
nID, topN int) ([]HomePlayerEntry, error) {
-	return queryTopPlayers(db, `
-		SELECT
-			pp.player_id, pp.name, pp.class_name, pp
.main_spec_id,
-			pp.global_ranking, pp.global_ranking_bra
cket,
-			pp.regional_ranking, pp.regional_ranking
_bracket,
-			pp.combined_best_time,
-			r.slug, r.name, r.region,
-			pd.avatar_url
-		FROM player_profiles pp
-		JOIN realms r ON pp.realm_id = r.id
-		LEFT JOIN player_details pd ON pp.player_
id = pd.player_id
-		WHERE pp.season_id = ?
-		  AND pp.has_complete_coverage = 1
-		  AND pp.global_ranking IS NOT NULL
-		  AND pp.global_ranking <= ?
-		ORDER BY pp.global_ranking
-	`, seasonID, topN)
-}
 
-func loadTopRegionalPlayers(db *sql.DB, sea
sonID int, region string, topN int) ([]HomeP
layerEntry, error) {
-	return queryTopPlayers(db, `
-		SELECT
-			pp.player_id, pp.name, pp.class_name, pp
.main_spec_id,
-			pp.global_ranking, pp.global_ranking_bra
cket,
-			pp.regional_ranking, pp.regional_ranking
_bracket,
-			pp.combined_best_time,
-			r.slug, r.name, r.region,
-			pd.avatar_url
-		FROM player_profiles pp
-		JOIN realms r ON pp.realm_id = r.id
-		LEFT JOIN player_details pd ON pp.player_
id = pd.player_id
-		WHERE pp.season_id = ?
-		  AND pp.has_complete_coverage = 1
-		  AND r.region = ?
-		  AND pp.regional_ranking IS NOT NULL
-		  AND pp.regional_ranking <= ?
-		ORDER BY pp.regional_ranking
-	`, seasonID, region, topN)
+	if err := fillAvatars(db, &lists); err != 
nil {
+		return lists, err
+	}
+	return lists, nil
 }
 
-// queryTopPlayers executes a player-list q
uery whose columns match the SELECT list abo
ve
-// and returns enriched HomePlayerEntry row
s. Last positional arg is interpreted as the
 rank
-// to populate Rank from (global_ranking fo
r the global list, regional_ranking for regi
onal).
-func queryTopPlayers(db *sql.DB, q string, 
args ...any) ([]HomePlayerEntry, error) {
-	rows, err := db.Query(q, args...)
+// account aggregates carry no avatar; look
 up the handful of displayed mains
+func fillAvatars(db *sql.DB, lists *HomePla
yerLists) error {
+	all := [][]HomePlayerEntry{lists.Global, l
ists.US, lists.EU, lists.KR, lists.TW}
+	idSet := map[int64]struct{}{}
+	for _, l := range all {
+		for _, e := range l {
+			idSet[e.PlayerID] = struct{}{}
+		}
+	}
+	if len(idSet) == 0 {
+		return nil
+	}
+	ids := make([]any, 0, len(idSet))
+	ph := make([]string, 0, len(idSet))
+	for id := range idSet {
+		ids = append(ids, id)
+		ph = append(ph, "?")
+	}
+	rows, err := db.Query(`
+		SELECT player_id, avatar_url
+		FROM player_details
+		WHERE player_id IN (`+strings.Join(ph, ",
")+`)
+		  AND avatar_url IS NOT NULL
+	`, ids...)
 	if err != nil {
-		return nil, err
+		return err
 	}
 	defer rows.Close()
-
-	var out []HomePlayerEntry
+	avatars := map[int64]string{}
 	for rows.Next() {
-		var (
-			e               HomePlayerEntry
-			className       sql.NullString
-			mainSpecID      sql.NullInt64
-			globalRank      sql.NullInt64
-			globalBracket   sql.NullString
-			regionalRank    sql.NullInt64
-			regionalBracket sql.NullString
-			combinedBest    sql.NullInt64
-			realmName       sql.NullString
-			avatarURL       sql.NullString
-		)
-		if err := rows.Scan(
-			&e.PlayerID, &e.Name, &className, &mainS
pecID,
-			&globalRank, &globalBracket,
-			&regionalRank, &regionalBracket,
-			&combinedBest,
-			&e.RealmSlug, &realmName, &e.Region,
-			&avatarURL,
-		); err != nil {
-			return nil, err
-		}
-		if className.Valid {
-			e.ClassName = className.String
-		}
-		if mainSpecID.Valid {
-			e.ActiveSpecID = int(mainSpecID.Int64)
+		var id int64
+		var url string
+		if err := rows.Scan(&id, &url); err != ni
l {
+			return err
 		}
-		if globalRank.Valid {
-			e.GlobalRanking = int(globalRank.Int64)
-		}
-		if globalBracket.Valid {
-			e.GlobalRankingBracket = globalBracket.S
tring
-		}
-		if regionalRank.Valid {
-			e.RegionalRanking = int(regionalRank.Int
64)
-		}
-		if regionalBracket.Valid {
-			e.RegionalRankingBracket = regionalBrack
et.String
-		}
-		if combinedBest.Valid {
-			e.CombinedBestTimeMs = combinedBest.Int6
4
-		}
-		if realmName.Valid {
-			e.RealmName = realmName.String
-		}
-		if avatarURL.Valid {
-			e.AvatarURL = avatarURL.String
-		}
-		// Resolve spec name; class_name comes fr
om player_profiles directly
-		if e.ActiveSpecID > 0 {
-			specPtr := e.ActiveSpecID
-			cls, spec := wow.FallbackClassAndSpec(e.
ClassName, "", &specPtr)
-			e.ClassName, e.ActiveSpecName = cls, spe
c
-		}
-		// Rank for the entry: regional ranking t
akes precedence (the per-region lists
-		// query for it); the global list will po
pulate from GlobalRanking via the
-		// fallback below.
-		if e.RegionalRanking > 0 && isRegionalQue
ry(q) {
-			e.Rank = e.RegionalRanking
-		} else {
-			e.Rank = e.GlobalRanking
+		avatars[id] = url
+	}
+	if err := rows.Err(); err != nil {
+		return err
+	}
+	for _, l := range all {
+		for i := range l {
+			l[i].AvatarURL = avatars[l[i].PlayerID]
 		}
-		out = append(out, e)
 	}
-	return out, rows.Err()
-}
-
-// heuristic on ORDER text so queryTopPlaye
rs can pick global vs regional rank
-func isRegionalQuery(q string) bool {
-	return strings.Contains(q, "ORDER BY pp.re
gional_ranking")
+	return nil
 }
 
 // loadSeasonName picks a representative se
ason_name for the season number.
 

diff --git a/web/src/components/Home/TopPlayersList/TopPlayersList.astro b/web/src
/components/Home/TopPlayersList/TopPlayersList.astro
index 276d133..f4a8b7d 100644
--- a/web/src/components/Home/TopPlayersList/TopPlayersList.astro
+++ b/web/src/components/Home/TopPlayersList/TopPlayersList.astro
@@ -5,12 +5,13 @@ import type { HomePlayerEntry } from "../../../lib/types";
 
 interface Props {
   title: string;
-  players: HomePlayerEntry[];
+  players: HomePlayerEntry[] | null | undefined;
   scope: "global" | "regional";
   viewAllHref?: string;
 }
 
-const { title, players, scope, viewAllHref } = Astro.props;
+const { title, players: rawPlayers, scope, viewAllHref } = Astro.props;
+const players: HomePlayerEntry[] = rawPlayers ?? [];
 
 // match badge color to the list's scope
 function bracketFor(p: HomePlayerEntry): string | undefined {

diff --git a/web/src/components/Home/TopPlay
ersList/TopPlayersList.astro b/web/src/compo
nents/Home/TopPlayersList/TopPlayersList.ast
ro
index 276d133..f4a8b7d 100644
--- a/web/src/components/Home/TopPlayersList
/TopPlayersList.astro
+++ b/web/src/components/Home/TopPlayersList
/TopPlayersList.astro
@@ -5,12 +5,13 @@ import type { HomePlayerEn
try } from "../../../lib/types";
 
 interface Props {
   title: string;
-  players: HomePlayerEntry[];
+  players: HomePlayerEntry[] | null | undef
ined;
   scope: "global" | "regional";
   viewAllHref?: string;
 }
 
-const { title, players, scope, viewAllHref 
} = Astro.props;
+const { title, players: rawPlayers, scope, 
viewAllHref } = Astro.props;
+const players: HomePlayerEntry[] = rawPlaye
rs ?? [];
 
 // match badge color to the list's scope
 function bracketFor(p: HomePlayerEntry): st
ring | undefined {
 
[ BACK TO LOG ]
──────────────────────────────────────────────────────────────────────────────────────
OOKNET
────────────────────────────────────────────────
OOKNET