From 0f01fb44300ae84af8843584cba2e09dda0afede Mon Sep 17 00:00:00 2001 From: Erik Date: Sun, 30 Aug 2026 10:23:39 +0200 Subject: [PATCH] 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 --- src/AcDream.App/Rendering/Walk/WalkWorld.cs | 5 + .../Rendering/Walk/WalkWorldDatAdapter.cs | 9 ++ .../Walk/WalkTraceConformanceTests.cs | 60 ++++++++++ .../Rendering/Walk/WalkTraceReplay.cs | 110 ++++++++++++++++++ 4 files changed, 184 insertions(+) create mode 100644 tests/AcDream.App.Tests/Rendering/Walk/WalkTraceConformanceTests.cs create mode 100644 tests/AcDream.App.Tests/Rendering/Walk/WalkTraceReplay.cs diff --git a/src/AcDream.App/Rendering/Walk/WalkWorld.cs b/src/AcDream.App/Rendering/Walk/WalkWorld.cs index 84a30759..8a3f60b0 100644 --- a/src/AcDream.App/Rendering/Walk/WalkWorld.cs +++ b/src/AcDream.App/Rendering/Walk/WalkWorld.cs @@ -36,6 +36,11 @@ public sealed class WalkCell public WalkPolygon[] PortalPolygons = []; public uint[] StabList = []; + /// Cell-local → landblock-local (retail CEnvCell.pos; + /// the frame the flood's plane tests and projections run in). + public Matrix4x4 WorldTransform = Matrix4x4.Identity; + public Matrix4x4 InverseWorldTransform = Matrix4x4.Identity; + // ---- walk state (retail: fields on CEnvCell) ---- public int NumView; public readonly List PortalViews = new(); diff --git a/src/AcDream.App/Rendering/Walk/WalkWorldDatAdapter.cs b/src/AcDream.App/Rendering/Walk/WalkWorldDatAdapter.cs index 767abf72..5e9fdcd7 100644 --- a/src/AcDream.App/Rendering/Walk/WalkWorldDatAdapter.cs +++ b/src/AcDream.App/Rendering/Walk/WalkWorldDatAdapter.cs @@ -58,12 +58,21 @@ public static class WalkWorldDatAdapter ?? 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 { CellId = cellId, Portals = portals, PortalPolygons = polygons, StabList = envCell.VisibleCells.Select(v => lbMask | v).ToArray(), + WorldTransform = worldTransform, + InverseWorldTransform = inverse, }; } diff --git a/tests/AcDream.App.Tests/Rendering/Walk/WalkTraceConformanceTests.cs b/tests/AcDream.App.Tests/Rendering/Walk/WalkTraceConformanceTests.cs new file mode 100644 index 00000000..8c064669 --- /dev/null +++ b/tests/AcDream.App.Tests/Rendering/Walk/WalkTraceConformanceTests.cs @@ -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; + +/// +/// 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. +/// +[Trait("Lane", "InstalledDat")] +public sealed class WalkTraceConformanceTests +{ + private sealed class Recorder : IWalkEventSink + { + public readonly List 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 frames = WalkOracleTrace.Load("posed/foundry-deep"); + Assert.NotEmpty(frames); + using DatCollection dats = OpenDats(); + Dictionary 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)); + } + } +} diff --git a/tests/AcDream.App.Tests/Rendering/Walk/WalkTraceReplay.cs b/tests/AcDream.App.Tests/Rendering/Walk/WalkTraceReplay.cs new file mode 100644 index 00000000..c6ac9e18 --- /dev/null +++ b/tests/AcDream.App.Tests/Rendering/Walk/WalkTraceReplay.cs @@ -0,0 +1,110 @@ +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 +{ + 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 _cells; + private readonly Matrix4x4 _viewProjection; + private readonly IWalkRayCaster _rays; + + public WalkTraceReplayContext( + WalkOraclePose pose, Dictionary 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 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 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")))}", + _ => "?", + })); +}