using System.Numerics;
using AcDream.App.Rendering.Walk;
namespace AcDream.App.Tests.Rendering.Walk;
///
/// 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):
///
/// - The dumped quaternion is retail Frame storage order w,x,y,z
/// (q0=w) — unit-norm verified on the captures.
/// - Retail's frame axes: +Y forward, +Z up (the camera looks along
/// the rotated +Y).
/// - Pose origin is landblock-local, the same space the adapter's
/// cell transforms produce.
///
///
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 _cells;
private readonly Matrix4x4 _viewProjection;
private readonly IWalkRayCaster _rays;
public WalkTraceReplayContext(WalkOraclePose pose, Dictionary cells)
{
_cells = cells;
// S3 review fix round 1 (F4b): the SAME cell id the harness hands
// WalkFrame's own cameraCellId (every call site in
// WalkTraceConformanceTests passes pose.CellId) — production wires
// the identical invariant (RetailPViewFrameInput.ViewerCellId ==
// WalkFrame's cameraCellId, see WalkFrameDriverTranscriptTests'
// own citation). Needed so a trailing weather "OC" this context's
// WeatherGateOpen now legitimately opens carries the SAME cell id
// retail's own capture recorded.
ViewerCellId = pose.CellId;
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);
}
/// Building placements (camera-block-local) for the landscape
/// fixtures; empty for the interior-only ones.
public Dictionary 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;
/// S3 review fix round 1 (F4b): overrides
/// 's default 0 — see the
/// constructor's own doc comment for why this equals the harness's own
/// WalkFrame cameraCellId argument.
public uint ViewerCellId { get; }
/// S3 review fix round 1 (F4b): overrides
/// 's default false with retail's
/// own gate — SmartBox::is_player_outside @0x00451e80,
/// (cellId & 0xFFFF) < 0x100 — so an outdoor-rooted
/// fixture's trailing weather turn fires during replay exactly where
/// retail's own capture recorded it, and an interior-rooted fixture's
/// never does (retail's own gate is unconditionally false whenever the
/// viewer's cell id has local part >= 0x100, regardless of whether an
/// exit view survives). This decomp-port context has no App-level
/// render-toggle concept to AND against — see the interface member's own
/// doc comment for why that is safe here (a pure walk conformance
/// harness assumes both toggles on, matching retail's default).
public bool WeatherGateOpen => (ViewerCellId & 0xFFFFu) < 0x100u;
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 output)
{
Matrix4x4 objectToClip = Buildings[building].WorldTransform * _viewProjection;
Span 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 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")))}",
_ => "?",
}));
///
/// S3 chunk 1 (§11.2 B3): the OH captures interleave LC/SC/
/// EC/OC lines the pre-chunk-3 FW0 fixtures never had.
/// 's own WalkEvent vocabulary has
/// exactly four kinds (Landscape/Building/DrawInside/DrawCells) — LC/SC/
/// EC/OC are separate hooks
/// never overrides, so the replay side of a
/// signature diff is silent on them by construction. Filtering them out
/// here (rather than mapping to a "?" placeholder) keeps this
/// comparison at the SAME DI/DC/BLD/LS level on both sides — S3's own
/// scope note ("no speculative pins" for LC/SC/EC/OC content) means this
/// method must not even attempt to compare them, not merely fail to.
///
public static string Signature(WalkOracleFrame frame)
=> string.Join("|", frame.Events
.Where(e => e.Kind is WalkOracleEventKind.Landscape
or WalkOracleEventKind.Building
or WalkOracleEventKind.DrawInside
or WalkOracleEventKind.DrawCells)
.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")))}",
_ => "?",
}));
///
/// S3 chunk 1 fix round 1 (G10): the OH kit-pose captures' full
/// eight-kind vocabulary — LS/BLD/DI/DC/LC/SC/EC/OC. ONLY the OH-rooted
/// conformance rows use this (WalkTraceConformanceTests'
/// OhCaptureRoot rows) — the older FW0 fixtures
/// (docs/research/2026-08-30-fw-walk-oracle/) predate the LC/SC/EC/OC
/// cdb breakpoints and carry none of those lines at all, so comparing
/// them at this level would spuriously diverge on every frame (the
/// replay always emits LC/SC; the FW0 oracle frame never has any).
///
/// S3 chunk 1 fix round 2 (§11.6 H3): the two sides of this comparison
/// are DELIBERATELY ASYMMETRIC. LC/SC/EC/OC all come straight from
/// 's own literal per-hook calls — the REPLAY
/// side's real turns at their real positions, EC/OC included ('s Recorder derives them via
/// at the exact hook that fires for each
/// flood — see that type's own doc comment). below, the ORACLE side, used to
/// derive EC/OC the SAME way from each DC's declared cell list — but
/// that derivation can never disagree with itself, so it silently
/// verified nothing about EC/OC placement or content (round 1's own
/// blind spot: G7's SC-ordering regression shipped green for the same
/// reason with LC/SC). Round 2 fixes this: the oracle side now reads its
/// own CAPTURED EC/OC lines verbatim, in the order retail's
/// cdb breakpoints actually recorded them — a real comparison against a
/// real trace, not a derivation compared to itself.
///
///
/// S3 review fix round 1 (F4b): round 2's own exclusion of the trailing
/// per-frame weather OC is GONE — 's Recorder now implements
/// IWalkEventSink.OnWeatherTurn (the interface's default was a
/// silent no-op), so the replay side records that exact "OC" line too,
/// at the exact point RetailFrameWalk.DrawLandscape fires it. Both
/// sides now carry the weather turn's own trailing OC literally — the
/// eight-kind signature pins its placement and value like every other
/// line, rather than the two sides mutually agreeing to stay silent
/// about it.
///
///
public static string Signature8(IReadOnlyList tokens) => string.Join("|", tokens);
/// The oracle-frame half of —
/// see that overload's doc comment for why this reads EC/OC literally
/// rather than deriving them.
public static string Signature8(WalkOracleFrame frame)
{
var tokens = new List();
IReadOnlyList events = frame.Events;
for (int i = 0; i < events.Count; i++)
{
WalkOracleEvent e = events[i];
switch (e.Kind)
{
case WalkOracleEventKind.Landscape:
tokens.Add("LS");
break;
case WalkOracleEventKind.Building:
tokens.Add($"BLD:{e.CellId!.Value:x8}");
break;
case WalkOracleEventKind.DrawInside:
tokens.Add($"DI:{e.CellId!.Value:x8}");
break;
case WalkOracleEventKind.DrawCells:
tokens.Add(
$"DC:ov={e.OutsideViewCount}:{string.Join(',', e.Cells.Select(c => c.ToString("x8")))}");
break;
case WalkOracleEventKind.LandCell:
tokens.Add($"LC:{e.CellId!.Value:x8}");
break;
case WalkOracleEventKind.SortCell:
tokens.Add($"SC:{e.CellId!.Value:x8}");
break;
case WalkOracleEventKind.EnvCellShell:
tokens.Add($"EC:{e.CellId!.Value:x8}");
break;
case WalkOracleEventKind.ObjectCellTurn:
// S3 review fix round 1 (F4b): every ObjectCellTurn now
// reads literally, trailing weather OC included — see
// this method's own class doc for why the former
// position/value exclusion is gone (the replay side now
// records the same line through
// WalkTraceConformanceTests.Recorder.OnWeatherTurn).
tokens.Add($"OC:{e.CellId!.Value:x8}");
break;
}
}
return string.Join("|", tokens);
}
/// PView::DrawCells's two complete reverse loops: every
/// EnvCell shell far-to-near, THEN every object-list turn far-to-near —
/// the SAME reversed cell order twice (not one reversed EC/OC pair per
/// cell). S3 chunk 1 fix round 2 (§11.6 H3): this is now ONLY the
/// REPLAY side's derivation — 's
/// Recorder calls this at the exact hook that fires for each
/// flood (a look-in's own DrawCells immediately; the interior
/// root's own flood at OnInteriorFloodDrawTurn), matching
/// RetailFrameWalk's real two-reverse-loop order
/// (WalkFrameDriver.EmitFloodTurns's own comment has the same
/// citation). no longer calls
/// this — the ORACLE side reads its own captured EC/OC lines literally
/// instead of re-deriving them (see that method's own doc comment for
/// why: a derivation compared to itself proves nothing).
internal static void AppendFloodTurns(List tokens, IReadOnlyList cells)
{
for (int i = cells.Count - 1; i >= 0; i--)
tokens.Add($"EC:{cells[i]:x8}");
for (int i = cells.Count - 1; i >= 0; i--)
tokens.Add($"OC:{cells[i]:x8}");
}
}