replace day/night cycle with a binary light/dark mode tied to UserSettings

The cycling sun/moon animation was distracting and not a fan favorite.
Drops the cycle path entirely — RenderSettings.dayNight.mode is now
"light" | "dark", and the cycle-only fields (cycleTicks, startPhase,
noonHold, nightHold) plus the passEnabled.dayNight toggle are gone.
getAmbient is a one-liner. The in-game mode follows the existing
darkMode UserSetting (same one that drives the page-level CSS class);
ClientGameRunner applies it on startup and on the per-key change event.
This commit is contained in:
evanpelle
2026-05-17 20:01:23 -07:00
parent 3eedaf7bbc
commit c197f5864f
6 changed files with 27 additions and 83 deletions
+17 -1
View File
@@ -29,7 +29,11 @@ import {
} from "../core/game/GameUpdates";
import { GameView, PlayerView } from "../core/game/GameView";
import { loadTerrainMap, TerrainMapData } from "../core/game/TerrainMapLoader";
import { UserSettings } from "../core/game/UserSettings";
import {
DARK_MODE_KEY,
USER_SETTINGS_CHANGED_EVENT,
UserSettings,
} from "../core/game/UserSettings";
import { WorkerClient } from "../core/worker/WorkerClient";
import { getPersistentID } from "./Auth";
import {
@@ -429,6 +433,18 @@ async function createClientGame(
const { view, glCanvas, cachedWebGLFrameCallback } =
createWebGLView(gameMap);
// Bind the WebGL renderer's day/night mode to the existing darkMode
// UserSetting so the in-game map matches the rest of the UI. Initial
// apply + live updates via the per-key settings-changed event.
const applyDayNightMode = (isDark: boolean): void => {
view.getSettings().dayNight.mode = isDark ? "dark" : "light";
};
applyDayNightMode(userSettings.darkMode());
globalThis.addEventListener(
`${USER_SETTINGS_CHANGED_EVENT}:${DARK_MODE_KEY}`,
(e) => applyDayNightMode((e as CustomEvent<string>).detail === "true"),
);
const gameRenderer = createRenderer(
inputOverlay,
gameView,
+1 -20
View File
@@ -18,7 +18,6 @@ export function buildTree(s: RenderSettings, d: RenderSettings): DebugNode[] {
toggle(s.passEnabled, "railroad", d.passEnabled),
toggle(s.passEnabled, "fx", d.passEnabled),
toggle(s.passEnabled, "bar", d.passEnabled),
toggle(s.passEnabled, "dayNight", d.passEnabled),
toggle(s.passEnabled, "nameDebug", d.passEnabled, "Name Debug Boxes"),
]),
@@ -50,25 +49,7 @@ export function buildTree(s: RenderSettings, d: RenderSettings): DebugNode[] {
]),
folder("Day / Night", [
select(
s.dayNight,
"mode",
d.dayNight,
["light", "dark", "cycle"],
"Mode",
),
slider(s.dayNight, "cycleTicks", d.dayNight, 60, 6000, 10),
slider(
s.dayNight,
"startPhase",
d.dayNight,
0,
1,
0.01,
"Start Phase (0=noon)",
),
slider(s.dayNight, "noonHold", d.dayNight, 0, 1, 0.01, "Noon Hold"),
slider(s.dayNight, "nightHold", d.dayNight, 0, 1, 0.01, "Night Hold"),
select(s.dayNight, "mode", d.dayNight, ["light", "dark"], "Mode"),
slider(s.dayNight, "nightAmbient", d.dayNight, 0, 1, 0.01),
slider(s.dayNight, "dayAmbient", d.dayNight, 0, 1, 0.01),
slider(s.dayNight, "falloffPower", d.dayNight, 0.5, 5, 0.1),
@@ -15,11 +15,6 @@ import { createFullscreenQuad, createProgram } from "../utils/gl-utils";
import compositeFragSrc from "../shaders/day-night/composite.frag.glsl?raw";
import fullscreenVertSrc from "../shaders/shared/fullscreen.vert.glsl?raw";
function smoothstep(edge0: number, edge1: number, x: number): number {
const t = Math.max(0, Math.min(1, (x - edge0) / (edge1 - edge0)));
return t * t * (3 - 2 * t);
}
export class NightCompositePass {
private gl: WebGL2RenderingContext;
private settings: RenderSettings;
@@ -51,38 +46,9 @@ export class NightCompositePass {
// Ambient
// -------------------------------------------------------------------------
getAmbient(tick: number): number {
getAmbient(): number {
const dn = this.settings.dayNight;
if (dn.mode === "light") return dn.dayAmbient;
if (dn.mode === "dark") return dn.nightAmbient;
// Normalize phase to [0, 1), 0 = noon
const phase = (((tick / dn.cycleTicks + dn.startPhase) % 1) + 1) % 1;
// Clamp holds so they never exceed the full cycle
const noonHold = Math.min(dn.noonHold, 1);
const nightHold = Math.min(dn.nightHold, Math.max(0, 1 - noonHold));
const halfTransition = (1 - noonHold - nightHold) / 2;
// Region boundaries (all in [0, 1))
const duskStart = noonHold / 2;
const duskEnd = duskStart + halfTransition; // = 0.5 - nightHold/2
const nightEnd = duskEnd + nightHold; // = 0.5 + nightHold/2
const dawnEnd = nightEnd + halfTransition; // = 1 - noonHold/2
let t: number;
if (phase < duskStart || phase >= dawnEnd) {
t = 1; // noon hold
} else if (phase < duskEnd) {
t = smoothstep(duskEnd, duskStart, phase); // day → night
} else if (phase < nightEnd) {
t = 0; // midnight hold
} else {
t = smoothstep(nightEnd, dawnEnd, phase); // night → day
}
return dn.nightAmbient + (dn.dayAmbient - dn.nightAmbient) * t;
return dn.mode === "dark" ? dn.nightAmbient : dn.dayAmbient;
}
// -------------------------------------------------------------------------
@@ -90,12 +56,12 @@ export class NightCompositePass {
// -------------------------------------------------------------------------
/** Pure combiner — receives captured scene + lightmap textures, outputs to screen. */
draw(tick: number, sceneTex: WebGLTexture, lightmapTex: WebGLTexture): void {
draw(sceneTex: WebGLTexture, lightmapTex: WebGLTexture): void {
const gl = this.gl;
gl.disable(gl.BLEND);
gl.useProgram(this.compositeProg);
gl.uniform1f(this.uCompositeAmbient, this.getAmbient(tick));
gl.uniform1f(this.uCompositeAmbient, this.getAmbient());
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, sceneTex);
+1 -6
View File
@@ -9,7 +9,6 @@
"railroad": true,
"fx": true,
"bar": true,
"dayNight": true,
"nameDebug": false
},
"falloutBloom": {
@@ -34,11 +33,7 @@
"heatDecayPerTick": 1
},
"dayNight": {
"mode": "cycle",
"cycleTicks": 6000,
"startPhase": 0,
"noonHold": 0.25,
"nightHold": 0.1,
"mode": "light",
"nightAmbient": 0.15,
"dayAmbient": 1,
"falloffPower": 2,
+1 -6
View File
@@ -11,7 +11,6 @@ export interface RenderSettings {
railroad: boolean;
fx: boolean;
bar: boolean;
dayNight: boolean;
nameDebug: boolean;
};
falloutBloom: {
@@ -36,11 +35,7 @@ export interface RenderSettings {
heatDecayPerTick: number;
};
dayNight: {
mode: "light" | "dark" | "cycle";
cycleTicks: number;
startPhase: number; // 01, where 0 = noon, 0.25 = dusk, 0.5 = midnight, 0.75 = dawn
noonHold: number; // fraction of cycle held at full brightness (01)
nightHold: number; // fraction of cycle held at full darkness (01); noonHold+nightHold ≤ 1
mode: "light" | "dark";
nightAmbient: number;
dayAmbient: number;
falloffPower: number;
+3 -12
View File
@@ -135,7 +135,6 @@ export class GPURenderer {
private animId: number | null = null;
private frameTick = 0;
private gameTick = 0;
private mapW = 0;
private mapH = 0;
@@ -598,7 +597,6 @@ export class GPURenderer {
updateUnits(units: Map<number, UnitState>, gameTick: number): void {
this.lastUnits = units;
this.frameTick++;
this.gameTick = gameTick;
this.unitPass.updateUnits(units, this.frameTick);
this.barPass.updateBars(units, this.lastStructures, gameTick);
this.pointLightPass.updateLights(units);
@@ -1015,7 +1013,7 @@ export class GPURenderer {
);
const lightTex = this.lightmapPass.draw(cam, cw, ch);
toScreen(this.gl, cw, ch, () =>
this.nightCompositePass.draw(this.gameTick, sceneTex, lightTex),
this.nightCompositePass.draw(sceneTex, lightTex),
);
} else {
toScreen(this.gl, cw, ch, () => this.drawBaseLayer(cam));
@@ -1025,11 +1023,7 @@ export class GPURenderer {
}
private isNightActive(): boolean {
const mode = this.settings.dayNight.mode;
return (
mode === "dark" ||
(mode === "cycle" && this.settings.passEnabled.dayNight)
);
return this.settings.dayNight.mode === "dark";
}
private resizeSceneTargetIfNeeded(cw: number, ch: number): void {
@@ -1099,10 +1093,7 @@ export class GPURenderer {
if (this.gridView) this.coordinateGridPass.draw(cam, zoom);
if (pe.name && !this.gridView)
this.namePass.draw(
cam,
this.nightCompositePass.getAmbient(this.gameTick),
);
this.namePass.draw(cam, this.nightCompositePass.getAmbient());
this.radialMenuPass.draw();