acdream/docs/research/2026-09-01-overhaul/oh1-built-mesh-view-contract.md

33 KiB

OH1 evidence — retail built-mesh/view/stamp contract

Date: 2026-09-01

Binding worktree: C:\Users\erikn\source\repos\acdream\.claude\worktrees\peaceful-blackburn-5333f0

Branch / audited HEAD: claude/campaign-w-retail-frame-walk / eaea8776620ad8b72f5f0aa44fbaffac6dde0a36

Scope: evidence only. No production source, test, plan, or other research document was changed by this review.

Verdict

Retail's portal views are admission and portal-polygon-clip state, not built-mesh triangle clip state.

The decisive contract is:

  1. Render::copy_view builds one screen polygon plus one world plane per edge.
  2. Render::set_view installs one polygon's plane list and screen bounds in globals.
  3. Render::viewconeCheck classifies a drawing sphere against those planes.
  4. Once an ordinary built CGfxObj is admitted, DrawMeshInternal submits the complete constructed mesh. It does not pass the active portal polygon or its planes to D3DPolyRender::DrawMesh.
  5. A non-player CPhysicsPart is stamped at the first admitted view, before its mesh submission. Later admitted views and later cell memberships in the same render stamp do not resubmit it.
  6. A built CEnvCell stamps itself on the first DrawEnvCell call and submits its complete constructed shell. Later portal views and later flood encounters in that render stamp are no-ops.
  7. The local player's parts deliberately bypass the inner part stamp, so they can submit once for every admitted cell/view. This is the one ordinary built-object exception.
  8. Actual polygon clipping is used for portal-chain construction and portal depth fans/seals (PView::GetClip and DrawPortalPolyInternal, both through ACRender::polyClipFinish). It is not used on ordinary built mesh triangles.
  9. A delayed transparent subset is owned by the built-mesh submission that first admitted the part/cell. Every AlphaListEntry stores mesh/subset/surface/flags; the first entry for each list also stores material and world matrix under bNewObject, and later entries inherit that replay state. It stores no portal view, clip-plane list, cell, or part identity.

Therefore, multiple portal views do not mean multiple clipped copies of a normal built mesh. For non-player objects and EnvCells they mean “try views until one admits, then draw whole once.” Any implementation that submits one built mesh per portal slice, or hardware-clips an admitted built mesh to that slice, contradicts retail.

Oracle and arbitration method

Named retail was read first:

  • docs/research/named-retail/acclient_2013_pseudo_c.txt
  • docs/research/named-retail/acclient.h

Every admission, clipping, built/non-built, and stamp branch below was then arbitrated against the live Ghidra MCP on port 8081 using both:

  • http://127.0.0.1:8081/decompile_function?address=...
  • http://127.0.0.1:8081/disassemble_function?address=...

The Ghidra checks matter here because several older notes inferred “one mesh draw per portal view” from the outer loop without following the inner stamp branch. The actual instruction order proves the opposite for non-player parts.

Retail structures and state

Offsets below are 32-bit retail object offsets confirmed by the named header and the accessed addresses in Ghidra disassembly.

View state

portal_view_type is 0x48 bytes:

Offset Field Meaning
0x00 DArray<portal_info> portal per-portal seen / inflag state
0x10 view_type view shared polygon and vertex pools
0x34 float max_indist flood distance key
0x38 uint view_count installed polygon count
0x3c int cell_view_done cell walk state
0x40 int view_timestamp view-generation stamp
0x44 int update_count view update count

view_type is 0x24 bytes: vertex_count_total @ +0x00, DArray<view_poly> @ +0x04, DArray<view_vertex> @ +0x14.

view_poly is 0x18 bytes: vertex_count, vertex_index, xmin, xmax, ymin, ymax at +0x00..+0x14.

view_vertex is 0x18 bytes: Vec2D pt @ +0x00 and Plane plane @ +0x08.

PView uses outside_view @ +0x00, cell_draw_list.data @ +0x50, cell_draw_num @ +0x60, and lscape @ +0x78.

Built content and stamps

Type Relevant fields
CPhysicsPart draw_state +0x1c, gfxobj +0x20, gfxobj_scale +0x24, draw_pos +0x78, material +0xc0, surfaces +0xc4, m_current_render_frame_num +0xdc, physobj +0xe0
CGfxObj constructed_mesh +0x6c, use_built_mesh +0x70, drawing_sphere +0x90, drawing_bsp +0x94
CEnvCell num_surfaces +0xf8, surfaces +0xfc, structure +0x100, num_portals +0x108, portals +0x10c, constructed_mesh +0x128, use_built_mesh +0x12c, m_current_render_frame_num +0x130, num_view +0x134, portal_view +0x138
RenderDevice m_nFrameStamp +0xb0

AlphaListEntry is 0x54 bytes:

Offset Field
0x00 MeshBuffer *pMeshBuffer
0x04 int surfaceNum
0x08 CSurface *surface
0x0c CMaterial *material
0x10 Matrix4 worldMatrix (through 0x4f)
0x50 bool bNewObject
0x51 bool overrideClipmapSetting

There is no field for portal_view_num, portal_vertex, portal_npnts, a cell, a part, or a clip-plane mask.

Function contract ledger

Render::copy_view0x0054dfc0

Purpose: append one portal view polygon and its edge planes to a portal_view_type.

Exact effects:

  • If the source point array is null, append the full viewport quad (0,H) (W,H) (W,0) (0,0) plus the repeated closing vertex. The supplied point count is ignored.
  • Otherwise, perspective-divide every input point in place when w != 1, set w = 1, prune consecutive points within one pixel, prune collinear points including the closing wrap, reject fewer than three survivors, and cap the result at 31 vertices.
  • Rejection returns without incrementing view_count.
  • Success records bounds, stores a repeated closing point, unprojects the surviving screen points, creates one world plane per edge, and increments view_count.
  • The edge plane is constructed from the two eye rays and passes through viewer_world_space.viewpoint.

This function creates view metadata. It neither draws nor attaches the view to a mesh submission.

Render::set_view0x0054d0e0

Purpose: install one view_poly as global active view state.

Exact writes from Ghidra disassembly:

  • portal_view_num (0x008662dc) = requested index
  • portal_view (0x00846044) = supplied view_type *
  • portal_npnts (0x00846050) = polygon vertex count
  • portal_inmask (0x008661b8) = (1 << (portal_npnts + 1)) - 1
  • portal_vertex (0x00846040) = the polygon's first view_vertex
  • xmin/xmax/ymin/ymax = polygon bounds

There is no render call, scissor installation, user clip-plane installation, or mesh mutation.

Render::obj_view_set0x0054b9b0

Purpose: transform the active world-space view edge planes, and the CY plane, into the current object frame.

It fills Render::portal_obj_plane[] from portal_vertex[].plane using FrameCurrent, and writes the object-local viewer CY plane. That state serves the legacy object/BSP/polygon machinery.

Crucial negative evidence:

  • ordinary built DrawMeshInternal(force=false) does not call obj_view_set;
  • the built D3DPolyRender::DrawMesh overload has no plane/view argument;
  • DrawEnvCell calls obj_view_set, but its built branch still calls D3DPolyRender::DrawMesh directly with only surfaces and the constructed mesh;
  • the force/building branch calls obj_view_set because it walks drawing_bsp portals, not because it clips the constructed mesh.

The presence of obj_view_set is not evidence of GPU clipping of a built mesh.

Render::viewconeCheck0x0054c250

Purpose: sphere admission against the active view cone.

State effects:

  • scale the authored sphere center and radius by Render::object_scale;
  • transform it through the current object/viewer frames;
  • publish Render::local_object_center and Render::local_object_radius before classification;
  • test the viewer CY plane, then every active edge plane.

Ghidra-arbitrated branch senses:

  • CY reject at 0x0054c31b..0x0054c32e: distance < -radius returns OUTSIDE (0); equality is not rejected.
  • edge reject at 0x0054c37b..0x0054c3bd: the same strict distance < -radius rule.
  • partial is inclusive: distance <= radius marks partial; distance == +radius is PARTIALLY_INSIDE (1), not entirely inside.
  • only when every tested distance is > radius is the result ENTIRELY_INSIDE (2).

This is a bounding-sphere classification. It does not clip any vertex or triangle.

Render::SetObjectScale @ 0x0050d430 sets object_scale to the maximum of the part's X/Y/Z scale, matching the conservative sphere radius expansion used by the current port.

CPhysicsPart::Draw0x0050d7a0

Purpose: resolve the current degrade mesh and enter RenderDeviceD3D::DrawMesh.

Ghidra-arbitrated outer gates:

  • draw_state & 1 at 0x0050d7a3..0x0050d7a7: set means skip.
  • ordinary call (force == 0) at 0x0050d7b8..0x0050d7c9: if the part stamp equals the device stamp, skip.
  • force/building-portal call (force != 0) branches around that outer stamp comparison.
  • a null resolved degrade CGfxObj skips.

On entry to the device it installs current material, surfaces, maximum object scale, s_current_physics_part, and calls DrawMesh(gfxobj, &draw_pos, force != 0). It clears s_current_physics_part afterward.

CPhysicsPart::Draw does not itself set the stamp. The first successful view admission sets it inside DrawMeshInternal.

RenderDeviceD3D::DrawMesh0x005a0860

Purpose: per-view sphere admission around DrawMeshInternal.

With Render::PortalList == null:

  1. push the object position;
  2. call viewconeCheck(drawing_sphere) once;
  3. on non-OUTSIDE, run selection-ray state and call DrawMeshInternal;
  4. on OUTSIDE, call DrawMeshInternal(force=true, OUTSIDE) only for the force flavor.

With a portal list:

  1. loop i = 0 .. view_count-1;
  2. skip indices excluded by building_view (building_view == -1 || building_view == i is the admission branch at 0x005a08c0..0x005a08d3);
  3. set_view(view, i);
  4. viewconeCheck(drawing_sphere);
  5. call DrawMeshInternal on every non-OUTSIDE result;
  6. for OUTSIDE, call it only in force mode.

The selection-ray check runs once at the first admitted view. The loop can call DrawMeshInternal more than once, but the inner part stamp normally makes only the first admitted call submit a mesh.

RenderDeviceD3D::DrawMeshInternal0x0059f360

This function resolves both the duplicate-submission rule and the built/non-built rule.

Ordinary non-player stamp branch

Ghidra disassembly 0x0059f365..0x0059f38c:

  • force set -> bypass stamp;
  • no current physics part -> bypass stamp;
  • IsPartOfPlayerObj true -> bypass stamp;
  • otherwise, GetDrawnThisFrame true -> return immediately at 0x0059f3fc;
  • otherwise, call SetDrawnThisFrame before lighting and mesh submission.

This placement is essential: a view that rejects the sphere never enters DrawMeshInternal, so it cannot stamp the part. The first admitted view stamps and submits; later admitted views immediately return.

Built/non-built branch

Ghidra disassembly 0x0059f3a3..0x0059f3fc:

  • use_built_mesh == 0: return without a mesh or polygon fallback in this function.
  • built + ordinary (force == false): call D3DPolyRender::DrawMesh(gfxobj, constructed_mesh) once. This is the complete constructed mesh.
  • built + force (force == true): call obj_view_set, save building_view, set it to portal_view_num, walk drawing_bsp with build_draw_portals_only(..., 1) and (..., 2), restore building_view, and return. It does not submit constructed_mesh.

Thus the force flavor is a portal-only BSP pass, not a forced whole-mesh draw.

RenderDeviceD3D::DrawEnvCell0x0059f170

Ghidra-arbitrated order:

  1. GetDrawnThisFrame; equal stamp jumps directly to return (0x0059f175..0x0059f17e).
  2. SetDrawnThisFrame immediately (0x0059f184..0x0059f186).
  3. obj_view_set, lighting minimization, and environment detail-state setup.
  4. if use_built_mesh != 0, update static-light vertex colors and call D3DPolyRender::DrawMesh(num_surfaces, surfaces, constructed_mesh, skipNoTexture=true), then return.
  5. otherwise append every structure polygon to Render::PolyList with planeMask = -1, then call polyListFinishInternal.

The built shell has no sphere gate and no per-view mesh clip. The first call in the current render stamp submits the complete eligible surface set; all later calls are suppressed by the cell stamp.

The non-built fallback is also not a portal-triangle clip path: D3DPolyRender::polyListFinishInternal @ 0x0059dba0 merely calls DrawPolyInternal @ 0x0059d7c0 for each queued polygon, and DrawPolyInternal does not call polyClipFinish. planeMask is not consumed by that finish function.

D3DPolyRender::DrawMesh0x0059d4a0 and 0x0059d790

DrawMesh(CGfxObj *, MeshBuffer *) @ 0x0059d790 forwards CGfxObj::num_surfaces, Render::curr_surfaces, and the constructed mesh to the surface-count overload.

DrawMesh(numSurfaces, surfaces, MeshBuffer *, skipNoTexture) @ 0x0059d4a0 loops every eligible surface subset. For each subset it either:

  • calls RenderMeshSubset immediately, or
  • appends that one subset to one of the delayed alpha lists.

The environment/building skipNoTexture rule can omit an ineligible no-texture subset. “Whole mesh” in this report means every eligible authored surface subset of the one constructed mesh, not an unconditional draw of data intentionally filtered by that retail rule.

The function accepts no active-view pointer, edge planes, scissor, clip mask, or portal_view_num. There is no portal polygon clipping in either built-mesh overload.

Transparent-subset branch — AddMeshToAlphaList @ 0x0059c230, FlushAlphaList @ 0x0059d2e0

Delay is conditional, not universal. DrawMesh @ 0x0059d4a0 can delay selected stipple/alpha/clip subsets only while the renderer is not drawing sky, s_AlphaDelayMask != 0, and Render::curr_detail_surface == null. If a detail surface is active, the subset goes through the immediate RenderMeshSubset branch. The complete branch table, mask priority, preference lifecycle, capacity behavior, and barriers are closed in oh1-alpha-list-contract.md.

When delayed:

  • AddMeshToAlphaList appends one AlphaListEntry to either the clip list or the alpha list; both are fixed at 3000 entries.
  • Every entry captures mesh buffer, surface index/pointer, bNewObject, and clipmap-setting override. Only bNewObject=true entries capture material and object world matrix; later entries inherit the replay state.
  • It captures no portal-view state. Therefore a deferred subset cannot later be redrawn or clipped “per view.” It belongs to the first built-mesh submission that queued it.
  • FlushAlphaList drains the clip list in append order, then the alpha list in append order, calling RenderMeshSubset for each. It performs no distance sort.

Ordering is established before append. DrawObjCellForDummies sorts its CShadowPart list through CShadowPart::insertion_sort @ 0x006b5130, which orders by CPhysicsPart::CYpt far-to-near. The outer PView/cell walk supplies the rest of the submission order.

Environment and building detail surfaces matter: DrawEnvCell installs environment_detail_surface; DrawBuilding installs building_detail_surface. When those pointers are non-null, their subsets render immediately rather than entering the delayed lists. If the relevant detail surface is null, the ordinary delay-mask branch can apply. An exact port therefore cannot give every “transparent cell shell” one unconditional ownership policy independent of detail state.

DrawObjCellForDummies and object-list path

Addresses:

  • RenderDeviceD3D::DrawObjCellForDummies0x005a0760
  • RenderDeviceD3D::DrawObjCell0x005a1a40
  • RenderDeviceD3D::DrawPartCell0x005a07a0
  • CShadowPart::draw0x006b50d0

Order:

  1. update the object cell;
  2. insertion-sort its shadow parts when there is more than one;
  3. DrawObjCell -> DrawPartCell walks each shadow part;
  4. CShadowPart::draw calls only CPhysicsPart::Draw(part, 0).

Although CShadowPart owns legacy plane-list storage, its draw function does not forward those planes to the built mesh. Object portal admission occurs in DrawMesh through the current cell's PortalList; duplicate suppression occurs through the part stamp.

Portal polygon clipping — what actually clips

Two explicit retail routes call ACRender::polyClipFinish @ 0x006b6d00:

  1. PView::GetClip @ 0x005a4320 transforms an authored portal polygon, fixes winding, and clips it to the current active view and near plane. Its output is passed to Render::copy_view by PView::ConstructView; this constructs the next view cone.
  2. D3DPolyRender::DrawPortalPolyInternal @ 0x0059bc90 transforms a portal polygon, calls polyClipFinish, rejects fewer than three survivors, then draws the resulting triangle fan with the portal depth state. This is the exit seal / building punch geometry.

polyClipFinish clips against the active portal_vertex screen edges and the near-W boundary. These are small portal polygons. No ordinary built mesh enters this function.

Standard device view/frustum clipping still applies to submitted primitives, of course. The negative claim is specifically that retail does not apply the portal view polygon as a per-triangle built-mesh clip.

Exact caller order

PView::DrawCells0x005a4840

When outside_view.view_count != 0:

  1. useSunlightSet(1);
  2. PortalList = &outside_view;
  3. LScape::draw;
  4. FlushAlphaList(0);
  5. increment RenderDevice::m_nFrameStamp at 0x005a4886;
  6. conditionally clear depth when forceClear is set or portalsDrawnCount was non-zero, then zero that count;
  7. reverse-walk the flood cells and every live view to draw clipped exit-portal seals.

Then, regardless of whether an outside view existed:

  1. disable sunlight and restore lighting;
  2. reverse-walk all flood cells for shells; for each live view call CEnvCell::setup_view @ 0x0052c430, then DrawEnvCell;
  3. restart at the reverse end and walk all object lists; install that cell's top portal_view_type as PortalList, then call DrawObjCellForDummies;
  4. restore scale/sunlight state.

The shell loop can invoke DrawEnvCell once per view, but the first call stamps and draws the shell whole. The object loop can invoke a part through multiple cell lists/views, but the first admitted view stamps a non-player part and later routes cannot resubmit it.

The frame-stamp increment is inside the outside_view.view_count != 0 branch. It deliberately re-arms both part and EnvCell stamps between the landscape/pre-clear scope and the interior repaint scope.

RenderDeviceD3D::DrawBuilding0x0059f2a0

After degrade/geometry admission:

  1. install building detail state;
  2. FlushAlphaList(0);
  3. CPhysicsPart::Draw(part, 1) — force portal-only BSP pass;
  4. set ObjBuildingOrBuildingPart = 1;
  5. CPhysicsPart::Draw(part, 0) — ordinary complete building shell;
  6. clear the flag/detail state.

The force pass bypasses stamps but does not submit the built mesh. It therefore cannot suppress the following ordinary shell: it never sets the part stamp. The ordinary shell follows normal per-view sphere admission and draws whole on the first admitted view.

Direct answers

Which built meshes draw whole?

Content Retail result
Ordinary built CGfxObj (force=false) Complete eligible constructed mesh on first admitted view
Built building shell ordinary pass Same ordinary CGfxObj rule: complete eligible shell on first admitted view
Built CEnvCell Complete eligible constructed cell shell on first DrawEnvCell call in the stamp
Built CGfxObj force/building-portal pass No constructed-mesh draw; portal-only drawing_bsp walk in two passes
Non-built CGfxObj in DrawMeshInternal No mesh or polygon fallback in that function
Non-built CEnvCell Every structure polygon is sent to the legacy polygon draw loop; not a built mesh and not portal-polygon-clipped there

What polygon-clips?

  • Portal polygons used to construct child views (PView::GetClip -> polyClipFinish).
  • Portal depth fans/seals (DrawPortalPolyInternal -> polyClipFinish).
  • Not ordinary built CGfxObj triangles.
  • Not built CEnvCell triangles.
  • Not the non-built EnvCell fallback in DrawPolyInternal.

Do multiple portal views duplicate built-mesh submissions?

Case Duplicate whole-mesh submissions in one render stamp?
Non-player CPhysicsPart No. First admitted view stamps/submits; later admitted views return before draw.
Same non-player part present in multiple object cells No, provided it is the same CPhysicsPart; the same stamp suppresses later cells.
Built CEnvCell called once per view No. First call stamps/submits; later calls return.
Local-player CPhysicsPart Yes. IsPartOfPlayerObj bypasses the inner stamp, so every admitted cell/view can submit the whole part.
Force building portal pass It may run once per selected view, but it submits portal polygons, not the built mesh.

The owner is the first admitted view, not necessarily index zero: rejected views do not stamp a part.

Which stamps suppress duplicates?

  • Part stamp: CPhysicsPart::m_current_render_frame_num +0xdc versus RenderDevice::m_nFrameStamp +0xb0.
    • Outer skip: CPhysicsPart::Draw @ 0x0050d7bd..0x0050d7c9.
    • First-admission set and within-call view-loop suppression: DrawMeshInternal @ 0x0059f37f..0x0059f38c.
    • Force bypasses both; local player bypasses the inner get/set and normally never acquires the current stamp.
  • Cell stamp: CEnvCell::m_current_render_frame_num +0x130 versus the same device stamp.
    • Get/set at DrawEnvCell @ 0x0059f175..0x0059f186.
    • It is set before lighting or mesh work.
  • Re-arm: PView::DrawCells @ 0x005a4886 increments the device stamp after landscape alpha flush and before the possible depth clear/interior repaint, but only when an outside view exists.

Current acdream comparison at eaea8776

Faithful or materially aligned pieces

Retail contract Current source Assessment
copy_view polygon/plane representation src/AcDream.App/Rendering/Walk/WalkViews.cs:28-58,80-280 Strong match: 0x48 semantic fields, root quad, in-place divide, one-pixel prune, wrap checks, reject <3, cap 31, closing point, bounds, edge planes.
strict-outside / inclusive-partial viewcone src/AcDream.App/Rendering/Walk/WalkVisibilityMath.cs:176-198 Exact branch sense, with explicit boundary tests in WalkVisibilityMathTests.cs:178-216.
whole-mesh admission rather than per-slice GPU clip src/AcDream.App/Rendering/Wb/WbDrawDispatcher.WalkClassify.cs:527-567 Correct: it tests all live views, but any admission returns RetailWholeMeshSlot (slot 0) rather than the admitted slice slots.
one batch set after admission WbDrawDispatcher.WalkClassify.cs:391-418,444-471,824-867 Correct for non-player content: one first admission emits every surviving mesh batch once.
part stamp after admission WbDrawDispatcher.WalkClassify.cs:79-136 Correct ordering: rejected parts do not stamp; accepted non-player parts dedupe by (ProjectionId, PartIndex).
local-player stamp bypass WbDrawDispatcher.WalkClassify.cs:126-136 Deliberately matches retail's IsPartOfPlayerObj exception.
whole EnvCell shell once per stamp src/AcDream.App/Rendering/Walk/WalkFrameDriver.cs:1413-1453 and RetailPViewPassExecutor.WalkLeaf.cs:246-272 Correct geometric contract: reverse shell loop, CPU cell stamp set, slot-zero complete shell.
mid-DrawCells stamp re-arm WalkFrameDriver.cs:1299-1319 and WbDrawDispatcher.WalkClassify.cs:102-118 Correctly re-arms parts and cells after a landscape turn. OnLandscape requires positive active views, so _skyDrawnThisFrame is the current semantic proxy for retail's outside-view branch.
all shells before all object lists WalkFrameDriver.cs:1419-1453 Matches the two reverse loops in PView::DrawCells.
building order WalkFrameDriver.cs:1215-1268 and RetailFrameWalk.cs:197-248 Matches alpha barrier -> portal pass -> shell. UseIndoorMembershipOnlyRouting clears GPU clip routing before the walk (RetailPViewPassExecutor.cs:212-217), so building shells are not slice-clipped by the base slot.

The focused current test ClassifyEntityForWalk_PortalViewsAdmitOneCompleteUnclippedMesh (WalkStaticStreamPopulatorTests.cs:433-461) uses two admitted view slots and asserts a single output batch at slot zero. ClassifyEntityForWalk_FrameScopeDoesNotStampPortalRejectedPart (:348-392) covers reject-then-admit. ClassifyEntityForWalk_LocalPlayerBypassesDrawnPartStampLikeRetail (:394-431) covers the player exception. RepeatedFloodTurns_DrawEnvCellShellWholeOncePerRetailFrameStamp and LandscapeStampBoundary_RearmsWholeShellForPostClearRootRepaint (WalkFrameDriverTests.cs:589-681) cover the cell stamp and its re-arm.

Differences and high-risk candidates

Priority Current behavior Exact retail behavior Risk / required proof before changing
High RetailAlphaQueue performs a scope-wide stable radix sort by viewer distance (src/AcDream.App/Rendering/RetailAlphaQueue.cs:393-447) and describes itself as one merged stream. Retail sorts each CObjCell's CShadowPart list by CYpt before submission, then appends subsets to two separate FIFO lists (clip first at flush, alpha second). FlushAlphaList does no sort. Outer cell/building walk order is part of the ordering contract. A scope-wide resort can reorder subsets across cells, shell turns, particles, and renderer sources, and it collapses the clip-list/alpha-list phase split. This is an exact known difference, not established equivalent behavior.
High EnvCell shells are always split into an opaque pass followed by an immediate transparent pass per cell (RetailPViewPassExecutor.WalkLeaf.cs:259-272; EnvCellRenderer.cs:832-842). Retail walks authored surface subsets once. Depending on curr_detail_surface and delay-mask state, each transparent subset is either immediate at its authored subset position or appended to the delayed list. Current behavior is not state-equivalent across detail settings and can change subset/object interleaving. Do not “fix” it by blindly queueing all EnvCell alpha: retail is conditional.
High Building/static/entity transparent batches are routed through the current global queue via WalkStaticStreamPopulator / SubmitWalkAlphaInstance (WbDrawDispatcher.WalkClassify.cs:882-918). A building with non-null building_detail_surface bypasses alpha delay and renders subsets immediately; with null detail surface, only mask-selected subsets delay. The current unconditional translucency-class routing does not model the retail detail-surface gate. This can move building surfaces across the pre-building and end-of-scope flush boundaries.
Medium Part dedupe identity is (RenderProjectionId, PartIndex). Retail identity is the actual CPhysicsPart *, shared wherever that part is inserted. Equivalence requires every repeated cell membership of one logical part to reuse the same projection id and composite part index. The static journal appears designed for stable ids, but an installed-data multi-cell identity audit is still required; duplicate projections would defeat the stamp.
Medium The port transforms the drawing sphere to world space and tests it against captured world planes, rather than caching portal_obj_plane through an obj_view_set equivalent. Retail transforms the active planes/sphere through FrameCurrent and publishes local_object_center/radius. Mathematically aligned for the current affine transform contract and maximum-axis radius scale, but side effects (local_object_*) and any nonstandard/skew transform consumer are not represented. Do not infer broader obj_view_set equivalence from the sphere result alone.
Medium Local-player stamp bypass is covered only as two repeated classifier calls without a real multi-cell/multi-view transparent part gate. Retail can submit the local player's whole built part for every admitted cell/view and can therefore append delayed subsets repeatedly. This seemingly duplicate behavior is retail. A generic “dedupe all parts” cleanup would reintroduce chopped-player seams; a live/installed-DAT transparent-player case remains unpinned.
Low Missing authored SelectionSphere falls back to “any view exists -> whole mesh.” Retail dereferences CGfxObj::drawing_sphere; normal production assets are expected to have it. Sensible corruption fallback, but not an exact retail branch. It must remain outside claims of byte-for-byte admission parity.

Dirty-worktree note

At evidence close, other agents had unrelated/uncommitted work visible in six source paths plus two other OH1 evidence files. Five source paths were content-identical to HEAD but worktree-marked (line-ending/index state); LandblockBuildFactory.cs had a one-blank-line deletion. None intersects the built-mesh/view functions audited here, and this report did not touch them. They must not be mistaken for fixes arising from this review.

Contradictions retired by this evidence

  1. “Every admitted portal view redraws a non-player built mesh.” False. The outer loop does call DrawMeshInternal, but the stamp check is inside that loop. Only the first admitted view submits. The statement is true only for the local-player exception; force mode is a separate portal-only path.
  2. obj_view_set proves the built mesh is clipped to the portal.” False. It prepares legacy object/BSP plane state. Ordinary built DrawMesh receives none of it.
  3. “DrawEnvCell once per view means one clipped shell copy per view.” False. DrawEnvCell stamps before work and the built branch draws the complete shell on the first call.
  4. “The alpha list remembers which portal admitted the subset.” False. AlphaListEntry has no such state.
  5. “All transparent shell subsets are delayed.” False. A non-null current detail surface forces the immediate subset path.

One older research source, docs/research/2026-06-11-holistic-map/wf1-statics-dynamics.md, contains both the correct first-admission description and a later per-view-redraw inference. The later inference must not be used as an oracle; the Ghidra-arbitrated inner stamp branch above resolves it.

Unresolved facts and bounded follow-up

These do not weaken the whole-mesh/stamp conclusion, but they remain necessary for a complete overhaul implementation:

  1. Runtime registry/user inputs may override s_AlphaDelayMask and MultiPassAlpha, and detail generation may return null. These are explicit inputs to the exact branch table in oh1-alpha-list-contract.md, not unresolved branch sense.
  2. No mask/list classification ambiguity remains. The complete constructed mask domain, priority, signed-stippling quirk, clip-versus-alpha selection, material-alpha fallback, and multipass clip-map duplicate are established in oh1-alpha-list-contract.md.
  3. Installed-DAT proof that one logical static/dynamic part appearing in several cell lists always carries one stable RenderProjectionId and part index in acdream.
  4. A real multi-view local-player case containing delayed transparent subsets, to pin whether repeated retail submissions visibly overblend or are neutralized by depth/material state.
  5. The non-built CGfxObj route is a no-op in DrawMeshInternal; whether any surviving retail caller renders such content through another path is outside this slice. It must not be “filled in” without a separate caller census.
  6. PView::DrawCells's num_view == 0 decomp path forms a 0xffff loop count. This appears to be an impossible/corrupt-state artifact under normal construction; current production should fail loud or preserve its established invariant rather than emulate 65,535 iterations.

Binding constraints for Campaign OVERHAUL implementation slices

  • Keep built meshes at slot zero/no portal GPU clip after sphere admission.
  • Preserve first-admitted-view semantics: rejected views cannot stamp.
  • Preserve separate part and EnvCell stamps, and the conditional mid-DrawCells device-stamp advance.
  • Preserve the local-player stamp exception.
  • Keep portal-plane polygon clipping confined to portal view construction and portal depth fans/seals unless another named-retail function proves a different consumer.
  • Do not “simplify” force mode into a whole building draw; it is a two-pass portal-only BSP walk followed by a separate ordinary shell draw.
  • Redesign transparent ownership from the exact detail-state + delay-mask branch and the two retail FIFO lists, not from a generic global distance-sort abstraction.