acdream/docs/research/2026-09-01-overhaul/oh1-alpha-list-contract.md

10 KiB
Raw Blame History

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:

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 13 and true for levels 45. 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.