Resolve render settings before renderer construction (#4271)

## What

The client now resolves render settings (defaults + user overrides) **up
front** and passes the result into the renderer, instead of the renderer
constructing defaults itself and the client re-applying overrides
afterward.

```
before:  new GPURenderer(...)         // this.settings = createRenderSettings()  (defaults)
         view.getSettings() → deepAssign(defaults) → applyGraphicsOverrides(...)  // patch after the fact

after:   const settings = createRenderSettings(); applyGraphicsOverrides(settings, ...); applyDarkModeOverride(settings, ...)
         new GPURenderer(..., settings)   // built with the final values
```

## Why

- **Removes the construct-with-defaults / re-apply-overrides dance.**
Every pass — including texture-baking ones like terrain that read their
settings *once* at build time rather than every frame — is now built
with the final values on the first try. (This is the cleanup that
motivated the change, surfaced while adding a terrain color override in
a separate PR.)
- **Fixes a latent context-restore bug.** On WebGL context loss/restore
the renderer was rebuilt via `createRenderSettings()` → fresh
**defaults**, silently dropping any user overrides until the next
settings change. `MapRenderer` now holds the resolved settings object
and hands the same one to the recreated `GPURenderer`, so overrides
survive a restore.

Live setting changes still work exactly as before:
`regenerateRenderSettings()` re-resolves and `deepAssign`s onto the
renderer's live settings object in place (passes hold a reference, so
they pick it up next frame).

## Changes
- `Renderer.ts` (`GPURenderer`) — constructor takes a `settings:
RenderSettings`; drops the internal `createRenderSettings()` call.
- `MapRenderer.ts` — holds the resolved settings and passes it through
on construction and on context-restore re-init.
- `ClientGameRunner.ts` — new `resolveRenderSettings()` helper used both
at construction and by `regenerateRenderSettings()`; `createWebGLView`
takes the resolved settings; the now-redundant initial
`regenerateRenderSettings()` call is removed.

## Testing
Verified live in a headless singleplayer game:
- A saved `nameScaleFactor` override is present in `getSettings()`
immediately after game start, with no settings-change event fired
(construction path).
- A mid-game override change is reflected in the live settings
(regenerate/in-place path).
- The map renders correctly through spawn phase.

`tsc` and ESLint clean.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Evan
2026-06-13 20:22:08 -07:00
committed by GitHub
co-authored by Claude Opus 4.8
parent 3a8249dfd1
commit 54a7042303
3 changed files with 31 additions and 7 deletions
+5
View File
@@ -50,6 +50,10 @@ export class MapRenderer {
private terrainBytes: Uint8Array,
private paletteData: Float32Array,
private config: Config,
// Resolved render settings (defaults + overrides). Held so the same object
// is re-used when a GPURenderer is recreated after a context restore,
// preserving any user overrides that were applied to it.
private settings: RenderSettings,
private raf?: typeof requestAnimationFrame,
private caf?: typeof cancelAnimationFrame,
) {
@@ -78,6 +82,7 @@ export class MapRenderer {
this.terrainBytes,
this.paletteData,
this.config,
this.settings,
this.raf,
this.caf,
);
+6 -2
View File
@@ -57,7 +57,7 @@ import { TerritoryPass } from "./passes/TerritoryPass";
import { TrailPass } from "./passes/TrailPass";
import { UnitPass } from "./passes/UnitPass";
import { WorldTextPass } from "./passes/WorldTextPass";
import { createRenderSettings, type RenderSettings } from "./RenderSettings";
import type { RenderSettings } from "./RenderSettings";
import { AffiliationPalette } from "./utils/Affiliation";
import { buildTerrainRGBA, getPaletteSize } from "./utils/ColorUtils";
import {
@@ -180,11 +180,15 @@ export class GPURenderer {
terrainBytes: Uint8Array,
paletteData: Float32Array,
config: Config,
settings: RenderSettings,
raf: typeof requestAnimationFrame = requestAnimationFrame.bind(window),
caf: typeof cancelAnimationFrame = cancelAnimationFrame.bind(window),
) {
this.canvas = canvas;
this.settings = createRenderSettings();
// Settings are resolved (defaults + user overrides) by the caller and
// passed in, so every pass — including texture-baking ones like terrain —
// is built with the final values. Live changes mutate this object in place.
this.settings = settings;
this.raf = raf;
this.caf = caf;