From 375065ba9441dd5f2c229d6893c8ff362acceeb2 Mon Sep 17 00:00:00 2001 From: Erik Date: Mon, 27 Apr 2026 23:23:48 +0200 Subject: [PATCH] fix(meshing): Translucent flag overrides Additive blend per retail SetSurface MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- src/AcDream.Core/Meshing/TranslucencyKind.cs | 54 ++++++++++++++++---- 1 file changed, 44 insertions(+), 10 deletions(-) diff --git a/src/AcDream.Core/Meshing/TranslucencyKind.cs b/src/AcDream.Core/Meshing/TranslucencyKind.cs index 9d0ab7b..07aaa29 100644 --- a/src/AcDream.Core/Meshing/TranslucencyKind.cs +++ b/src/AcDream.Core/Meshing/TranslucencyKind.cs @@ -40,17 +40,38 @@ public enum TranslucencyKind public static class TranslucencyKindExtensions { - // Priority order (highest wins): - // 1. Additive — SurfaceType.Additive (0x10000) - // 2. InvAlpha — SurfaceType.InvAlpha (0x200) - // 3. AlphaBlend — SurfaceType.Alpha (0x100) OR SurfaceType.Translucent (0x10) - // 4. ClipMap — SurfaceType.Base1ClipMap (0x04) - // 5. Opaque — everything else + // Translucent override comes FIRST, then the existing priority chain: + // 1. Translucent override — Translucent (0x10) AND (ClipMap OR opaque-base) + // → AlphaBlend (matches retail's blend forcing). + // 2. Additive — SurfaceType.Additive (0x10000) + // 3. InvAlpha — SurfaceType.InvAlpha (0x200) + // 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), - // but acdream keeps its existing alpha-discard approach for clip-map surfaces - // (they render opaque with per-fragment discard) and introduces a separate - // translucent pass only for the genuinely blended surface types. + // The Translucent override matches retail's D3DPolyRender::SetSurface + // at 0x0059c4d0 (decomp lines 425083-425260). Verbatim from the + // Translucent branch at 425246: + // + // 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. /// /// Maps a flags value to the correct @@ -58,6 +79,19 @@ public static class TranslucencyKindExtensions /// 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) return TranslucencyKind.Additive;