Repository navigation
Show level badges on the ranked leaderboard and clan member lists - #5764
Conversation
A level badge (level bands, prestige emblems, the Legend crown) and an XP bar for the account menu, player profiles and the game stats modal. On the end-of-game screen, the game's XP is revealed a source at a time: a caption per source rolls in over a segmented bar that climbs, pausing with an animation at each level reached, while each source's card lands below. Multipliers then wipe across the cards in the bonus's colours, a subscription tier's own (Sovereign, Warlord, Vanguard), and a milestone or Legend gets its own card. Tapping skips to the end. The modal's buttons get the store's texture and a hover. Every API state is handled (signed out, calculating, ineligible, result), and nothing shows while the API reports progression is off. Resolves #5725 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CodeQL flags the regex (incomplete multi-character sanitization). These tests only compare SVG markup, but removing the comment nodes through the DOM is simpler to read and leaves nothing to flag. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Parse a malformed /users/@me progress object as absent instead of failing the whole profile (which made the player look signed out). - Decide "signed out" for the XP panel from auth state: a /users/@me failure with a live session now hides the panel rather than telling a signed-in player to sign in. The rule lives in ProgressionAccount and is shared by WinModal and GameStatsModal, so both accept a CrazyGames session. - Show a player who was already a Legend with the Legend frame for the whole reveal. - Remove each poll wait's abort listener when its timer fires. - Load the profile's level independently of the profile itself, still dropping a response for a superseded load. - Add a keyboard-reachable skip button for the reveal, and give the XP progress bars an accessible name and value text. - Translate the prestige tab's "P{prestige}" label. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…, abortable XP fetch - Explain games recorded ineligible as "unverified" (stats the votes did not agree on) instead of the generic line. - Only play the XP reveal while the win modal is on screen: a result that lands after it is dismissed shows in its final state, and dismissing it mid-reveal jumps to the end. A panel removed mid-reveal also ends it, so it never comes back half-revealed and aria-busy. - Pass pollGameXp's abort signal into the request itself (combined with its timeout via AbortSignal.any), so aborting cancels a request in flight. - Default the breakdown's per-source fields and leftEarly, so XP rows scored under the first XP rules (no `played`/`leftEarly`) still parse. - Read your own profile's level from the public endpoint, not the /users/@me copy cached at page load. - Put the level in the nav account button's accessible name, which its aria-label otherwise hid from screen readers. - Drop xpBar's unused animate/durationMs options and fix the milestone comment that mentioned a share button. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The API now pays +100 XP for the first eligible public game each UTC day,
win or lose, instead of +200 for the first win, and reports it as
`firstGame` in the XP breakdown. Rename the schema field, the line key and
its label ("First game of the day"). Feats stay in the breakdown but are
always 0 for now; zero lines are already left off the cards.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The subscription tiers' XP multipliers move from 1.2x / 1.3x / 1.5x to
1.25x / 1.5x / 2x, so the client now recognises Vanguard at 1250,
Warlord at 1500 and Sovereign at 2000 permille. The bonus line's percent
is still derived from the multiplier ("+100% XP (Sovereign bonus)"), and
any other value, the old ones included, shows as a plain subscriber bonus.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The game server already calls /users/@me for every signed-in joiner; it now reads `progress` from that response and stamps a display-only levelBadge { level, prestige, legend } onto the player's lobby roster entry (ClientInfo), so the lobby list and in-game panel can show level badges without the client knowing anyone's publicId. - ClientInfo gains an optional `levelBadge` as its last field. It takes the last free bit of the one-byte header, so entries without a badge encode byte-identically and pre-badge frames still decode. - Worker join: levelBadgeFromProgress() keeps only whole level >= 1 and prestige >= 0, so a bad API value drops the badge instead of throwing out of the lobby_info broadcast. No progress (guest, progression off, failed/malformed lookup) means no badge. - NameVisibility omits it on anonymized entries; it shows beside real names only (self, host-granted reveals, pinned teammates). - Trust: ClientInfo is built server-side only; the join schema strips and the join frame cannot carry level fields. - Client keeps the last roster's badges for in-game use: lobbyLevelBadge(gameID, clientID) in src/client/LobbyRosterLevels.ts. - No UI. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Renders the roster's levelBadge as a <level-badge> in front of the name. Lobby list (LobbyPlayerView), all four layouts — FFA pills, the team-mode Players list, team cards and spectator pills: a 24px badge before the display name, ahead of the verified check / friend mark / host label. In the two column layouts (Players list, team cards) a player without a badge gets an empty aria-hidden 24px slot so the names line up, but only when someone in that list has a visible badge; a lobby with no badges renders exactly as before. The wrapped pills never get a slot. Streamer mode: with anonymous names on, other players' badges are hidden (the viewer's own still shows), as the verified check already is. Hidden badges count as none for the slot rule. In-game player panel: flag, then a 28px badge, then the name, for human players with a badge in the remembered lobby roster (lobbyLevelBadge(gameID, clientID)). Hidden for anyone but the viewer's own player while anonymous names are on, matching PlayerView.displayName(). The name keeps truncating beside the badge. The lit-mocking PlayerPanel tests now mock the LevelBadge module, since it draws with lit's svg tag at import time. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The API now stamps optional `level`, `prestige` and `legend` onto ranked leaderboard entries and clan member rows. Show them as a 24px <level-badge> in front of the player's name. - Schemas: the three fields are optional on RankedLeaderboardEntrySchema, PlayerLeaderboardEntrySchema and ClanMemberSchema (shared as RowLevelFields), so responses without them still parse. toPlayerEntry passes them through. - Ranked leaderboard (1v1 and 2v2): a badge only for prestiged players and Legends. Other rows get an empty aria-hidden 24px slot so names line up, but only when some loaded row in that ladder has a badge, so the board is unchanged until levels launch. The pinned "your ranking" row follows the same rule. Tribe and clan boards are untouched. - Clan member list (renderMemberRow, via ClanDetailView): a badge for every member with a level; members without one get the slot when any member in the visible (search-filtered) list has a badge. - Shared rendering in components/LevelBadgeSlot.ts. No new text: the badge carries its own accessible label. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Important Review skippedReview was skipped as selected files did not have any reviewable changes. ⚙️ Run configuration
You can disable this status message by setting the Use the checkbox below for a quick retry:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info
WalkthroughThe change adds optional level, prestige, and legend fields to leaderboard and clan-member schemas. Leaderboard and clan rows use these fields to render badges for eligible players and aligned empty slots where needed. ChangesLeaderboard and Clan Level Badges
Priority: ⬇️ Low Estimated code review effort: 3 (Moderate) | ~25 minutes Change: Feature Sequence Diagram(s)sequenceDiagram
participant LeaderboardPlayerList
participant ClanDetailView
participant ClanShared
participant levelBadgeSlot
participant LevelBadge
LeaderboardPlayerList->>levelBadgeSlot: Render badge or reserved slot
ClanDetailView->>ClanShared: Render filtered member rows with level-slot setting
ClanShared->>levelBadgeSlot: Render badge or reserved slot
levelBadgeSlot->>LevelBadge: Render badge when level fields are complete
Suggested reviewers: Merge Risk: ⚪ Minimal · up to No actionable issue remains from this review; the change is mergeable after normal checks. Pre-merge checks |
|
On a phone the bar under the XP reveal is under 200px wide, so the centred "+1,150 XP" counter ran straight into the truncated "444 / 2,41…" at its right end. Below a 16rem bar (a container query on the bar's column) the two now stack, centred, at a fixed height so nothing moves during the reveal. Wider bars keep today's layout. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The stats modal's compact reveal panel becomes a static card between the game summary and the tabs: the total, the level change (with a Milestone chip and glow at 10/25/50/75/100) or the level and progress, each source at its base value in three columns (one on a phone), and each multiplier as its own line with the XP it added. The XP endpoint only returns the caller's own record, so any 200 is shown, including why a game didn't earn XP and the coming before_progression reason. A 404 shows "Calculating XP…" and polls only for the player's own game (account history or own profile) that ended in the last 10 minutes; otherwise it shows nothing, as before. The API rounds once over subtotal x game x subscriber, so a multiplier's amount is total - subtotal only when it is the only one; with both, the lines show no amount rather than a guessed split. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The lobby roster went out with levelBadge as a nested { level, prestige,
legend } object: per badged entry its own header byte plus two varints and
an extra object encode/decode step. lobby_info is encoded once per
recipient every second, so a 150-player all-badged lobby paid that 22,500
times a broadcast (+25-41% broadcast time, +13% bytes).
ClientInfo.levelBadge is now a single optional zb.uint():
bits 0-6 level (1..100), bits 7-10 prestige (0..10), bit 11 legend
One varint byte at prestige 0, two at most (P10 L100 legend = 3428). Still
the last field with its one header presence bit, so badge-less entries
stay byte-identical. The field is new in this PR and not on main, so no
deployed client or server speaks the old shape.
- src/core/LevelBadgeWire.ts: LevelBadge, packLevelBadge (undefined for
anything it cannot represent) and unpackLevelBadge (undefined for an
out-of-range level/prestige, reserved bits, or a non-integer — never
throws). The schema has no range so one bad value cannot fail the whole
roster parse; the decoder drops it instead.
- Server: Client keeps the readable badge and packs it once at
construction (wireLevelBadge); NameVisibility sends that, so broadcasts
do no packing. levelBadgeFromProgress now also drops level > 100 and
prestige > 10.
- Client: lobbyLevelBadge and LobbyPlayerView unpack back to
{ level, prestige, legend }; the badge components are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The lobby roster's levelBadge is now one packed integer; the leaderboard and clan lists read levels from their own API payloads, so nothing here changes. No conflicts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A 150-player lobby put 150 badges (300 in team mode, with the team cards) on screen in one frame, and the browser's style and layout of all those SVGs made the roster's first paint long. The badge drawing itself is cheap now; the layout is what's left. Lobby badges now carry a `stagger` attribute. When more than 8 of them arrive in one render (a lobby opening, a big batch of joins, a switch to team mode), each first renders an empty square the size of the badge, and a shared requestAnimationFrame loop (LevelBadgeFill) draws them a few per frame, nearest the viewport first. The rows frame lays out only the rows. After that, each frame draws a batch and measures its script plus forced layout, and steers the next batch to about 5 ms. A few badges arriving at once (a player joining, a badge changing) are drawn straight away, as before, also while a fill is running. The square is the drawn badge's exact box inside the same host element, so nothing moves when a badge is drawn. Badges taken off the page leave the queue, and the loop cancels its frame once nothing is waiting. Streamer mode and anonymised rosters are unchanged: those rows never get a badge element. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
🤖 Claude Code ReviewVerdict: No issues found. 0 findings (0 high, 0 medium, 0 low). Checked for bugs and CLAUDE.md compliance. Things I checked:
|
Celant
left a comment
There was a problem hiding this comment.
Approving: CI Claude review clean at 5b34daeb, required checks green, and every finding from the earlier review rounds closed and verified in code.
Resolves #5762
Stacked on the lobby badges PR (#5763), which is stacked on #5726. The base is that PR's branch, so this diff shows only this change. Part of #5760.
Description:
Level badges on the ranked leaderboard and clan member lists, designed so the leaderboard doesn't get cluttered.
The data
The API adds optional
level,prestigeandlegendto each player row of/leaderboard/rankedand the clan member lists. Players without a level, or any response while progression is off, have no fields. They're one shared optional field set (RowLevelFields) onRankedLeaderboardEntrySchema,PlayerLeaderboardEntrySchemaandClanMemberSchema, so older responses still parse.toPlayerEntrypasses them through.On screen
LeaderboardPlayerList.ts): a 24px<level-badge>in front of the name, only for players who have prestiged or are Legends. Every other row is unchanged, so the badge stays rare enough to mean something. The pinned "your ranking" row follows the same rule.renderMemberRowinClanShared.ts): a 24px badge in front of every member's name. At 24px the badge shows its prestige tab ("P3"); at 20px it was hidden.aria-hiddenbadge-sized space (LevelBadgeSlot.ts).Screenshots
These are screenshots of the code on this branch at 2x, with sample names and levels in the same shape the API sends them.
Ranked leaderboard. Badges only for players who have prestiged (P3 Kestrel, P1 Hollis_7, P2 Emberline) or are Legends (Marrow). Everyone else's name sits in the same column, so nothing zigzags.
Clan members. A 24px badge in front of every member, with the prestige tab visible. The guest at the bottom gets an empty space and still lines up.
Testing
ClanDetailViewincluding search.tsc, oxlint, eslint and prettier are clean.Please complete the following:
Please put your Discord username so you can be contacted if a bug or regression is found:
Iamlewis
🤖 Generated with Claude Code