fix #427: sky drawn without fog like retail; world fog range is the keyframe's authored MinWorldFog/MaxWorldFog
Two April stand-ins, neither registered, met at the horizon from altitude as a hard line between the dome's rim and the fog-coloured clear: 1. sky.frag fogged every non-additive sky layer with a 0.2 floor. Retail's GameSky::Draw @0x00506FF0 disables fixed-function fog around the whole sky draw unless an AdminEnvirons fog override is active (SetFFFogEnable( LScape::m_override_enabled ? 1 : 0)); additive layers stay unfogged via SetFFFogAlphaDisabled(1) at D3DPolyRender::SetSurface 0x59c882. The sky pass now sets ApplyFog only for (override active && !additive), with no floor. 2. WorldRenderFrameBuilder overwrote the authored fog range with one derived from the streaming window (538..2189 m always). Retail sets FOGSTART/ FOGEND straight from the keyframe's MinWorldFog/MaxWorldFog (SkyDesc::GetWorldFog @0x00500CE0 -> SetFFFogProperties @0x005A2F70) with no draw-distance scaling; zfar is a constant 4000 m. The builder now leaves SceneLightingUbo.Build's values alone; ACDREAM_FOG_START_MULT / _END_MULT are deleted from RuntimeOptions. Guards: SkyFogRuleTests (source-level, the sky renderer has no hermetic harness); sky.frag.spv re-pinned in VulkanShaderManifestTests with the reason. Research note 2026-04-23-sky-fog.md carries a correction banner. App hermetic 6,070/0, Core 4,707/0 (Release). Owner look gate owed: night and rain fog are now retail's shorter authored ranges. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
517d17b4b3
commit
cc42edc8e3
11 changed files with 180 additions and 62 deletions
86
tests/AcDream.App.Tests/Rendering/Sky/SkyFogRuleTests.cs
Normal file
86
tests/AcDream.App.Tests/Rendering/Sky/SkyFogRuleTests.cs
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using Xunit;
|
||||
|
||||
namespace AcDream.App.Tests.Rendering.Sky;
|
||||
|
||||
/// <summary>
|
||||
/// Retail draws the sky with fixed-function fog DISABLED (GameSky::Draw
|
||||
/// @0x00506FF0: SetFFFogEnable(LScape::m_override_enabled ? 1 : 0) around
|
||||
/// the sky draw) and sets the world fog range straight from the keyframe's
|
||||
/// authored MinWorldFog/MaxWorldFog (SkyDesc::GetWorldFog @0x00500CE0 ->
|
||||
/// RenderDeviceD3D::SetFFFogProperties @0x005A2F70, no draw-distance
|
||||
/// scaling). Two April-2026 stand-ins contradicted that — a 0.2 "fog floor"
|
||||
/// on the dome and a fog range derived from the streaming window — and
|
||||
/// together showed as a hard line below the horizon from altitude
|
||||
/// (2026-08-23). These source guards keep both retail rules in place; the
|
||||
/// renderer itself needs a GPU and the installed DATs, so there is no
|
||||
/// hermetic behavioural harness for it.
|
||||
/// </summary>
|
||||
public sealed class SkyFogRuleTests
|
||||
{
|
||||
[Fact]
|
||||
public void SkyFragmentShaderFogsTheDomeOnlyThroughUApplyFogAndWithoutAFloor()
|
||||
{
|
||||
string frag = File.ReadAllText(Path.Combine(ShaderRoot(), "sky.frag"));
|
||||
string code = StripLineComments(frag);
|
||||
|
||||
Assert.DoesNotContain("SKY_FOG_FLOOR", code, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("max(vFogFactor", code, StringComparison.Ordinal);
|
||||
Assert.Contains("if (uApplyFog > 0.5 && fogMode != 0)", code, StringComparison.Ordinal);
|
||||
Assert.Contains("rgb = mix(uFogColor.rgb, rgb, vFogFactor);", code, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SkyRendererAppliesFogOnlyUnderAnAdminEnvironsOverrideAndNeverOnAdditiveLayers()
|
||||
{
|
||||
string source = File.ReadAllText(Path.Combine(
|
||||
RepositoryRoot(), "src", "AcDream.App", "Rendering", "Sky", "SkyRenderer.cs"));
|
||||
string code = StripLineComments(source);
|
||||
|
||||
Assert.Contains(
|
||||
"_params.ApplyFog = environOverrideActive && !sub.DisableFog ? 1f : 0f;",
|
||||
code,
|
||||
StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("_params.ApplyFog = sub.DisableFog ? 0f : 1f;", code, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WorldFrameBuilderLeavesTheAuthoredFogRangeAlone()
|
||||
{
|
||||
string source = File.ReadAllText(Path.Combine(
|
||||
RepositoryRoot(), "src", "AcDream.App", "Rendering", "WorldRenderFrameBuilder.cs"));
|
||||
string code = StripLineComments(source);
|
||||
|
||||
// SceneLightingUbo.Build writes FogParams.xy from AtmosphereSnapshot
|
||||
// (the keyframe's authored range, SceneLightingUboTests pins that);
|
||||
// nothing downstream may overwrite them from the streaming window.
|
||||
Assert.DoesNotContain("ubo.FogParams = new Vector4(", code, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("FogEndMultiplier", code, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("FogStartMultiplier", 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