From 6159f1f1868b9be3ce55cb9eac637a779fc471e7 Mon Sep 17 00:00:00 2001 From: Evan Date: Thu, 18 Jun 2026 14:16:57 -0700 Subject: [PATCH] Fix anonymous-names setting not hiding names on the map (#4345) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Problem Enabling the **hidden names** (anonymous names) setting hid names in the leaderboard/HUD but **not on the map**. The GL name renderer (`NamePass`) drew `slot.static.displayName` — always the real name — and never consulted `userSettings.anonymousNames()`. The HUD works because it calls `PlayerView.displayName()` (which honors the setting) on each render, but the names baked into the GPU texture bypassed that path entirely. ## Fix Push the *resolved* name into the renderer instead of the raw static name: - **`WebGLFrameBuilder.syncPlayers`** registers each player with `displayName: p.displayName()` (honors the setting) instead of `static.displayName`. Covers enabling the setting before a game and players who join after a toggle. - **`WebGLFrameBuilder.refreshNames` → `MapRenderer` → `Renderer` → `NamePass.refreshNames`** is a new path that re-resolves cached names and forces a re-upload (resets `slot.nameLen = 0`, which also recomputes the name half-width so it stays centered). - **`ClientGameRunner`** listens for the `settings.anonymousNames` change event and calls `refreshNames`, mirroring the existing territory-patterns live toggle. ## Behavior - Enabled before a game → players register with anonymous names. - Toggled mid-game → map names flip to/from anonymous on the next sim tick (~100ms), matching the leaderboard. - Your own name is unaffected (unchanged — `PlayerView` maps the local player's anonymous name to their real name). ## Testing `tsc --noEmit` passes for all edited files. This is a WebGL rendering change with no straightforward unit test; verified by tracing the data flow (resolved name → cached `slot.static.displayName` → re-upload on dirty). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.8 (1M context) --- src/client/ClientGameRunner.ts | 8 ++++++++ src/client/WebGLFrameBuilder.ts | 16 ++++++++++++++++ src/client/render/gl/MapRenderer.ts | 3 +++ src/client/render/gl/Renderer.ts | 5 +++++ src/client/render/gl/passes/name-pass/index.ts | 16 ++++++++++++++++ 5 files changed, 48 insertions(+) diff --git a/src/client/ClientGameRunner.ts b/src/client/ClientGameRunner.ts index d561c0e88..977759e9c 100644 --- a/src/client/ClientGameRunner.ts +++ b/src/client/ClientGameRunner.ts @@ -525,6 +525,14 @@ async function createClientGame( { signal: graphicsListenerAbort.signal }, ); + // Re-resolve names drawn on the map when the anonymous-names setting toggles + // so they switch live, like the leaderboard. + globalThis.addEventListener( + `${USER_SETTINGS_CHANGED_EVENT}:settings.anonymousNames`, + () => webglBuilder.refreshNames(gameView), + { signal: graphicsListenerAbort.signal }, + ); + // Re-resolve settings and copy them onto the renderer's live object in // place (passes hold a reference to it, so they pick the change up). const regenerateRenderSettings = (): void => { diff --git a/src/client/WebGLFrameBuilder.ts b/src/client/WebGLFrameBuilder.ts index 17b76b9b0..0e6422a96 100644 --- a/src/client/WebGLFrameBuilder.ts +++ b/src/client/WebGLFrameBuilder.ts @@ -70,6 +70,19 @@ export class WebGLFrameBuilder { this.view.updatePalette(this.palette); } + /** + * Re-resolve every player's display name (e.g. after toggling the + * anonymous-names setting) and push it to the renderer so the names drawn on + * the map switch live, matching the leaderboard. + */ + refreshNames(gameView: GameView): void { + const displayNames = new Map(); + for (const p of gameView.players()) { + displayNames.set(p.id(), p.displayName()); + } + this.view.refreshNames(displayNames); + } + update(gameView: GameView): void { this.syncPlayers(gameView); this.syncPlayerSpawns(gameView); @@ -224,6 +237,9 @@ export class WebGLFrameBuilder { newPlayers.push({ ...p.static, + // displayName() honors the anonymous-names setting; static.displayName + // is always the real name. + displayName: p.displayName(), flag: flagUrl, color: p.territoryColor().toHex(), }); diff --git a/src/client/render/gl/MapRenderer.ts b/src/client/render/gl/MapRenderer.ts index 20777d2b3..08ba9d862 100644 --- a/src/client/render/gl/MapRenderer.ts +++ b/src/client/render/gl/MapRenderer.ts @@ -163,6 +163,9 @@ export class MapRenderer { ): void { this.renderer?.updateNames(names, players, snap, statusData); } + refreshNames(displayNames: Map): void { + this.renderer?.refreshNames(displayNames); + } updateRelations(data: Uint8Array, size: number): void { this.renderer?.updateRelations(data, size); } diff --git a/src/client/render/gl/Renderer.ts b/src/client/render/gl/Renderer.ts index 5d01e518e..64e6b7454 100644 --- a/src/client/render/gl/Renderer.ts +++ b/src/client/render/gl/Renderer.ts @@ -779,6 +779,11 @@ export class GPURenderer { } } + /** Re-resolve player name strings live (e.g. anonymous-names toggle). */ + refreshNames(displayNames: Map): void { + this.namePass.refreshNames(displayNames); + } + updateRelations(data: Uint8Array, size: number): void { this.borderPass.updateRelations(data, size); this.affiliationPalette.updateRelations(data, size); diff --git a/src/client/render/gl/passes/name-pass/index.ts b/src/client/render/gl/passes/name-pass/index.ts index 6072c82de..b09835d70 100644 --- a/src/client/render/gl/passes/name-pass/index.ts +++ b/src/client/render/gl/passes/name-pass/index.ts @@ -244,6 +244,22 @@ export class NamePass { } } + /** + * Replace cached name strings (e.g. after the anonymous-names setting toggles) + * and force a re-upload on the next updateNames pass. slot.static is the same + * object as the playerByID entry, so updating displayName here is what the + * nameLen === 0 re-upload branch reads. + */ + refreshNames(displayNames: Map): void { + for (const [id, name] of displayNames) { + const p = this.playerByID.get(id); + if (p === undefined) continue; + p.displayName = name; + const slot = this.slots.get(id); + if (slot !== undefined) slot.nameLen = 0; + } + } + /** * Request the texture layer for a slot's flag (called once at slot creation). * If the image is already loaded the layer index is set immediately; otherwise