From 474b641499e5b936bc01b36ddcb7103e43c5d593 Mon Sep 17 00:00:00 2001
From: pkuzic
Date: Fri, 25 Sep 2026 16:57:20 +0100
Subject: [PATCH] MONKEY (fix-sky): day-long f64 sky clock; twinkle rates whole
cycles per wrap
Review B2: the f32 clock wrapped hourly and every star's twinkle jumped at the
wrap. It now accumulates in f64 and wraps at 86400 s; the shader rounds each
twinkle rate to whole cycles per wrap, so the stars are seamless. The High
cloud detail drift still re-patterns once per 24 h (noted).
Co-Authored-By: Claude Opus 5.5
---
crates/benilla-world/src/shaders/sky_fx.wgsl | 12 +++++++++++-
crates/benilla-world/src/sky_fx.rs | 17 ++++++++++++++---
2 files changed, 25 insertions(+), 4 deletions(-)
diff --git a/crates/benilla-world/src/shaders/sky_fx.wgsl b/crates/benilla-world/src/shaders/sky_fx.wgsl
index e547f90b..251fcb04 100644
--- a/crates/benilla-world/src/shaders/sky_fx.wgsl
+++ b/crates/benilla-world/src/shaders/sky_fx.wgsl
@@ -7,6 +7,15 @@
#define_import_path benilla_world::sky_fx
const TAU: f32 = 6.2831853;
+// MONKEY (fix-sky): the sky clock's wrap (`sky_fx::SKY_CLOCK_WRAP_S`, a day). Twinkle rates are
+// whole cycles per wrap, so the stars never jump at the wrap.
+const SKY_WRAP: f32 = 86400.0;
+
+// The phase of a `rate` rad/s oscillator, rounded to whole cycles per wrap.
+fn wrap_phase(t: f32, rate: f32) -> f32 {
+ let cycles = round(rate * SKY_WRAP / TAU);
+ return fract(cycles * (t / SKY_WRAP)) * TAU;
+}
fn srgb_to_linear(c: vec3) -> vec3 {
let lo = c / 12.92;
@@ -201,7 +210,8 @@ fn star_field(dir: vec3, t: f32, px: f32, band: f32) -> vec3 {
let sigma = px * (0.55 + 0.7 * mag);
let spot = exp(-d * d / (2.0 * sigma * sigma));
let tw_amp = 0.18 + 0.3 * (1.0 - clamp(dir.y, 0.0, 1.0));
- let tw = 1.0 + tw_amp * sin(t * (1.3 + 3.1 * j.x) + TAU * j.y) * sin(t * (0.6 + 1.7 * j.z) + TAU * h.z);
+ let tw = 1.0 + tw_amp * sin(wrap_phase(t, 1.3 + 3.1 * j.x) + TAU * j.y)
+ * sin(wrap_phase(t, 0.6 + 1.7 * j.z) + TAU * h.z);
// Star colour: mostly white, a few warm and a few blue-white.
let tint = mix(vec3(0.78, 0.86, 1.0), vec3(1.0, 0.88, 0.72), h.z);
acc += tint * (b * spot * tw);
diff --git a/crates/benilla-world/src/sky_fx.rs b/crates/benilla-world/src/sky_fx.rs
index 3e5f7acd..e8462579 100644
--- a/crates/benilla-world/src/sky_fx.rs
+++ b/crates/benilla-world/src/sky_fx.rs
@@ -36,14 +36,23 @@ impl SkyQuality {
}
}
-/// The sky's own animation clock (star twinkle, cloud detail drift), seconds wrapped to an hour so
-/// the shader's `sin` keeps its precision. Frozen in capture mode, at `$WOW_CAPTURE_SKY_T` or 0.
+/// The sky's own animation clock (star twinkle, cloud detail drift), seconds wrapped to a day.
+/// Frozen in capture mode, at `$WOW_CAPTURE_SKY_T` or 0.
+///
+/// MONKEY (fix-sky): it wrapped hourly in f32, and every star's twinkle jumped at the wrap. Now it
+/// accumulates in f64 and wraps at [`SKY_CLOCK_WRAP_S`]; the shader's twinkle rates are whole
+/// cycles per wrap (seamless). The High cloud-detail drift still re-patterns at the wrap, once
+/// per 24 h of continuous play.
#[derive(Resource, Default)]
pub struct SkyClock {
pub secs: f32,
+ acc: f64,
frozen: Option,
}
+/// The sky clock's wrap in seconds; `sky_fx.wgsl`'s `SKY_WRAP` must match.
+pub const SKY_CLOCK_WRAP_S: f64 = 86_400.0;
+
/// How strongly the glow shows: a broad halo at `GLOW_GAIN` × the sun colour at the sun itself.
pub(crate) const GLOW_GAIN: f32 = 0.22;
@@ -77,6 +86,7 @@ impl Plugin for SkyFxPlugin {
});
app.insert_resource(SkyClock {
secs: frozen.unwrap_or(0.0),
+ acc: 0.0,
frozen,
});
let quality = SkyQuality(SkyQuality::env_override().unwrap_or(0));
@@ -92,7 +102,8 @@ fn tick_sky_clock(time: Res