From c197f5864f0c1ba26c48bf1545134c6ae0dbdad8 Mon Sep 17 00:00:00 2001 From: evanpelle Date: Sun, 17 May 2026 20:01:23 -0700 Subject: [PATCH] replace day/night cycle with a binary light/dark mode tied to UserSettings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- src/client/ClientGameRunner.ts | 18 +++++++- src/client/render/gl/debug/layout.ts | 21 +--------- .../render/gl/passes/night-composite-pass.ts | 42 ++----------------- src/client/render/gl/render-settings.json | 7 +--- src/client/render/gl/render-settings.ts | 7 +--- src/client/render/gl/renderer.ts | 15 ++----- 6 files changed, 27 insertions(+), 83 deletions(-) diff --git a/src/client/ClientGameRunner.ts b/src/client/ClientGameRunner.ts index 26b61103e..db807385f 100644 --- a/src/client/ClientGameRunner.ts +++ b/src/client/ClientGameRunner.ts @@ -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).detail === "true"), + ); + const gameRenderer = createRenderer( inputOverlay, gameView, diff --git a/src/client/render/gl/debug/layout.ts b/src/client/render/gl/debug/layout.ts index 9bfd49a81..14551bf2e 100644 --- a/src/client/render/gl/debug/layout.ts +++ b/src/client/render/gl/debug/layout.ts @@ -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), diff --git a/src/client/render/gl/passes/night-composite-pass.ts b/src/client/render/gl/passes/night-composite-pass.ts index b87146bd4..a8616fafb 100644 --- a/src/client/render/gl/passes/night-composite-pass.ts +++ b/src/client/render/gl/passes/night-composite-pass.ts @@ -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); diff --git a/src/client/render/gl/render-settings.json b/src/client/render/gl/render-settings.json index 2a0105c4c..e38b0e3c1 100644 --- a/src/client/render/gl/render-settings.json +++ b/src/client/render/gl/render-settings.json @@ -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, diff --git a/src/client/render/gl/render-settings.ts b/src/client/render/gl/render-settings.ts index 1b661dea9..d9bda1b20 100644 --- a/src/client/render/gl/render-settings.ts +++ b/src/client/render/gl/render-settings.ts @@ -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; // 0–1, where 0 = noon, 0.25 = dusk, 0.5 = midnight, 0.75 = dawn - noonHold: number; // fraction of cycle held at full brightness (0–1) - nightHold: number; // fraction of cycle held at full darkness (0–1); noonHold+nightHold ≤ 1 + mode: "light" | "dark"; nightAmbient: number; dayAmbient: number; falloffPower: number; diff --git a/src/client/render/gl/renderer.ts b/src/client/render/gl/renderer.ts index 67775ff0b..a1e831cae 100644 --- a/src/client/render/gl/renderer.ts +++ b/src/client/render/gl/renderer.ts @@ -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, 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();