Skip to content

Add Graphics Settings modal with live name-label tuning#4065

Merged
evanpelle merged 2 commits into
mainfrom
graphic-settings
May 28, 2026
Merged

Add Graphics Settings modal with live name-label tuning#4065
evanpelle merged 2 commits into
mainfrom
graphic-settings

Conversation

@evanpelle
Copy link
Copy Markdown
Collaborator

@evanpelle evanpelle commented May 28, 2026

Description:

  • Add a user-facing Graphics Settings modal accessible from the in-game Settings menu, with live preview as sliders change.
  • First two knobs: Name Scale and Minimum Name Size (the name-cull threshold).
  • Overrides stored as a single JSON blob in localStorage under settings.graphics, validated by a Zod schema (GraphicsOverridesSchema). Future graphics knobs just extend the schema + slider list.

How it fits together

  • generateRenderSettings(overrides) (RenderSettings.ts) — pure function: clones render-settings.json defaults, layers overrides on top, returns a fresh RenderSettings.
  • UserSettings.graphicsOverrides() / setGraphicsOverrides() — read/write the blob; falls back to {} on a missing/corrupt entry.
  • ClientGameRunner listens for USER_SETTINGS_CHANGED_EVENT:settings.graphics, regenerates, and Object.assigns each category into the live view.getSettings() slice so passes pick up the new values on the next frame (no renderer reconstruction).
  • Modal reads defaults straight from render-settings.json so there's no duplication.
Screenshot 2026-05-28 at 11 18 43 AM

Please complete the following:

  • I have added screenshots for all UI updates
  • I process any text displayed to the user through translateText() and I've added it to the en.json file
  • I have added relevant tests to the test directory
  • I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced

Please put your Discord username so you can be contacted if a bug or regression is found:

evan

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 28, 2026

Review Change Stack

Walkthrough

Adds a graphics settings modal UI, schema and storage for graphics overrides, a helper to generate RenderSettings from overrides, renderer and runner wiring to apply overrides live, settings-menu navigation, and English localization plus DOM insertion.

Changes

Graphics Settings Modal and Integration

Layer / File(s) Summary
Graphics overrides schema and storage
src/client/render/gl/GraphicsOverrides.ts, src/core/game/UserSettings.ts
Zod schema and exported GraphicsOverrides type; GRAPHICS_KEY constant; UserSettings.graphicsOverrides() and setGraphicsOverrides() with safe JSON parsing, validation, and localStorage persistence.
Render settings generation with overrides
src/client/render/gl/RenderSettings.ts, src/client/render/gl/index.ts
generateRenderSettings(overrides) builds fresh RenderSettings and conditionally applies name.nameScaleFactor and name.cullThreshold from overrides; re-exports updated to include the helper.
GraphicsSettingsModal LitElement component
src/client/hud/layers/GraphicsSettingsModal.ts
New GraphicsSettingsModal controller and ShowGraphicsSettingsModalEvent; lifecycle/event wiring, Escape/outside-click close, sliders bound to UserSettings.graphicsOverrides().name, incremental patching on input, and reset-to-defaults action.
Game renderer modal lifecycle integration
src/client/hud/GameRenderer.ts
Imports GraphicsSettingsModal, queries the DOM for <graphics-settings-modal>, validates the instance, assigns userSettings and eventBus, and includes it in renderer layers.
Game runner override application and live updates
src/client/ClientGameRunner.ts
Adds applyGraphicsOverrides using generateRenderSettings(userSettings.graphicsOverrides()) and applies name settings to the WebGL view; subscribes to ${USER_SETTINGS_CHANGED_EVENT}:${GRAPHICS_KEY} via an AbortController; constructor and stop() updated to manage the listener lifecycle.
Settings modal graphics option
src/client/hud/layers/SettingsModal.ts
Adds a "Graphics Settings" button that emits ShowGraphicsSettingsModalEvent, updates closeModal to accept a keepPause flag, and preserves pause state when navigating to the graphics modal.
Pause-state read fix
src/client/hud/layers/HeadsUpMessage.ts
tick() now safely reads pause updates with optional chaining and uses the last pause update to determine isPaused.
Localization and DOM structure
index.html, resources/lang/en.json
Inserts the <graphics-settings-modal> element into the overlay and adds English translation keys for the graphics settings menu, name label controls, and reset messaging.

Sequence Diagram

sequenceDiagram
  participant GraphicsSettingsModal
  participant UserSettings
  participant ClientGameRunner
  participant WebGLView
  GraphicsSettingsModal->>UserSettings: setGraphicsOverrides({ name: { nameScaleFactor, cullThreshold } })
  UserSettings->>ClientGameRunner: emit USER_SETTINGS_CHANGED_EVENT:settings.graphics
  ClientGameRunner->>WebGLView: generateRenderSettings(userSettings.graphicsOverrides()) -> apply name settings
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested labels

UI/UX, Feature

Poem

In overlays soft the sliders slide,
Names grow near or shrink to hide,
Settings saved where cookies bide,
Adjust, apply, then take a ride. 🎮

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 60.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: adding a Graphics Settings modal with live tuning controls for name labels.
Description check ✅ Passed The description is well-related to the changeset, detailing the modal implementation, storage strategy, event handling, and component integration.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/client/ClientGameRunner.ts`:
- Around line 495-498: You register a global listener with
globalThis.addEventListener(`${USER_SETTINGS_CHANGED_EVENT}:${GRAPHICS_KEY}`,
applyGraphicsOverrides) but never remove it; store the handler reference (e.g.,
const graphicsSettingsHandler = applyGraphicsOverrides or a bound wrapper) in
the ClientGameRunner scope and use
globalThis.removeEventListener(`${USER_SETTINGS_CHANGED_EVENT}:${GRAPHICS_KEY}`,
graphicsSettingsHandler) in the same teardown/stop method (e.g.,
ClientGameRunner.stop or dispose) so the listener is unregistered when the
runner is stopped and rejoining won't stack handlers.

In `@src/client/hud/GameRenderer.ts`:
- Around line 194-202: The code queries document.querySelector for
"graphics-settings-modal" into graphicsSettingsModal but then unconditionally
accesses graphicsSettingsModal.userSettings and .eventBus, which will throw if
the element wasn't found; update the wiring to first check that
graphicsSettingsModal is an instance of GraphicsSettingsModal (or non-null)
before assigning userSettings and eventBus (return early or skip assignments
when the guard fails), keeping the existing console.error log when the modal is
missing; reference the graphicsSettingsModal variable and the userSettings and
eventBus property assignments in your change.

In `@src/client/hud/layers/GraphicsSettingsModal.ts`:
- Around line 152-153: The img alt attribute in GraphicsSettingsModal is
hardcoded as alt="graphicsSettings"; replace it to call translateText(...) with
a new key (e.g. "graphics.settings.iconAlt") and add that key/value to
resources/lang/en.json; update the JSX in GraphicsSettingsModal to use
translateText("graphics.settings.iconAlt") for the alt prop so all user-visible
text goes through translateText.
- Around line 12-21: The schema GraphicsOverridesSchema currently allows any
numeric value for nameScaleFactor and cullThreshold; update the zod types in the
GraphicsOverridesSchema definition to enforce the same slider bounds (e.g.,
z.number().min(MIN_NAME_SCALE).max(MAX_NAME_SCALE) for nameScaleFactor and
z.number().min(MIN_CULL_THRESHOLD).max(MAX_CULL_THRESHOLD) for cullThreshold),
and ensure persisted values are validated/parsed with this schema before being
applied so out-of-range localStorage edits are clamped or rejected.

In `@src/client/hud/layers/SettingsModal.ts`:
- Around line 187-190: The onGraphicsSettingsButtonClick currently calls
closeModal(), which triggers pauseGame(false) inside closeModal() and
unintentionally resumes gameplay; remove the call to closeModal() or change
closeModal to accept a flag to skip resuming (e.g., closeModal({ resume: false
})) and update its internal logic (where pauseGame(false) is called) to only
resume when the flag allows; keep the ShowGraphicsSettingsModalEvent emission so
the graphics modal opens without unpausing the game.

In `@src/client/render/gl/RenderSettings.ts`:
- Line 1: RenderSettings.ts currently imports the GraphicsOverrides type from
the HUD component module (GraphicsSettingsModal), creating cross-layer coupling;
extract the GraphicsOverrides type (the contract/interface) into a new shared
settings/contracts module (e.g., settings/contracts or settings/types) and
export it there, then update RenderSettings.ts to import GraphicsOverrides from
that new shared module and also update GraphicsSettingsModal to import the same
shared type so both modules reference the shared contract instead of
hud/layers/GraphicsSettingsModal.

In `@src/core/game/UserSettings.ts`:
- Around line 1-4: UserSettings is importing GraphicsOverrides and
GraphicsOverridesSchema from a client HUD module, which breaks core→client
layering; extract the GraphicsOverrides type and GraphicsOverridesSchema
validator into a non-UI shared module (e.g., a new src/shared or src/core/types
validator file) or re-create an equivalent core-local type/validator, then
update UserSettings to import GraphicsOverrides and GraphicsOverridesSchema from
that new shared/core file instead of
"../../client/hud/layers/GraphicsSettingsModal"; ensure the new schema has the
same runtime shape and export names so functions in UserSettings continue to use
GraphicsOverrides and GraphicsOverridesSchema unchanged.
- Around line 362-377: Add unit tests for UserSettings.graphicsOverrides and
UserSettings.setGraphicsOverrides to satisfy the requirement that changes to
src/core/ are covered: write tests that (1) persist a valid GraphicsOverrides
via setGraphicsOverrides and confirm graphicsOverrides() returns the same object
(round-trip), (2) store invalid JSON under the GRAPHICS_KEY and confirm
graphicsOverrides() returns {}, (3) store syntactically valid JSON that fails
GraphicsOverridesSchema validation and confirm graphicsOverrides() returns {},
and (4) confirm an absent key returns {}. Target the UserSettings class and its
graphicsOverrides, setGraphicsOverrides methods and assert behavior against
GraphicsOverridesSchema and GRAPHICS_KEY; add these tests to the src/core/ test
suite using the project's existing test harness.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 2c912933-b74b-4e25-ae4c-925eb587072b

📥 Commits

Reviewing files that changed from the base of the PR and between 8142bc1 and cfa83ac.

📒 Files selected for processing (9)
  • index.html
  • resources/lang/en.json
  • src/client/ClientGameRunner.ts
  • src/client/hud/GameRenderer.ts
  • src/client/hud/layers/GraphicsSettingsModal.ts
  • src/client/hud/layers/SettingsModal.ts
  • src/client/render/gl/RenderSettings.ts
  • src/client/render/gl/index.ts
  • src/core/game/UserSettings.ts

Comment thread src/client/ClientGameRunner.ts
Comment thread src/client/hud/GameRenderer.ts
Comment on lines +12 to +21
export const GraphicsOverridesSchema = z
.object({
name: z
.object({
nameScaleFactor: z.number(),
cullThreshold: z.number(),
})
.partial(),
})
.partial();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add bounds checks for persisted override numbers.

nameScaleFactor and cullThreshold currently accept any numeric value. If localStorage is edited manually, extreme values can be applied and destabilize rendering behavior. Please enforce the same min/max limits used by the sliders before values are applied.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/client/hud/layers/GraphicsSettingsModal.ts` around lines 12 - 21, The
schema GraphicsOverridesSchema currently allows any numeric value for
nameScaleFactor and cullThreshold; update the zod types in the
GraphicsOverridesSchema definition to enforce the same slider bounds (e.g.,
z.number().min(MIN_NAME_SCALE).max(MAX_NAME_SCALE) for nameScaleFactor and
z.number().min(MIN_CULL_THRESHOLD).max(MAX_CULL_THRESHOLD) for cullThreshold),
and ensure persisted values are validated/parsed with this schema before being
applied so out-of-range localStorage edits are clamped or rejected.

Comment thread src/client/hud/layers/GraphicsSettingsModal.ts
Comment thread src/client/hud/layers/SettingsModal.ts
Comment thread src/client/render/gl/RenderSettings.ts Outdated
Comment thread src/core/game/UserSettings.ts Outdated
Comment thread src/core/game/UserSettings.ts
@github-project-automation github-project-automation Bot moved this from Triage to Development in OpenFront Release Management May 28, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 28, 2026

Actionable comments posted: 0

@evanpelle evanpelle changed the title graphics Add Graphics Settings modal with live name-label tuning May 28, 2026
@evanpelle evanpelle added this to the v32 milestone May 28, 2026
@evanpelle evanpelle marked this pull request as ready for review May 28, 2026 20:01
@evanpelle evanpelle requested a review from a team as a code owner May 28, 2026 20:01
@evanpelle evanpelle merged commit 20bc311 into main May 28, 2026
13 of 14 checks passed
@evanpelle evanpelle deleted the graphic-settings branch May 28, 2026 20:06
@github-project-automation github-project-automation Bot moved this from Development to Complete in OpenFront Release Management May 28, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
src/client/hud/layers/GraphicsSettingsModal.ts (1)

42-46: 💤 Low value

Prefer @state() for internal-only reactive fields.

shouldPause and wasPausedWhenOpened are set from event data and used internally. Using @property exposes them as HTML attributes, which could lead to unintended external manipulation. For consistency with isVisible (line 36) and to signal these are private state, use @state() instead.

♻️ Suggested change
-  `@property`({ type: Boolean })
-  shouldPause = false;
-
-  `@property`({ type: Boolean })
-  wasPausedWhenOpened = false;
+  `@state`()
+  private shouldPause = false;
+
+  `@state`()
+  private wasPausedWhenOpened = false;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/client/hud/layers/GraphicsSettingsModal.ts` around lines 42 - 46, Change
the two internal-only reactive fields shouldPause and wasPausedWhenOpened from
`@property` to `@state` in the GraphicsSettingsModal component so they are treated
as private reactive state (match how isVisible is declared); update the
decorator import so that state is imported from the same decorator module used
in the file (replace or add state from 'lit/decorators' as appropriate) and keep
any other `@property` usages unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/client/hud/layers/GraphicsSettingsModal.ts`:
- Around line 42-46: Change the two internal-only reactive fields shouldPause
and wasPausedWhenOpened from `@property` to `@state` in the GraphicsSettingsModal
component so they are treated as private reactive state (match how isVisible is
declared); update the decorator import so that state is imported from the same
decorator module used in the file (replace or add state from 'lit/decorators' as
appropriate) and keep any other `@property` usages unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f882adf6-d49b-4694-8df6-ca98580d6702

📥 Commits

Reviewing files that changed from the base of the PR and between 5b9ddd0 and 7c6af87.

📒 Files selected for processing (8)
  • src/client/ClientGameRunner.ts
  • src/client/hud/layers/GraphicsSettingsModal.ts
  • src/client/hud/layers/HeadsUpMessage.ts
  • src/client/hud/layers/SettingsModal.ts
  • src/client/render/gl/GraphicsOverrides.ts
  • src/client/render/gl/RenderSettings.ts
  • src/client/render/gl/index.ts
  • src/core/game/UserSettings.ts
✅ Files skipped from review due to trivial changes (1)
  • src/client/render/gl/GraphicsOverrides.ts
🚧 Files skipped from review as they are similar to previous changes (5)
  • src/client/render/gl/index.ts
  • src/client/render/gl/RenderSettings.ts
  • src/core/game/UserSettings.ts
  • src/client/hud/layers/SettingsModal.ts
  • src/client/ClientGameRunner.ts

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Complete

Development

Successfully merging this pull request may close these issues.

1 participant