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
|
|
@ -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"] = "b3b544829f2dd85be04b6b16d78a0490e1fe7884590cb541b95756b7d7511620",
|
||||
["sky.vert.spv"] = "77176cf33c761ee4e9730357895c941dbf5949d8e0d28e0bb0dcde87f4d30288",
|
||||
["sky.frag.spv"] = "8105984072fc1b9075b5efdffd75d959c4087710d354d50a0e00ee0500462ca7",
|
||||
["sky.vert.spv"] = "3b51945fa4ff1be1604144df92866bdd47aade22f9dd90267591ef36adb28cde",
|
||||
["terrain_modern.frag.spv"] = "7b3cdb01b837ed77ee20559a81c1ce5c9d5395300efcc072560ab0be3c5a1af9",
|
||||
["terrain_modern.vert.spv"] = "9f4cb221ea6aed94a8d23af6cb8e3f3ed96c3cce6e50d135a72d3b55667b1557",
|
||||
["ui_text.frag.spv"] = "37a281bf80441cb425eaa3ad8e0b3a43cfa21b74b60973ed4201718b9dc102df",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,106 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using Xunit;
|
||||
|
||||
namespace AcDream.App.Tests.Rendering.Sky;
|
||||
|
||||
/// <summary>
|
||||
/// Source guards for the enhanced night sky (register IA-26, user-directed
|
||||
/// 2026-08-23): a render-pack-gated replacement of the DAT star layer
|
||||
/// (GfxObj 0x010015EF) with sky.frag's procedural view-direction starfield.
|
||||
/// The retail look must stay the default: everything here is inert unless the
|
||||
/// Atmospheric pack is the active runtime. The shader itself needs a GPU, so
|
||||
/// these pin the wiring the way SkyFogRuleTests pins the fog rules.
|
||||
/// </summary>
|
||||
public sealed class EnhancedNightSkyRuleTests
|
||||
{
|
||||
[Fact]
|
||||
public void SkyFragmentShaderGatesTheProceduralSkyOnParamA()
|
||||
{
|
||||
string frag = File.ReadAllText(Path.Combine(ShaderRoot(), "sky.frag"));
|
||||
string code = StripLineComments(frag);
|
||||
|
||||
Assert.Contains("if (uParamA > 0.5)", code, StringComparison.Ordinal);
|
||||
Assert.Contains("nightSky(normalize(vDir), uint(uParamB))", 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.
|
||||
Assert.Contains("fwidth(g)", code, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SkyRendererSwapsExactlyTheStarLayerAndOnlyUnderTheProvider()
|
||||
{
|
||||
string source = File.ReadAllText(Path.Combine(
|
||||
RepositoryRoot(), "src", "AcDream.App", "Rendering", "Sky", "SkyRenderer.cs"));
|
||||
string code = StripLineComments(source);
|
||||
|
||||
Assert.Contains(
|
||||
"private const uint StarLayerGfxObjId = 0x010015EFu;",
|
||||
code,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains(
|
||||
"bool nightSky = gfxObjId == StarLayerGfxObjId",
|
||||
code,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains(
|
||||
"(EnhancedNightSkyActive?.Invoke() ?? false)",
|
||||
code,
|
||||
StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NightSkyDrawForcesTheAdditivePipeline()
|
||||
{
|
||||
string source = File.ReadAllText(Path.Combine(
|
||||
RepositoryRoot(), "src", "AcDream.App", "Rendering", "Sky", "SkyRenderer.Rhi.cs"));
|
||||
string code = StripLineComments(source);
|
||||
|
||||
Assert.Contains(
|
||||
"nightSky || sub.IsAdditive ? _additivePipeline! : _alphaPipeline!",
|
||||
code,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains("ParamA = nightSky ? 1f : 0f,", code, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CompositionGatesTheNightSkyOnTheAtmosphericPackRuntime()
|
||||
{
|
||||
string source = File.ReadAllText(Path.Combine(
|
||||
RepositoryRoot(), "src", "AcDream.App", "Composition", "FrameRootComposition.cs"));
|
||||
string code = StripLineComments(source);
|
||||
|
||||
Assert.Contains("EnhancedNightSkyActive = () =>", code, StringComparison.Ordinal);
|
||||
Assert.Contains(
|
||||
"nightSkyController.ActiveRuntime?.Descriptor.Id",
|
||||
code,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains(
|
||||
"BuiltInAtmosphericRenderPack.Id",
|
||||
code,
|
||||
StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
private static string StripLineComments(string source)
|
||||
{
|
||||
var lines = source.Split('\n');
|
||||
for (int i = 0; i < lines.Length; i++)
|
||||
{
|
||||
int idx = lines[i].IndexOf("//", StringComparison.Ordinal);
|
||||
if (idx >= 0)
|
||||
lines[i] = lines[i][..idx];
|
||||
}
|
||||
return string.Join('\n', lines);
|
||||
}
|
||||
|
||||
private static string ShaderRoot() =>
|
||||
Path.Combine(RepositoryRoot(), "src", "AcDream.App", "Rendering", "Shaders");
|
||||
|
||||
private static string RepositoryRoot()
|
||||
{
|
||||
var directory = new DirectoryInfo(AppContext.BaseDirectory);
|
||||
while (directory is not null && !File.Exists(Path.Combine(directory.FullName, "AcDream.slnx")))
|
||||
directory = directory.Parent;
|
||||
return directory?.FullName
|
||||
?? throw new InvalidOperationException("Could not locate repository root.");
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue