Three pinned corrections close the outdoor frame: (1) the camera basis RE-pinned to +Y-forward - the terrace-edge fixture EXTERNAL ground truth (the ledge faces the F518 vista, east) overrules the walkabout motion sweep, whose camera was mouse-turned off the run line; (2) alwaysDrawObjects (.data default 1 @0x00820ed4) drawn into the DrawSortCell gate - cell contents draw for every cell of an in-view block; (3) the building portal side decode data-pinned as the INVERSE of the cell 0x2 bit (the sweep winning arm). Street result: all 13 buildings in retail exact order (a9b3003c in, aab50002 correctly absent), punches at exactly 001a and 0022 opening the exact first cells 16e/164. SOLE remaining delta: look-in flood depth (mine 1 cell, retail 3-4) - the doorway-still fixture adjudicates the interior flood in isolation next. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
159 lines
7.1 KiB
C#
159 lines
7.1 KiB
C#
using System.Numerics;
|
||
using AcDream.App.Rendering.Walk;
|
||
|
||
namespace AcDream.App.Tests.Rendering.Walk;
|
||
|
||
/// <summary>
|
||
/// The FW1 conformance replay harness: reconstructs the camera state from a
|
||
/// pose-stamped oracle frame and drives the ported walk over
|
||
/// adapter-built world data. Convention notes (adjudicate against the
|
||
/// fixtures, loudly, on any mismatch):
|
||
/// <list type="bullet">
|
||
/// <item>The dumped quaternion is retail Frame storage order w,x,y,z
|
||
/// (q0=w) — unit-norm verified on the captures.</item>
|
||
/// <item>Retail's frame axes: +Y forward, +Z up (the camera looks along
|
||
/// the rotated +Y).</item>
|
||
/// <item>Pose origin is landblock-local, the same space the adapter's
|
||
/// cell transforms produce.</item>
|
||
/// </list>
|
||
/// </summary>
|
||
public sealed class WalkTraceReplayContext : IWalkFrameContext, IRetailFrameWalkContext
|
||
{
|
||
// Retail projection globals, dumped live from the capture client
|
||
// (recon 2026-08-30 evening: Render::bw/bh/xinvscale/yinvscale/tx/ty/vdst).
|
||
public const float RetailViewportWidth = 1024f;
|
||
public const float RetailViewportHeight = 720f;
|
||
public const float RetailXInvScale = 0.00025f;
|
||
public const float RetailYInvScale = 0.00025f;
|
||
public const float RetailTx = 0.127875f;
|
||
public const float RetailTy = 0.089875f;
|
||
public const float RetailVdst = 0.1330766976f;
|
||
|
||
private sealed class RetailRayCaster(
|
||
Vector3 right, Vector3 forward, Vector3 up) : IWalkRayCaster
|
||
{
|
||
// Retail's unproject (copy_view's ray path; equal to
|
||
// ScreenToViewTransform for these globals):
|
||
// u = sx·xinvscale − tx; w = sy·yinvscale − ty
|
||
// ray = Xaxis·u + Yaxis·vdst − Zaxis·w
|
||
public Vector3 RayThrough(float screenX, float screenY)
|
||
{
|
||
float u = screenX * RetailXInvScale - RetailTx;
|
||
float w = screenY * RetailYInvScale - RetailTy;
|
||
return right * u + forward * RetailVdst - up * w;
|
||
}
|
||
}
|
||
|
||
private readonly Dictionary<uint, WalkCell> _cells;
|
||
private readonly Matrix4x4 _viewProjection;
|
||
private readonly IWalkRayCaster _rays;
|
||
|
||
public WalkTraceReplayContext(WalkOraclePose pose, Dictionary<uint, WalkCell> cells)
|
||
{
|
||
_cells = cells;
|
||
WorldViewpoint = pose.Origin;
|
||
var rotation = new Quaternion(pose.Q1, pose.Q2, pose.Q3, pose.Q0);
|
||
// Basis convention RE-pinned 2026-08-30 (second pass): storage
|
||
// w,x,y,z; forward = rotated +Y — the retail Frame convention. The
|
||
// motion sweep briefly favored +X, but the walkabout camera was
|
||
// mouse-turned off the run direction; the terrace-edge fixture's
|
||
// EXTERNAL ground truth (the ledge faces the F518 vista, east)
|
||
// decodes east ONLY under +Y-forward, and +Y makes the street
|
||
// fixture's punch on/off-screen pattern match retail 4-for-4.
|
||
Vector3 forward = Vector3.Transform(Vector3.UnitY, rotation);
|
||
Vector3 up = Vector3.Transform(Vector3.UnitZ, rotation);
|
||
Vector3 right = Vector3.Transform(Vector3.UnitX, rotation);
|
||
|
||
// The exact retail frustum: tan(halfFovY) = ty/vdst, aspect = tx/ty.
|
||
float fovY = 2f * MathF.Atan(RetailTy / RetailVdst);
|
||
Matrix4x4 view = Matrix4x4.CreateLookAt(pose.Origin, pose.Origin + forward, up);
|
||
Matrix4x4 projection = Matrix4x4.CreatePerspectiveFieldOfView(
|
||
fovY, RetailTx / RetailTy, 0.1f, 5000f);
|
||
_viewProjection = view * projection;
|
||
|
||
ViewportWidth = RetailViewportWidth;
|
||
ViewportHeight = RetailViewportHeight;
|
||
_rays = new RetailRayCaster(right, forward, up);
|
||
|
||
// The retail CY near plane: N = forward, d = −dot(eye, forward) − znear.
|
||
CyPlane = new WalkPlane(forward, -Vector3.Dot(pose.Origin, forward) - 0.1f);
|
||
}
|
||
|
||
/// <summary>Building placements (camera-block-local) for the landscape
|
||
/// fixtures; empty for the interior-only ones.</summary>
|
||
public Dictionary<WalkBuilding, WalkWorldDatAdapter.BuildingEntry> Buildings { get; set; }
|
||
= new();
|
||
|
||
private Vector2[] _activeViewVerts = new Vector2[32];
|
||
private int _activeViewVertCount;
|
||
|
||
public Vector3 ViewpointIn(WalkCell cell)
|
||
=> Vector3.Transform(WorldViewpoint, cell.InverseWorldTransform);
|
||
|
||
public Matrix4x4 ObjectToClip(WalkCell cell)
|
||
=> cell.WorldTransform * _viewProjection;
|
||
|
||
public WalkCell? GetVisible(uint cellId) => _cells.GetValueOrDefault(cellId);
|
||
public IWalkRayCaster Rays => _rays;
|
||
public Vector3 WorldViewpoint { get; }
|
||
public float ViewportWidth { get; }
|
||
public float ViewportHeight { get; }
|
||
public WalkPlane CyPlane { get; }
|
||
public IWalkFrameContext CellContext => this;
|
||
|
||
public void SetActiveView(WalkPortalView views, int index)
|
||
{
|
||
WalkViewPoly poly = views.View.Polys[index];
|
||
if (_activeViewVerts.Length < poly.VertexCount)
|
||
_activeViewVerts = new Vector2[poly.VertexCount];
|
||
for (int k = 0; k < poly.VertexCount; k++)
|
||
_activeViewVerts[k] = views.View.Vertices[poly.VertexIndex + k].Point;
|
||
_activeViewVertCount = poly.VertexCount;
|
||
}
|
||
|
||
public Vector3 ViewpointInBuilding(WalkBuilding building)
|
||
=> Vector3.Transform(WorldViewpoint, Buildings[building].InverseWorldTransform);
|
||
|
||
public float ViewerDistanceTo(WalkBuilding building)
|
||
=> Vector3.Distance(
|
||
WorldViewpoint,
|
||
Vector3.Transform(building.SortCenter, Buildings[building].WorldTransform));
|
||
|
||
public int ClipBuildingPolygon(
|
||
WalkBuilding building, WalkPolygon polygon, int side, Span<WalkScreenPoint> output)
|
||
{
|
||
Matrix4x4 objectToClip = Buildings[building].WorldTransform * _viewProjection;
|
||
Span<WalkScreenPoint> projected = stackalloc WalkScreenPoint[polygon.Vertices.Length];
|
||
for (int i = 0; i < polygon.Vertices.Length; i++)
|
||
projected[i] = WalkScreenClip.TransformToScreen(
|
||
polygon.Vertices[i], objectToClip, ViewportWidth, ViewportHeight);
|
||
if (side != 0)
|
||
projected.Reverse();
|
||
return WalkScreenClip.ClipAgainstView(
|
||
projected, _activeViewVerts.AsSpan(0, _activeViewVertCount), output);
|
||
}
|
||
|
||
// ---- signatures for comparing walk output to oracle frames ----
|
||
|
||
public static string Signature(IEnumerable<WalkEvent> events)
|
||
=> string.Join("|", events.Select(e => e.Kind switch
|
||
{
|
||
WalkEventKind.Landscape => "LS",
|
||
WalkEventKind.Building => $"BLD:{e.CellId:x8}",
|
||
WalkEventKind.DrawInside => $"DI:{e.CellId:x8}",
|
||
WalkEventKind.DrawCells =>
|
||
$"DC:ov={e.OutsideViewCount}:{string.Join(',', e.Cells.Select(c => c.ToString("x8")))}",
|
||
_ => "?",
|
||
}));
|
||
|
||
public static string Signature(WalkOracleFrame frame)
|
||
=> string.Join("|", frame.Events.Select(e => e.Kind switch
|
||
{
|
||
WalkOracleEventKind.Landscape => "LS",
|
||
WalkOracleEventKind.Building => $"BLD:{e.CellId!.Value:x8}",
|
||
WalkOracleEventKind.DrawInside => $"DI:{e.CellId!.Value:x8}",
|
||
WalkOracleEventKind.DrawCells =>
|
||
$"DC:ov={e.OutsideViewCount}:{string.Join(',', e.Cells.Select(c => c.ToString("x8")))}",
|
||
_ => "?",
|
||
}));
|
||
}
|