acdream/src/AcDream.App/Rendering/Walk/WalkWorld.cs
Erik 0f01fb4430 feat(render) Campaign FW1: FIRST TRACE CONFORMANCE GREEN (foundry-deep)
The replay harness reconstructs the camera from a pose-stamped oracle
frame (Frame quaternion w,x,y,z storage order; +Y forward / +Z up;
landblock-local origin) and drives the ported walk over adapter-built
cells (cell transforms from EnvCell.Position now populated). The
foundry-deep fixture - the pure-interior frame shape - reproduces
retail EXACTLY on every complete frame: DI + DC(ov=0, [cell]) with no
landscape, 39/39. Conventions are now pinned by live retail output; the
remaining nine fixtures need the outdoor world build-out (landscape
blocks, terrain z-slabs, building transforms + active-view clip) and
join the same gate.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-30 10:23:39 +02:00

94 lines
3.4 KiB
C#

using System.Numerics;
namespace AcDream.App.Rendering.Walk;
/// <summary>A cell-local portal polygon with its plane (retail
/// <c>CPolygon</c>: vertices + <c>plane</c> @+0x20).</summary>
public sealed class WalkPolygon
{
public Vector3[] Vertices = [];
public WalkPlane Plane;
}
/// <summary>Retail <c>CCellPortal</c> (stride 0x18). The exit-to-landscape
/// sentinel is <c>OtherCellId == 0xFFFFFFFF</c>; <c>OtherPortalId == -1</c>
/// means "no reciprocal".</summary>
public struct WalkCellPortal
{
public uint OtherCellId;
public int PolygonIndex;
public int PortalSide;
public int OtherPortalId;
public bool ExactMatch;
}
/// <summary>
/// The flood's cell model (retail <c>CEnvCell</c> as the walk sees it).
/// Retail stores the walk state ON the cell (num_view / portal_view stack /
/// cached neighbor pointers); this port keeps the same placement so the
/// transcription stays line-by-line — production adapters construct these
/// from the committed cell registry (FW3 wiring).
/// </summary>
public sealed class WalkCell
{
public uint CellId;
public WalkCellPortal[] Portals = [];
public WalkPolygon[] PortalPolygons = [];
public uint[] StabList = [];
/// <summary>Cell-local → landblock-local (retail <c>CEnvCell.pos</c>;
/// the frame the flood's plane tests and projections run in).</summary>
public Matrix4x4 WorldTransform = Matrix4x4.Identity;
public Matrix4x4 InverseWorldTransform = Matrix4x4.Identity;
// ---- walk state (retail: fields on CEnvCell) ----
public int NumView;
public readonly List<WalkPortalView> PortalViews = new();
public WalkCell?[] CachedNeighbors = [];
public WalkPortalView TopView => PortalViews[NumView - 1];
/// <summary><c>CEnvCell::curr_view_push</c> @0x005a5090: push one
/// view-recursion level (lazy slot, exactly three counters reset).</summary>
public void PushView()
{
while (PortalViews.Count <= NumView)
PortalViews.Add(new WalkPortalView());
PortalViews[NumView].ResetForPush();
NumView++;
if (CachedNeighbors.Length < Portals.Length)
CachedNeighbors = new WalkCell?[Portals.Length];
}
public void PopView() => NumView--;
}
/// <summary>
/// The per-frame context retail keeps in globals (<c>Render::FrameCurrent</c>
/// after <c>positionPush(3, cell.pos)</c>, the projection state, the visible
/// cell registry, and the <c>cliplandscape</c> toggle — .data default 1).
/// </summary>
public interface IWalkFrameContext
{
/// <summary>The eye position in the cell's local frame (retail: the
/// pushed frame's <c>viewer.viewpoint</c>).</summary>
Vector3 ViewpointIn(WalkCell cell);
/// <summary>Object(cell-local)→clip matrix for projecting the cell's
/// portal polygons (retail: the pushed frame composed with
/// WorldToView·ViewToClip).</summary>
Matrix4x4 ObjectToClip(WalkCell cell);
/// <summary><c>CEnvCell::GetVisible</c>: the committed/visible cell
/// registry. Returning null skips the portal silently (retail behavior —
/// but implementations should count the miss for the fail-loud rule).</summary>
WalkCell? GetVisible(uint cellId);
IWalkRayCaster Rays { get; }
Vector3 WorldViewpoint { get; }
float ViewportWidth { get; }
float ViewportHeight { get; }
/// <summary>Retail global <c>cliplandscape</c> (.data @0x00820f4c = 1).</summary>
bool ClipLandscape => true;
}