fix(meshing): Translucent flag overrides Additive blend per retail SetSurface

acdream's TranslucencyKindExtensions.FromSurfaceType picked Additive
first (priority order). Retail's D3DPolyRender::SetSurface at
0x0059c4d0 (decomp 425083+) has a different resolution: when the
Translucent flag (0x10) is set AND either Base1ClipMap (0x04) is set
OR the surface would otherwise be opaque (no Additive/Alpha/InvAlpha),
the blend is *forced* to (SrcAlpha, InvSrcAlpha) — i.e. standard
alpha-blend, not additive. Verbatim from decomp lines 425246-425260:

    if ((curr_surface_type & 0x10) != 0) {
        if (skipChk != 0 || ebx == 0 || arg3 == 1) {
            edi_2 = BLEND_SRCALPHA;       // src
            ebp   = BLEND_INVSRCALPHA;    // dst   ← alpha-blend
        }
        curr_alpha = _ftol2(translucency * 255);
    }

Where `arg3 == 1` is set after the Base1ClipMap branch and `ebx == 0`
is the opaque-base case in Branch 2.

Concrete impact: Dereth's inner cloud sheet GfxObj 0x01004C35 uses
surface 0x08000023 with Type=0x10114 (B1ClipMap|Translucent|Alpha|
Additive). Retail renders it alpha-blend; acdream was rendering it
additive. Additive on a dark cloud texture only brightens the
background — sun shines through unchanged — which doesn't match
retail's denser cloud appearance.

Rain surface 0x080000C5 (Type=0x10112 = B1Image|Translucent|Alpha|
Additive, NO ClipMap) hits Branch 1 → Additive, ClipMap branch is
skipped, the Translucent override doesn't fire (arg3 stays 0) → stays
Additive. Visual rain rendering is unchanged.

User reported no visible difference at the verification launch; the
remaining cloud-density gap likely lives in the PES particle layer
(issue #28). Keeping this fix because the classification is now
decomp-correct regardless of immediate visual impact — issue #29
documents the residual gap.

1227 tests pass.
This commit is contained in:
Erik 2026-04-27 23:23:48 +02:00
parent 034a684f02
commit 375065ba94

View file

@ -40,17 +40,38 @@ public enum TranslucencyKind
public static class TranslucencyKindExtensions public static class TranslucencyKindExtensions
{ {
// Priority order (highest wins): // Translucent override comes FIRST, then the existing priority chain:
// 1. Additive — SurfaceType.Additive (0x10000) // 1. Translucent override — Translucent (0x10) AND (ClipMap OR opaque-base)
// 2. InvAlpha — SurfaceType.InvAlpha (0x200) // → AlphaBlend (matches retail's blend forcing).
// 3. AlphaBlend — SurfaceType.Alpha (0x100) OR SurfaceType.Translucent (0x10) // 2. Additive — SurfaceType.Additive (0x10000)
// 4. ClipMap — SurfaceType.Base1ClipMap (0x04) // 3. InvAlpha — SurfaceType.InvAlpha (0x200)
// 5. Opaque — everything else // 4. AlphaBlend — SurfaceType.Alpha (0x100) OR SurfaceType.Translucent (0x10)
// 5. ClipMap — SurfaceType.Base1ClipMap (0x04)
// 6. Opaque — everything else
// //
// Note: ACViewer groups Base1ClipMap with the alpha-draw bucket (AlphaSurfaceTypes), // The Translucent override matches retail's D3DPolyRender::SetSurface
// but acdream keeps its existing alpha-discard approach for clip-map surfaces // at 0x0059c4d0 (decomp lines 425083-425260). Verbatim from the
// (they render opaque with per-fragment discard) and introduces a separate // Translucent branch at 425246:
// translucent pass only for the genuinely blended surface types. //
// if ((curr_surface_type & 0x10) != 0) {
// if (skipChk != 0 || ebx == 0 || arg3 == 1) {
// edi_2 = BLEND_SRCALPHA; // src
// ebp = BLEND_INVSRCALPHA; // dst ← alpha-blend
// ebx = 1; arg1 = 1; arg3 = 0;
// }
// curr_alpha = _ftol2(translucency * 255);
// }
//
// Where `arg3 = 1` after the ClipMap branch and `ebx == 0` happens
// in Branch 2 when the surface would otherwise be opaque (no Additive,
// Alpha, or InvAlpha bits). So Translucent + ClipMap (e.g. cloud
// surface 0x08000023, Type=0x10114) renders ALPHA-BLEND in retail
// even though the Additive flag is also set; previously acdream's
// priority-Additive-first classification mis-routed it as additive.
// Empirically: this is the surface for cloud GfxObj 0x01004C35 in
// every Cloudy/Rainy DayGroup. Misclassifying it as additive made
// acdream's clouds barely-visible "brightness adders" rather than
// the dense alpha-blended sheets retail shows.
/// <summary> /// <summary>
/// Maps a <see cref="SurfaceType"/> flags value to the correct /// Maps a <see cref="SurfaceType"/> flags value to the correct
@ -58,6 +79,19 @@ public static class TranslucencyKindExtensions
/// </summary> /// </summary>
public static TranslucencyKind FromSurfaceType(SurfaceType type) public static TranslucencyKind FromSurfaceType(SurfaceType type)
{ {
// Step 1: Translucent override — matches retail's branch at
// decomp line 425250 where (skipChk || ebx == 0 || arg3 == 1)
// forces (SrcAlpha, InvSrcAlpha) regardless of Additive.
bool isTranslucent = (type & SurfaceType.Translucent) != 0;
bool isClipMap = (type & SurfaceType.Base1ClipMap) != 0;
bool wouldBeOpaque =
(type & (SurfaceType.Additive
| SurfaceType.Alpha
| SurfaceType.InvAlpha)) == 0;
if (isTranslucent && (isClipMap || wouldBeOpaque))
return TranslucencyKind.AlphaBlend;
// Step 2..6: existing priority order for non-overridden surfaces.
if ((type & SurfaceType.Additive) != 0) if ((type & SurfaceType.Additive) != 0)
return TranslucencyKind.Additive; return TranslucencyKind.Additive;