fix(sky): retail-faithful keyframe lerp — separate-channel color/bright

Retail's SkyDesc::GetLighting at 0x00500ac9 (decomp lines 261317-261331)
lerps each color channel and the brightness scalar SEPARATELY, then
multiplies post-lerp:

  arg4.r = lerp(k1.amb_color.r, k2.amb_color.r, u)
  arg4.g = lerp(k1.amb_color.g, k2.amb_color.g, u)
  arg4.b = lerp(k1.amb_color.b, k2.amb_color.b, u)
  arg3   = lerp(k1.amb_bright, k2.amb_bright, u)
  final  = (arg4.rgb * arg3, ...)

acdream pre-multiplied (color × bright) at LOAD time
(`SkyDescLoader.cs:558-559`) and then lerped the product. For any
keyframe pair where both color and brightness change, the two are
mathematically distinct. Example, k1=(white, b=0.5) k2=(black, b=1.0)
at u=0.5:
  - retail: color=gray(0.5), bright=0.75 → final = (0.375, 0.375, 0.375)
  - acdream: lerp((0.5,0.5,0.5), (0,0,0), 0.5) = (0.25, 0.25, 0.25)

For Rainy/Cloudy DayGroups transitioning between dim and bright
keyframes, this contributes to subtle brightness divergence vs retail.

Refactor:
  SkyKeyframe stores DirColor / DirBright / AmbColor / AmbBright
    SEPARATELY (raw, not pre-multiplied).
  Computed properties SunColor and AmbientColor return the
    post-multiplied product, keeping the shader uniform interface
    (uSunColor / uAmbientColor) unchanged.
  SkyStateProvider.Interpolate lerps each raw channel, then constructs
    a new SkyKeyframe whose computed properties yield the correct
    post-lerp multiply.
  SkyDescLoader now stores raw values without pre-multiplying.
  GameWindow comment updated; no functional change there.
  Default factory + tests updated to use the new constructor parameters
    with DirBright=AmbBright=1.0 (preserving exact existing behavior).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-04-27 15:02:35 +02:00
parent dbe6690a4e
commit 63b50c5291
5 changed files with 97 additions and 31 deletions

View file

@ -551,12 +551,23 @@ public static class SkyDescLoader
_ => FogMode.Off,
};
// Store DirColor / AmbColor RAW and DirBright / AmbBright SEPARATE
// (NOT pre-multiplied) so the keyframe interpolator can lerp each
// channel independently — matches retail SkyDesc::GetLighting at
// 0x00500ac9 (decomp lines 261317-261331). Multiplying at load
// time and lerping the product produces mathematically different
// results than retail when both color and brightness change
// between adjacent keyframes. The post-multiplied values are
// available via `kf.SunColor` / `kf.AmbientColor` computed
// properties for shader-uniform plumbing.
var kf = new SkyKeyframe(
Begin: s.Begin,
SunHeadingDeg: s.DirHeading,
SunPitchDeg: s.DirPitch,
SunColor: ColorToVec3(s.DirColor) * s.DirBright,
AmbientColor: ColorToVec3(s.AmbColor) * s.AmbBright,
DirColor: ColorToVec3(s.DirColor),
DirBright: s.DirBright,
AmbColor: ColorToVec3(s.AmbColor),
AmbBright: s.AmbBright,
FogColor: ColorToVec3(s.WorldFogColor),
FogDensity: 0f,
FogStart: s.MinWorldFog,