acdream/docs/research/2026-08-22-vm2-retail-detail-path-cdb.md
Erik fe085cadaa research(vm2): retail runs SINGLE-PASS detail texturing - the #226 port targets the fallback
Live cdb read on the PDB-paired retail client (owner's AMD GPU):
  m_caps.bCanDoSinglePassDetailing = 1, trysinglepass = 1,
  MaxSimultaneousTextures = 8, bTexOpDotProduct3 = 1,
  LandscapeDetailTextures = 0, EnvironmentDetailTextures = 1,
  landscape/object detail surfaces null, building/environment non-null,
  tiling 4 everywhere.

So retail's detail combine on modern hardware is the texture-stage path in
D3DPolyRender::SetSurface (stage0 alpha PREMODULATE, stage1 colour
BLENDCURRENTALPHA): lerp(base*diffuse, detail.rgb, detail.a*diffuse.a) -
a mild DARKENING (~-10% mid-tones with 0x06006D58), not the
DSTCOLOR+INVSRCALPHA brightening that Campaign AR ported and that the
2026-08-21 findings doc + AR review both analysed. Those described the
fallback for adapters without D3DTEXOPCAPS_PREMODULATE; the reviewer's
guess that consumer drivers rarely advertised it was wrong.

VM1 now carries the re-port (SRCALPHA+INVSRCALPHA, detail.rgb /
detail.a*diffuseAlpha, neutral at a==0) together with the fade removal.
No distance fade exists on either path. Scripts are read-only attaches
with no breakpoints.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-22 18:23:27 +02:00

136 lines
6.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# VM2 — which detail-texturing path does retail actually run? (cdb, live)
**Date:** 2026-08-22 · **Campaign:** VM slice VM2 · **Status:** CLOSED — answered
**Method:** read-only cdb attach to the live PDB-paired retail client
(`C:\Turbine\Asheron's Call\acclient.exe`, v11.4186, PDB GUID
`9e847e2f-777c-4bd9-886c-22256bb87f32`, verified `MATCH` by
`tools/pdb-extract/check_exe_pdb.py`), in-world on the owner's AMD GPU.
Scripts: `tools/cdb/vm2-detail-caps.cdb` (+ runner) and
`tools/cdb/vm2-trysinglepass.cdb`. No breakpoints; `qd` at top level.
## The question
Campaign AR's #226 port (`mesh_detail.frag`, pipeline blend
`DSTCOLOR + INVSRCALPHA`) reproduces the path retail takes when
`ACRender::SetDetailSurfaceInternal(0)` is called — the **two-pass
framebuffer fallback**. `RenderMeshSubset` (0x0059ca10) and `landPolyDraw`
(0x006b6320) only take that path when
`trysinglepass == 0 || !m_caps.bCanDoSinglePassDetailing`. Otherwise they
call `SetDetailSurfaceInternal(1)` and the combine is the texture-stage
setup in `D3DPolyRender::SetSurface` (0x0059c4d0). The review (F3) said the
fallback was "probably" what players saw. That was a guess. This settles it.
## The readings (verbatim from the logs)
```
acclient!RenderDevice::render_device->m_caps
+0x008 MaxSimultaneousTextures : 8
+0x00c MaxTextureBlendStages : 8
+0x01c bCanDoSinglePassDetailing : 1 <-- THE ANSWER
+0x01d bTexOpDotProduct3 : 1
+0x01e bTexOpBumpEnvMap : 1
trysinglepass (file-static, decomp 0x00820e40 / 0x00835c04 / 0x00835c10)
00820e40 00000001
00835c04 00000001
00835c10 00000001
acclient!Render::m_RenderPrefs
+0x004 LandscapeDetailTextures : 0
+0x005 EnvironmentDetailTextures : 1
+0x006 MultiPassAlpha : 1
acclient!Render::landscape_detail_surface = 0x00000000 (OFF)
acclient!Render::building_detail_surface = 0x0332e6a8 (ON)
acclient!Render::environment_detail_surface = 0x03416de0 (ON)
acclient!Render::object_detail_surface = 0x00000000 (OFF)
acclient!Render::landscape_detail_tiling = 4
acclient!Render::building_detail_tiling = 4
acclient!Render::environment_detail_tiling = 4
acclient!Render::object_detail_tiling = 4
acclient!Render::curr_detail_src_blend = 5 (static default; unused on this path)
acclient!Render::curr_detail_dst_blend = 6
```
## What this means
**Retail on this hardware uses the single-pass texture-stage path.** The
framebuffer blend that Campaign AR ported — and that the 2026-08-21 findings
doc and the AR review both analysed as "retail's detail pass brightens" —
is the fallback for adapters that cannot advertise
`D3DTEXOPCAPS_PREMODULATE`. A modern AMD driver advertises it. The reviewer's
recollection that consumer drivers rarely did was **wrong**; recorded here so
nobody repeats it.
The single-pass combine, from `D3DPolyRender::SetSurface` with the detail
flag set (args are `(stage, op, arg1, arg2)`; `0 = DIFFUSE`, `1 = CURRENT`,
`2 = TEXTURE`):
```
stage 0 colour = MODULATE(TEXTURE, DIFFUSE) = base.rgb * diffuse.rgb
stage 0 alpha = PREMODULATE(DIFFUSE, DIFFUSE) = diffuse.a * detail.a (premodulate = multiply by the NEXT stage's texture)
stage 1 colour = BLENDCURRENTALPHA(TEXTURE, CURRENT) = detail.rgb * f + current.rgb * (1 - f), f = stage-0 alpha
stage 1 alpha = MODULATE(TEXTURE, CURRENT) = detail.a * current.a
stage 2 DISABLE
```
So the pixel retail actually draws is
```
lerp(base * diffuse, detail.rgb, detail.a * diffuse.a)
```
a **blend toward the detail colour by the detail alpha** — not
`dest × (detail + 1 α)`. With the live category texture `0x06006D58`
(mean rgb 0.165, mean α 0.132) and opaque diffuse (α = 1), the average
effect is `≈ 0.868 × base + 0.022`: a mild **darkening** noise of roughly
10 % on mid-tones. Rougher and darker — the intended look — not brighter.
Consequences:
1. The community remark "they did it backwards, looks like it reflects
more" describes the **fallback** path. On hardware like the owner's it
does not apply.
2. Campaign AR's #226 port reproduces the wrong path for every modern GPU.
It must be re-ported (below). The existing `RetailDetailTextureContract`
"brightening is expected" statement and the findings doc §2 table are
true only of the fallback and must say so (VM4).
3. There is **no distance fade on either path** (VM1 stands): the stage-1
sampler is LINEAR/LINEAR/LINEAR with WRAP; attenuation is the mip chain
converging to the texture mean.
4. `LandscapeDetailTextures` is a real, separate preference, **0** here.
Landscape detail is off by preference, not only by the `ChangeRegion`
literal; there is no Options row for it in the 2013 client. Terrain
detail remains out of #226's scope.
5. `bTexOpDotProduct3 = 1`, `bTexOpBumpEnvMap = 1`: the DOT3 machinery is
available to retail on this hardware, which makes "what uses the BumpMap
path" (findings §7 q4) a live question, still untraced.
## The re-port (folds into VM1; it is small)
Because the lerp is expressible as a framebuffer blend, the existing
separate detail replay pass stays; only its pixel contract changes:
- pipeline blend: `SRCALPHA + INVSRCALPHA`, `ADD` (the pair retail stores
as 5/6) instead of `DSTCOLOR + INVSRCALPHA`;
- `mesh_detail.frag` outputs `vec4(detail.rgb, detail.a * diffuseAlpha)`,
where `diffuseAlpha` is the base subset's vertex/material alpha (1 for
opaque subsets; the translucent subsets already carry it);
- neutral point: `detail.a == 0` (no blend), not `detail.rgb == detail.a`;
- `RetailDetailTextureContract.FramebufferFactor` becomes
`lerp(1, detail.rgb / base, detail.a)` semantics — rewrite the helper and
its tests around `Expected(base, detail, diffuseAlpha)`;
- the 10 m / 50 m fade is deleted as planned (VM1).
A register row is **not** needed for the single-pass port — it is the
retail path. A row IS needed if the fallback is ever exposed (it should
not be).
## Closed / corrected by this note
- Review F3: answered — single-pass. The review's "probably fallback" was
wrong.
- Findings doc `2026-08-21-terrain-and-atmospheric-rendering-findings.md` §2
"retail's own detail pass BRIGHTENS": true for the fallback only; the
hardware path darkens. VM4 amends the doc.
- AR plan + #226 pseudocode note: same amendment.