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>
94 lines
3.4 KiB
C#
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;
|
|
}
|