Add territory saturation and opacity graphics settings

Expose two new user-configurable map-overlay controls in the graphics
settings modal: territory saturation (mutes fill colors toward grayscale)
and territory opacity (lets terrain show through the fill).

The territory fragment shader blends the fill toward its luminance based
on uSaturation and applies uTerritoryAlpha as the absolute fill opacity.
Both are wired through RenderSettings, the GraphicsOverrides schema,
applyGraphicsOverrides, the debug Layout sliders, and TerritoryPass
uniforms, with defaults (saturation 1, alpha 0.588) in render-settings.json.
Adds the corresponding en.json label/description strings.
This commit is contained in:
evanpelle
2026-06-09 19:15:54 -07:00
parent 855695b78e
commit 2d28d5463b
10 changed files with 184 additions and 0 deletions
@@ -19,6 +19,8 @@ export const GraphicsOverridesSchema = z
highlightFillBrighten: z.number(),
highlightBrighten: z.number(),
highlightThicken: z.number(),
territorySaturation: z.number(),
territoryAlpha: z.number(),
})
.partial(),
railroad: z
+7
View File
@@ -35,6 +35,13 @@ export function applyGraphicsOverrides(
settings.mapOverlay.highlightThicken =
overrides.mapOverlay.highlightThicken;
}
if (overrides.mapOverlay?.territorySaturation !== undefined) {
settings.mapOverlay.territorySaturation =
overrides.mapOverlay.territorySaturation;
}
if (overrides.mapOverlay?.territoryAlpha !== undefined) {
settings.mapOverlay.territoryAlpha = overrides.mapOverlay.territoryAlpha;
}
if (overrides.railroad?.railMinZoom !== undefined) {
settings.railroad.railMinZoom = overrides.railroad.railMinZoom;
}
+4
View File
@@ -69,6 +69,10 @@ export interface RenderSettings {
trailAlpha: number;
defenseCheckerDarken: number;
territoryDefenseDarken: number;
/** Saturation of the territory fill. 1 = full color, 0 = grayscale. */
territorySaturation: number;
/** Absolute opacity of the territory fill. 1 = fully opaque (terrain hidden), ~0.588 = default. */
territoryAlpha: number;
staleNukeBase: number;
staleNukeVariation: number;
staleNukeAlpha: number;
+18
View File
@@ -123,6 +123,24 @@ export function buildTree(s: RenderSettings, d: RenderSettings): DebugNode[] {
slider(s.mapOverlay, "trailAlpha", d.mapOverlay, 0, 1, 0.01),
slider(s.mapOverlay, "defenseCheckerDarken", d.mapOverlay, 0, 1, 0.01),
slider(s.mapOverlay, "territoryDefenseDarken", d.mapOverlay, 0, 1, 0.01),
slider(
s.mapOverlay,
"territorySaturation",
d.mapOverlay,
0,
1,
0.01,
"Territory Saturation",
),
slider(
s.mapOverlay,
"territoryAlpha",
d.mapOverlay,
0,
1,
0.01,
"Territory Alpha",
),
slider(s.mapOverlay, "staleNukeBase", d.mapOverlay, 0, 0.3, 0.005),
slider(s.mapOverlay, "staleNukeVariation", d.mapOverlay, 0, 0.3, 0.005),
slider(s.mapOverlay, "staleNukeAlpha", d.mapOverlay, 0, 1, 0.01),
@@ -41,6 +41,8 @@ export class TerritoryPass {
private uShowPatterns: WebGLUniformLocation;
private uIsTeamMode: WebGLUniformLocation;
private uDefenseDarken: WebGLUniformLocation;
private uSaturation: WebGLUniformLocation;
private uTerritoryAlpha: WebGLUniformLocation;
private highlightOwner = 0;
private isTeamMode = false;
@@ -165,6 +167,11 @@ export class TerritoryPass {
this.program,
"uDefenseDarken",
)!;
this.uSaturation = gl.getUniformLocation(this.program, "uSaturation")!;
this.uTerritoryAlpha = gl.getUniformLocation(
this.program,
"uTerritoryAlpha",
)!;
gl.useProgram(this.program);
gl.uniform1i(gl.getUniformLocation(this.program, "uTileTex"), 0);
@@ -458,6 +465,8 @@ export class TerritoryPass {
);
gl.uniform1i(this.uIsTeamMode, this.isTeamMode ? 1 : 0);
gl.uniform1f(this.uDefenseDarken, mo.territoryDefenseDarken);
gl.uniform1f(this.uSaturation, mo.territorySaturation);
gl.uniform1f(this.uTerritoryAlpha, mo.territoryAlpha);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, this.tileTex);
@@ -67,6 +67,8 @@
"trailAlpha": 0.588,
"defenseCheckerDarken": 0.7,
"territoryDefenseDarken": 0.85,
"territorySaturation": 1,
"territoryAlpha": 0.588,
"staleNukeBase": 0,
"staleNukeVariation": 0.05,
"staleNukeAlpha": 1,
@@ -25,6 +25,8 @@ uniform float uHighlightBrighten; // hover contrast boost strength; 0 = disable
uniform sampler2D uDefenseCoverageTex; // R8 — 1.0 = tile defended by same-owner post
uniform float uDefenseDarken; // multiplier applied to fill on defended tiles
uniform sampler2D uBorderTex; // RGBA8 — border flags; R > 0.25 = border tile
uniform float uSaturation; // 1 = full color, 0 = grayscale
uniform float uTerritoryAlpha; // absolute fill opacity; 1 = fully opaque
in vec2 vWorldPos;
out vec4 fragColor;
@@ -121,5 +123,13 @@ void main() {
color.rgb *= uDefenseDarken;
}
// Adjust how saturated the fill is by blending toward its luminance.
if (uSaturation != 1.0) {
float luma = dot(color.rgb, vec3(0.299, 0.587, 0.114));
color.rgb = mix(vec3(luma), color.rgb, uSaturation);
}
color.a = uTerritoryAlpha;
fragColor = color;
}