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>
This commit is contained in:
parent
a6b37c0aac
commit
0f01fb4430
4 changed files with 184 additions and 0 deletions
|
|
@ -36,6 +36,11 @@ public sealed class WalkCell
|
||||||
public WalkPolygon[] PortalPolygons = [];
|
public WalkPolygon[] PortalPolygons = [];
|
||||||
public uint[] StabList = [];
|
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) ----
|
// ---- walk state (retail: fields on CEnvCell) ----
|
||||||
public int NumView;
|
public int NumView;
|
||||||
public readonly List<WalkPortalView> PortalViews = new();
|
public readonly List<WalkPortalView> PortalViews = new();
|
||||||
|
|
|
||||||
|
|
@ -58,12 +58,21 @@ public static class WalkWorldDatAdapter
|
||||||
?? new WalkPolygon();
|
?? new WalkPolygon();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Matrix4x4 worldTransform =
|
||||||
|
Matrix4x4.CreateFromQuaternion(envCell.Position.Orientation)
|
||||||
|
* Matrix4x4.CreateTranslation(
|
||||||
|
envCell.Position.Origin.X,
|
||||||
|
envCell.Position.Origin.Y,
|
||||||
|
envCell.Position.Origin.Z);
|
||||||
|
Matrix4x4.Invert(worldTransform, out Matrix4x4 inverse);
|
||||||
return new WalkCell
|
return new WalkCell
|
||||||
{
|
{
|
||||||
CellId = cellId,
|
CellId = cellId,
|
||||||
Portals = portals,
|
Portals = portals,
|
||||||
PortalPolygons = polygons,
|
PortalPolygons = polygons,
|
||||||
StabList = envCell.VisibleCells.Select(v => lbMask | v).ToArray(),
|
StabList = envCell.VisibleCells.Select(v => lbMask | v).ToArray(),
|
||||||
|
WorldTransform = worldTransform,
|
||||||
|
InverseWorldTransform = inverse,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,60 @@
|
||||||
|
using AcDream.App.Tests.Rendering;
|
||||||
|
using AcDream.App.Rendering.Walk;
|
||||||
|
using DatReaderWriter;
|
||||||
|
using DatReaderWriter.Options;
|
||||||
|
|
||||||
|
namespace AcDream.App.Tests.Rendering.Walk;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// FW1's conformance gate, first slice: replay pose-stamped oracle
|
||||||
|
/// fixtures through the ported walk and require the identical event
|
||||||
|
/// sequence. The projection-light interior fixtures gate first
|
||||||
|
/// (docs/research/2026-08-30-fw-walk-oracle/README.md, posed round);
|
||||||
|
/// the outdoor/landscape fixtures join as the landscape world build
|
||||||
|
/// lands.
|
||||||
|
/// </summary>
|
||||||
|
[Trait("Lane", "InstalledDat")]
|
||||||
|
public sealed class WalkTraceConformanceTests
|
||||||
|
{
|
||||||
|
private sealed class Recorder : IWalkEventSink
|
||||||
|
{
|
||||||
|
public readonly List<WalkEvent> Events = new();
|
||||||
|
public void Emit(in WalkEvent walkEvent) => Events.Add(walkEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static DatCollection OpenDats()
|
||||||
|
{
|
||||||
|
string? datDir = CornerFloodReplayTests.ResolveDatDir();
|
||||||
|
if (datDir is null)
|
||||||
|
{
|
||||||
|
Assert.Fail("Lane=InstalledDat requires an installed retail DAT directory; see docs/release-gate.md.");
|
||||||
|
}
|
||||||
|
return new DatCollection(datDir!, DatAccessType.Read);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Foundry_deep_reproduces_every_complete_frame_exactly()
|
||||||
|
{
|
||||||
|
IReadOnlyList<WalkOracleFrame> frames = WalkOracleTrace.Load("posed/foundry-deep");
|
||||||
|
Assert.NotEmpty(frames);
|
||||||
|
using DatCollection dats = OpenDats();
|
||||||
|
Dictionary<uint, WalkCell> cells =
|
||||||
|
WalkWorldDatAdapter.BuildInteriorCells(dats, 0xA9B40000u);
|
||||||
|
var landscape = new WalkLandscape { MidWidth = 1, Blocks = new WalkLandBlock?[1] };
|
||||||
|
|
||||||
|
foreach (WalkOracleFrame frame in frames)
|
||||||
|
{
|
||||||
|
Assert.NotNull(frame.Pose);
|
||||||
|
WalkCell camera = Assert.Contains(frame.Pose!.CellId, cells);
|
||||||
|
var ctx = new WalkTraceReplayContext(frame.Pose, cells);
|
||||||
|
var walk = new RetailFrameWalk();
|
||||||
|
var recorder = new Recorder();
|
||||||
|
|
||||||
|
walk.WalkFrame(frame.Pose.CellId, camera, landscape, ctx, recorder);
|
||||||
|
|
||||||
|
Assert.Equal(
|
||||||
|
WalkTraceReplayContext.Signature(frame),
|
||||||
|
WalkTraceReplayContext.Signature(recorder.Events));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
110
tests/AcDream.App.Tests/Rendering/Walk/WalkTraceReplay.cs
Normal file
110
tests/AcDream.App.Tests/Rendering/Walk/WalkTraceReplay.cs
Normal file
|
|
@ -0,0 +1,110 @@
|
||||||
|
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
|
||||||
|
{
|
||||||
|
private sealed class BasisRayCaster(
|
||||||
|
Vector3 right, Vector3 forward, Vector3 up,
|
||||||
|
float halfWidth, float halfHeight, float focal) : IWalkRayCaster
|
||||||
|
{
|
||||||
|
public Vector3 RayThrough(float screenX, float screenY)
|
||||||
|
{
|
||||||
|
// Screen origin top-left, y down (the xformStart convention).
|
||||||
|
float nx = (screenX - halfWidth) / halfWidth;
|
||||||
|
float ny = (halfHeight - screenY) / halfHeight;
|
||||||
|
return right * (nx / focal) + forward + up * (ny / focal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly Dictionary<uint, WalkCell> _cells;
|
||||||
|
private readonly Matrix4x4 _viewProjection;
|
||||||
|
private readonly IWalkRayCaster _rays;
|
||||||
|
|
||||||
|
public WalkTraceReplayContext(
|
||||||
|
WalkOraclePose pose, Dictionary<uint, WalkCell> cells,
|
||||||
|
float viewportWidth = 800f, float viewportHeight = 600f,
|
||||||
|
float verticalFovRadians = 1.0f)
|
||||||
|
{
|
||||||
|
_cells = cells;
|
||||||
|
WorldViewpoint = pose.Origin;
|
||||||
|
var rotation = new Quaternion(pose.Q1, pose.Q2, pose.Q3, pose.Q0);
|
||||||
|
Vector3 forward = Vector3.Transform(Vector3.UnitY, rotation);
|
||||||
|
Vector3 up = Vector3.Transform(Vector3.UnitZ, rotation);
|
||||||
|
Vector3 right = Vector3.Transform(Vector3.UnitX, rotation);
|
||||||
|
|
||||||
|
Matrix4x4 view = Matrix4x4.CreateLookAt(pose.Origin, pose.Origin + forward, up);
|
||||||
|
Matrix4x4 projection = Matrix4x4.CreatePerspectiveFieldOfView(
|
||||||
|
verticalFovRadians, viewportWidth / viewportHeight, 0.1f, 5000f);
|
||||||
|
_viewProjection = view * projection;
|
||||||
|
|
||||||
|
ViewportWidth = viewportWidth;
|
||||||
|
ViewportHeight = viewportHeight;
|
||||||
|
float focal = MathF.Tan(verticalFovRadians / 2f);
|
||||||
|
_rays = new BasisRayCaster(
|
||||||
|
right, forward, up, viewportWidth / 2f, viewportHeight / 2f, focal);
|
||||||
|
|
||||||
|
// The retail CY near plane: N = forward, d = −dot(eye, forward) − znear.
|
||||||
|
CyPlane = new WalkPlane(forward, -Vector3.Dot(pose.Origin, forward) - 0.1f);
|
||||||
|
}
|
||||||
|
|
||||||
|
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) { }
|
||||||
|
|
||||||
|
public Vector3 ViewpointInBuilding(WalkBuilding building) => WorldViewpoint;
|
||||||
|
|
||||||
|
public int ClipBuildingPolygon(
|
||||||
|
WalkBuilding building, WalkPolygon polygon, int side, Span<WalkScreenPoint> output)
|
||||||
|
=> 0; // building look-ins join the conformance surface with the landscape fixtures
|
||||||
|
|
||||||
|
// ---- 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")))}",
|
||||||
|
_ => "?",
|
||||||
|
}));
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue