acdream/docs/research/2026-09-01-overhaul/oh1-depth-lifecycle.md

36 KiB
Raw Blame History

OH1 — retail outside-view and depth lifecycle

Date: 2026-09-01

Campaign: OVERHAUL, OH1 evidence slice

Scope: the complete retail PView::DrawCells outside-view/depth lifecycle, from the roots that can reach it through landscape/building/cell order, including the persistent portal-draw counter, render frame stamp, far-Z building punches, true-depth exit seals, and the corresponding acdream implementation. Production changes: none. This file is the only artifact produced by this audit.

Verdict

The current acdream walk is not executing retail's depth lifecycle. The largest semantic differences are deterministic, not visual guesses:

  1. Retail wraps landscape draw, alpha flush, an additional mid-frame render-stamp increment, previous-call counter consumption/reset, optional full depth clear, and current exit seals in one outside_view.view_count != 0 block. acdream emits ClearInteriorDepth and ExitSeals for every interior root, including outside_view == 0.
  2. Retail's portalsDrawnCount is a persistent uint16 populated only by non-far portal-mask attempts. An eligible DrawCells consumes and clears the value produced by the previous eligible outside-view call, then the current true-depth exit-seal attempts repopulate it. acdream has no equivalent state and clears on the first eligible frame even when retail would not.
  3. Retail normally advances RenderDevice::m_nFrameStamp once per present in RenderDeviceD3D::Flip @0x0059FE50/0x0059FEC8. DrawCells @0x005A4885 performs an additional conditional mid-frame increment only after an outside landscape and alpha flush, immediately before the counter/clear. That second barrier re-arms landscape-admitted content for the root repaint; it is not the ordinary frame-start/present increment. acdream's CPU stamp mirror is near the second turn, but is not tied to the missing counter state and operates at collection time rather than at the GPU call boundary.
  4. Retail's far punch uses exact depth bits 0x3F7FFFEF (0.999998987f) and rejects a whole polygon if all local input vertices, before xformStart, lie on any x/y == +/-12 boundary plane. acdream uses 0.99999988, lacks that whole-polygon reject, and relies on a capped GPU clip-distance approximation.
  5. Retail interleaves DrawLandCell then DrawSortCell per landscape cell. acdream knowingly emits all terrain before all building/static turns. This changes the position of building far-Z punches relative to terrain depth.

These differences are sufficient to produce both opposite failure modes seen during the campaign: clearing/sealing where retail does neither can remove valid walls; failing to reproduce the previous-call gate and exact punch/seal production can leave stale or misplaced depth that exposes geometry behind walls.

Evidence method and arbitration rule

The primary source was the named retail corpus, searched before any fresh disassembly:

  • docs/research/named-retail/acclient_2013_pseudo_c.txt
    • PView::DrawCells around lines 432709432889
    • DrawPortalPolyInternal around lines 424490424698
    • PView::ClipPortals, both ConstructView overloads, DrawInside, and DrawPortal
  • docs/research/named-retail/acclient.h
    • portal_view_type around lines 3234632355
    • PView around lines 4593445944
    • RenderDevice around lines 3898839016

Every behavior-changing branch sense reported below was then checked against the local Ghidra service on port 8081 with decompile_function and disassemble_function. Global producer/consumer claims were checked with xrefs_to; callers were checked with function_xrefs/xrefs_to. The decisive Ghidra functions were:

Function Address
SmartBox::RenderNormalMode 0x00453AA0
LScape::draw 0x00506330
PView::ClipPortals 0x005A5520
PView::ConstructView(CEnvCell) 0x005A57B0
PView::DrawInside 0x005A5860
PView::ConstructView(CBldPortal) 0x005A59A0
PView::DrawPortal 0x005A5AB0
PView::DrawCells 0x005A4840
DrawPortalPolyInternal 0x0059BC90
RenderDeviceD3D::Clear 0x0059FD30
RenderDeviceD3D::Flip 0x0059FE50 (m_nFrameStamp store at 0x0059FEC8)
RenderDeviceD3D::DrawBlock 0x005A17C0
RenderDeviceD3D::DrawSortCell 0x0059F140
RenderDeviceD3D::DrawBuilding 0x0059F2A0
RenderDeviceD3D::DrawMeshInternal 0x0059F360
RenderDeviceD3D::DrawEnvCell 0x0059F170

Retail state layout

The offsets below come from the named header where available and were cross-checked against the access widths/strides in Ghidra.

Owner Offset/address Type Meaning
PView +0x00 portal_view_type outside_view
PView +0x38 uint32 outside_view.view_count
PView +0x48 int32 draw_landscape
PView +0x4C pointer outdoor_portal_list
PView +0x50 DArray cell_draw_list
PView +0x60 uint32 cell_draw_num
PView +0x64 DArray cell_todo_list
PView +0x74 uint32 cell_todo_num
PView +0x78 pointer lscape
portal_view_type +0x00 DArray portal refs
portal_view_type +0x10 uint32 total view-poly vertex count
portal_view_type +0x14 DArray view polygons
portal_view_type +0x24 DArray view vertices
portal_view_type +0x34 float max_indist
portal_view_type +0x38 uint32 view_count
portal_view_type +0x3C uint32 cell_view_done
portal_view_type +0x40 uint32 view_timestamp
portal_view_type +0x44 uint32 update_count
CEnvCell +0x100 pointer structure
CEnvCell +0x108 uint32 num_portals
CEnvCell +0x10C pointer CCellPortal[]
CEnvCell +0x130 uint32 current render-frame stamp
CEnvCell +0x134 uint16 num_view
CEnvCell +0x138 DArray portal views
CCellPortal +0x00 uint32 other_cell_id; 0xFFFFFFFF means outside
CCellPortal +0x04 pointer other_cell_ptr
CCellPortal +0x08 pointer portal polygon
CCellPortal +0x0C scalar portal side
CCellPortal +0x10 scalar other portal id
CCellPortal +0x14 scalar exact-match flag
RenderDevice +0xB0 uint32 m_nFrameStamp
global 0x008719B4 uint16 portalsDrawnCount, initialized zero
global 0x008ED824 int32 forceClear, initialized zero
global 0x00820E14 mode maxZ2 == 6, true-depth mode
global 0x00820E18 mode maxZ1 == 7, far-Z mode
data 0x007E5450 float bits far-Z 0x3F7FFFEF = 0.999998987f

portal_view_type is 0x48 bytes and CCellPortal is 0x18 bytes. Those sizes matter because DrawCells indexes the top portal view and walks portal arrays by those exact strides.

Caller/callee order

Root selection

SmartBox::RenderNormalMode @0x00453AA0 decides from the camera cell id:

  • low 16 bits < 0x100: outdoor root. It updates landscape/viewpoint state and calls LScape::draw. It does not call top-level PView::DrawCells; consequently there is no top-level full depth clear and no exit-seal loop.
  • low 16 bits >= 0x100: indoor root. It establishes the viewpoint and reaches RenderDeviceD3D::DrawInside, which calls PView::DrawInside on the indoor PView.
  • after either root, FlushAlphaList occurs at the top level.

Retail creates two different PViews during render-device initialization:

  • indoor PView: constructed with draw_landscape = 1;
  • outdoor/building-look-in PView: constructed with draw_landscape = 0.

This distinction is binding. Only the indoor PView can accumulate outside_view from portals whose other_cell_id == 0xFFFFFFFF.

Indoor root

PView::DrawInside @0x005A5860 performs:

  1. push/add the root view and stab views;
  2. copy the current full view into the root cell's top view;
  3. ConstructView(root, 0xFFFF);
  4. DrawCells(this, 0);
  5. pop/remove the temporary views.

PView::ConstructView(CEnvCell) @0x005A57B0 clears outside_view.view_count, increments the separate PView::master_timestamp, clears the todo/draw counts, and floods through ClipPortals. PView::master_timestamp is not RenderDevice::m_nFrameStamp; it is a visibility-construction generation used by this flood.

PView::ClipPortals @0x005A5520 installs the current cell's top portal view, accepts only live views, resolves ordinary neighbor cells, and handles an outside portal as follows:

  • if other_cell_id != 0xFFFFFFFF, clip/propagate to the neighbor;
  • if other_cell_id == 0xFFFFFFFF and draw_landscape != 0, append the clipped portal view (or full view when cliplandscape == 0) to outside_view;
  • if draw_landscape == 0, do not produce an outside view.

Landscape and building look-ins

LScape::draw @0x00506330 orders sky, reverse visible-block traversal, and weather. Within each block, RenderDeviceD3D::DrawBlock @0x005A17C0 walks its authored cell order. For each land cell it calls:

  1. DrawLandCell when the land cell is in view;
  2. DrawSortCell when alwaysDrawObjects != 0 (retail default is 1) or the cell is in view.

DrawSortCell @0x0059F140 calls building draw first, then the land cell's object draw. DrawBuilding @0x0059F2A0 does:

  1. publish the outdoor PView's portal array;
  2. flush alpha;
  3. draw building part with portal flavor (Draw(part, 1));
  4. set building flag and draw shell (Draw(part, 0));
  5. restore the flag.

The portal-flavor DrawMeshInternal @0x0059F360 installs building_view, then runs the building BSP's portal-only walk in two passes:

  • pass 1: ConstructView(CBldPortal) emits DrawPortalPolyInternal(poly, true) — the colorless far-Z punch. PView::DrawPortal sees mode 1 and skips DrawCells.
  • pass 2: it recursively constructs the destination cell view, and PView::DrawPortal calls DrawCells for that look-in flood.

Because this is the outdoor PView (draw_landscape = 0), the look-in DrawCells has outside_view.view_count == 0. It therefore skips landscape, the additional DrawCells stamp increment, counter consumption, full clear, and exit seals; it draws its reverse shell pass and reverse object-cell pass, resets object scale, and ends with useSunlightSet(1).

Exact whole-frame ordering

Outdoor root:

LScape::draw
  sky
  each visible block (reverse block_draw_list)
    each authored land cell
      DrawLandCell
      DrawSortCell
        DrawBuilding
          alpha flush
          every building pass-1 far-Z punch
          every building pass-2 look-in DrawCells flood
            reverse shells, then reverse object cells
            reset object scale; useSunlightSet(1)
          building shell
        land-cell objects
  weather
top-level alpha flush
later RenderDeviceD3D::Flip present: m_nFrameStamp++

Indoor root with at least one outside view:

ConstructView(root)
DrawCells(indoor PView)
  install sunlight + outside_view
  LScape::draw                 (same per-cell interleave as above)
  alpha flush
  m_nFrameStamp++
  consume/reset previous portalsDrawnCount
  full depth clear iff consumed count != 0
  current exit portals: true-depth seal attempts
  useSunlightSet(0); restore_all_lighting
  all root-flood cell shells, reverse cell_draw_list
  all root-flood object cells, reverse cell_draw_list
  reset object scale; useSunlightSet(1)
top-level alpha flush
later RenderDeviceD3D::Flip present: m_nFrameStamp++

Indoor root with no outside view:

ConstructView(root)
DrawCells(indoor PView)
  useSunlightSet(0); restore_all_lighting
  all root-flood cell shells, reverse cell_draw_list
  all root-flood object cells, reverse cell_draw_list
  reset object scale; useSunlightSet(1)
top-level alpha flush
later RenderDeviceD3D::Flip present: m_nFrameStamp++

There is no landscape, intermediate alpha flush, additional DrawCells stamp increment, counter test/reset, clear, or seal in the last case. The normal Flip increment still occurs when that frame is presented.

PView::DrawCells exact lifecycle

PView::DrawCells @0x005A4840 has only two callers in the binary: DrawInside @0x005A595B and DrawPortal @0x005A5B53. Its second integer parameter is unused.

Order PC/range Condition State before Action State after
1 0x5A48490x5A4852 outside_view.view_count == 0 any pending counter jump to 0x5A49F1 counter and stamp unchanged within this call; the normal later Flip increment is independent
2 0x5A485A0x5A486B outside view nonzero outside views installed by indoor flood sunlight on; Render::PortalList = &outside_view; LScape::draw(lscape) landscape/building/look-in depth and color exist
3 0x5A4872 same alpha may be queued FlushAlphaList(0) landscape stage drained
4 0x5A48850x5A4886 same current presented-frame render stamp increment RenderDevice::m_nFrameStamp a second time, mid-frame landscape-drawn shells/parts become eligible for interior repaint
5 0x5A488C0x5A48A7 same persistent prior counter if shipped forceClear == 0, read counter, test it, and always reset it to zero prior value retained only in a register
6 0x5A48A90x5A48BD prior value nonzero, or theoretical forceClear != 0 depth contains landscape/look-ins clear full depth attachment to 1.0 color remains; depth is empty
7 0x5A48C00x5A49EB cell draw list nonempty counter now zero in shipped path reverse cells; for each live view, draw every portal with other_cell_id == 0xFFFFFFFF using DrawPortalPolyInternal(false) counter increments modulo 65,536 per accepted seal attempt; true depth restores outside occlusion
8 0x5A49F10x5A49FB always outside stage complete or skipped sunlight off; restore lighting indoor lighting active
9 0x5A4A000x5A4ADE drawing BSP exists root/look-in view list reverse cells, each live view: setup_view, then DrawEnvCell complete constructed shell drawn once per render stamp
10 0x5A4ADE0x5A4B1A cells exist shells complete reverse cells; install top portal view; DrawObjCellForDummies statics/dynamics drawn through cell views
11 0x5A4B1C0x5A4B5D always all cell passes complete reset object scale; useSunlightSet(1) sunlight is enabled before returning to the caller and its top-level alpha flush

The full clear is a true full-viewport depth-only clear. RenderDeviceD3D::Clear @0x0059FD30 maps engine flag 4 to D3DCLEAR_ZBUFFER, uses Count=0/pRects=null, and clears Z to 1.0 without clearing color.

Persistent counter: producer, consumer, and timing

The complete Ghidra xref set for portalsDrawnCount @0x008719B4 contains exactly three accesses:

Access Function/PC Meaning
read/write DrawPortalPolyInternal @0x0059BD74 word increment, modulo 65,536, for an accepted non-far mask attempt
read DrawCells @0x005A4895 consume previous value
write DrawCells @0x005A489E reset to zero

There is no frame-start reset and no other producer. The exact shipped state machine is:

if outside_view.view_count != 0:
    draw landscape
    flush alpha
    ++RenderDevice.m_nFrameStamp

    previous = portalsDrawnCount
    portalsDrawnCount = 0
    if previous != 0:
        clear full depth to 1.0

    for cell in reverse(cell_draw_list):
        for each live view of cell:
            setup_view(cell, view)
            for portal in cell.portals:
                if portal.other_cell_id == 0xFFFFFFFF:
                    DrawPortalPolyInternal(portal.poly, farZ=false)
                    # accepted attempt increments the uint16 counter modulo 65536

draw shells in reverse(cell_draw_list)
draw objects in reverse(cell_draw_list)
reset object scale
useSunlightSet(1)

# outside DrawCells, at the normal later present:
RenderDeviceD3D::Flip increments m_nFrameStamp once

forceClear is initialized zero and Ghidra finds no writes in this binary. If patched nonzero, it branches directly to the clear and bypasses both counter read and reset. That is a dormant/debug override, not the shipped lifecycle.

Consecutive-call example

Eligible call outside_view counter on entry clear? accepted current seals counter on exit
startup/N >0 0 no K K mod 65536
N+1 >0 K yes if K != 0 M M mod 65536
N+2 0 M no; outside block skipped none M
N+3 0 M no; outside block skipped none M
N+4 >0 M yes if M != 0 Q Q mod 65536

K, M, and Q are accepted-attempt counts; the stored counter is a machine uint16, and INC word ptr [0x008719B4] wraps modulo 65,536. In the pathological exactly-65,536-attempt case the stored value returns to zero and therefore does not arm the next clear.

Calling this merely “previous frame” is slightly imprecise. It is the previous eligible outside-view DrawCells call. Under the ordinary one-indoor-root-per-presented-frame path those are normally adjacent frames; a run of outside_view == 0 frames leaves the pending count untouched.

Far-Z punches and true-depth exit seals

Both use DrawPortalPolyInternal @0x0059BC90, but the boolean selects materially different depth:

Use Call site Boolean Mode Depth Counter delta
building portal pass 1 ConstructView(CBldPortal) @0x5A5A7B true 7 / maxZ1 exact far Z, bits 0x3F7FFFEF 0
outside exit seal DrawCells @0x5A49B7 false 6 / maxZ2 true projected z/w +1 after boundary reject, before clip
failed mode-3 portal fallback DrawPortal @0x5A5B7C false 6 true projected z/w +1 if reached/accepted; not used by the two-pass building walker

The common polygon path is:

  1. initialize four whole-polygon predicates: every local input vertex on x == +12, x == -12, y == +12, or y == -12;
  2. for each original CVertex, compare its local x/y values first (0x59BCD60x59BD20), then call xformStart for that same vertex at 0x59BD28;
  3. after all vertices, return without count/draw if any local-input predicate remains true;
  4. if boolean is false, increment portalsDrawnCount modulo 65,536;
  5. polyClipFinish against the installed render view;
  6. return if clipped vertex count < 3;
  7. submit a triangle fan with no texture, alpha test off, blend SRCALPHA/INVSRCALPHA/ADD, depth test ALWAYS, depth write on, cull none, and normal/default color writes still enabled. Modes 6 and 7 build vertices with source alpha zero, so blending preserves destination color even though the color attachment is writable.

The counter therefore records accepted attempts, not successful GPU fans: an exit polygon that passes the boundary test but clips to fewer than three vertices still increments the next-call clear gate. Far-Z punches never increment it.

Ghidra branch arbitration table

This table records the behavior-changing senses used in the findings. It is intentionally more precise than the named pseudo-C wherever a decompiler inversion could matter.

PC Ghidra instruction/sense Retail behavior
0x5A484C / 0x5A4852 TEST EAX,EAX; JBE 0x5A49F1 zero outside views skip the entire outside/depth stage
0x5A4891 / 0x5A4893 test forceClear; JNZ 0x5A48A9 nonzero override goes straight to clear and bypasses counter reset
0x5A489C / 0x5A48A7 test prior counter; reset store occurs before JZ 0x5A48C0 shipped path always resets, and clears only when prior count was nonzero
0x5A48C3 / 0x5A48C9 test reverse-list count; JBE 0x5A49F1 no exit-seal traversal when draw list empty
0x5A496C / 0x5A496F test top view count; JBE no seal work for cells without live views
0x5A49A9 / 0x5A49AF compare portal other id with -1; JNZ skips only outside portals are sealed
0x59BCD60x59BD28, then 0x59BD400x59BD66 each source vertex's local x/y is compared before its xformStart; four final predicate tests branch to return whole poly on any local-input x/y == +/-12 boundary is rejected before count/clip
0x59BD700x59BD74 test boolean; nonzero jumps past INC word ptr [0x8719B4] false/true-depth seals increment modulo 65,536; true/far punches do not
0x59BDBC0x59BDBF compare clipped count with 3; JL return fewer than 3 clipped vertices submit no fan, after possible increment
0x5A56660x5A566C compare other_cell_id with -1; JNZ neighbor path 0xFFFFFFFF is outside path
0x5A56710x5A5673 test draw_landscape; zero skips only the indoor PView accumulates outside view
0x5A5A6E0x5A5A7B mode 2 skips punch; mode 1 falls through to DrawPortalPolyInternal(true) building pass 1 punches; pass 2 constructs destination
0x5A5B460x5A5B53 success then compare mode 1; mode 1 skips DrawCells far-punch pass has no look-in draw; later pass does
0x5A5B740x5A5B7C on failure, only mode 3 draws false-mode portal poly failure fallback is true-depth only for mode 3
0x59FEC00x59FEC8 unconditional load, INC, store after the present/reset tail in Flip normal per-present m_nFrameStamp advancement is independent of DrawCells' conditional mid-frame increment

Render-frame stamp versus visibility timestamp

Retail maintains two unrelated generation variables:

  • PView::master_timestamp @0x008ED6B8 increments in every ConstructView(CEnvCell) and drives visibility construction.
  • RenderDevice::m_nFrameStamp @ RenderDevice+0xB0 is the render dedupe stamp. It has two increment sites relevant here:
    1. RenderDeviceD3D::Flip @0x0059FE50, load/increment/store at 0x0059FEC00x0059FEC8, increments once after the normal swap-chain present, texture-stage unbind, dynamic-buffer reset, and profiler-frame reset;
    2. DrawCells @0x005A48850x005A4886 adds a conditional mid-frame increment after an outside landscape/alpha flush and before the counter/clear.

The render stamp is consumed by at least:

  • CEnvCell+0x130: GetDrawnThisFrame @0x0052C0C0, SetDrawnThisFrame @0x0052C0E0;
  • CPhysicsPart+0xDC: GetDrawnThisFrame @0x0050D4D0, SetDrawnThisFrame @0x0050D4F0.

DrawEnvCell @0x0059F170 begins with the stamp gate and the built-mesh path draws the complete constructed shell once for that stamp. DrawMeshInternal @0x0059F360 applies a corresponding stamp to non-local-player physics parts. The normal Flip increment makes the following presented frame eligible. When an indoor outside stage runs, content admitted during landscape/building look-ins is stamped with that frame's value; the additional DrawCells increment then re-arms it for the root repaint after the possible clear. A faithful port therefore needs both boundaries: the ordinary per-present boundary and the conditional mid-frame barrier. Neither may be conflated with the PView flood timestamp.

Current acdream correspondence

The inspected production locations are in the binding worktree named at the top of this report.

Retail mechanism Current acdream Assessment
separate indoor/outdoor PViews; outdoor DrawLandscape=false src/AcDream.App/Rendering/Walk/RetailFrameWalk.cs:4655; WalkPView.cs:3950 faithful basic ownership
outside-view reset and 0xFFFFFFFF producer gated by DrawLandscape WalkPView.cs:5975, 175250 faithful basic producer
indoor flood calls landscape only when outside views exist RetailFrameWalk.cs:118142 faithful at walk level
landscape then root-flood turn RetailFrameWalk.cs:118142 faithful coarse order
persistent uint16 portalsDrawnCount across calls/frames no production state found; source search finds only comments missing
counter populated only by accepted false-mode attempts OnPunchGeometry records only punch events; exit seal draw is a replay callback missing producer semantics
outside-view block gates stamp, consume/reset, clear, and seals WalkFrameDriver.cs:12991339 emits clear/seals unconditionally for every interior flood wrong
first eligible call with counter zero does not clear WalkFrameDriverTests.cs:260293 expects CLEAR on the first outside-view fixture wrong test contract
outside_view==0 performs no clear/seal/additional DrawCells stamp WalkFrameDriverTests.cs:432440 explicitly expects unconditional CLEAR, SEALS wrong test contract; normal per-present frame boundary is unaffected
normal stamp boundary once per present WalkFrameDriver.BeginFrame at WalkFrameDriver.cs:784818 calls BeginWalkPartFrame and clears shell dedupe; Wb/WbDrawDispatcher.WalkClassify.cs:8999 clears part dedupe semantically represents the ordinary per-frame eligibility boundary, though it is tied to collection rather than an actual present
additional stamp barrier after landscape/flush and before clear WalkFrameDriver.cs:13041319 advances part-pass stamp and clears shell dedupe when _skyDrawnThisFrame close in semantic placement and correctly conditional on a landscape turn; collection-time mirror, not exact GPU execution boundary
full Z clear to 1.0, no color VulkanWorldPassScope.cs:87120 faithful clear operation
shell pass then object pass, both reverse order WalkFrameDriver.cs:14191453 faithful coarse ordering
far punch depth exact bits 0x3F7FFFEF Shaders/portal_depth.vert:53 uses decimal 0.99999988 wrong constant
whole-polygon local-input, pre-xformStart x/y == +/-12 rejection no equivalent in PortalDepthMaskRenderer.Rhi.cs:106156 or punch event production missing
CPU polyClipFinish, count after boundary reject but before clip success GPU gl_ClipDistance, capped to ClipFrame.MaxPlanes == 8; no clipped-vertex result approximation, not byte/vertex exact
depth ALWAYS, depth write on, no cull; color writes enabled with SRCALPHA/INVSRCALPHA and source alpha zero PortalDepthMaskRenderer.Rhi.cs:58104 uses ColorWrite=false depth/color outcome-equivalent, but render state diverges from retail
per-landcell DrawLandCell then DrawSortCell WalkFrameDriver.cs:105114, 13451379 explicitly emits all terrain before per-cell building/static turns known wrong order
Render::PortalList=&outside_view remains installed for the whole outside LScape::draw RetailPViewPassExecutor.WalkLeaf.cs:2850, 94116 deliberately draws sky/terrain without exact slice clipping approximation; leaf-level pixel consequence unresolved here
true-depth seals emitted for each outside portal of each live view, reverse flood RetailPViewRenderer.cs:656685 geometry loop is broadly shaped correctly, but called under wrong gate and has no counter feedback

The current comments in WalkFrameDriver.cs:138165 accurately label the clear gate as an acknowledged approximation, but the later implementation comment at 13211325 calls clear/seals “both unconditional.” Ghidra proves that statement false: the entire stage is skipped when outside-view count is zero, and the clear has a second persistent prior-counter gate.

Current event ordering that must not be mistaken for retail

Today the interior event list is effectively:

BeginFrame:
    clear CPU part/shell dedupe       # ordinary presented-frame boundary analogue
if outside_view > 0:
    SKY
    TERRAIN (all terrain before buildings/statics)
if sky was emitted:
    advance CPU render-stamp mirrors # additional DrawCells barrier analogue
CLEAR                         # always, including outside_view == 0
SEALS                         # always, including outside_view == 0
reverse shells
reverse cell contents

Retail is:

# frame starts with the stamp established by the prior Flip
if outside_view > 0:
    LANDSCAPE                 # terrain/building/object interleaved per landcell
    alpha flush
    ++render stamp             # additional mid-frame barrier
    prior = sealAttemptCount
    sealAttemptCount = 0
    if prior != 0:
        CLEAR
    SEAL CURRENT EXITS        # accepted attempts repopulate counter
reverse shells
reverse object cells
reset object scale; useSunlightSet(1)
top-level alpha flush
Flip/present: ++render stamp  # ordinary per-present boundary

Required executable transcript

An implementation slice should not be visually gated before it can emit an exact, machine-assertable transcript. Aggregate owner ids are insufficient. Use one record per DrawCells call, one per mask attempt, and one per normal present boundary.

DrawCells record

[oh1-dc] present=<u64> call=<u64> source=inside|building-portal \
root=0x<8hex> pview=indoor|outdoor drawLandscape=0|1 outsideViews=<u32> \
counterBefore=<u16> forceClear=0|1 landscape=0|1 alphaFlush=0|1 \
stampBefore=<u32> midStampIncrement=0|1 stampAfter=<u32> \
consumed=<u16> counterReset=0|1 depthClear=0|1 \
sealAttempts=<u32> sealGpuSubmissions=<u32> counterAfter=<u16> \
shellCells=<ordered cell ids> objectCells=<ordered cell ids>

Assertions enabled by this line:

  • outsideViews==0 implies landscape=alphaFlush=midStampIncrement=counterReset=depthClear=sealAttempts=0 and stampAfter==stampBefore within this call;
  • with shipped forceClear==0, outsideViews>0 implies consumed==counterBefore, counterReset==1, and depthClear==(consumed!=0);
  • after the reset, counterAfter == sealAttempts mod 65536 unless another false-mode caller occurs before line emission; the general assertion is counterAfter == (counterBase + acceptedAttempts) & 0xffff;
  • sealGpuSubmissions is only CPU-side submission count. It is not retail fanDrawn, because the current GPU clip-distance path returns no surviving-vertex or fragment result;
  • shell and object lists are independently reverse ordered.

Normal present/stamp record

[oh1-present] present=<u64> flipCall=<u64> stampBefore=<u32> \
swapResult=ok|deviceLost stampAfter=<u32>

After Flip's Present retry loop terminates, every return path executes stampAfter == stampBefore + 1 mod 2^32 (the device-lost result sets its flag first). This is independent of the optional midStampIncrement in a DrawCells record.

Portal mask record

[oh1-mask] present=<u64> call=<u64> ordinal=<u32> \
source=building-pass1|exit-seal|mode3-fallback cell=0x<8hex> \
view=<u16> portal=<u16> other=0x<8hex> mode=far|true \
inputVerts=<u16> localBoundaryWouldReject=0|1 boundaryRejectApplied=0|1 \
gpuSubmitted=0|1 clipPath=gpu-clip-distance|retail-polyClipFinish \
counterBefore=<u16> counterDelta=0|1 counterAfter=<u16> \
depthBits=0x3f7fffef|projected depthTest=always depthWrite=1 cull=none \
retailBlend=srcalpha/invsrcalpha retailSourceAlpha=0 retailColorWrite=1 \
activeColorWrite=0|1

Assertions enabled by this line:

  • mode=far always has counterDelta=0 and exact depthBits=0x3f7fffef;
  • boundaryRejectApplied=1 implies counterDelta=0 and gpuSubmitted=0;
  • an accepted mode=true attempt has conceptual counterDelta=1, with counterAfter == (counterBefore + 1) & 0xffff, even if retail clipping would later remove the fan;
  • the current pipeline must report activeColorWrite=0; a state-faithful retail port reports 1 with the listed blend/source-alpha state. Both preserve destination color, but they are not the same render state;
  • only other=0xffffffff appears with source=exit-seal.

The current GPU path cannot truthfully populate clippedVerts or fanDrawn: gl_ClipDistance runs after CPU submission and returns neither value. Do not synthesize them from gpuSubmitted. If polyClipFinish is ported or a retail capture is instrumented, emit a separate record:

[oh1-retail-clip] present=<u64> call=<u64> ordinal=<u32> \
inputVerts=<u16> clippedVerts=<u16> fanDrawn=0|1

Only that audited CPU clip result may assert fanDrawn == (clippedVerts >= 3). Porting or line-by-line auditing polyClipFinish @0x006B6D00 is required before claiming exact clip/fan parity.

Landscape ordering record

[oh1-order] present=<u64> dcCall=<u64> ordinal=<u32> \
stage=sky|landcell|building-flush|punch|lookin-shell|lookin-object|building-shell|landcell-object|weather|outside-flush|mid-stamp|clear|seal|root-shell|root-object|sunlight-tail|top-alpha-flush|flip-stamp \
landblock=0x<8hex> landcell=0x<8hex> building=<stable instance key> \
cell=0x<8hex> view=<i32> portal=<i32>

This record makes the per-landcell interleave and punch position testable without asking a user to identify a moving slab visually.

Implementation constraints derived from retail

This section is evidence-derived guidance, not a production edit.

  1. Introduce one persistent ushort counter at the render/depth lifecycle owner. Do not reset it at frame start, world-walk start, or outdoor root start. Increment it with unchecked modulo-65,536 semantics.
  2. Gate the entire outside stage on outside_view.view_count != 0.
  3. Within that block preserve the exact order: landscape → alpha flush → additional mid-frame render-stamp increment → consume/reset counter → optional full depth clear → current true-depth exit seals.
  4. Evaluate the +/-12 whole-polygon predicate from original local input vertex x/y before transformation. Count accepted true-depth mask attempts after that rejection and before final clipping decides whether a fan exists.
  5. Far punches never affect the counter. Use the exact far-Z bit pattern, not a rounded decimal approximation.
  6. Preserve both render-stamp increments: the ordinary Flip/present increment and the conditional mid-DrawCells barrier. Keep that render stamp distinct from PView's flood timestamp.
  7. Preserve independent reverse shell and object passes after the optional outside stage, then reset object scale and execute the final useSunlightSet(1) before the caller's top-level alpha flush.
  8. Restore retail's state-faithful portal fan setup: color writes enabled, SRCALPHA/INVSRCALPHA blending, and source alpha zero. The current ColorWrite=false path is outcome-equivalent for destination color but is not the same state.
  9. Port or line-by-line audit polyClipFinish before asserting clipped vertex counts or successful retail fan draws. Until then, trace only boundary decisions, attempt counts, and GPU submissions.
  10. Restore retail's per-landcell terrain/building/object interleave before claiming full order parity. Merely fixing the counter leaves punch-versus-terrain ordering different.
  11. Replace tests that demand unconditional clear/seals with a state-machine matrix: first eligible call, consecutive eligible calls, zero-outside gap, resume after gap, boundary-rejected seals, clipped-away accepted seals, far punches, and ushort wrap at 65,535/65,536 accepted attempts.

Unresolved facts

  1. The exact pixel-level effect of keeping Render::PortalList = &outside_view installed throughout retail sky/terrain rendering was not expanded through every LScape leaf in this slice. The top-level state and order are proven; whether each leaf clips geometry or only uses the view union requires a separate leaf audit.
  2. GPU gl_ClipDistance may be geometrically similar to retail polyClipFinish, but numerical/vertex-set equivalence is not proven. The current eight-plane cap, interpolation rules, and lack of a clipped vertex count prevent a claim of byte/execution parity.
  3. The dormant forceClear branch is fully decoded, but no write to 0x008ED824 exists in this retail binary. Its practical activation mechanism, if any, is external to the binary and irrelevant to shipped behavior.
  4. The mode-3 failure fallback exists in DrawPortal, but the examined two-pass building BSP walk supplies modes 1 and 2. No production caller reaching mode 3 was established in this slice.

Closeout

The decisive OH1 correction is not “find the guilty slab.” Retail has a small persistent depth state machine whose timing crosses eligible DrawCells calls. acdream currently substitutes unconditional per-interior-root events and a differently ordered landscape stream. The next implementation slice can be deterministic: port the state machine and exact mask predicates/constants, emit the transcript above, and only then run the cathedral visual gate.