feat(airconsole): global highscore leaderboard#167
Conversation
On game end, store each played player's lines to AirConsole's native High Score API on two boards (all-time and current month) via level_version bucketing, then fetch and render a global top-10 panel on the results screen. The panel sits beside the session leaderboard, annotates each player with their world rank, and auto-toggles (~6s) between the All-time and Monthly boards a session player ranks in (world top 100). The pills double as a manual override. Feature-detected so the AC_MOCK path no-ops, and normal mode is untouched (the server-side board for normal mode is deferred to a later phase). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Claude finished @tim4724's task in 4m 53s —— View job Code Review
Overall this is a well-structured feature. The request queue, debounce, and watchdog are solid, ARIA tabs are complete, and the gallery scenario gives the renderer good test coverage. Three findings worth addressing, one of which is a real bug. Bug: watchdog not cleared in
|
…w fixes The leaderboard never appeared because the AirConsole SDK returns `uids`/`nicknames` as arrays, not the pipe-joined strings the typedef documents. The code split on "|", matched no players (so qualifies was always false and the panel stayed hidden) and fell every name back to the "Player" default. Normalize both array and string forms. Also addresses code-review findings: - skip storing 0-line results (instant topouts) so they don't create "0 lines" board entries - watchdog clears the request in-flight flag if onHighScores never fires, so a network blip can't permanently stall the panel - complete the ARIA tab pattern (aria-controls, role=tabpanel, aria-labelledby) and add a .gl-tab focus-visible ring - drop the redundant window.airconsole guard Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Drop the boxed look: the global panel loses its container box, the rows lose their per-row cards (now hairline-separated), and the All-time/Monthly tabs become flat text with an accent underline instead of filled pills. The panel is set off from the session list by a single divider rule (left in two-column, top when stacked), keeping it flat and in the existing style. Add a "Results (AirConsole)" gallery scene that injects results and populates the global top-10 panel (session players interleaved at world ranks so the per-row badges render) so the leaderboard is covered by the gallery UI surface. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Make the global Top 10 rows use the same card treatment as the session leaderboard (filled bg-card with border/radius, compact) so the two columns read as one component instead of a flat list beside cards. Replace the per-session-row "World #N" badge with color: a session player's entry in the global board is tinted in their player color (rank + name), mirroring the session cards, so they spot themselves at a glance. Removes annotateResultWorldRanks, the result-row data-player-id, the .result-world-rank style, and the now-unused leaderboard_world_rank string. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
What
Adds a global highscore leaderboard to AirConsole mode (phase 1). On game end the display stores each played player's lines to AirConsole's native High Score API and renders a global top-10 panel on the results screen.
This is phase 1 of the highscore feature. Normal mode (a server-side board + nickname-confirmation dialog) is deferred to a later phase. Score metric is lines cleared.
How it works
level_versionbucketing: all-time (v1-all) and current month (v1-<YYYY-MM>, UTC).playerIdis the ACdevice_id, sogetUID(playerId)attributes the score to the right account.requestHighScores(..., total=20, top=10)so the global top 10 and connected players' own context entries (theirranks.world) come back.onHighScoreStoredtriggers a debounced re-fetch so freshly-set bests reflect.AC_MOCKsimulator and any SDK without the High Score API no-op cleanly. Normal mode never loads this code.Why no native weekly/monthly: AirConsole's High Score API has no time-range parameter (ranks are spatial/social only), so monthly is emulated by bucketing into
level_version.Files
public/display/display-airconsole.js— store / fetch / queue / auto-toggle (AC-only bootstrap)public/display/DisplayUI.js—renderGlobalLeaderboard,annotateResultWorldRanks, rowdata-player-idpublic/display/index.html—.results-layout+#global-leaderboardpanel markuppublic/display/display.css— two-column layout, panel/pill/row styles, responsive stackingpublic/display/DisplayState.js— panel DOM refspublic/shared/i18n.js— 4 keys across 11 locales(
screen.html/controller.htmlare gitignored, regenerated at build viascripts/generate-airconsole-html.js.)Testing
npm run test:e2e:airconsole), confirming theonGameEndwrap doesn't break the mock path🤖 Generated with Claude Code