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 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-08-23 18:21:17 +02:00
parent 91084b9a83
commit 0dac024cdb
10 changed files with 98 additions and 15 deletions

View file

@ -106,6 +106,13 @@ public sealed partial class SkyRenderer : IDisposable
/// </summary>
internal Func<bool>? EnhancedNightSkyActive { get; set; }
/// <summary>
/// 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.
/// </summary>
internal Func<float>? NightSkyRotationTurns { get; set; }
/// <summary>Deterministic seed for the procedural sky's hash grids —
/// the approved-look generator's seed (gen_starfield2.py, 2026-08-23).</summary>
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
/// <summary>
/// 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.
/// </summary>
public float NightSkyRotationTurns; // 256
public float NsPadA; // 260
public float NsPadB; // 264
public float NsPadC; // 268
/// <summary>256 — the std140 size of the block, a whole number of vec4s.</summary>
public const int SizeInBytes = 256;
/// <summary>272 — the std140 size of the block, a whole number of vec4s.</summary>
public const int SizeInBytes = 272;
}
private sealed class SubMeshGpu