## Summary
The client half of the verified-name plan: subscribers with a claimed
bare name can opt in to play under it, and the game renders a
**server-validated** blue verified check next to their name.
### Verified toggle (username row)
- Blue check-circle badge + "Verified" label act as a toggle button in
the play username row, shown to all users (hidden on CrazyGames via
`no-crazygames`); both turn blue when active and the input locks to the
bare account name — `getUsername()` feeds every join path.
- Non-subscribers (logged out, `unclaimed`, lapsed `claimed`) get a
subscribe-first dialog whose **View store** routes to
`#modal=store&tab=subscriptions`. Entitled players without a usable name
(never set, or `TEMPORARY####`) are routed to the account modal instead.
- The opt-in persists in localStorage but never auto-enables while
ineligible; unchecking restores the saved free-form name. Anonymity
stays a first-class option.
- After a successful username save the page reloads so every consumer
restarts from a fresh `/users/@me`.
### In-game badge (GL name pass)
- New `verified` boolean on `PlayerCosmeticRefsSchema` (client claim)
and `PlayerCosmeticsSchema` (resolved). `getPlayerCosmeticsRefs()` sets
it from the toggle state, covering both the multiplayer join and
locally-resolved singleplayer paths.
- **Server-validated at join, today**: the Worker already fetches
`/users/@me` with the client's token on every authenticated join
(flares/friends/clans), and that response carries the account username
since #4644 — so `verifiedBadgeAllowed` keeps the claim only when the
bare-name status is `premium`/`indefinite` AND the join name exactly
matches the account's resolved display name. Zero extra requests, no
token-claim staleness. Mismatches strip the badge rather than rejecting
the join; a pre-start rejoin identity change also drops it (that path
skips join-time validation). Anonymous persistent-ID joins exist only in
Dev and keep the claim for local testing.
- Rendering: 10th instanced slot in the name pass's `StatusIconProgram`,
anchored just right of the name text (`nameHalfWidth` was already in the
player data texture), slightly below the name line's center. The badge
art is a new cell (index 11) in `status-atlas.png`; the flag rides the
free `pd8.y` column. Anonymized viewers never see it (cosmetics are
already stripped for hidden players).
## Test plan
- Full suite passes (2,047 + 173), including new tests: cosmetics schema
`verified` (optional/boolean-only), `Privilege.isAllowed` pass-through,
and `verifiedBadgeAllowed` (exact match, case rejection, unentitled
statuses, missing name).
- Headless real-app verification of every toggle state (dialogs,
persistence, silent drops, store-tab routing, save→reload) with stubbed
API routes.
- Drove a real singleplayer game headless (WebGL via ANGLE Metal): the
blue check renders to the right of "Bob", scaled and tucked to the name;
bot/nation names show no badge. Screenshot-verified.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
## Description:
Adds nameRevealPublicIds to GameConfig — the same per-player reveal as
nameReveals but keyed by stable account publicId instead of per-game
clientID. Lets an automated host (the admin bot / OFM) grant casters and
observers real-name vision at create_game, where it only knows publicIds
and never learns a client's per-game clientID.
viewerSeesAllNames resolves the viewer's clientID to its publicId via
allClients and checks membership; nameReveals (clientID) is unchanged.
## Please complete the following:
- [x] I have added screenshots for all UI updates
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [x] I have added relevant tests to the test directory
## Please put your Discord username so you can be contacted if a bug or
regression is found:
zixer._
**Add approved & assigned issue number here:**
Resolves#4296
## Description:
Adds an "Anonymous players" option to private lobbies (host toggle, off
by default).
When it is on, the server sends each client anonymized usernames for
everyone except themselves. The lobby creator and admins still see real
names so they can moderate. Names are hidden on every player-facing
surface: the game start message, lobby info, /api/game/:id, and the link
preview. It is enforced server-side, so a client extension cannot read
real names off the wire. Initially added as part of our overhaul of
OpenFront masters, but this feature can very well be useful for content
creators, and other tournament hosts.
Anonymized names reuse the existing tribe word lists (no emoji), so they
pass UsernameSchema, and they are seeded per user, so a player looks
different to different users but stays consistent from the lobby into
the game.
The saved game record keeps real names (anonymization is a per-send
transform, gameStartInfo is never mutated), so replays and stats are
unaffected. Nothing changes for normal games.
New option selection:
<img width="990" height="918" alt="image"
src="https://github.com/user-attachments/assets/31df0b0b-7757-4b2b-9bff-84310faee8d9"
/>
The host, when enabling the option, gets a little eye icon next to the
players(including himself to enable/disable the anon names for himself,
and/or other player)
By default(the names everyone will see are random and unique):
<img width="979" height="188" alt="image"
src="https://github.com/user-attachments/assets/f0caa4a4-9f14-41d3-89c6-9a38e8c2e6f0"
/>
Toggling the eye ON for yourself (the host, or any given player, will
allow them to see the real names of everyone, in the lobby and in game):
<img width="969" height="138" alt="image"
src="https://github.com/user-attachments/assets/89abf0e0-1433-43ea-9870-49d96ca46d30"
/>
## Please complete the following:
- [x] I have added screenshots for all UI updates
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [x] I have added relevant tests to the test directory
## Please put your Discord username so you can be contacted if a bug or
regression is found:
zixer._