mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-07-22 22:58:06 +00:00
refactor(glow): move small-player glow strength into graphics overrides (#4634)
## Summary Moves the small-player glow strength from a standalone UserSettings key (`settings.highlightGlowStrength`) into the graphics overrides blob (`smallPlayerGlow.strength`), and moves its slider from the basic settings modal into the **Effects** section of the graphics settings modal. The pass now reads strength from its live `RenderSettings` slice like every other graphics knob, flowing through `applyGraphicsOverrides`. This removes the dedicated push chain (ClientGameRunner listener → MapRenderer → Renderer → pass setter) and MapRenderer's context-restore reapply — strength survives WebGL context restores automatically now, participates in the graphics modal's "Reset to defaults", and shows up in the render debug GUI. ## Bug fix The old slider had no visible effect below ~115%: strength only drove the blur-iteration count (`round(strength ** 2.74)`, floored at 1 pass), and the composite intensity (`alpha * pulse * sqrt(passes)`) never factored strength directly — so 10% rendered identical to 100%. Strength is now a linear opacity fade on a fixed-width aura: - 100% = the aura's full brightness, 10% = barely visible, 0% = off - Slider capped at 100% (the >100% aura-widening mechanism became unreachable and is removed: `passesFor`, `lastPasses`, and the blur-iteration loop) - Default is 25% - The pass clamps persisted values above 1 so stale settings from the old 500% range can't over-brighten ## Notes - Reuses the existing `user_setting.highlight_glow_strength_label` / `highlight_small_players_desc` translation keys — no en.json change - The legacy `settings.highlightGlowStrength` localStorage key is not migrated (the slider only shipped in #4588 a few days ago); users reset to the 25% default ## Test plan - [x] `tests/GraphicsOverrides.test.ts` — schema validation + apply coverage for the new override group - [x] Full suite (171 tests), tsc, eslint all pass 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -572,17 +572,6 @@ async function createClientGame(
|
||||
{ signal: graphicsListenerAbort.signal },
|
||||
);
|
||||
|
||||
// Push the small-player glow Strength to the pass (which is a pure consumer)
|
||||
// on the settings-changed event, so moving the slider updates the glow live
|
||||
// even while the settings modal has the sim paused.
|
||||
view.setSmallPlayerGlowStrength(userSettings.highlightGlowStrength());
|
||||
globalThis.addEventListener(
|
||||
`${USER_SETTINGS_CHANGED_EVENT}:settings.highlightGlowStrength`,
|
||||
() =>
|
||||
view.setSmallPlayerGlowStrength(userSettings.highlightGlowStrength()),
|
||||
{ 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(
|
||||
|
||||
@@ -70,6 +70,12 @@ const RAIL_THICKNESS_MIN = 0.5;
|
||||
const RAIL_THICKNESS_MAX = 3;
|
||||
const RAIL_THICKNESS_STEP = 0.1;
|
||||
|
||||
// Small-player glow strength is shown as a percentage: 0% = off, 100% = the
|
||||
// glow's full brightness.
|
||||
const GLOW_STRENGTH_MIN = 0;
|
||||
const GLOW_STRENGTH_MAX = 100;
|
||||
const GLOW_STRENGTH_STEP = 5;
|
||||
|
||||
// "Ambient light" level shown to the player: 0 = no darkening (lighting off),
|
||||
// 10 = darkest with the strongest glow. Mapped linearly onto the renderer's
|
||||
// ambient value (1 = identity, AMBIENT_MIN = darkest).
|
||||
@@ -597,6 +603,29 @@ export class GraphicsSettingsModal extends LitElement implements Controller {
|
||||
this.patchPassEnabled({ fallout: !this.currentFallout() });
|
||||
}
|
||||
|
||||
private patchSmallPlayerGlow(
|
||||
patch: Partial<GraphicsOverrides["smallPlayerGlow"]>,
|
||||
) {
|
||||
const current = this.userSettings.graphicsOverrides();
|
||||
this.userSettings.setGraphicsOverrides({
|
||||
...current,
|
||||
smallPlayerGlow: { ...current.smallPlayerGlow, ...patch },
|
||||
});
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
private currentGlowStrength(): number {
|
||||
return (
|
||||
this.userSettings.graphicsOverrides().smallPlayerGlow?.strength ??
|
||||
renderDefaults.smallPlayerGlow.strength
|
||||
);
|
||||
}
|
||||
|
||||
private onGlowStrengthChange(event: Event) {
|
||||
const value = parseFloat((event.target as HTMLInputElement).value) / 100;
|
||||
this.patchSmallPlayerGlow({ strength: value });
|
||||
}
|
||||
|
||||
/** Whether colorblind mode is currently enabled. */
|
||||
private currentColorblind(): boolean {
|
||||
return (
|
||||
@@ -679,6 +708,7 @@ export class GraphicsSettingsModal extends LitElement implements Controller {
|
||||
const nukeColor = this.currentNukeColor();
|
||||
const ambientLevel = this.currentAmbientLevel();
|
||||
const unitGlow = this.currentUnitGlow();
|
||||
const glowStrength = this.currentGlowStrength();
|
||||
const colorblind = this.currentColorblind();
|
||||
|
||||
return html`
|
||||
@@ -1426,6 +1456,31 @@ export class GraphicsSettingsModal extends LitElement implements Controller {
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<div
|
||||
class="flex gap-3 items-center w-full text-left p-3 hover:bg-slate-700 rounded-sm text-white transition-colors"
|
||||
>
|
||||
<div class="flex-1">
|
||||
<div class="font-medium">
|
||||
${translateText("user_setting.highlight_glow_strength_label")}
|
||||
</div>
|
||||
<div class="text-sm text-slate-400">
|
||||
${translateText("user_setting.highlight_small_players_desc")}
|
||||
</div>
|
||||
<input
|
||||
type="range"
|
||||
min=${GLOW_STRENGTH_MIN}
|
||||
max=${GLOW_STRENGTH_MAX}
|
||||
step=${GLOW_STRENGTH_STEP}
|
||||
.value=${String(glowStrength * 100)}
|
||||
@input=${this.onGlowStrengthChange}
|
||||
class="w-full border border-slate-500 rounded-lg"
|
||||
/>
|
||||
</div>
|
||||
<div class="text-sm text-slate-400 w-12 text-right">
|
||||
${Math.round(glowStrength * 100)}%
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="px-3 py-1 text-xs font-semibold text-slate-400 uppercase tracking-wider mt-2"
|
||||
>
|
||||
|
||||
@@ -19,7 +19,6 @@ import { ShowGraphicsSettingsModalEvent } from "./GraphicsSettingsModal";
|
||||
const cursorPriceIcon = assetUrl("images/CursorPriceIconWhite.svg");
|
||||
const emojiIcon = assetUrl("images/EmojiIconWhite.svg");
|
||||
const exitIcon = assetUrl("images/ExitIconWhite.svg");
|
||||
const highlightIcon = assetUrl("images/HighlightIconWhite.svg");
|
||||
const mouseIcon = assetUrl("images/MouseIconWhite.svg");
|
||||
const ninjaIcon = assetUrl("images/NinjaIconWhite.svg");
|
||||
const settingsIcon = assetUrl("images/SettingIconWhite.svg");
|
||||
@@ -205,12 +204,6 @@ export class SettingsModal extends LitElement implements Controller {
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
private onHighlightGlowStrengthChange(event: Event) {
|
||||
const strength = parseFloat((event.target as HTMLInputElement).value) / 100;
|
||||
this.userSettings.setHighlightGlowStrength(strength);
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.isVisible) {
|
||||
return null;
|
||||
@@ -357,36 +350,6 @@ export class SettingsModal extends LitElement implements Controller {
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<div
|
||||
class="flex gap-3 items-center w-full text-left p-3 hover:bg-slate-700 rounded-sm text-white transition-colors"
|
||||
>
|
||||
<img
|
||||
src=${highlightIcon}
|
||||
alt="highlightGlowStrength"
|
||||
width="20"
|
||||
height="20"
|
||||
/>
|
||||
<div class="flex-1">
|
||||
<div class="font-medium">
|
||||
${translateText("user_setting.highlight_glow_strength_label")}
|
||||
</div>
|
||||
<div class="text-sm text-slate-400">
|
||||
${translateText("user_setting.highlight_small_players_desc")}
|
||||
</div>
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
max="500"
|
||||
.value=${this.userSettings.highlightGlowStrength() * 100}
|
||||
@input=${this.onHighlightGlowStrengthChange}
|
||||
class="w-full border border-slate-500 rounded-lg"
|
||||
/>
|
||||
</div>
|
||||
<div class="text-sm text-slate-400">
|
||||
${Math.round(this.userSettings.highlightGlowStrength() * 100)}%
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
class="flex gap-3 items-center w-full text-left p-3 hover:bg-slate-700 rounded-sm text-white transition-colors"
|
||||
@click="${this.onToggleAlertFrameButtonClick}"
|
||||
|
||||
@@ -41,6 +41,12 @@ export const GraphicsOverridesSchema = z
|
||||
railThickness: z.number(),
|
||||
})
|
||||
.partial(),
|
||||
smallPlayerGlow: z
|
||||
.object({
|
||||
// Aura around small players' territory: 0 = off, 1 = full brightness.
|
||||
strength: z.number(),
|
||||
})
|
||||
.partial(),
|
||||
passEnabled: z
|
||||
.object({
|
||||
fx: z.boolean(),
|
||||
|
||||
@@ -36,10 +36,6 @@ import type { RenderSettings } from "./RenderSettings";
|
||||
export class MapRenderer {
|
||||
private renderer: GPURenderer | null = null;
|
||||
private resizeObs: ResizeObserver | null = null;
|
||||
// Persisted so a WebGL context restore (which recreates GPURenderer via
|
||||
// initRenderer) reapplies the user's chosen glow strength instead of
|
||||
// resetting it to the pass default until the next settings change.
|
||||
private smallPlayerGlowStrength = 1;
|
||||
|
||||
/**
|
||||
* Called after a lost WebGL context is restored and the renderer has been
|
||||
@@ -96,8 +92,6 @@ export class MapRenderer {
|
||||
|
||||
const rect = this.canvas.getBoundingClientRect();
|
||||
if (rect.width > 0) this.renderer.resize(rect.width, rect.height);
|
||||
// Reapply state that lives outside RenderSettings so it survives a restore.
|
||||
this.renderer.setSmallPlayerGlowStrength(this.smallPlayerGlowStrength);
|
||||
};
|
||||
|
||||
private handleContextLost = (e: Event) => {
|
||||
@@ -254,12 +248,6 @@ export class MapRenderer {
|
||||
this.renderer?.updateSmallPlayerGlow(set);
|
||||
}
|
||||
|
||||
/** Set the small-player glow Strength (0 = off, 1 = default, capped at 5). */
|
||||
setSmallPlayerGlowStrength(strength: number): void {
|
||||
this.smallPlayerGlowStrength = strength;
|
||||
this.renderer?.setSmallPlayerGlowStrength(strength);
|
||||
}
|
||||
|
||||
// ---- Selection box ----
|
||||
|
||||
/** Set multiple selected units (multi-select). Pass [] to clear. */
|
||||
|
||||
@@ -85,6 +85,9 @@ export function applyGraphicsOverrides(
|
||||
if (overrides.railroad?.railThickness !== undefined) {
|
||||
settings.railroad.railThickness = overrides.railroad.railThickness;
|
||||
}
|
||||
if (overrides.smallPlayerGlow?.strength !== undefined) {
|
||||
settings.smallPlayerGlow.strength = overrides.smallPlayerGlow.strength;
|
||||
}
|
||||
if (overrides.passEnabled?.fx !== undefined) {
|
||||
settings.passEnabled.fx = overrides.passEnabled.fx;
|
||||
}
|
||||
|
||||
@@ -389,6 +389,7 @@ export interface RenderSettings {
|
||||
color: number[]; // RGB, each 0–1
|
||||
alpha: number; // peak opacity (0–1)
|
||||
pulseSpeed: number; // breath animation speed
|
||||
strength: number; // opacity fade: 0 = off, 1 = full brightness (default 0.25)
|
||||
};
|
||||
altView: {
|
||||
gridFontSize: number;
|
||||
|
||||
@@ -1013,10 +1013,6 @@ export class GPURenderer {
|
||||
this.smallPlayerGlowPass.update(set);
|
||||
}
|
||||
|
||||
setSmallPlayerGlowStrength(strength: number): void {
|
||||
this.smallPlayerGlowPass.setGlowStrength(strength);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Queries
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@@ -57,8 +57,6 @@ export class SmallPlayerGlowPass {
|
||||
private dirty = false; // aura needs rebuilding (set changed)
|
||||
private animTime = 0;
|
||||
private lastTime = 0;
|
||||
private lastPasses = 1; // last width the aura was blurred at (to detect change)
|
||||
private glowStrength = 1; // pushed via setGlowStrength; 0 = off, 1 = default, capped at 5
|
||||
|
||||
constructor(
|
||||
gl: WebGL2RenderingContext,
|
||||
@@ -123,16 +121,6 @@ export class SmallPlayerGlowPass {
|
||||
this.quadVao = createFullscreenQuad(gl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Push the glow Strength (0 = off, 1 = default, capped at 5). The client reads
|
||||
* it from UserSettings and pushes it here so the pass stays a pure consumer
|
||||
* (passes never read game/DOM state directly); pushing on the settings-changed
|
||||
* event keeps it live even while the settings modal has the sim paused.
|
||||
*/
|
||||
setGlowStrength(strength: number): void {
|
||||
this.glowStrength = strength;
|
||||
}
|
||||
|
||||
/** Push the highlight set (1 byte per owner smallID), or null to disable. */
|
||||
update(set: Uint8Array | null): void {
|
||||
if (set === null) {
|
||||
@@ -156,13 +144,6 @@ export class SmallPlayerGlowPass {
|
||||
this.dirty = true;
|
||||
}
|
||||
|
||||
// Strength (0 = off, 1 = default width) -> blur iterations; each iteration
|
||||
// widens the Gaussian aura. Non-linear so the slider ramps gently low and
|
||||
// grows fast toward the top: 500% ≈ 82 passes (very wide). Clamped for perf.
|
||||
private passesFor(strength: number): number {
|
||||
return Math.min(96, Math.max(1, Math.round(strength ** 2.74)));
|
||||
}
|
||||
|
||||
// One separable-blur axis: sample `src`, write the blurred result into `dst`.
|
||||
private blurAxis(
|
||||
src: RenderTarget,
|
||||
@@ -180,9 +161,11 @@ export class SmallPlayerGlowPass {
|
||||
}
|
||||
|
||||
draw(cameraMatrix: Float32Array): void {
|
||||
// Strength is pushed by the client (not tick-gated), so a slider change is
|
||||
// live even while the sim is paused (e.g. the settings modal is open).
|
||||
const strength = this.glowStrength;
|
||||
// Strength is a graphics override read from the live settings slice each
|
||||
// frame (not tick-gated), so a slider change is live even while the sim is
|
||||
// paused (e.g. the graphics settings modal is open). Clamped to 1 so a
|
||||
// value persisted from the old 500% range can't over-brighten.
|
||||
const strength = Math.min(1, this.settings.strength);
|
||||
if (!this.active || strength <= 0) return;
|
||||
|
||||
const gl = this.gl;
|
||||
@@ -199,11 +182,6 @@ export class SmallPlayerGlowPass {
|
||||
}
|
||||
this.lastTime = now;
|
||||
const pulse = 0.5 + 0.5 * Math.sin(this.animTime);
|
||||
const passes = this.passesFor(strength);
|
||||
if (passes !== this.lastPasses) {
|
||||
this.dirty = true; // width changed -> rebuild the aura this frame
|
||||
this.lastPasses = passes;
|
||||
}
|
||||
|
||||
// Rebuild the blurred aura only when the set changed (~1/s); its inputs
|
||||
// don't move faster than that. The composite below still runs every frame.
|
||||
@@ -222,14 +200,11 @@ export class SmallPlayerGlowPass {
|
||||
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
||||
});
|
||||
|
||||
// Separable blur, iterated `passes` times to widen the aura (more
|
||||
// iterations = wider Gaussian). Each pass: horizontal A->B, vertical B->A.
|
||||
// Separable blur: horizontal A->B, then vertical B->A.
|
||||
gl.useProgram(this.blurProg);
|
||||
gl.activeTexture(gl.TEXTURE0);
|
||||
for (let k = 0; k < passes; k++) {
|
||||
this.blurAxis(a, b, 1 / a.w, 0); // horizontal A->B
|
||||
this.blurAxis(b, a, 0, 1 / b.h); // vertical B->A
|
||||
}
|
||||
this.blurAxis(a, b, 1 / a.w, 0); // horizontal A->B
|
||||
this.blurAxis(b, a, 0, 1 / b.h); // vertical B->A
|
||||
this.dirty = false;
|
||||
}
|
||||
|
||||
@@ -243,9 +218,9 @@ export class SmallPlayerGlowPass {
|
||||
gl.uniformMatrix3fv(this.uCompositeCam, false, cameraMatrix);
|
||||
gl.uniform2f(this.uCompositeMapSize, this.mapW, this.mapH);
|
||||
gl.uniform3fv(this.uGlowColor, s.color);
|
||||
// Widening spreads the mask thinner, so scale intensity up to keep the
|
||||
// glow about as bright (wider, not darker), capped by the shader's alpha.
|
||||
gl.uniform1f(this.uIntensity, s.alpha * pulse * Math.sqrt(passes));
|
||||
// Strength fades the glow linearly: 1 = the aura's full alpha, 0.1 =
|
||||
// barely visible.
|
||||
gl.uniform1f(this.uIntensity, s.alpha * pulse * strength);
|
||||
gl.activeTexture(gl.TEXTURE0);
|
||||
gl.bindTexture(gl.TEXTURE_2D, a.tex);
|
||||
gl.bindVertexArray(this.mapVao);
|
||||
|
||||
@@ -335,7 +335,8 @@
|
||||
"smallPlayerGlow": {
|
||||
"color": [1.0, 0.12, 0.1],
|
||||
"alpha": 0.9,
|
||||
"pulseSpeed": 0.003
|
||||
"pulseSpeed": 0.003,
|
||||
"strength": 0.25
|
||||
},
|
||||
"altView": {
|
||||
"gridFontSize": 24,
|
||||
|
||||
@@ -140,13 +140,6 @@ export class UserSettings {
|
||||
return this.getBool("settings.emojis", true);
|
||||
}
|
||||
|
||||
highlightGlowStrength() {
|
||||
// 0 = off, 1 = default; capped at 5 (the 500% slider max) so a value
|
||||
// persisted from an older, larger range can't display/apply above it.
|
||||
const v = this.getFloat("settings.highlightGlowStrength", 1);
|
||||
return Math.min(5, Math.max(0, v));
|
||||
}
|
||||
|
||||
performanceOverlay() {
|
||||
return this.getBool(PERFORMANCE_OVERLAY_KEY, false);
|
||||
}
|
||||
@@ -199,10 +192,6 @@ export class UserSettings {
|
||||
this.setBool("settings.emojis", !this.emojis());
|
||||
}
|
||||
|
||||
setHighlightGlowStrength(value: number) {
|
||||
this.setFloat("settings.highlightGlowStrength", value);
|
||||
}
|
||||
|
||||
// Performance overlay specifically needs a direct setter for Shift-D
|
||||
setPerformanceOverlay(value: boolean) {
|
||||
this.setBool(PERFORMANCE_OVERLAY_KEY, value);
|
||||
|
||||
@@ -74,6 +74,17 @@ describe("GraphicsOverridesSchema", () => {
|
||||
}
|
||||
});
|
||||
|
||||
test("accepts partial smallPlayerGlow overrides", () => {
|
||||
const cases = [
|
||||
{ smallPlayerGlow: {} },
|
||||
{ smallPlayerGlow: { strength: 0 } },
|
||||
{ smallPlayerGlow: { strength: 0.5 } },
|
||||
];
|
||||
for (const c of cases) {
|
||||
expect(GraphicsOverridesSchema.safeParse(c).success).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
test("accepts partial lighting overrides", () => {
|
||||
const cases = [
|
||||
{ lighting: {} },
|
||||
@@ -128,6 +139,11 @@ describe("GraphicsOverridesSchema", () => {
|
||||
railroad: { railThickness: "wide" },
|
||||
}).success,
|
||||
).toBe(false);
|
||||
expect(
|
||||
GraphicsOverridesSchema.safeParse({
|
||||
smallPlayerGlow: { strength: "strong" },
|
||||
}).success,
|
||||
).toBe(false);
|
||||
expect(
|
||||
GraphicsOverridesSchema.safeParse({
|
||||
lighting: { ambient: "dark" },
|
||||
@@ -374,6 +390,28 @@ describe("applyGraphicsOverrides", () => {
|
||||
expect(z.railThickness).toBe(defaults.railThickness);
|
||||
});
|
||||
|
||||
test("applies smallPlayerGlow strength override (including 0 = off)", () => {
|
||||
expect(
|
||||
gen({ smallPlayerGlow: { strength: 0.5 } }).smallPlayerGlow.strength,
|
||||
).toBe(0.5);
|
||||
expect(
|
||||
gen({ smallPlayerGlow: { strength: 0 } }).smallPlayerGlow.strength,
|
||||
).toBe(0);
|
||||
});
|
||||
|
||||
test("smallPlayerGlow strength absent → keeps default of 0.25", () => {
|
||||
expect(gen({}).smallPlayerGlow.strength).toBe(0.25);
|
||||
expect(gen({ smallPlayerGlow: {} }).smallPlayerGlow.strength).toBe(0.25);
|
||||
});
|
||||
|
||||
test("smallPlayerGlow override leaves other glow fields at defaults", () => {
|
||||
const defaults = createRenderSettings().smallPlayerGlow;
|
||||
const g = gen({ smallPlayerGlow: { strength: 0.25 } }).smallPlayerGlow;
|
||||
expect(g.alpha).toBe(defaults.alpha);
|
||||
expect(g.pulseSpeed).toBe(defaults.pulseSpeed);
|
||||
expect(g.color).toEqual(defaults.color);
|
||||
});
|
||||
|
||||
test("ambient < 1 sets ambient and enables the lighting pass", () => {
|
||||
const l = gen({ lighting: { ambient: 0.5 } }).lighting;
|
||||
expect(l.ambient).toBe(0.5);
|
||||
|
||||
@@ -58,31 +58,3 @@ describe("UserSettings effect selection", () => {
|
||||
expect(s.getSelectedEffectName("hydro")).toBe("hydro_boom");
|
||||
});
|
||||
});
|
||||
|
||||
describe("UserSettings highlight glow strength", () => {
|
||||
beforeEach(() => {
|
||||
localStorage.clear();
|
||||
(
|
||||
UserSettings as unknown as { cache: Map<string, string | null> }
|
||||
).cache.clear();
|
||||
});
|
||||
|
||||
it("defaults to 1 (100%, on)", () => {
|
||||
expect(new UserSettings().highlightGlowStrength()).toBe(1);
|
||||
});
|
||||
|
||||
it("persists a set value, including 0 (off)", () => {
|
||||
const s = new UserSettings();
|
||||
s.setHighlightGlowStrength(2.5);
|
||||
expect(s.highlightGlowStrength()).toBe(2.5);
|
||||
s.setHighlightGlowStrength(0);
|
||||
expect(s.highlightGlowStrength()).toBe(0);
|
||||
});
|
||||
|
||||
it("shares state across instances via the static cache", () => {
|
||||
// The settings modal and the renderer's frame builder each hold their own
|
||||
// UserSettings; a change in one must be visible to the other.
|
||||
new UserSettings().setHighlightGlowStrength(3);
|
||||
expect(new UserSettings().highlightGlowStrength()).toBe(3);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user