The distilled port-ready model for FW1: camera-cell rooting, the invisible-panel primitive (punch far-Z / seal own-depth, byte-verified constants), the far-to-near landscape walk, the building two-pass portal machinery with its push/pop asymmetry, the interior flood + DrawCells passes, the view machinery, and the constants/struct anchor table. The panel question is resolved: retail DOES draw depth-only portal-polygon panels via DrawPortalPolyInternal - AD-117 re-invented a real mechanism at the wrong site. FW0 is complete: oracle fixtures, replay helper, decomp model. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
14 KiB
FW0 — the retail frame walk, port-ready pseudocode model (2026-08-30)
The distilled model FW1 ports from. Sources: the five decomp reads in
2026-08-30-fw-walk-pseudocode-appendix.md
(BN pseudo-C cross-checked against the Ghidra patchmem decomp wherever
x87-flag mush demanded it), reconciled against the live traces in
2026-08-30-fw-walk-oracle/.
Every function below carries its named symbol + address; the appendix has
the full extractions with per-function gotchas.
1. The frame root — SmartBox::RenderNormalMode @0x00453aa0
Per frame (via SmartBox::Draw → DrawNoBlit @0x00454c20, which first
runs update_viewer and skips rendering entirely when viewer_cell is
null):
if ((viewer.objcell_id & 0xFFFF) < 0x100) { // CAMERA cell is outdoor
LScape::update_viewpoint(lscape, viewer.objcell_id);
Render::update_viewpoint(&viewer);
Render::set_default_view(); // full-screen view
Render::useSunlightSet(1);
LScape::draw(lscape); // → §3
} else { // camera in an EnvCell
if (viewer_cell->seen_outside)
LScape::update_viewpoint(lscape, Position::get_outside_cell_id(&viewer));
Render::update_viewpoint(&viewer);
render_device->DrawInside(viewer_cell); // → PView::DrawInside, §5
}
D3DPolyRender::FlushAlphaList(0f);
SmartBox::update_viewer @0x00453ce0 resolves the camera pose and cell:
a CTransition sphere sweep (the global viewer_sphere) from a pivot on
the player (CameraManager pivot part + offset) toward the camera's sought
position; t->sphere_path.curr_cell becomes viewer_cell. Fallbacks:
AdjustPosition on the raw sought position, else camera at the player
with viewer_cell = null (frame skipped). The frame roots at the
CAMERA's cell — proven live by the porch-cam trace.
2. The invisible-panel primitive — D3DPolyRender::DrawPortalPolyInternal @0x0059bc90
The single primitive behind both "punch" and "seal": draws one portal
polygon as an untextured CULL_NONE triangle fan, depth-test ALWAYS,
z-write ON, color alpha 0 (invisible under SRCALPHA/INVSRCALPHA).
Byte-verified in the raw pseudo-C: SetDepthBufferMode(DEPTHTEST_ALWAYS, (mode>>2)&1) @0059be02; forced Z 0.999998987f @0059bf4b.
flag=1(mode globalmaxZ1=7): every vertex Z forced to 0.999998987 (far plane) — the PUNCH: re-opens the aperture's depth so look-in content can draw into it.flag=0(modemaxZ2=6): real projected Z — the SEAL: writes the portal surface's own depth at the aperture. Only seals incrementportalsDrawnCount(the deferred z-clear trigger).- Trivial reject: polygon wholly outside a ±12.0 local-XY box (the
24 m cell); the reject-loop polarity is an FP-status idiom (
test ah,0x44) BN could not lift — re-verify polarity at FW1 against the binary bytes before porting. - Debug: raising the mode's alpha bit (via the maxZ1/maxZ2 globals, .data @0x820e18/0x820e14) renders the panels visibly in cycling colors.
- Goes through
DrawPrimitiveUP, gated by the registry bools_bAllowDrawPrimitiveUP(default 1). - Restores NO state — callers rely on backup/restore_curr_state.
This resolves the campaign's panel question: the "seal panels" are not mesh subsets (those authored Translucency-1.0 subsets stay never-drawn); they are the buildings'/cells' PORTAL POLYGONS drawn depth-only by this primitive. AD-117's stamps unknowingly re-invented a real retail mechanism at the wrong site with the wrong geometry.
3. The outdoor walk — LScape::draw @0x00506330
GameSky::Draw(sky, 0) // dome
LScape::draw_check_blocks(this) // per-view visibility union (below)
for i = mid_width² - 1 .. 0: // block_draw_list built near-to-far →
blk = block_draw_list[i] // backwards walk = FAR-TO-NEAR
if (blk && blk->in_view) render_device->DrawBlock(blk)
if (weather) GameSky::Draw(sky, 1)
- Grid:
mid_width=11 × 11 blocks (mid_radius=5); block side 192 m (FPU-elided in BN — pinned by the explicit 24 m cell pitch). get_block_order@0x00504c50: viewer's block first, then concentric rings (8 quadrant-symmetric slots per step from static coefficient tables); rebuild only when the viewer crosses a LANDBLOCK boundary.- Block frames are viewer-relative: origin
((row−viewer_b_xoff)·192, (col−viewer_b_yoff)·192, 0)— the landscape renders in viewer-block-local space for float precision. draw_check_blocks@0x00505f80: clears every block/cellin_view, then FOR EACH VIEW ofRender::PortalList(outdoor: the default view; interior: the exit views) unions visibility via clip-height interval grids (get_clip_heightcorners →block_checkvs max/min_zval);landcell_check@0x005050a0 refines per-cell (non-8×8 far blocks: all cells PARTIAL; ENTIRELY_INSIDE blocks: all cells 2; else per-cell corner grids at 24 m pitch). A cell marked by an earlier view is never downgraded.CLandBlock::calc_sq_draw_order@0x0052f4a0: per-block cell order, closest cell (picked by the 9-case compass direction switch) written LAST, rings filled backwards → forward iteration = far-to-near. Early-out compares onlyclosest.x/y, notdir(keep this quirk).
Per block, RenderDeviceD3D::DrawBlock @0x005a17c0: pass 1 updates
in-view cells' shadow objects + insertion-sorts shadow parts by depth;
pass 2 per cell (far-to-near): terrain (DrawLandCell — detail texture
only on full 8×8 blocks) then DrawSortCell @0x0059f140:
if (cell->building) render_device->DrawBuilding(cell->building) // §4
render_device->DrawObjCell(cell) // scenery/statics/dynamics via sorted shadow parts → CPhysicsPart::Draw(part, 0)
Mid-walk alpha pressure valve: FlushAlphaList(flush) with the global
flush=0.75 (flushes only above ~2250 queued alpha meshes).
4. Buildings — RenderDeviceD3D::DrawBuilding @0x0059f2a0
outdoor_pview->outdoor_portal_list = b->portals; // ALWAYS
CPhysicsPart::UpdateViewerDistance(part0); // degrade level
if (part0->gfxobj[deg_level] == 0) return; // degraded out → whole building skipped
CBuildingObj::curr_pos = &b->m_position; // frame-restore anchor for the portal pass
set building detail-surface state; FlushAlphaList(0f);
CPhysicsPart::Draw(part0, 1); // PORTAL pass
ObjBuildingOrBuildingPart = 1;
CPhysicsPart::Draw(part0, 0); // SHELL pass (normal mesh; per-frame dedupe applies)
ObjBuildingOrBuildingPart = 0;
The portal pass, through DrawMesh @0x005a0860 (per portal view,
building_view-gated; portal-flagged draws run even when viewconeCheck
says OUTSIDE) → DrawMeshInternal @0x0059f360, walks the building's
drawing-BSP twice (BSPTREE::build_draw_portals_only — portal nodes
visited far-to-near, plane epsilon 0.0002):
- Pass 1 — punch. Each in_portal →
PView::DrawPortal@0x005a5ab0 →ConstructView(CBldPortal)@0x005a59a0: viewer sidedness must match the authoredportal_side(IN_PLANE within ±0.0002 fails BOTH gates);GetClipmust leave ≥3 points; the destination EnvCell must be currently Visible (CEnvCell::GetVisible— not loaded ⇒ silently no punch, no fallback). On success:DrawPortalPolyInternal(poly, 1)— the far-Z punch — and the clipped view is pushed onto the destination cell's topportal_viewviaRender::copy_view. - Pass 2 — look-in. Same walk/gates; on success recurses
ConstructView(CEnvCell)(the §5 flood seeded throughother_portal_id) and callsPView::DrawCells— the trace'sDC ov=0punches: interior cells + objects drawn into the punched aperture (no landscape, no clear, no sealing on this path). - Push/pop asymmetry (MUST preserve):
ConstructView(CBldPortal)pops the building frame only on SUCCESS (before recursing); on failure the frame stays pushed andDrawPortalcompensates by re-pushingCBuildingObj::curr_posonly on success. DrawCellsbumpsm_nFrameStamp, re-arming the per-frame part dedupe so objects already drawn outdoors can draw again inside a look-in.- DEAD in 2013 (do not port):
DrawBuildingLeaf+curr_leaf_cells(pre-EnvCell interior model);PView::DrawPortal's pass==3 seal-on-fail branch (pass only ever ∈ {1,2}).
5. Interior frames — PView::DrawInside @0x005a5860
CEnvCell::curr_view_push(cell); // num_view++ (0x48-byte lazy slot, counters reset)
PView::add_views(this, cell->num_stabs, cell->stab_list); // push a view slot on every visible stab cell
Render::positionPush(3, identity Position stamped with cell DID);
Render::copy_view(cell->top_view, null, 4); // root view = full viewport quad
PView::ConstructView(this, cell, 0xffff); // the flood (below)
PView::DrawCells(this, 0); // dead arg
Render::positionPop(); remove_views(...); cell->num_view -= 1;
ConstructView(CEnvCell) @0x005a57b0 — the flood: zero
outside_view.view_count, bump master_timestamp, reset todo/draw
lists; seed InitCell(cell, 0xffff) + InsCellTodoList(cell, 0);
then pop-from-END: append cell to cell_draw_list (grow +30), mark top
view cell_view_done, ClipPortals → AddViewToPortals (clips each
portal against the cell's current view and enqueues neighbors with their
clipped views; portals leading outdoors raise outside_view).
PView::DrawCells @0x005a4840 — the draw, all passes far-to-near
(cell_draw_list walked from the end):
if (outside_view.view_count > 0) { // 'ov' in the traces (field, not the dead arg)
Render::PortalList = &outside_view;
LScape::draw(lscape); // landscape THROUGH the exit views (§3)
FlushAlphaList(0f); m_nFrameStamp += 1;
if (forceClear || portalsDrawnCount != 0) // count reset only on the !forceClear arm
Clear(flag 4 /*z-buffer*/, black, z=1.0); // deferred clear triggered by earlier SEALS
for cells far-to-near, per view of the cell's top portal_view:
for each portal with other_cell_id == 0xFFFFFFFF: // exits to the landscape
DrawPortalPolyInternal(portal.poly, 0); // SEAL @ own depth (verified @0059a49b7)
}
useSunlightSet(0); restore_all_lighting();
for cells far-to-near, per view: setup_view; DrawEnvCell(cell); // BSP geometry
for cells far-to-near: PortalList = cell top view; DrawObjCellForDummies(cell); // objects
Degenerate guard NOT to port: num_view==0 would loop 65535 views —
unreachable (every listed cell was curr_view_pushed).
6. View machinery
Render::set_view@0x0054d0e0 — pure CPU global install: the active view's edge count, in-mask (npnts+1bits: edges + the CY plane), vertex/plane base, screen bounds. No GPU state.Render::copy_view@0x0054dfc0 — appends ONE view: perspective-divided screen points (drop near-duplicates/collinear within ~1 px; <3 survivors ⇒ reject; cap 31), bounds, and per-edge WORLD-space planes (N = normalize(cross(ray_i, ray_i+1)),d = −dot(N, viewpoint)).copy_view(dest, null, 4)= the full-viewport root quad.PView::GetClip@0x005a4320 — projects the portal polygon (xformStart), reverses winding for NEGATIVE side, and (do_clip) clips viaACRender::polyClipFinishagainst the installed view. ≤32 verts assumed. The only CPU polygon clip in the frame.Render::viewconeCheck@0x0054c250 — sphere vs the CY plane + the active view's edge planes, in viewer-block space. Cull strict (d < −r); partial inclusive (d ≤ r); publisheslocal_object_center/radiusALWAYS (even when OUTSIDE). BN's version of this body is unusable (inverted flags) — the model here is Ghidra-verified.
7. Constants and anchors
| Item | Value |
|---|---|
| Outdoor cell test | (objcell_id & 0xFFFF) < 0x100 |
Plane/side epsilon F_EPSILON |
0.000199999995f (0x3951B717) |
| Punch far-Z | 0.999998987f |
| Panel depth mode | test ALWAYS, write ON (mode bit2), alpha 0 |
| Panel local-XY reject box | ±12.0 |
| Landscape grid | mid_width 11, mid_radius 5, block 192 m, cell 24 m |
| Alpha pressure valve | global flush = 0.75 (~2250 meshes) |
| copy_view vertex cap | 31 (0x1F) |
| Flood list growth | +30 (0x1E) |
| BoundingType | 0 OUTSIDE, 1 PARTIALLY_INSIDE, 2 ENTIRELY_INSIDE |
| Sidedness | 0 POSITIVE, 1 NEGATIVE, 2 IN_PLANE (always rejects) |
| Exit-portal sentinel | other_cell_id == 0xFFFFFFFF (cell), 0xFFFF (no-through seed) |
Struct anchors (acclient.h): PView {outside_view@0 (view_count@+0x38),
draw_landscape, outdoor_portal_list, cell_draw_list@+0x50,
cell_draw_num@+0x60, cell_todo_list, cell_todo_num, lscape@+0x78};
CEnvCell.m_DID@+0x28, num_view@+0x134, portal_view@+0x138;
CCellPortal stride 0x18 {other_cell_id, other_cell_ptr, portal@+8,
portal_side, other_portal_id, exact_match}; CBldPortal {portal_side,
other_cell_id, other_portal_id, exact_match, num_stabs, stab_list};
CLandBlock.in_view@+0xFC, CLandCell stride 0x108 (in_view@+0x104).
8. What the traces + code jointly prove about #456
Outdoor frames draw EVERYTHING distance-eligible — the far building first (far-to-near) — and correctness is pure depth: nearer terrain, shells, punches, and look-in cell geometry bury the vista. Interior frames never roster the far building (exit-view cone culls it) and SEAL their exit apertures at own depth after drawing the landscape through them, so nothing drawn later can leak into the aperture. There is no hiding mechanism to invent; FW3/FW4 reproducing this walk resolves #456 wholesale.
9. Verification status
Byte-verified against the raw pseudo-C by hand: the seal loop
(other_cell_id==0xffffffff → DrawPortalPolyInternal(poly,0) @0059a49af),
the deferred z-clear (@005a48a9), DEPTHTEST_ALWAYS + z-write-from-mode
(@0059be02), punch Z 0.999998987f (@0059bf4b), the fan submit (@0059bf84).
Ghidra-arbitrated where BN mush was flagged: the rooting condition, the
viewconeCheck comparisons, the ConstructView sidedness decode, the
DrawCells dead argument. Re-verify at FW1 before relying on: the ±12
reject polarity in DrawPortalPolyInternal (FP-status idiom), the
DrawBlock alpha-flush comparison polarity, and InsCellTodoList's
insert position (LIFO pop order depends on it — port it from its body
@0x005a4f50, not from assumption). Resolved non-issue: the trace's ov
is the FIELD outside_view.view_count (probe read PView+0x38), not
DrawCells' dead argument — reports 2 and 5 agree once combined.