using System; using System.IO; using Xunit; namespace AcDream.App.Tests.Rendering.Sky; /// /// 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. /// 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, and the // cube-face fwidth seams must not return (the 2026-08-23 gate's // glowing "Y"): the crispness anchor is the seamless 3D-lattice // tangent-plane solve on the direction derivatives. Assert.Contains("dFdx(dir)", code, StringComparison.Ordinal); Assert.DoesNotContain("fwidth(g)", code, StringComparison.Ordinal); // No diffraction spikes — user-directed: flare is photographic. Assert.DoesNotContain("spike", code, StringComparison.OrdinalIgnoreCase); } [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."); } }