From 0dac024cdb1b4b634533c8927f3f4a3e1f31db6c Mon Sep 17 00:00:00 2001 From: Erik Date: Sun, 23 Aug 2026 18:21:17 +0200 Subject: [PATCH] feat(sky): the night sky wheels with the Dereth clock; uniform time fade with a scoped twilight band Rotation (user-directed): the procedural starfield rotates once per Dereth day (~2 real hours - constellations visibly wheel through a night) about a celestial pole ~41 deg above the northern horizon, plus dayOfYear/360 of seasonal drift so the 360-day year changes the night sky. One SkyParams float (272-byte block, layout test re-pinned) carries dayFraction + dayOfYear/360 from the world clock; sky.frag applies a Rodrigues rotation to the sample direction so stars and mottle turn together. Impossible with retail's static stretched layer. Fade rework (the 2026-08-23 two-screenshot gate finding): the per-vertex vTint signal carried the sun-facing product and blanked stars across the entire twilight half of the sky. The fade now reads the UNIFORM ambient term - identical star visibility in every compass direction, same dusk-to-dawn schedule - with one deliberate exception: a thin suppression band hugging the low sky toward the sun's azimuth while the sun term is strong, so stars still wash out inside the actual twilight glow. Guards updated (rotation anchor, uniform-fade anchor, 272-byte layout); both sky SPIR-V hashes re-pinned. Co-Authored-By: Claude Fable 5 --- .../Composition/FrameRootComposition.cs | 12 +++++ src/AcDream.App/Rendering/Shaders/sky.frag | 48 +++++++++++++++--- src/AcDream.App/Rendering/Shaders/sky.vert | 4 ++ .../Shaders/spv/shaders.manifest.json | 4 +- .../Rendering/Shaders/spv/sky.frag.spv | Bin 26428 -> 27676 bytes .../Rendering/Shaders/spv/sky.vert.spv | Bin 3912 -> 4008 bytes src/AcDream.App/Rendering/Sky/SkyRenderer.cs | 24 ++++++++- .../Gpu/Vk/VulkanShaderManifestTests.cs | 4 +- .../Sky/EnhancedNightSkyRuleTests.cs | 7 ++- .../Rendering/SkyParamsLayoutTests.cs | 10 +++- 10 files changed, 98 insertions(+), 15 deletions(-) diff --git a/src/AcDream.App/Composition/FrameRootComposition.cs b/src/AcDream.App/Composition/FrameRootComposition.cs index bfb78468..3dc8f5d7 100644 --- a/src/AcDream.App/Composition/FrameRootComposition.cs +++ b/src/AcDream.App/Composition/FrameRootComposition.cs @@ -333,6 +333,18 @@ internal sealed class FrameRootCompositionPhase skyForNightSky.EnhancedNightSkyActive = () => nightSkyController.ActiveRuntime?.Descriptor.Id == AcDream.App.Rendering.Packs.BuiltInAtmosphericRenderPack.Id; + // The starfield wheels with the Dereth clock: one turn per + // day about the celestial pole plus ~1 degree/night of + // seasonal drift (dayOfYear/360) — sky.frag's rotation block. + var nightSkyTime = d.WorldTime; + skyForNightSky.NightSkyRotationTurns = () => + { + var cal = nightSkyTime.CurrentCalendar; + int dayOfYear = (int)cal.Month + * AcDream.Core.World.DerethDateTime.DaysInAMonth + + (cal.Day - 1); + return (float)(nightSkyTime.DayFraction + dayOfYear / 360.0); + }; } } diff --git a/src/AcDream.App/Rendering/Shaders/sky.frag b/src/AcDream.App/Rendering/Shaders/sky.frag index a511ef4b..e56e4cae 100644 --- a/src/AcDream.App/Rendering/Shaders/sky.frag +++ b/src/AcDream.App/Rendering/Shaders/sky.frag @@ -43,6 +43,10 @@ layout(std140, ACDREAM_UBO_SET binding = 4) uniform SkyParams { vec2 uUvScroll; float uApplyFog; float uSurfOpacity; + float uNightSkyRotationTurns; // enhanced night sky (IA-26): sky rotation + float uNsPadA; + float uNsPadB; + float uNsPadC; }; struct Light { @@ -186,18 +190,50 @@ vec3 nightSky(vec3 dir, uint seed) void main() { if (uParamA > 0.5) { - // The star layer's own retail lighting product (Emissive 0 -> - // ambient + diffuse) is the day/night signal: ~0.1 at deep night, - // ~1.0 at noon. Stars at full strength only under a dark sky. - float dayL = dot(vTint, vec3(0.299, 0.587, 0.114)); - float night = 1.0 - smoothstep(0.18, 0.45, dayL); + // Day/night fade from the UNIFORM ambient term only — the first + // gate's vTint signal carried the per-vertex sun-facing product and + // blanked stars across the whole twilight half of the sky + // (2026-08-23 screenshots). Ambient is one value for the sky, so + // star visibility is even everywhere; the twilight glow gets its + // own tightly-scoped suppression below. + float dayL = dot(uAmbientColor, vec3(0.299, 0.587, 0.114)); + float night = 1.0 - smoothstep(0.10, 0.32, dayL); if (night < 0.004) { // Daytime: the whole lattice would multiply to zero — skip it // so the enhanced sky costs nothing outside dusk-to-dawn. fragColor = vec4(0.0, 0.0, 0.0, 1.0); return; } - vec3 sky = nightSky(normalize(vDir), uint(uParamB)); + + vec3 d = normalize(vDir); + + // Subtle twilight suppression: only the low sky (bottom ~19 deg) + // toward the sun's azimuth, and only while the sun term is strong + // (dusk/dawn) — stars near the bright horizon wash out, the rest + // of the sky keeps its full carpet. + vec3 dflat = normalize(vec3(d.xy, 0.0) + vec3(1e-5, 0.0, 0.0)); + vec3 sflat = normalize(vec3(uSunDir.xy, 0.0) + vec3(1e-5, 0.0, 0.0)); + float glowSide = max(dot(dflat, sflat), 0.0); + float lowSky = 1.0 - clamp(d.z * 3.0, 0.0, 1.0); + float twilight = clamp( + dot(uSunColor, vec3(0.299, 0.587, 0.114)) * uDiffuseFactor, + 0.0, 1.0); + night *= 1.0 - 0.85 * twilight * glowSide * glowSide * lowSky; + + // The sky wheels: one revolution per Dereth day about a celestial + // pole ~41 deg above the northern horizon (+Y north, +Z up), plus + // ~1 deg/night of seasonal drift folded into the turns value + // (dayFraction + dayOfYear/360). Rodrigues rotation of the sample + // direction; stars and mottle turn together. + const vec3 NS_POLE = vec3(0.0, 0.6598, 0.7514); + float ang = 6.28318530718 * fract(uNightSkyRotationTurns); + float ca = cos(ang); + float sa = sin(ang); + vec3 rdir = d * ca + + cross(NS_POLE, d) * sa + + NS_POLE * dot(NS_POLE, d) * (1.0 - ca); + + vec3 sky = nightSky(rdir, uint(uParamB)); // Additive pipeline (forced for this draw): rgb adds over the dome. fragColor = vec4(sky * night, 1.0); return; diff --git a/src/AcDream.App/Rendering/Shaders/sky.vert b/src/AcDream.App/Rendering/Shaders/sky.vert index 28a94d1a..e21fcdee 100644 --- a/src/AcDream.App/Rendering/Shaders/sky.vert +++ b/src/AcDream.App/Rendering/Shaders/sky.vert @@ -67,6 +67,10 @@ layout(std140, ACDREAM_UBO_SET binding = 4) uniform SkyParams { vec2 uUvScroll; float uApplyFog; // 1 for foggable layers; raw-additive keeps fog off float uSurfOpacity; // final surface opacity multiplier from the CPU + float uNightSkyRotationTurns; // enhanced night sky (IA-26): sky rotation + float uNsPadA; + float uNsPadB; + float uNsPadC; }; // Shared SceneLighting UBO — we need uFogParams.xy (fog start/end) to diff --git a/src/AcDream.App/Rendering/Shaders/spv/shaders.manifest.json b/src/AcDream.App/Rendering/Shaders/spv/shaders.manifest.json index dd84a830..b4eaf1b0 100644 --- a/src/AcDream.App/Rendering/Shaders/spv/shaders.manifest.json +++ b/src/AcDream.App/Rendering/Shaders/spv/shaders.manifest.json @@ -311,12 +311,12 @@ "stages": [ { "stage": "vert", - "sourceSha256": "9102b156bd4fd667831353640b2feee2ddde66e88579a4e425566c9b67304b64", + "sourceSha256": "80c190d5544433bd6b08810c1e5040cec5090fe91839f436728d5454ed7f3c31", "compiled": true }, { "stage": "frag", - "sourceSha256": "e7ccdb4d5fece2ceeb48eb43aaa827424df2b68614987eeb3dfb7880dff8ea83", + "sourceSha256": "76481dcc1a321c65308bf9c081254978ee7f12413166df5d32a1606228bdc768", "compiled": true } ] diff --git a/src/AcDream.App/Rendering/Shaders/spv/sky.frag.spv b/src/AcDream.App/Rendering/Shaders/spv/sky.frag.spv index f6fc74417a2ed6bcd1bca4f8f9d420bb6bf8abb2..2914922e256caa17aa0a5f04517779e348dc2fd1 100644 GIT binary patch literal 27676 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 Z2w{KY^;=WcBYkx}a$Wj_E4Tse{{e&frl9}; diff --git a/src/AcDream.App/Rendering/Shaders/spv/sky.vert.spv b/src/AcDream.App/Rendering/Shaders/spv/sky.vert.spv index e69c1a43307eb3b22c4da48f0b454f3272a1404c..3cff9d37989ae55e7070701e5c8b91979e274aa3 100644 GIT binary patch delta 108 zcmX>hw?ck{0F$2w11p070|NsO5Gw;Q10w?iOpF&Q#)2fq2NmN$661%8@i1=YW17Rn UC^UIBizzD`P?T-+RhD8d0P!FS6#xJL delta 23 fcmZ1>e?o490Mq6Krddpke3O5(m~M7p&ENt6U>yfx diff --git a/src/AcDream.App/Rendering/Sky/SkyRenderer.cs b/src/AcDream.App/Rendering/Sky/SkyRenderer.cs index aec455b6..84975d70 100644 --- a/src/AcDream.App/Rendering/Sky/SkyRenderer.cs +++ b/src/AcDream.App/Rendering/Sky/SkyRenderer.cs @@ -106,6 +106,13 @@ public sealed partial class SkyRenderer : IDisposable /// internal Func? EnhancedNightSkyActive { get; set; } + /// + /// Dereth-clock rotation source for the enhanced night sky, in turns + /// (dayFraction + dayOfYear/360). Composition wires it to the world + /// time service; evaluated once per sky pass. + /// + internal Func? NightSkyRotationTurns { get; set; } + /// Deterministic seed for the procedural sky's hash grids — /// the approved-look generator's seed (gen_starfield2.py, 2026-08-23). private const float NightSkySeed = 11f; @@ -209,6 +216,8 @@ public sealed partial class SkyRenderer : IDisposable // that FOV here, including the near-180-degree teleport transition. var skyProj = SkyProjection.WithDepthRange(camera.Projection, Near, Far); + _params.NightSkyRotationTurns = NightSkyRotationTurns?.Invoke() ?? 0f; + // View with translation zeroed — keeps the sky at camera origin // regardless of camera position in the world. var skyView = camera.View; @@ -726,9 +735,20 @@ public sealed partial class SkyRenderer : IDisposable public Vector2 UvScroll; // 240 public float ApplyFog; // 248 public float SurfOpacity; // 252 + /// + /// Whole-sky rotation in TURNS for the enhanced night sky (IA-26): + /// dayFraction + dayOfYear/360, so the procedural starfield wheels + /// once per Dereth day about the celestial pole and drifts ~1 degree + /// per night through the 360-day year (seasonal constellations). + /// Zero when the pack is inactive; the retail path never reads it. + /// + public float NightSkyRotationTurns; // 256 + public float NsPadA; // 260 + public float NsPadB; // 264 + public float NsPadC; // 268 - /// 256 — the std140 size of the block, a whole number of vec4s. - public const int SizeInBytes = 256; + /// 272 — the std140 size of the block, a whole number of vec4s. + public const int SizeInBytes = 272; } private sealed class SubMeshGpu diff --git a/tests/AcDream.App.Tests/Rendering/Gpu/Vk/VulkanShaderManifestTests.cs b/tests/AcDream.App.Tests/Rendering/Gpu/Vk/VulkanShaderManifestTests.cs index 6a54192b..b33558fc 100644 --- a/tests/AcDream.App.Tests/Rendering/Gpu/Vk/VulkanShaderManifestTests.cs +++ b/tests/AcDream.App.Tests/Rendering/Gpu/Vk/VulkanShaderManifestTests.cs @@ -52,8 +52,8 @@ public sealed class VulkanShaderManifestTests // override, retail's GameSky::Draw @0x00506FF0 rule (see // SkyFogRuleTests). A deliberate default-path change, reviewed // with the world-fog-range fix in the same commit. - ["sky.frag.spv"] = "e307a7f3cf84aada86db7c2859c55841a650b46732f12149986562cef0a90ca8", - ["sky.vert.spv"] = "3b51945fa4ff1be1604144df92866bdd47aade22f9dd90267591ef36adb28cde", + ["sky.frag.spv"] = "83a7b4e265f7df4726d716a812ebaa74ba31ea47036bc31005cd8dc1d8981967", + ["sky.vert.spv"] = "988eff4e106422ca483d665ba25818e8394737a247e9bae346db3220ac31fd28", ["terrain_modern.frag.spv"] = "7b3cdb01b837ed77ee20559a81c1ce5c9d5395300efcc072560ab0be3c5a1af9", ["terrain_modern.vert.spv"] = "9f4cb221ea6aed94a8d23af6cb8e3f3ed96c3cce6e50d135a72d3b55667b1557", ["ui_text.frag.spv"] = "37a281bf80441cb425eaa3ad8e0b3a43cfa21b74b60973ed4201718b9dc102df", diff --git a/tests/AcDream.App.Tests/Rendering/Sky/EnhancedNightSkyRuleTests.cs b/tests/AcDream.App.Tests/Rendering/Sky/EnhancedNightSkyRuleTests.cs index 48ad7a82..9a4fac2b 100644 --- a/tests/AcDream.App.Tests/Rendering/Sky/EnhancedNightSkyRuleTests.cs +++ b/tests/AcDream.App.Tests/Rendering/Sky/EnhancedNightSkyRuleTests.cs @@ -21,7 +21,12 @@ public sealed class EnhancedNightSkyRuleTests string code = StripLineComments(frag); Assert.Contains("if (uParamA > 0.5)", code, StringComparison.Ordinal); - Assert.Contains("nightSky(normalize(vDir), uint(uParamB))", code, StringComparison.Ordinal); + Assert.Contains("nightSky(rdir, uint(uParamB))", code, StringComparison.Ordinal); + // The fade must stay UNIFORM across the sky (the first gate's + // per-vertex vTint signal blanked the whole twilight half) and the + // starfield must wheel with the Dereth clock. + Assert.Contains("dot(uAmbientColor, vec3(0.299, 0.587, 0.114))", code, StringComparison.Ordinal); + Assert.Contains("fract(uNightSkyRotationTurns)", code, StringComparison.Ordinal); // Screen-pixel star sizing is the reason this exists — the stretched // texture flaw must not creep back in via a fixed-size grid, and the // cube-face fwidth seams must not return (the 2026-08-23 gate's diff --git a/tests/AcDream.App.Tests/Rendering/SkyParamsLayoutTests.cs b/tests/AcDream.App.Tests/Rendering/SkyParamsLayoutTests.cs index 7a9a0a82..c9127818 100644 --- a/tests/AcDream.App.Tests/Rendering/SkyParamsLayoutTests.cs +++ b/tests/AcDream.App.Tests/Rendering/SkyParamsLayoutTests.cs @@ -8,7 +8,7 @@ namespace AcDream.App.Tests.Rendering; /// /// Campaign V slice V6e: the sky's dozen loose uniforms became one std140 /// uniform block, and this is the test that keeps the CPU struct and the GLSL -/// block describing the same 256 bytes. +/// block describing the same 272 bytes. /// /// It exists because the failure mode is silent. std140 gives a /// vec3 16-byte alignment while only using 12, so the block deliberately @@ -45,6 +45,12 @@ public sealed class SkyParamsLayoutTests [InlineData("UvScroll", 240)] [InlineData("ApplyFog", 248)] [InlineData("SurfOpacity", 252)] + // IA-26: the enhanced night sky's rotation turns plus its three pad + // words, filling the seventeenth vec4 exactly. + [InlineData("NightSkyRotationTurns", 256)] + [InlineData("NsPadA", 260)] + [InlineData("NsPadB", 264)] + [InlineData("NsPadC", 268)] public void EveryMemberSitsWhereStd140PutsIt(string member, int expectedOffset) { Assert.Equal( @@ -60,7 +66,7 @@ public sealed class SkyParamsLayoutTests ?.GetRawConstantValue() ?? throw new InvalidOperationException("SkyParams.SizeInBytes is missing.")); - Assert.Equal(256, declared); + Assert.Equal(272, declared); Assert.Equal(declared, Marshal.SizeOf(SkyParamsType)); // std140 rounds a block up to its largest member's alignment (16). Assert.Equal(0, declared % 16);