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:
Erik 2026-08-23 13:42:42 +02:00
parent 517d17b4b3
commit cc42edc8e3
11 changed files with 180 additions and 62 deletions

View file

@ -57,19 +57,24 @@ void main() {
vec3 rgb = sampled.rgb * vTint;
// SHOULD-FIX S2/mech NOTE N-3 (OP4 review-fix round, 2026-08-11):
// PlayerOption DisableDistanceFog forces FogMode.Off (uFogParams.w
// == 0) — mesh_modern.frag/terrain_modern.frag both gate their own
// fog blend on this same word (`if (mode == 0) return lit;`), but the
// sky dome's blend here read only uApplyFog (the CPU per-submesh
// "is this layer foggable at all" flag) and never uFogParams.w, so
// toggling the option stopped terrain/objects fading into fog while
// the dome's horizon band kept blending toward fog color.
// Retail draws the sky with fixed-function fog DISABLED:
// GameSky::Draw @0x00506FF0 saves GetFFFogEnable, calls
// SetFFFogEnable(LScape::m_override_enabled ? 1 : 0), draws every sky
// object, and restores it. So the dome is fogged ONLY while an
// AdminEnvirons fog override is active; the horizon tint otherwise
// comes from the authored dome textures and the keyframe tint, and the
// frame's fog-coloured clear meets the dome's own colour at the rim.
// The CPU sets uApplyFog = 1 only for (override active && surface not
// Additive — SetFFFogAlphaDisabled(1) at D3DPolyRender::SetSurface
// 0x59c882). uFogParams.w == 0 is PlayerOption DisableDistanceFog
// (LScape::m_fFogEnabled -> SetFFFogUserDisabled), honoured here like
// every other fogged pass. The 2026-04-27 "SKY_FOG_FLOOR = 0.2" clamp
// was a stand-in for this rule before it was read from retail; it
// left the dome's rim at 80 % fog against a 100 % fog clear, which
// showed as a hard line below the horizon from altitude (2026-08-23).
int fogMode = int(uFogParams.w);
if (uApplyFog > 0.5 && fogMode != 0) {
const float SKY_FOG_FLOOR = 0.2;
float skyFogFactor = max(vFogFactor, SKY_FOG_FLOOR);
rgb = mix(uFogColor.rgb, rgb, skyFogFactor);
rgb = mix(uFogColor.rgb, rgb, vFogFactor);
}
float flash = uFogParams.z;

View file

@ -316,7 +316,7 @@
},
{
"stage": "frag",
"sourceSha256": "2ddf210d69b0c4a3c0870eecfb0ccba2097d93b365729739402bfe91e3b120d0",
"sourceSha256": "b1f3b924af2040c909337c02461ed6795b5320fb04b5bb371ce1546feead567c",
"compiled": true
}
]

View file

@ -372,18 +372,19 @@ public sealed partial class SkyRenderer : IDisposable
// with texture alpha and keyframe transparency.
_params.SurfOpacity = sub.SurfOpacity;
// Retail D3DPolyRender::SetSurface at 0x59c882 calls
// SetFFFogAlphaDisabled(1) when the Additive flag (0x10000)
// is set on the Surface — so the sun, moon, stars, and any
// additive cloud sheet are drawn WITHOUT fog. Skipping fog
// on additive surfaces keeps the sun bright at horizon
// dusk/dawn (where fog would otherwise dim it to fog color).
// Non-additive sky meshes (the dome/background layers)
// still mix toward keyframe fog with the floor mitigation
// in sky.frag. That restores the broad green/purple Rainy
// DayGroup tint behind the cloud sheet while raw-additive
// 0x08000023 remains unfogged and keeps the pink detail.
_params.ApplyFog = sub.DisableFog ? 0f : 1f;
// Retail GameSky::Draw @0x00506FF0 draws the whole sky with
// fixed-function fog DISABLED unless an AdminEnvirons fog
// override is active (SetFFFogEnable(LScape::
// m_override_enabled ? 1 : 0) around the sky draw). Within
// that, D3DPolyRender::SetSurface at 0x59c882 calls
// SetFFFogAlphaDisabled(1) for Additive surfaces (sun,
// moon, stars, additive cloud sheets), so those are never
// fogged. The dome's horizon tint is the authored texture
// plus keyframe tint — not fog — which is why the frame's
// fog-coloured clear meets the dome cleanly at its rim.
// (Until 2026-08-23 every non-additive layer was fogged
// with a 0.2 floor; see sky.frag for the symptom.)
_params.ApplyFog = environOverrideActive && !sub.DisableFog ? 1f : 0f;
// Sky meshes need per-object wrap mode driven by the
// mesh's authored UV range, not by TexVelocity:

View file

@ -500,17 +500,18 @@ internal sealed class RuntimeWorldFrameEnvironmentPreparation
in atmosphere,
camera.Position,
(float)_worldTime.DayFraction);
float fogStart = _ranges.NearRadius
* LandblockSize
* _options.FogStartMultiplier;
float fogEnd = _ranges.FarRadius
* LandblockSize
* _options.FogEndMultiplier;
ubo.FogParams = new Vector4(
fogStart,
fogEnd,
ubo.FogParams.Z,
ubo.FogParams.W);
// FogParams.xy stay the keyframe's authored MinWorldFog/MaxWorldFog
// that SceneLightingUbo.Build wrote (AtmosphereSnapshot.FogStart/
// FogEnd). Retail sets D3DRS_FOGSTART/FOGEND straight from those
// (SkyDesc::GetWorldFog @0x00500CE0 -> LScape::UseTime ->
// RenderDeviceD3D::SetFFFogProperties @0x005A2F70) with no draw-
// distance scaling; its far plane is a constant 4000 m
// (Render::zfar @0x0081EC88). Until 2026-08-23 this site overwrote
// them with a range derived from the streaming window (0.7 x near
// radius .. 0.95 x far radius = 538..2189 m at every hour and
// weather), which thinned retail's night (0..400 m) and rain
// (150..1500 m) fog and hid the fact that the sky pass was being
// fogged at all (see SkyRenderer / sky.frag).
_lightingUbo?.Upload(ubo);
RenderingDiagnostics.EmitLight(