acdream/tests/AcDream.App.Tests/Rendering/Walk/WalkTraceReplay.cs
Erik 28475d0544 feat(render) Campaign FW1: street fixture retail-exact except flood depth
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>
2026-08-30 11:02:16 +02:00

159 lines
7.1 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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")))}",
_ => "?",
}));
}