feat(sky): pack-gated procedural night sky replacing the stretched DAT star layer; register IA-26
User-directed enhancement ('I want the night sky to look very good'):
retail's star layer is one small texture stretched over a 10-poly dome
cap, so stars smear regardless of source-image quality. With the
Atmospheric render pack active, sky.frag now renders the star layer
(GfxObj 0x010015EF, identical in all 20 Dereth day groups) as a fully
procedural sky computed from the view direction: hash-derived stars on
a cube-face grid in three density tiers plus sparse diffraction-spiked
standouts, sized in SCREEN pixels via derivatives so they stay crisp at
any resolution and FOV, over the user-approved 0.4-1.3% cool mottle
(gen_starfield2.py seed 11, approved 2026-08-23). The draw is forced
additive; the day/night fade rides the star layer's existing retail
lighting product so the schedule matches the authored keyframes. Pack
inactive = retail look byte-untouched.
EnhancedNightSkyRuleTests pins the uParamA gate, the exact star-layer
id, the forced-additive draw, and the pack-runtime wiring; sky shader
SPIR-V recompiled and re-pinned. Hermetic App suite green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
6d8f165d38
commit
508cefdeb1
11 changed files with 302 additions and 11 deletions
|
|
@ -209,7 +209,7 @@ public sealed unsafe partial class SkyRenderer
|
|||
/// those binds are what select the descriptor scope the sections must land in
|
||||
/// (plan §5.5.14 item 2).</para>
|
||||
/// </summary>
|
||||
private void DrawSubMeshRhi(SubMeshGpu sub, uint textureSlot)
|
||||
private void DrawSubMeshRhi(SubMeshGpu sub, uint textureSlot, bool nightSky = false)
|
||||
{
|
||||
if (sub.IndexCount == 0 || sub.VertexBuffer is null || sub.IndexBuffer is null)
|
||||
return;
|
||||
|
|
@ -220,12 +220,16 @@ public sealed unsafe partial class SkyRenderer
|
|||
?? throw new InvalidOperationException(
|
||||
"SkyRenderer requires an open IGpuFrame (see GpuDeviceFrameLifetime).");
|
||||
|
||||
encoder.BindPipeline(sub.IsAdditive ? _additivePipeline! : _alphaPipeline!);
|
||||
// The enhanced night sky ADDS starlight over the dome, so its draw
|
||||
// rides the additive pipeline even though the authored star surface
|
||||
// is alpha-blended (register IA-26).
|
||||
encoder.BindPipeline(nightSky || sub.IsAdditive ? _additivePipeline! : _alphaPipeline!);
|
||||
var pushConstants = new GpuPushConstants
|
||||
{
|
||||
// uViewProjection is unread by sky.vert — the sky carries its own
|
||||
// camera-anchored view and dome projection in SkyParams — so the
|
||||
// block's only live member here is the texture slot.
|
||||
// live members here are the texture slot and the night-sky
|
||||
// ParamA/ParamB pair (sky.frag's uParamA gate + seed).
|
||||
ViewProjection = System.Numerics.Matrix4x4.Identity,
|
||||
DrawIdOffset = 0,
|
||||
LightingMode = 0,
|
||||
|
|
@ -233,8 +237,8 @@ public sealed unsafe partial class SkyRenderer
|
|||
LightDebug = 0,
|
||||
TextureIndexA = textureSlot,
|
||||
TextureIndexB = 0,
|
||||
ParamA = 0f,
|
||||
ParamB = 0f,
|
||||
ParamA = nightSky ? 1f : 0f,
|
||||
ParamB = NightSkySeed,
|
||||
};
|
||||
encoder.SetPushConstants(in pushConstants);
|
||||
encoder.BindVertexBuffer(0, sub.VertexBuffer, 0);
|
||||
|
|
|
|||
|
|
@ -89,6 +89,27 @@ public sealed partial class SkyRenderer : IDisposable
|
|||
public float Near { get; set; } = 0.1f;
|
||||
public float Far { get; set; } = 1_000_000f;
|
||||
|
||||
/// <summary>
|
||||
/// Dereth's star layer — the one sky GfxObj (verified identical across
|
||||
/// all 20 day groups) whose draw the enhanced night sky replaces.
|
||||
/// </summary>
|
||||
private const uint StarLayerGfxObjId = 0x010015EFu;
|
||||
|
||||
/// <summary>
|
||||
/// User-directed enhancement gate (register IA-26): when this returns
|
||||
/// true, the star layer renders as sky.frag's procedural night sky
|
||||
/// (view-direction stars, pixel-crisp at any resolution) instead of the
|
||||
/// authored stretched texture. Wired by composition to "the Atmospheric
|
||||
/// render pack is active"; null/false keeps the retail look. Evaluated
|
||||
/// per draw — never capture a stale pre-session snapshot
|
||||
/// (claude-memory/feedback_resolve_deferred_funcs_per_call.md).
|
||||
/// </summary>
|
||||
internal Func<bool>? EnhancedNightSkyActive { 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;
|
||||
|
||||
/// <summary>
|
||||
/// Draw all NON-WEATHER sky objects (dome, sun, moon, stars, clouds —
|
||||
/// every <c>SkyObject</c> with <c>Properties & 0x04 == 0</c>).
|
||||
|
|
@ -426,7 +447,9 @@ public sealed partial class SkyRenderer : IDisposable
|
|||
|| obj.TexVelocityX != 0f
|
||||
|| obj.TexVelocityY != 0f;
|
||||
uint slot = TextureTableSlot(sub.SurfaceId, needsRepeat);
|
||||
DrawSubMeshRhi(sub, slot);
|
||||
bool nightSky = gfxObjId == StarLayerGfxObjId
|
||||
&& (EnhancedNightSkyActive?.Invoke() ?? false);
|
||||
DrawSubMeshRhi(sub, slot, nightSky);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue