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>
This commit is contained in:
Erik 2026-08-22 18:23:27 +02:00
parent 4dbbe8fead
commit fe085cadaa
6 changed files with 228 additions and 4 deletions

View file

@ -52,15 +52,14 @@ moves a pack-off pixel outside its declared mask is a bug, not a tuning.
|---|---|---|
| VM0 | F1 — default-path invariance | automated + Fable report |
| VM1 | F2 — invented detail fade | automated |
| VM2 | F3 — which retail detail path ran | **owner action** (launch retail) |
| VM2 | F3 — which retail detail path ran | **CLOSED 2026-08-22** — single-pass; see [cdb note](../research/2026-08-22-vm2-retail-detail-path-cdb.md) |
| VM3 | F4 — linear-light post stack | automated + **owner visual** |
| VM4 | F5 — overclaiming docs, incl. the reviewer's own | docs |
| VM5 | F8 — volumetric banding; F7 filed | automated |
| VM6 | Foliage wind | automated + **owner visual** |
| VM7 | Closeout: full gates, register, roadmap, merge | **owner** |
Order is VM0 → VM1 → VM4 → VM5 → VM3 → VM6 → VM2 (whenever the owner has
retail up) → VM7. VM0 goes first because everything after it is measured
Order is VM2 (done) → VM0 → VM1 → VM4 → VM5 → VM3 → VM6 → VM7. VM0 goes first because everything after it is measured
against the baseline it establishes.
---
@ -114,7 +113,7 @@ explains it.
`DrawBuilding`/`DrawEnvCell`/`RenderMeshSubset`/`SetDetailSurfaceInternal`
carry no distance term; attenuation is the LINEAR mip chain.
**Decision (Fable, retail-faithful rule):** remove the ramp. Mip averaging
**Decision (Fable, retail-faithful rule):** remove the ramp. **Amended by VM2:** VM1 also re-ports the blend to the single-pass math (see the VM2 outcome) — the fade removal and the blend correction land together, with the `RetailDetailTextureContract` helper and tests rewritten around `Expected(base, detail, diffuseAlpha)`. Mip averaging
already converges the live category texture to its 1.033 mean factor.
**Implementation**
@ -155,6 +154,8 @@ review doc.
**Acceptance:** the value is recorded with the binary GUID and the date.
**OUTCOME (2026-08-22): `bCanDoSinglePassDetailing = 1`, `trysinglepass = 1`.** Retail on the owner's GPU runs the single-pass stage path: `lerp(base·diffuse, detail.rgb, detail.a·diffuse.a)` — a mild darkening, not the fallback's brightening. The #226 port targets the wrong path and is re-ported inside VM1 (blend `SRCALPHA+INVSRCALPHA`, output `detail.rgb, detail.a·diffuseAlpha`, neutral at `detail.a == 0`). Also read: `LandscapeDetailTextures = 0` is a real separate preference. Evidence and the exact stage math: [2026-08-22-vm2-retail-detail-path-cdb.md](../research/2026-08-22-vm2-retail-detail-path-cdb.md).
## VM3 — Linear-light post stack (F4)
**Why:** bloom threshold, ACES, Rec.709 luma, saturation and the 0.5