using System.Numerics;
namespace AcDream.App.Rendering.Walk;
/// The composition context: the cell/building halves plus the
/// frame-level state retail keeps in globals (the CY near plane from
/// Render::update_viewpoint, and the active-view installation the
/// building clip consumes).
public interface IRetailFrameWalkContext : IWalkBuildingFrameContext
{
WalkPlane CyPlane { get; }
/// Render::set_view at the frame level: install view
/// of as the active
/// clip context for subsequent building-polygon clips.
void SetActiveView(WalkPortalView views, int index);
float ViewportWidth { get; }
float ViewportHeight { get; }
}
///
/// Campaign FW1 — the frame walk root, composing the ported machinery into
/// retail's frame shapes (model:
/// docs/research/2026-08-30-fw-walk-pseudocode.md §1, §3-§5; oracle:
/// docs/research/2026-08-30-fw-walk-oracle/README.md):
///
///
/// - Rooting (SmartBox::RenderNormalMode @0x00453aa0): the
/// CAMERA cell's low word < 0x100 → outdoor root (default full-screen
/// view + the landscape walk); otherwise → DrawInside.
/// - Outdoor (LScape::draw @0x00506330): visibility per view,
/// blocks far-to-near, per-block cells far-to-near, buildings at their
/// cell's turn with the two-pass portal machinery (punches + look-ins on
/// the OUTDOOR pview — the traces' pv=009d4b08 ov=0).
/// - Interior (PView::DrawInside @0x005a5860 +
/// DrawCells @0x005a4840): the flood on the INTERIOR pview
/// (pv=009d4a80), then the landscape drawn THROUGH the exit views
/// when any survive (ov>0).
///
///
/// Two PView instances exist exactly as the live traces showed.
///
public sealed class RetailFrameWalk
{
private readonly WalkPView _interiorPView = new();
// The outdoor pview never draws the landscape through its look-in
// floods (the landscape is already drawn when buildings punch), so its
// draw_landscape is FALSE and look-in DCs always read ov=0 — exactly
// the pv=…/ov=0 pattern of every look-in in the oracle traces.
private readonly WalkPView _outdoorPView = new() { DrawLandscape = false };
private readonly WalkPortalView _defaultView = new();
public WalkPView InteriorPView => _interiorPView;
public WalkPView OutdoorPView => _outdoorPView;
/// Retail global alwaysDrawObjects (.data default 1
/// @0x00820ed4): cell contents draw for every cell of an in-view block
/// regardless of per-cell visibility.
public bool AlwaysDrawObjects = true;
/// Retail global Render::deg_mul — DYNAMIC: the
/// auto-tuner (auto_update_deg_mul) swings it with frame load
/// (positive ⇒ degrade thresholds slide toward each level's max;
/// negative ⇒ toward its min). The oracle captures pin ≈+0.99 for every
/// fixture except doorway-still, whose capture ran under cdb load with
/// the multiplier depressed (the recon session's live dump read −0.99
/// under the same conditions).
public float DegradeMultiplier = WalkBuilding.DefaultDegradeMultiplier;
/// The per-frame root (SmartBox::RenderNormalMode).
/// may be null only when the camera is
/// outdoors.
public void WalkFrame(
uint cameraCellId, WalkCell? cameraCell, WalkLandscape landscape,
IRetailFrameWalkContext ctx, IWalkEventSink sink)
{
if ((cameraCellId & 0xFFFF) < 0x100)
{
// Render::set_default_view @0x0054ef50: the full-screen quad view.
_defaultView.ResetForPush();
WalkCopyView.AppendFullViewportQuad(
_defaultView, ctx.Rays, ctx.WorldViewpoint,
ctx.ViewportWidth, ctx.ViewportHeight);
DrawLandscape(landscape, _defaultView, ctx, sink);
}
else
{
DrawInside(
cameraCell ?? throw new ArgumentNullException(nameof(cameraCell)),
landscape, ctx, sink);
}
}
/// PView::DrawInside + the event half of
/// DrawCells. The geometry/object passes emit no walk events.
public void DrawInside(
WalkCell cell, WalkLandscape landscape,
IRetailFrameWalkContext ctx, IWalkEventSink sink)
{
sink.Emit(WalkEvent.DrawInside(cell.CellId));
cell.PushView();
AddViews(cell.StabList, ctx);
WalkCopyView.AppendFullViewportQuad(
cell.TopView, ctx.Rays, ctx.WorldViewpoint,
ctx.ViewportWidth, ctx.ViewportHeight);
_interiorPView.ConstructView(cell, 0xFFFF, ctx.CellContext);
EmitDrawCells(_interiorPView, sink);
if (_interiorPView.OutsideView.ViewCount > 0)
DrawLandscape(landscape, _interiorPView.OutsideView, ctx, sink);
RemoveViews(cell.StabList, ctx);
cell.PopView();
}
/// LScape::draw: visibility per active view, then blocks
/// far-to-near, cells far-to-near, buildings at their cell's turn.
public void DrawLandscape(
WalkLandscape landscape, WalkPortalView activeViews,
IRetailFrameWalkContext ctx, IWalkEventSink sink)
{
sink.Emit(WalkEvent.Landscape());
landscape.CalcDrawOrder();
landscape.CheckBlocks(ctx.CyPlane, activeViews);
for (int i = landscape.BlockDrawCount - 1; i >= 0; i--)
{
WalkLandBlock? block = landscape.Blocks[landscape.BlockDrawList[i]];
if (block is null || block.InView == WalkBoundingType.Outside)
continue;
int cellCount = block.SideCellCount * block.SideCellCount;
for (int k = 0; k < cellCount; k++)
{
int cellIndex = block.DrawArray[k];
// RenderDeviceD3D::DrawBlock @0x005a19d9: DrawSortCell runs
// when alwaysDrawObjects != 0 (retail .data default 1
// @0x00820ed4) OR the cell IsInView; terrain (DrawLandCell)
// emits no walk event.
if (!AlwaysDrawObjects
&& block.CellInView[cellIndex] == WalkBoundingType.Outside)
{
continue;
}
// RenderDeviceD3D::DrawSortCell @0x0059f140 (decomp-
// confirmed 2026-08-30): DrawBuilding(building) FIRST, then
// DrawObjCell(cell) UNCONDITIONALLY — the building's turn
// (shell + portal machinery) precedes this cell's own
// outdoor-static turn.
if (block.CellBuildings[cellIndex] is WalkBuilding building)
DrawBuilding(building, activeViews, ctx, sink);
sink.OnLandscapeCellTurn((block.LandblockId & 0xFFFF0000u) | (uint)(cellIndex + 1));
}
}
}
/// RenderDeviceD3D::DrawBuilding @0x0059f2a0 at the walk
/// level: the BLD event fires at ENTRY (before the degrade check — the
/// oracle traces' breakpoint sat there, so this stays unconditional for
/// conformance). The ENTIRE rest of the body — the alpha barrier, the
/// portal pass, and the shell draw — sits inside retail's
/// if (part->gfxobj[part->deg_level] != 0) @0x0059f2d3; a
/// degraded-out slot draws NOTHING beyond the BLD event.
/// HasGeometry + a non-null SelectDrawingBsp together model
/// that one gate. Inside the gate, retail's own order
/// (@0x0059f30b–0x0059f345) is D3DPolyRender::FlushAlphaList(0f) →
/// CPhysicsPart::Draw(parts, 1) (the PORTAL flavor — the two-pass
/// punch/look-in walk below) → CPhysicsPart::Draw(parts, 0) (the
/// plain mesh — the building's own SHELL) → flag reset: the alpha
/// barrier and the portal pass both precede the shell draw, not follow
/// it.
public void DrawBuilding(
WalkBuilding building, WalkPortalView activeViews,
IRetailFrameWalkContext ctx, IWalkEventSink sink)
{
sink.Emit(WalkEvent.Building(building.PositionCellId));
if (!building.HasGeometry) return;
// Retail walks the CURRENT degrade level's drawing BSP
// (part->gfxobj[deg_level]); a degraded-out slot skips everything
// after publishing the portal list.
WalkBspNode? bsp = building.SelectDrawingBsp(
ctx.ViewerDistanceTo(building),
degradeMultiplier: DegradeMultiplier);
if (bsp is null) return;
// Additive (Campaign FW3.2b-1): the alpha barrier
// (D3DPolyRender::FlushAlphaList(0f) @0x0059f30b) — gated by the
// SAME part->gfxobj[deg_level]!=0 check as everything below it, so
// this fires only now that both HasGeometry and the bsp lookup have
// passed.
sink.OnBuildingTurn(building);
int viewCount = Math.Max(activeViews.ViewCount, 0);
var passSink = new PortalPassSink(building, sink);
Vector3 viewpoint = ctx.ViewpointInBuilding(building);
for (int v = 0; v < viewCount; v++)
{
ctx.SetActiveView(activeViews, v);
WalkBuildingPortals.BuildDrawPortalsOnly(
bsp, 1, viewpoint,
(portalRef, pass) => WalkBuildingPortals.DrawPortal(
_outdoorPView, building, portalRef, pass, ctx, passSink));
WalkBuildingPortals.BuildDrawPortalsOnly(
bsp, 2, viewpoint,
(portalRef, pass) => WalkBuildingPortals.DrawPortal(
_outdoorPView, building, portalRef, pass, ctx, passSink));
}
// Additive (Campaign FW3.2b-1): CPhysicsPart::Draw(parts, 0)
// @0x0059f331 — the building's own shell mesh — runs AFTER the
// portal walk completes (CPhysicsPart::Draw(parts, 1) just above),
// not before it.
sink.OnBuildingShellTurn(building);
}
private void EmitDrawCells(WalkPView pview, IWalkEventSink sink)
{
uint[] cells = new uint[pview.CellDrawList.Count];
for (int i = 0; i < cells.Length; i++)
cells[i] = pview.CellDrawList[i].CellId;
sink.Emit(WalkEvent.DrawCells(pview.OutsideView.ViewCount, cells));
}
private void AddViews(uint[] stabList, IRetailFrameWalkContext ctx)
{
foreach (uint id in stabList)
ctx.GetVisible(id)?.PushView();
}
private void RemoveViews(uint[] stabList, IRetailFrameWalkContext ctx)
{
foreach (uint id in stabList)
ctx.GetVisible(id)?.PopView();
}
private sealed class PortalPassSink(WalkBuilding building, IWalkEventSink sink)
: WalkBuildingPortals.IWalkPortalPassSink
{
public void OnPunch(WalkPolygon polygon)
{
// The far-Z punch is a depth-only GPU submission (FW2's ordered
// stream); the oracle traces do not log it. Additive (Campaign
// FW3.2b-1): forward to the richer sink so a driver can flush +
// draw the punch fan — building-local space, world transform is
// the driver's job (WalkProductionFrameContext-style lookup).
sink.OnPunchGeometry(building, polygon);
}
public void OnDrawCells(WalkPView pview)
{
uint[] cells = new uint[pview.CellDrawList.Count];
for (int i = 0; i < cells.Length; i++)
cells[i] = pview.CellDrawList[i].CellId;
sink.Emit(WalkEvent.DrawCells(pview.OutsideView.ViewCount, cells));
}
}
}