using System.Numerics; using AcDream.App.Rendering.Walk; namespace AcDream.App.Tests.Rendering.Walk; public sealed class RetailFrameWalkTests { private sealed class Recorder : IWalkEventSink { public readonly List Events = new(); public void Emit(in WalkEvent walkEvent) => Events.Add(walkEvent); public string Signature() => 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")))}", _ => "?", })); } private sealed class Caster : IWalkRayCaster { public Vector3 RayThrough(float screenX, float screenY) => new(screenX, screenY, 100f); } private sealed class TestContext : IWalkFrameContext, IRetailFrameWalkContext { public readonly Dictionary Cells = new(); private readonly Matrix4x4 _viewProj; public TestContext() { Matrix4x4 view = Matrix4x4.CreateLookAt( Vector3.Zero, new Vector3(0, 0, -1), Vector3.UnitY); Matrix4x4 proj = Matrix4x4.CreatePerspectiveFieldOfView(1.2f, 1f, 0.1f, 1000f); _viewProj = view * proj; } public Vector3 ViewpointIn(WalkCell cell) => Vector3.Zero; public Matrix4x4 ObjectToClip(WalkCell cell) => _viewProj; public WalkCell? GetVisible(uint cellId) => Cells.GetValueOrDefault(cellId); public IWalkRayCaster Rays { get; } = new Caster(); public Vector3 WorldViewpoint => Vector3.Zero; public float ViewportWidth => 640f; public float ViewportHeight => 480f; public Vector3 ViewpointInBuilding(WalkBuilding building) => Vector3.Zero; public float ViewerDistanceTo(WalkBuilding building) => 0f; public IWalkFrameContext CellContext => this; // Permissive near plane: every column wholly inside. public WalkPlane CyPlane => new(new Vector3(0, 0, 1), 0f); public void SetActiveView(WalkPortalView views, int index) { } public int ClipBuildingPolygon( WalkBuilding building, WalkPolygon polygon, int side, Span output) => 0; // no BSP-driven look-ins in these composition fixtures } private static WalkPolygon Quad(float z, bool facingViewer = true) => new() { Vertices = [ new Vector3(-0.5f, -0.5f, z), new Vector3(0.5f, -0.5f, z), new Vector3(0.5f, 0.5f, z), new Vector3(-0.5f, 0.5f, z), ], Plane = new WalkPlane(new Vector3(0, 0, facingViewer ? 1f : -1f), facingViewer ? -z : z), }; private static WalkLandscape Landscape3x3() { var landscape = new WalkLandscape { MidWidth = 3, Blocks = new WalkLandBlock?[9], ViewerBlockX = 1, ViewerBlockY = 1, ViewerCellX = 0, ViewerCellY = 0, }; for (int i = 0; i < 9; i++) { landscape.Blocks[i] = new WalkLandBlock { MaxZ = 10f, MinZ = 0f }; landscape.Blocks[i]!.EnsureCellArrays(); } return landscape; } [Fact] public void Outdoor_frame_emits_landscape_then_buildings_far_to_near() { var ctx = new TestContext(); WalkLandscape landscape = Landscape3x3(); // A building in ring-1 block (0,0), and two in the viewer block: // one at the block's far corner, one at the viewer's closest cell. var ringBuilding = new WalkBuilding { PositionCellId = 0xAAAA0001 }; var farBuilding = new WalkBuilding { PositionCellId = 0xBBBB0002 }; var nearBuilding = new WalkBuilding { PositionCellId = 0xCCCC0003 }; landscape.Blocks[0]!.CellBuildings[0] = ringBuilding; // block (0,0) WalkLandBlock viewerBlock = landscape.Blocks[1 * 3 + 1]!; viewerBlock.CellBuildings[7 * 8 + 7] = farBuilding; // far corner cell viewerBlock.CellBuildings[0] = nearBuilding; // the closest cell (viewer at 0,0) var walk = new RetailFrameWalk(); var recorder = new Recorder(); // ViewCount == 0 exercises the CY-only visibility arm deterministically. walk.DrawLandscape(landscape, new WalkPortalView(), ctx, recorder); Assert.Equal( "LS|BLD:aaaa0001|BLD:bbbb0002|BLD:cccc0003", recorder.Signature()); } [Fact] public void Degraded_building_still_emits_its_entry_event() { var ctx = new TestContext(); var walk = new RetailFrameWalk(); var recorder = new Recorder(); var building = new WalkBuilding { PositionCellId = 0xF518002E, HasGeometry = false }; walk.DrawBuilding(building, new WalkPortalView(), ctx, recorder); Assert.Equal("BLD:f518002e", recorder.Signature()); } [Fact] public void Interior_frame_without_exit_views_skips_the_landscape() { var ctx = new TestContext(); // One facing portal only: the flood stays in the cell, no exit view. var cell = new WalkCell { CellId = 0xA9B40178, Portals = [new WalkCellPortal { OtherCellId = 0xA9B40179, PolygonIndex = 0, PortalSide = 1, OtherPortalId = 0, }], PortalPolygons = [Quad(-2f)], }; ctx.Cells[cell.CellId] = cell; var walk = new RetailFrameWalk(); var recorder = new Recorder(); walk.WalkFrame(cell.CellId, cell, Landscape3x3(), ctx, recorder); Assert.Equal("DI:a9b40178|DC:ov=0:a9b40178", recorder.Signature()); } [Fact] public void Interior_frame_with_an_exit_view_draws_the_landscape_through_it() { var ctx = new TestContext(); var cell = new WalkCell { CellId = 0xA9B40150, Portals = [new WalkCellPortal { OtherCellId = 0xFFFFFFFF, PolygonIndex = 0, PortalSide = 0, OtherPortalId = -1, }], PortalPolygons = [Quad(-2f)], }; ctx.Cells[cell.CellId] = cell; var walk = new RetailFrameWalk(); var recorder = new Recorder(); var landscape = new WalkLandscape { MidWidth = 1, Blocks = new WalkLandBlock?[1], ViewerBlockX = 0, ViewerBlockY = 0, }; walk.WalkFrame(cell.CellId, cell, landscape, ctx, recorder); Assert.Equal("DI:a9b40150|DC:ov=1:a9b40150|LS", recorder.Signature()); } [Fact] public void Outdoor_camera_cell_roots_the_landscape_walk() { var ctx = new TestContext(); var walk = new RetailFrameWalk(); var recorder = new Recorder(); // Low word < 0x100 = an outdoor landcell id. walk.WalkFrame(0xA9B40015, null, Landscape3x3(), ctx, recorder); Assert.StartsWith("LS", recorder.Signature()); } [Fact] public void View_state_unwinds_completely_after_an_interior_frame() { var ctx = new TestContext(); var stabCell = new WalkCell { CellId = 0xA9B40151 }; var cell = new WalkCell { CellId = 0xA9B40150, StabList = [0xA9B40151u], Portals = [new WalkCellPortal { OtherCellId = 0xA9B40151, PolygonIndex = 0, PortalSide = 0, OtherPortalId = 0, }], PortalPolygons = [Quad(-2f)], }; stabCell.Portals = [new WalkCellPortal { OtherCellId = 0xA9B40150, PolygonIndex = 0, PortalSide = 1, OtherPortalId = 0, }]; stabCell.PortalPolygons = [Quad(-2f)]; ctx.Cells[cell.CellId] = cell; ctx.Cells[stabCell.CellId] = stabCell; var walk = new RetailFrameWalk(); walk.WalkFrame(cell.CellId, cell, Landscape3x3(), ctx, new Recorder()); Assert.Equal(0, cell.NumView); Assert.Equal(0, stabCell.NumView); } }