205 lines
10 KiB
Markdown
205 lines
10 KiB
Markdown
# OH1 evidence — retail alpha classification and list contract
|
||
|
||
**Campaign:** OVERHAUL / OH1
|
||
|
||
**Status:** evidence complete; behavior belongs to OH7
|
||
|
||
**Retail build:** September 2013 End of Retail
|
||
|
||
**Arbitration:** named-retail pseudo-C plus Ghidra/disassembly for field offsets
|
||
|
||
This note closes the bounded alpha ambiguity left by the first OH1 synthesis.
|
||
It is an executable contract for OH7, not permission to change alpha order
|
||
before OH5 restores the landscape/building/object walk that owns the barriers.
|
||
|
||
## 1. Retail anchors
|
||
|
||
| Retail symbol | Address | Contract established here |
|
||
|---|---:|---|
|
||
| `D3DPolyRender::ConstructMesh` | `0x0059DFA0` | initial/final subset mask |
|
||
| `D3DPolyRender::DrawMesh` | `0x0059D4A0` | immediate versus delayed branch table |
|
||
| `D3DPolyRender::AddMeshToAlphaList` | `0x0059C230` | list selection, capacity, entry state |
|
||
| `D3DPolyRender::FlushAlphaList` | `0x0059D2E0` | threshold and drain order |
|
||
| `D3DPolyRender::RenderMeshSubset` | `0x0059CA10` | delayed replay/detail behavior |
|
||
| `D3DPolyRender::SetSurface` | `0x0059C4D0` | clip-map override behavior |
|
||
| `D3DPolyRender::Begin` | `0x0059EAD0` | delay-mask registry binding |
|
||
| `RenderDeviceD3D::DrawEnvCell` | `0x0059F170` | environment-detail owner |
|
||
| `RenderDeviceD3D::DrawBuilding` | `0x0059F2A0` | building-detail owner and barrier |
|
||
| `RenderDeviceD3D::DrawPartCell` | `0x005A07A0` | ordinary-object detail reset |
|
||
| `RenderDeviceD3D::DrawBlock` | `0x005A18D0` | per-land-cell emergency barrier |
|
||
| `PView::DrawCells` | `0x005A4840` | post-landscape full barrier |
|
||
| `SmartBox::RenderNormalMode` | `0x00453AA0` | top-level full barrier |
|
||
| `LScape::CleanupDetailSurfaces` | `0x00504AE0` | detail-surface release/reset |
|
||
| `LScape::SetDetailTexturing` | `0x00506B40` | detail-surface regeneration |
|
||
| `LScape::ChangeRegion` | `0x00506CB0` | detail preference application |
|
||
| `GameSky::Draw` | `0x00506FF0` | `m_currentlyDrawingSky` producer |
|
||
| `Render::SetOverallGraphicsQuality` | `0x0054B020` | `MultiPassAlpha` quality defaults |
|
||
| `Render::Startup` | `0x0054E9B0` | preference registration/safe override |
|
||
|
||
The named-retail pseudo-C establishes the control flow. Ghidra and instruction
|
||
disassembly arbitrate the two facts for which a wrong field name changes the
|
||
branch meaning: the renderer byte at `+0x7E0` is
|
||
`m_currentlyDrawingSky`, while material-alpha mode is at `+0x794`; and polygon
|
||
stippling is compared as a **signed** byte.
|
||
|
||
## 2. Exact constructed subset mask
|
||
|
||
For each source surface, `ConstructMesh` chooses exactly one initial mask by
|
||
this priority:
|
||
|
||
```text
|
||
if (Surface.Type & 0x00010300) != 0: mask = 0x02
|
||
else if (Surface.Type & 0x00000004) != 0: mask = 0x08
|
||
else if (Surface.Type & 0x00000010) != 0: mask = 0x04
|
||
else: mask = 0x00
|
||
```
|
||
|
||
`0x00010300` is the union of `ALPHA (0x100)`, `INVALPHA (0x200)`, and
|
||
`ADDITIVE (0x10000)`. The order is material: a surface carrying both an
|
||
alpha-family bit and `BASE1_CLIPMAP` receives mask `0x02`, not `0x08`.
|
||
|
||
For each polygon, retail then ORs bit `0x01` into the **positive surface's**
|
||
mask only when `(signed char)polygon.stippling > 0`. Raw bytes `0x80..0xFF`
|
||
therefore do not set the bit. The complete constructed mask domain is:
|
||
|
||
| Surface class | Without positive stippling | With positive stippling |
|
||
|---|---:|---:|
|
||
| plain | `0x00` | `0x01` |
|
||
| alpha / inverse-alpha / additive | `0x02` | `0x03` |
|
||
| translucent | `0x04` | `0x05` |
|
||
| clip map | `0x08` | `0x09` |
|
||
|
||
Bit `0x01` is absent from the registry help text, but a configured delay mask
|
||
still intersects it normally. It is a retail quirk, not a flag to normalize.
|
||
|
||
## 3. Delay-mask and preference state
|
||
|
||
`s_AlphaDelayMask @ 0x00820D88` has static default `0x0E`. Its only behavior
|
||
read is in `DrawMesh`; `D3DPolyRender::Begin` exposes it as
|
||
`RenderD3D.AlphaDelayMask` with help for Alpha=`2`, Translucent=`4`, and
|
||
ClipMap=`8`. The registry may replace the value. Unsupported higher bits can
|
||
keep the outer delay gate nonzero but never intersect a constructed mask.
|
||
|
||
`m_RenderPrefs.MultiPassAlpha @ 0x0081EF96` has static default false.
|
||
`SetOverallGraphicsQuality` selects false for quality levels 1–3 and true for
|
||
levels 4–5. Startup registers the user preference, and the safe-render branch
|
||
forces it false. `DrawMesh` is its sole draw-path reader;
|
||
`DetermineOverallGraphicsQuality` also reads it while deriving the aggregate
|
||
quality setting.
|
||
|
||
The building and environment detail globals default null. Their only writers
|
||
are `SetBuildingDetailSurface` and `SetEnvironmentDetailSurface`; region
|
||
cleanup clears/releases them, and region detail setup regenerates both from
|
||
the `EnvironmentDetailTextures` preference. Generation may still yield null.
|
||
|
||
## 4. Exact `DrawMesh` branch table
|
||
|
||
After the subset passes its ordinary draw-eligibility gate, apply these rows
|
||
in order:
|
||
|
||
| Priority | Condition | Result |
|
||
|---:|---|---|
|
||
| 1 | `m_currentlyDrawingSky` **or** delay mask is zero **or** `curr_detail_surface != null` | render immediately; enqueue nothing |
|
||
| 2 | `MultiPassAlpha && (subsetMask & 0x08) != 0` | append to **CLIP** with `overrideClipmap=true`, then also render immediately with `overrideClipmap=false` |
|
||
| 3 | `(delayMask & subsetMask) != 0` | append to **CLIP** iff bit `0x08` is set, otherwise **ALPHA**; no immediate draw |
|
||
| 4 | `(delayMask & 0x04) != 0 && currentMaterial != null && currentMaterial.has_alpha != 0` | append to **ALPHA**; no immediate draw |
|
||
| 5 | otherwise | render immediately |
|
||
|
||
Row 2 depends on the global delay mask being nonzero because it is inside the
|
||
outer delay gate, but it does **not** require delay-mask bit `0x08`. It is the
|
||
clip-map base pass now plus an alpha-blended second pass later.
|
||
|
||
Under default delay mask `0x0E`:
|
||
|
||
- masks `0x02/0x03` and `0x04/0x05` enter ALPHA;
|
||
- masks `0x08/0x09` enter CLIP, with the additional immediate pass when
|
||
`MultiPassAlpha` is true;
|
||
- masks `0x00/0x01` are immediate unless the material-alpha fallback applies.
|
||
|
||
`overrideClipmap=false` lets `SetSurface` enable the clip-map alpha-test arm.
|
||
`overrideClipmap=true` selects the alpha-blend arm and suppresses that clip-map
|
||
alpha-test arm. Immediate rendering passes
|
||
`detailEnabled=(curr_detail_surface != null)`; delayed replay always passes
|
||
`detailEnabled=false`.
|
||
|
||
## 5. List entry, capacity, and replay
|
||
|
||
Retail owns two independent static arrays of 3000 `AlphaListEntry` records and
|
||
two `uint16` counts. `AddMeshToAlphaList` appends strictly FIFO; it performs no
|
||
distance or material sort.
|
||
|
||
Each `DrawMesh` invocation owns two independent `first-for-this-list` flags.
|
||
The first subset appended to each list captures the current `CMaterial` and
|
||
object matrix and sets `bNewObject=true`. Later entries for that list inherit
|
||
that state during replay. The entry also stores `overrideClipmap`; it stores no
|
||
portal view, clip planes, cell, or part identity.
|
||
|
||
When a list is already at capacity, append returns false. A non-multipass
|
||
delayed subset has no immediate fallback draw. Exact capacity behavior must be
|
||
preserved and tested rather than silently expanded or recovered differently.
|
||
|
||
`FlushAlphaList(threshold)` returns false without changing either list only
|
||
when **both** counts are below `threshold * 3000`. Otherwise it drains the
|
||
entire CLIP list in append order, resets it, then drains the entire ALPHA list
|
||
in append order and resets it. It restores the object matrix after replay; it
|
||
does not restore the material. The DrawBlock threshold is the immutable global
|
||
`0.75`, so equality at 2250 in either list triggers a full two-list drain.
|
||
|
||
## 6. Detail owner consequences
|
||
|
||
- `DrawEnvCell` installs the environment detail surface, calls the built
|
||
`DrawMesh(skipNoTexture=true)` path, then clears the current detail. A
|
||
non-null environment detail makes every eligible subset immediate.
|
||
- `DrawBuilding` installs building detail, performs `FlushAlphaList(0)` before
|
||
portal/part/shell work, then draws its forced portal-BSP pass and normal
|
||
built shell. A non-null building detail makes the shell immediate.
|
||
- `DrawBuildingLeaf` preserves outer state, but `DrawPartCell` explicitly
|
||
forces current detail null. Ordinary leaf parts can therefore enqueue even
|
||
when their containing building shell uses detail.
|
||
- Ordinary object-cell GfxObj paths do not install the separate object detail
|
||
surface; their current detail is normally null.
|
||
|
||
## 7. Exact normal-world flush barriers
|
||
|
||
1. `DrawBuilding` calls `FlushAlphaList(0)` after installing building detail
|
||
and before any building portal/leaf/shell draw. Delayed replay itself has
|
||
detail disabled.
|
||
2. `DrawBlock` visits each admitted land cell as `DrawLandCell` followed by
|
||
`DrawSortCell` (building, then objects), then calls
|
||
`FlushAlphaList(0.75)`. This is normally an emergency capacity drain.
|
||
3. `PView::DrawCells` calls `FlushAlphaList(0)` after outside landscape and
|
||
before the mid-frame stamp/depth-clear/interior repaint block.
|
||
4. `SmartBox::RenderNormalMode` calls `FlushAlphaList(0)` after the complete
|
||
world path.
|
||
|
||
The fifth retail caller is the private `CreatureMode::Render @ 0x004529D0`
|
||
(call site `0x00452BF0`). After `DrawObjCellForDummies(creature_cell)` and its
|
||
frame pop, that private presentation mode calls `FlushAlphaList(0)`. It is not
|
||
part of the normal-world OH5/OH7 ordering cutover, but it must remain an exact
|
||
consumer of the shared two-list implementation.
|
||
|
||
Consequently, OH7 cannot be implemented faithfully while current acdream
|
||
still stages all terrain before all building/object turns. OH5 must first
|
||
restore the per-cell landscape interleave on which these barriers operate.
|
||
|
||
## 8. Required OH7 cutover
|
||
|
||
Current `RetailAlphaQueue` is a single scope-global, viewer-distance-sorted
|
||
stream with an approximate `FlushFartherThan`. Retail instead requires:
|
||
|
||
- separate CLIP and ALPHA FIFO lists;
|
||
- no queue-level sort;
|
||
- preservation of upstream cell order, `CShadowPart` order, and authored
|
||
subset order;
|
||
- raw surface type plus the exact constructed mask and positive-surface
|
||
stippling ownership;
|
||
- `overrideClipmap` and independent per-list object-boundary state;
|
||
- the multipass clip-map immediate-plus-delayed duplicate;
|
||
- material `has_alpha` fallback;
|
||
- sky/detail immediate gates and delayed `detailEnabled=false`;
|
||
- exact capacity and threshold behavior;
|
||
- full drains at the exact building, landscape, and top-level barriers.
|
||
|
||
Runtime state remains configurable: registry may override delay mask, the user
|
||
preference may override MultiPassAlpha, and detail generation may return null.
|
||
Those are input states to the exact branch table, not unresolved branch sense.
|