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:
Render::copy_viewbuilds one screen polygon plus one world plane per edge.Render::set_viewinstalls one polygon's plane list and screen bounds in globals.Render::viewconeCheckclassifies a drawing sphere against those planes.- Once an ordinary built
CGfxObjis admitted,DrawMeshInternalsubmits the complete constructed mesh. It does not pass the active portal polygon or its planes toD3DPolyRender::DrawMesh. - A non-player
CPhysicsPartis 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. - A built
CEnvCellstamps itself on the firstDrawEnvCellcall and submits its complete constructed shell. Later portal views and later flood encounters in that render stamp are no-ops. - 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.
- Actual polygon clipping is used for portal-chain construction and portal depth fans/seals (
PView::GetClipandDrawPortalPolyInternal, both throughACRender::polyClipFinish). It is not used on ordinary built mesh triangles. - A delayed transparent subset is owned by the built-mesh submission that first admitted the part/cell. Every
AlphaListEntrystores mesh/subset/surface/flags; the first entry for each list also stores material and world matrix underbNewObject, 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.txtdocs/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_view — 0x0054dfc0
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, setw = 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_view — 0x0054d0e0
Purpose: install one view_poly as global active view state.
Exact writes from Ghidra disassembly:
portal_view_num(0x008662dc) = requested indexportal_view(0x00846044) = suppliedview_type *portal_npnts(0x00846050) = polygon vertex countportal_inmask(0x008661b8) =(1 << (portal_npnts + 1)) - 1portal_vertex(0x00846040) = the polygon's firstview_vertexxmin/xmax/ymin/ymax= polygon bounds
There is no render call, scissor installation, user clip-plane installation, or mesh mutation.
Render::obj_view_set — 0x0054b9b0
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 callobj_view_set; - the built
D3DPolyRender::DrawMeshoverload has no plane/view argument; DrawEnvCellcallsobj_view_set, but its built branch still callsD3DPolyRender::DrawMeshdirectly with only surfaces and the constructed mesh;- the force/building branch calls
obj_view_setbecause it walksdrawing_bspportals, not because it clips the constructed mesh.
The presence of obj_view_set is not evidence of GPU clipping of a built mesh.
Render::viewconeCheck — 0x0054c250
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_centerandRender::local_object_radiusbefore classification; - test the viewer CY plane, then every active edge plane.
Ghidra-arbitrated branch senses:
- CY reject at
0x0054c31b..0x0054c32e:distance < -radiusreturnsOUTSIDE (0); equality is not rejected. - edge reject at
0x0054c37b..0x0054c3bd: the same strictdistance < -radiusrule. - partial is inclusive:
distance <= radiusmarks partial;distance == +radiusisPARTIALLY_INSIDE (1), not entirely inside. - only when every tested distance is
> radiusis the resultENTIRELY_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::Draw — 0x0050d7a0
Purpose: resolve the current degrade mesh and enter RenderDeviceD3D::DrawMesh.
Ghidra-arbitrated outer gates:
draw_state & 1at0x0050d7a3..0x0050d7a7: set means skip.- ordinary call (
force == 0) at0x0050d7b8..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
CGfxObjskips.
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::DrawMesh — 0x005a0860
Purpose: per-view sphere admission around DrawMeshInternal.
With Render::PortalList == null:
- push the object position;
- call
viewconeCheck(drawing_sphere)once; - on non-
OUTSIDE, run selection-ray state and callDrawMeshInternal; - on
OUTSIDE, callDrawMeshInternal(force=true, OUTSIDE)only for the force flavor.
With a portal list:
- loop
i = 0 .. view_count-1; - skip indices excluded by
building_view(building_view == -1 || building_view == iis the admission branch at0x005a08c0..0x005a08d3); set_view(view, i);viewconeCheck(drawing_sphere);- call
DrawMeshInternalon every non-OUTSIDEresult; - 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::DrawMeshInternal — 0x0059f360
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;
IsPartOfPlayerObjtrue -> bypass stamp;- otherwise,
GetDrawnThisFrametrue -> return immediately at0x0059f3fc; - otherwise, call
SetDrawnThisFramebefore 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): callD3DPolyRender::DrawMesh(gfxobj, constructed_mesh)once. This is the complete constructed mesh. - built + force (
force == true): callobj_view_set, savebuilding_view, set it toportal_view_num, walkdrawing_bspwithbuild_draw_portals_only(..., 1)and(..., 2), restorebuilding_view, and return. It does not submitconstructed_mesh.
Thus the force flavor is a portal-only BSP pass, not a forced whole-mesh draw.
RenderDeviceD3D::DrawEnvCell — 0x0059f170
Ghidra-arbitrated order:
GetDrawnThisFrame; equal stamp jumps directly to return (0x0059f175..0x0059f17e).SetDrawnThisFrameimmediately (0x0059f184..0x0059f186).obj_view_set, lighting minimization, and environment detail-state setup.- if
use_built_mesh != 0, update static-light vertex colors and callD3DPolyRender::DrawMesh(num_surfaces, surfaces, constructed_mesh, skipNoTexture=true), then return. - otherwise append every
structurepolygon toRender::PolyListwithplaneMask = -1, then callpolyListFinishInternal.
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::DrawMesh — 0x0059d4a0 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
RenderMeshSubsetimmediately, 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:
AddMeshToAlphaListappends oneAlphaListEntryto 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. OnlybNewObject=trueentries 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.
FlushAlphaListdrains the clip list in append order, then the alpha list in append order, callingRenderMeshSubsetfor 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::DrawObjCellForDummies—0x005a0760RenderDeviceD3D::DrawObjCell—0x005a1a40RenderDeviceD3D::DrawPartCell—0x005a07a0CShadowPart::draw—0x006b50d0
Order:
- update the object cell;
- insertion-sort its shadow parts when there is more than one;
DrawObjCell -> DrawPartCellwalks each shadow part;CShadowPart::drawcalls onlyCPhysicsPart::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:
PView::GetClip @ 0x005a4320transforms an authored portal polygon, fixes winding, and clips it to the current active view and near plane. Its output is passed toRender::copy_viewbyPView::ConstructView; this constructs the next view cone.D3DPolyRender::DrawPortalPolyInternal @ 0x0059bc90transforms a portal polygon, callspolyClipFinish, 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::DrawCells — 0x005a4840
When outside_view.view_count != 0:
useSunlightSet(1);PortalList = &outside_view;LScape::draw;FlushAlphaList(0);- increment
RenderDevice::m_nFrameStampat0x005a4886; - conditionally clear depth when
forceClearis set orportalsDrawnCountwas non-zero, then zero that count; - reverse-walk the flood cells and every live view to draw clipped exit-portal seals.
Then, regardless of whether an outside view existed:
- disable sunlight and restore lighting;
- reverse-walk all flood cells for shells; for each live view call
CEnvCell::setup_view @ 0x0052c430, thenDrawEnvCell; - restart at the reverse end and walk all object lists; install that cell's top
portal_view_typeasPortalList, then callDrawObjCellForDummies; - 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::DrawBuilding — 0x0059f2a0
After degrade/geometry admission:
- install building detail state;
FlushAlphaList(0);CPhysicsPart::Draw(part, 1)— force portal-only BSP pass;- set
ObjBuildingOrBuildingPart = 1; CPhysicsPart::Draw(part, 0)— ordinary complete building shell;- 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
CGfxObjtriangles. - Not built
CEnvCelltriangles. - 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 +0xdcversusRenderDevice::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.
- Outer skip:
- Cell stamp:
CEnvCell::m_current_render_frame_num +0x130versus the same device stamp.- Get/set at
DrawEnvCell @ 0x0059f175..0x0059f186. - It is set before lighting or mesh work.
- Get/set at
- Re-arm:
PView::DrawCells @ 0x005a4886increments 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
- “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. - “
obj_view_setproves the built mesh is clipped to the portal.” False. It prepares legacy object/BSP plane state. Ordinary builtDrawMeshreceives none of it. - “DrawEnvCell once per view means one clipped shell copy per view.” False.
DrawEnvCellstamps before work and the built branch draws the complete shell on the first call. - “The alpha list remembers which portal admitted the subset.” False.
AlphaListEntryhas no such state. - “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:
- Runtime registry/user inputs may override
s_AlphaDelayMaskandMultiPassAlpha, and detail generation may return null. These are explicit inputs to the exact branch table inoh1-alpha-list-contract.md, not unresolved branch sense. - 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. - Installed-DAT proof that one logical static/dynamic part appearing in several cell lists always carries one stable
RenderProjectionIdand part index in acdream. - 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.
- The non-built
CGfxObjroute is a no-op inDrawMeshInternal; 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. PView::DrawCells'snum_view == 0decomp path forms a0xffffloop 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-
DrawCellsdevice-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.