feat(render) Campaign FW1: the frame-walk composition root

RetailFrameWalk composes the ported machinery into retail frame shapes:
SmartBox::RenderNormalMode rooting (camera-cell low word < 0x100),
PView::DrawInside + the DrawCells event half (interior pview, the
traces pv=009d4a80), LScape::draw (per-view visibility, blocks
far-to-near, per-block cells far-to-near, buildings at their cell turn)
and RenderDeviceD3D::DrawBuilding (BLD at entry before the degrade
check; two-pass BSP portal walk per active view on the outdoor pview,
the traces pv=009d4b08). WalkLandscape ports the block grid +
draw_check_blocks/landcell_check visibility (192 m/24 m pitch,
viewer-relative, union across views, never downgrade). Six composition
tests: outdoor far-to-near emission, degrade-entry event, interior
ov=0/ov=1 shapes, rooting, complete view unwind.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-08-30 10:06:09 +02:00
parent f05b93b5b5
commit 4b401a08ed
3 changed files with 638 additions and 0 deletions

View file

@ -0,0 +1,194 @@
using System.Numerics;
namespace AcDream.App.Rendering.Walk;
/// <summary>The composition context: the cell/building halves plus the
/// frame-level state retail keeps in globals (the CY near plane from
/// <c>Render::update_viewpoint</c>, and the active-view installation the
/// building clip consumes).</summary>
public interface IRetailFrameWalkContext : IWalkBuildingFrameContext
{
WalkPlane CyPlane { get; }
/// <summary><c>Render::set_view</c> at the frame level: install view
/// <paramref name="index"/> of <paramref name="views"/> as the active
/// clip context for subsequent building-polygon clips.</summary>
void SetActiveView(WalkPortalView views, int index);
float ViewportWidth { get; }
float ViewportHeight { get; }
}
/// <summary>
/// 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):
///
/// <list type="bullet">
/// <item>Rooting (<c>SmartBox::RenderNormalMode</c> @0x00453aa0): the
/// CAMERA cell's low word &lt; 0x100 → outdoor root (default full-screen
/// view + the landscape walk); otherwise → <c>DrawInside</c>.</item>
/// <item>Outdoor (<c>LScape::draw</c> @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' <c>pv=009d4b08 ov=0</c>).</item>
/// <item>Interior (<c>PView::DrawInside</c> @0x005a5860 +
/// <c>DrawCells</c> @0x005a4840): the flood on the INTERIOR pview
/// (<c>pv=009d4a80</c>), then the landscape drawn THROUGH the exit views
/// when any survive (<c>ov&gt;0</c>).</item>
/// </list>
///
/// Two PView instances exist exactly as the live traces showed.
/// </summary>
public sealed class RetailFrameWalk
{
private readonly WalkPView _interiorPView = new();
private readonly WalkPView _outdoorPView = new();
private readonly WalkPortalView _defaultView = new();
public WalkPView InteriorPView => _interiorPView;
public WalkPView OutdoorPView => _outdoorPView;
/// <summary>The per-frame root (<c>SmartBox::RenderNormalMode</c>).
/// <paramref name="cameraCell"/> may be null only when the camera is
/// outdoors.</summary>
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);
}
}
/// <summary><c>PView::DrawInside</c> + the event half of
/// <c>DrawCells</c>. The geometry/object passes emit no walk events.</summary>
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();
}
/// <summary><c>LScape::draw</c>: visibility per active view, then blocks
/// far-to-near, cells far-to-near, buildings at their cell's turn.</summary>
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];
// DrawSortCell runs for in-view cells (alwaysDrawObjects
// default 0); terrain (DrawLandCell) emits no walk event.
if (block.CellInView[cellIndex] == WalkBoundingType.Outside)
continue;
if (block.CellBuildings[cellIndex] is WalkBuilding building)
DrawBuilding(building, activeViews, ctx, sink);
}
}
}
/// <summary><c>RenderDeviceD3D::DrawBuilding</c> @0x0059f2a0 at the walk
/// level: the BLD event fires at ENTRY (before the degrade check); the
/// portal pass walks the drawing BSP twice per active view (punch, then
/// look-in + DrawCells on the outdoor pview); the shell pass emits no
/// walk event.</summary>
public void DrawBuilding(
WalkBuilding building, WalkPortalView activeViews,
IRetailFrameWalkContext ctx, IWalkEventSink sink)
{
sink.Emit(WalkEvent.Building(building.PositionCellId));
if (!building.HasGeometry) return;
int viewCount = Math.Max(activeViews.ViewCount, 0);
var passSink = new PortalPassSink(sink);
Vector3 viewpoint = ctx.ViewpointInBuilding(building);
for (int v = 0; v < viewCount; v++)
{
ctx.SetActiveView(activeViews, v);
WalkBuildingPortals.BuildDrawPortalsOnly(
building.DrawingBsp, 1, viewpoint,
(portalRef, pass) => WalkBuildingPortals.DrawPortal(
_outdoorPView, building, portalRef, pass, ctx, passSink));
WalkBuildingPortals.BuildDrawPortalsOnly(
building.DrawingBsp, 2, viewpoint,
(portalRef, pass) => WalkBuildingPortals.DrawPortal(
_outdoorPView, building, portalRef, pass, ctx, passSink));
}
}
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(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.
}
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));
}
}
}