22 KiB
Leaderboard
Open Leaderboard in the main navigation to compare players across the fleet,
with one entry per GUID combining Black Ops I (T5) and Black Ops II (T6). Game, map,
and starting squad-size filters limit which matches contribute to each player's statistics and
rank. Choose a ranking, search a name or GUID, and share the page URL with its
filters intact. Results refresh while the page is visible. The page opens on a
recorded map by default; choose All maps for a fleet-wide comparison. A URL
with map= explicitly selects all maps. Players selects Solo, Duo, Trio or
Four players using the recorded starting roster; unknown starting counts appear
only under All squad sizes. The JSON API still defaults to all maps.
Qualifying matches
Matches only appear in server/global history once they reach round 2.
Leaderboards, summaries, map options, profiles and player history include only
completed matches with a received game_end. Active and interrupted matches do
not contribute. A player must also be observed at round 2 or later; players who
left before a completed game's end retain their recorded stats.
This also excludes existing round 1 records. Early telemetry is retained so
qualifying games keep their start time, roster and cumulative counters.
Player profiles and match results
Profiles and filters
Click a player's name to open their shareable profile at
/leaderboard/players/[guid]. Profiles combine Black Ops I (T5) and Black Ops II
(T6) records for the same GUID. Existing /leaderboard/players/[game]/[guid]
links redirect to the combined profile. Profiles show all ten lifetime statistics,
headshots as a percentage of kills, kills per down, average kills and observed
rounds per game, and completion rate. Each map has its own complete set of
records; map records and match history identify their game. Match history shows
individual counters, server, date and completion credit in pages of 25 matches
across both games. Map names link back to that map's leaderboard in its game.
Profiles update every 30 seconds while visible; the freshness indicator offers Retry if an update fails. Ratios with a zero denominator
display a dash. Lifetime statistics and per-map records always cover all recorded
maps across both games.
Opening a player from the leaderboard preserves the originating game, map, squad size, metric, search and page in the Leaderboard return link. The profile's Filters controls initialize from that game/map/squad/metric and show the player's rank within that competition, independent of the original search. These controls also filter match history; lifetime totals stay unchanged. History pagination retains the selection and return link. Map links preserve the selected ranking metric. A player without qualifying matches in a selection can still view their lifetime profile and choose another scope.
On phones, per-map records and match history use compact cards with the map, game, record values and match identity up front. Record values link directly to their completed match, where the full evidence is available. Desktop profiles use compact tables; both views use the same history filters and pagination. History dates say First observed, since a missing start report can make the first recorded observation later than the actual game start.
Record evidence and match results
Highest-round, best-game-kill and highest-score records link to the completed match that earned them on the leaderboard and in lifetime/per-map profile stats. Each source names its match and map. Open the match to see completion time, server, whether its start was recorded, and player participation evidence. Presence is not proof of continuous survival. Equal personal bests link to the earliest recorded completion, then the lowest match ID when times tie.
Completed matches link to their own results page at /matches/[id] from global
Match History, server history, and player profiles. The page shows the recorded
map and server, final round, start/end times, duration when the start was observed,
player counts, and the full saved roster. Players missing from the final report
retain their earlier counters; players with no stats display dashes. Counters and
score are the highest reported values within the match, not necessarily its final
scoreboard. Active and interrupted matches remain listed without results links.
Match pages remain available after the original server is removed.
Player identity
The leaderboard uses the existing telemetry events; no reporter change is required. The leaderboard and profiles identify a player by GUID, displaying the latest reported name across both games, regardless of game or map filters. Different names for the same GUID do not split their leaderboard entry or profile; identical names with different GUIDs remain separate. Stored player records remain keyed by game + GUID for telemetry ingestion; combining their identity requires no rekeying. The record-source upgrade automatically adds nullable participation-start fields; existing rows keep an unknown start instead of inferring one. GUIDs stay strings.
Recorded statistics
| Statistic | Meaning |
|---|---|
| Highest round | Highest round reported with that player present, including players who joined mid-game. |
| Total kills, headshots, revives, downs | Sum of each player's highest reported counter in each game. Round snapshots are cumulative, so they are never added together within a game. |
| Total rounds | Round progression during observed participation, added across games. A player observed at game start and then round 8 receives seven rounds. A player first seen at round 8 gets a highest-round record of 8, but zero total rounds; reaching round 9 then adds one. |
| Games played | Completed games in which a stats report at round 2 or later contains the player, even if they left before the end. Start-only and round 1 participation do not count. |
| Games completed | Games whose game_end roster contains the player. Disconnected players retain earlier stats without receiving completion credit. |
| Best game kills | Highest kill count recorded in any one game. |
| Highest reported score | Highest observed score balance. Spending points can lower the score, so this is not cumulative points earned. |
Every ranking sorts highest first, including downs (a count, not an efficiency rating). Equal values share a rank; name and GUID provide a stable display order. Searching preserves the player's rank for the selected game/map filters, or across both games when unfiltered. Summary cards describe the same selection, regardless of the search text. Player counts include each qualifying GUID once, including when they have records in both games. Summary rounds are player-rounds: four players progressing from game start to round 8 contribute 28. Highest-round records always use the round reached in a completed match, including the final round and players who joined mid-game.
Efficiency rankings
Efficiency rankings are also available for Headshots / kills, Kills per down, Average kills, Average rounds, and Completion rate. Each requires at least five completed games in the selected game/map scope. Headshot rate also requires 100 kills; kills per down requires five downs. The board shows the numerator, denominator and game count beside each ratio. Ratios are computed from aggregate counters, not an average of individual match percentages. Completion rate is the share of those ended games whose final report contained the player; it is not a win rate. Active and interrupted games do not enter any denominator. Zero-denominator ratios remain unknown and are not ranked.
Qualification is applied before ranking, search and pagination. Summary cards still describe all players in the game/map selection, including those below the threshold. Profile metrics show their values and whether the player qualifies for the combined lifetime board; a scoped board can have a smaller sample.
Participation limits
Round totals require the player to appear in consecutive observations. If a report omits them, their return starts a new participation interval and the gap is not credited. The payload cannot tell whether a present player was alive, spectating, or absent between reports. Mid-game snapshots still contribute their reported kills and other counters, even when earlier participation is unknown.
Persistence and match boundaries
Match and player records live in SQLite alongside the rest of Electric Cherry. They persist across app restarts and server deletion, and are not subject to campaign history retention. The latest scoreboard and leaderboard update in the same transaction. Each match keeps player counters even after someone leaves.
A game_start opens a match; game_end completes it. A changed map, a lower round,
or a round report after a completed game can also establish a new match when the
start was missed. Superseded active matches remain in history as interrupted.
Repeated starts before any stats belong to the same match. Identical consecutive
payloads (ignoring receipt time and roster order) do not add records. Repeated
snapshots in a match cannot increase counters or rounds twice.
The first upgrade imports the latest stored full scoreboard from each server once. That provides the available counters and highest round, but not discarded earlier games or unobserved round progression. Old round-only snapshots have no player identity and cannot be imported. Tracking continues from future reports.
T6 selections sharing an engine map, such as Town Survival and TranZit, remain separate using the server's registered map variant. Unknown variants remain explicitly identified instead of being labeled as a known selection. A match retains the game/map identity recorded at ingestion even if the server is later reconfigured or removed.
The current protocol sends events in gameplay order and has no match ID, event ID, or source sequence. A late report from an older game cannot be reliably distinguished from a new game. Likewise, identical starts before any stats are indistinguishable from retries. Send reports once in order. Within a match, counter decreases use the previous high-water mark; if a reconnect resets a player's counters, subsequent activity may be undercounted until those counters pass their earlier maxima. Exact replay handling and counter-reset accounting would require additional reporter identity/ordering information.
Read API
This reference describes the current repository implementation. A deployed instance must run the corresponding code to provide the same behavior. The sources are the HTTP handler, response types, query implementation, and authentication guard.
Authentication and requests
GET /api/leaderboard is a public JSON endpoint; HEAD is public too. A tool
does not need an account, session cookie, API key or authorization header to
read it. Reads also work before administrator setup and with an expired session
or a session awaiting a password change. This exception applies only to reads
of this exact API route. Leaderboard/profile HTML pages, match-history APIs and
management routes still require a session; see Users and access.
For example:
EC_BASE_URL='https://ec.example.com'
curl --fail-with-body --silent --show-error \
--get "$EC_BASE_URL/api/leaderboard" \
--data-urlencode 'game=t6' \
--data-urlencode 'metric=kills' \
--data-urlencode 'limit=100' \
--data-urlencode 'offset=0'
HTTPS is required. Local HTTP development requires EC_AUTH_ALLOW_HTTP=true
and a loopback hostname. The read request does not require an Origin header.
This endpoint does not add cross-origin browser access (CORS) headers; the
example above works for command-line or server-side clients. Successful responses use
Content-Type: application/json, Cache-Control: no-store and Vary: Cookie.
Query parameters
All parameters are optional. Names and enumerated values are case-sensitive. Use URL encoding for map keys and search text.
| Parameter | Default | Accepted values and behavior |
|---|---|---|
game |
Both games | Exactly t5 or t6. An empty value is invalid. |
map |
All maps | Opaque maps[].key, at most 1024 characters. Empty means no map filter. An unknown key or a game/map mismatch returns an empty selection, not an error. |
players |
All squad sizes | Exactly 1, 2, 3, or 4, matching the recorded starting player count. Unknown counts contribute only when this filter is absent. |
metric |
highestRound |
One of the metric keys below. An empty or unknown value is invalid. |
q |
No search | At most 100 characters before trimming. Matches a literal substring of the current name or GUID, ignoring ASCII letter case. Surrounding whitespace is trimmed; whitespace-only input means no search. % and _ are literal characters, not wildcards. |
limit |
50 |
Integer from 1 to 100. |
offset |
0 |
Integer from 0 to 1,000,000; counts matching entries to skip, not ranks or pages. |
limit and offset accept decimal digits only. Empty strings, signs, spaces,
fractions and exponent notation are rejected rather than clamped. String
length limits use JavaScript string length (UTF-16 code units). Unrecognized
query parameters are ignored; if a parameter is repeated, its first value wins.
Metric keys are highestRound, kills, roundsSurvived, headshots, revives,
downs, gamesPlayed, gamesCompleted, bestKills, bestScore, headshotRate,
killsPerDown, averageKills, averageRounds, and completionRate.
Success response
HTTP 200 returns the following top-level fields directly, with no ok or
data wrapper:
| Field | Type | Meaning |
|---|---|---|
entries |
Entry array | Requested page, ordered by the selected metric descending. |
maps |
Map array | Available completed-match map options, filtered only by game, independent of map, q, metric qualification and pagination. |
total |
number | Number of matching GUIDs after metric qualification and search, before pagination. |
limit, offset |
number | Accepted pagination values. |
metric |
string | Selected metric key, including the default when omitted. |
summary |
object | Totals for all qualifying completed participation in the game/map scope, independent of q, metric qualification and pagination. |
Each entry combines one GUID's qualifying participation across the selected games/maps. It contains all of these fields regardless of the selected metric:
| Entry field | Type | Meaning |
|---|---|---|
guid |
string | Player identity. Preserve it as a string, including leading zeros. |
name |
string | Latest reported name across both games, even if that report is outside the selected scope or from an unfinished match. |
rank |
number | Rank within the scope after metric qualification, before search/pagination. Ties share rank and leave gaps: 1, 1, 3. |
highestRound, kills, roundsSurvived, headshots, revives, downs, gamesPlayed, gamesCompleted, bestKills, bestScore |
number | The ten counters defined under Recorded statistics. |
headshotRate, killsPerDown, averageKills, averageRounds, completionRate |
number or null | Ratios defined below; null if the denominator is zero. |
lastSeen |
string | Latest player observation among the qualifying participation in this scope, not the latest global identity update. |
records |
object | Personal-best evidence keyed by highestRound, bestKills and bestScore. |
Entries have no game field. Equal metric values are displayed in name order
(ASCII case-insensitive, then exact name), then GUID order. q is a substring
search, not an exact-GUID lookup; a tool looking up one GUID must check equality
on returned guid values and account for pagination.
| Ratio | Calculation | Requirement to rank by this metric |
|---|---|---|
headshotRate |
headshots / kills |
gamesPlayed >= 5 and kills >= 100 |
killsPerDown |
kills / downs |
gamesPlayed >= 5 and downs >= 5 |
averageKills |
kills / gamesPlayed |
gamesPlayed >= 5 |
averageRounds |
roundsSurvived / gamesPlayed |
gamesPlayed >= 5 |
completionRate |
gamesCompleted / gamesPlayed |
gamesPlayed >= 5 |
These are unrounded ratios, not formatted strings. For headshotRate and
completionRate, 0.5 means 50%. Requirements use the selected game/map scope
and apply only to the metric being ranked. Entries on a counter leaderboard
still expose ratio values even when they would not qualify for a ratio ranking.
gamesPlayed counts ended matches with qualifying participation, including
matches the player left early; it does not require completion credit.
Each map option has string fields key, game, map and name. map is the
engine map identifier; name is the display name. Pass key as the request's
map parameter, preferably together with the option's game. Options come from
completed matches reaching round 2, so an option can exist without any qualifying
players. Removed servers' maps remain available.
| Summary field | Type | Meaning |
|---|---|---|
players |
number | Unique qualifying GUIDs, combined across games. |
matches |
number | Distinct completed matches with qualifying player participation; not the sum of gamesPlayed. |
kills, roundsSurvived |
number | Sums across qualifying players; rounds are player-rounds. |
highestRound |
number | Highest qualifying player's recorded round. |
firstTrackedAt |
string or null | Earliest match startedAt among those matches. |
updatedAt |
string or null | Latest match report receipt time among those matches; not a response-generation time or a complete data-change token. |
For an empty scope, numeric summary fields are zero and both timestamps are
null. A search with no results or an offset beyond total returns
entries: [] while preserving the scope's summary and available maps. An
efficiency board can likewise have no entries while summary.players > 0.
Each records value has the following fields. Evidence uses the same game/map
scope as the entry, and ties choose the earliest completion, then lowest match
ID. The three records can refer to different matches.
| Evidence field | Type | Meaning |
|---|---|---|
matchId, value |
number | Completed match ID and that personal-best value. Link to /matches/{matchId} for its results page. |
game, mapKey, mapName, server |
string | Recorded game, opaque map key, map display name and server name. |
startedAt |
string | First recorded match observation; only the game start when startObserved is true. |
endedAt |
string or null | Recorded completion time; can be unknown in older stored records. |
startObserved |
boolean | Whether a game_start was received for the match. |
presentAtStart |
boolean or null | Whether the player was first observed at the recorded start; null for unknown older participation. |
firstObservedRound |
number or null | Round of the player's first observation, or unknown. This does not prove continuous survival. |
Timestamps reflect controller receipt/observation times, normally ISO 8601 UTC
strings such as 2026-10-05T12:00:00.000Z, not reporter gameplay-clock times.
Errors, pagination and freshness
Invalid query values return HTTP 400 with
{ "ok": false, "kind": "bad-request", "error": "..." }. Public reads do not
return 401 for missing/expired sessions or 403 for a required password change.
Sending host/enrollment bearer credentials still returns 403 because those
credentials are restricted to the host-agent API; omit them. Disallowed HTTP transport
returns 426 with an error string. Report-service failures can produce 5xx;
check status and content type before assuming the success schema or a uniform
error body.
For successive pages, retain the same filters and increase offset by limit
until offset + entries.length >= total or the page is empty. There is no cursor,
snapshot token, date-range filter or incremental since parameter. Each report
reads a consistent database snapshot, but separate requests may see newly ended
matches or name changes. A multi-page crawl can therefore miss or repeat players
as their ordering changes; deduplicate by GUID and refresh as needed. The offset
cap also limits how far a tool can page through a very large selection.
Reports run in a worker and use caches keyed by database revision, invalidated
by writes to match, participation and player-identity tables. There is no fixed
TTL wait before newly committed leaderboard data becomes eligible for a fresh
report. Identical requests already in flight may share a response. Neither
summary.updatedAt nor an entry's lastSeen should be used as a cache revision:
an identity rename can change results without changing those timestamps.
Related routes and current limits
GET /api/matches and GET /api/servers/{name}/matches return JSON match
summaries as { "matches": [...], "total": number }. These routes still require
session authentication. Both accept only limit (default 25, range 1–10,000); there is
no offset pagination. Results are newest first by first observation, then match
ID, and include active/interrupted matches that reached round 2. Their
ServerMatch fields differ from leaderboard entries
and do not include player counters or a roster. Unknown server names return an
empty list. URL-encode server names in paths.
/leaderboard/players/{guid} and /matches/{id} are HTML pages backed by server
loaders. There is currently no dedicated JSON endpoint at
/api/leaderboard/players/{guid} or /api/matches/{id} for complete profiles,
player match history or match rosters. A tool needing those records will need
an API extension; the leaderboard endpoint supplies aggregate stats and
personal-best evidence only.
Query performance
On a cache miss, leaderboard reads aggregate the selected completed participation for rankings and summary totals. The ranked scope and map options are reused across search/pagination requests at the same database revision; filtering and pagination operate on that ranked list. Empty and out-of-range pages still return accurate totals. Record evidence is fetched only for requested entries. Exact player-rank reads use the same ranked scope, including its summaries and map list. Report work runs in a dedicated worker with revision-keyed response caching as described above.
See the historical SQL benchmark for the earlier optimization measurements and methodology.