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)); } } }