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(); /// S3 chunk 3 (§9.3 T2): the SAME sequence /// reads plus LC/SC land-cell turns interleaved at the /// exact point fires /// them — proves the terrain/building/object-turn ORDER, not just /// each vocabulary in isolation. public readonly List Combined = new(); public int BuildingAlphaTurns; public readonly List ShellSelections = new(); public void OnBuildingTurn(WalkBuilding building) => BuildingAlphaTurns++; public void OnBuildingShellTurn( WalkBuilding building, WalkBuildingSelection selection) => ShellSelections.Add(selection); public void Emit(in WalkEvent walkEvent) { Events.Add(walkEvent); Combined.Add(walkEvent.Kind switch { WalkEventKind.Landscape => "LS", WalkEventKind.Building => $"BLD:{walkEvent.CellId:x8}", WalkEventKind.DrawInside => $"DI:{walkEvent.CellId:x8}", WalkEventKind.DrawCells => $"DC:ov={walkEvent.OutsideViewCount}:{string.Join(',', walkEvent.Cells.Select(c => c.ToString("x8")))}", _ => "?", }); } // S3 chunk 3: LC uses the SAME "(landblockId & 0xFFFF0000) | // (cellIndex+1)" convention as the interface's own OnLandscapeCellTurn // default (below) — comparable directly against SC for the same cell. public void OnLandCellTurn(uint landblockId, int sideCellCount, int cellIndex) => Combined.Add( $"LC:{(landblockId & 0xFFFF0000u) | (uint)(cellIndex + 1):x8}"); public void OnLandscapeCellTurn(uint cellId) => Combined.Add($"SC:{cellId:x8}"); /// S4-c2: retail RenderDeviceD3D::DrawBlock's own /// per-land-cell FlushAlphaList(0.75f) — recorded distinctly /// from 's "SC" line so a test /// can assert its exact position relative to both this cell's own SC /// turn and the NEXT cell's LC turn. public void OnSortCellExit(uint landblockId, int sideCellCount, int cellIndex) => Combined.Add( $"SCX:{(landblockId & 0xFFFF0000u) | (uint)(cellIndex + 1):x8}"); 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 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 virtual float ViewerDistanceTo(WalkBuilding building) => 0f; public IWalkFrameContext CellContext => this; // Permissive near plane: every column wholly inside. // S3 chunk 3 (T2): settable, default unchanged — a permissive // horizontal near plane every pre-existing test in this file still // gets. T2 overrides it with a VERTICAL plane to carve a real // out-of-view cell out of an admitted block via WalkVisibilityMath's // OWN geometry (not a hand-set CellInView CheckBlocks would just // overwrite). public WalkPlane CyPlane { get; set; } = 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, GfxObjId = 0 }; walk.DrawBuilding(building, new WalkPortalView(), ctx, recorder); Assert.Equal("BLD:f518002e", recorder.Signature()); Assert.Equal(0, recorder.BuildingAlphaTurns); Assert.Empty(recorder.ShellSelections); } [Fact] public void NullBspSelectedGfxStillRunsAlphaAndShellWithoutPortalWalk() { var building = new WalkBuilding { PositionCellId = 0xF518002Eu, GfxObjId = 0x01000001u, DrawingBsp = null, }; var recorder = new Recorder(); new RetailFrameWalk().DrawBuilding( building, new WalkPortalView(), new TestContext(), recorder); Assert.Equal("BLD:f518002e", recorder.Signature()); Assert.Equal(1, recorder.BuildingAlphaTurns); WalkBuildingSelection selected = Assert.Single(recorder.ShellSelections); Assert.Equal(0x01000001u, selected.GfxObjId); Assert.Null(selected.DrawingBsp); } [Fact] public void SelectionUsesStrictThresholdAndFinalZeroIsCompleteBodyFailure() { var firstBsp = new WalkBspNode(); var building = new WalkBuilding { GfxObjId = 0x0100FFFFu, DegradeLevels = [ new(0x01000001u, 1u, 10f, 20f, 30f, firstBsp), new(0u, 1u, 30f, 40f, 50f, null), ], }; Assert.Equal(0x01000001u, building.Select(29.999f, 0f, 1f).GfxObjId); WalkBuildingSelection equality = building.Select(30f, 0f, 1f); Assert.Equal(0u, equality.GfxObjId); Assert.Equal(1, equality.Level); var recorder = new Recorder(); var context = new DistanceContext(80f); new RetailFrameWalk().DrawBuilding( building, new WalkPortalView(), context, recorder); Assert.Equal("BLD:00000000", recorder.Signature()); Assert.Equal(0, recorder.BuildingAlphaTurns); Assert.Empty(recorder.ShellSelections); } [Fact] public void SelectionComparesStoredFloatEffectiveDistanceToWideThreshold() { const float multiplier = 0.0020020019728690386f; const float effective = 24.04804801940918f; Assert.Equal(0x41C06267u, BitConverter.SingleToUInt32Bits(effective)); var building = new WalkBuilding { DegradeLevels = [ new(0x01000001u, 1u, 0f, 24f, 48f, null), new(0x01000002u, 1u, 48f, 64f, 80f, null), ], }; WalkBuildingSelection selected = building.Select( effective, degradeDistance: 0f, degradeMultiplier: multiplier); Assert.Equal(0, selected.Level); Assert.Equal(0x01000001u, selected.GfxObjId); } [Fact] public void DirectAndLadderSelectionsCarryExactIdBspLevelAndMode() { var directBsp = new WalkBspNode(); var direct = new WalkBuilding { GfxObjId = 0x01000010u, DrawingBsp = directBsp, }; Assert.Equal( new WalkBuildingSelection(0x01000010u, directBsp, 0, 1u), direct.Select(float.NaN, 50f, float.NaN)); var nearBsp = new WalkBspNode(); var middleBsp = new WalkBspNode(); var ladder = new WalkBuilding { GfxObjId = 0x0100FFFFu, DegradeLevels = [ new(0x01000011u, 3u, 10f, 20f, 30f, nearBsp), new(0x01000012u, 5u, 30f, 40f, 50f, middleBsp), new(0u, 7u, 50f, 60f, 70f, null), ], }; Assert.Equal( new WalkBuildingSelection(0x01000011u, nearBsp, 0, 3u), ladder.Select(29f, 5f, 0.5f)); // effective 24 < positive threshold 25 Assert.Equal( new WalkBuildingSelection(0x01000011u, nearBsp, 0, 3u), ladder.Select(-19f, 5f, -0.5f)); // effective 14 < negative threshold 15 Assert.Equal( new WalkBuildingSelection(0x01000012u, middleBsp, 1, 5u), ladder.Select(30f, 5f, 0.5f)); // equality advances Assert.Equal(2, ladder.Select(float.PositiveInfinity, 5f, 0f).Level); Assert.Equal(2, ladder.Select(float.NegativeInfinity, 5f, 0f).Level); Assert.Equal(2, ladder.Select(float.NaN, 5f, 0f).Level); } [Fact] public void DisableAndForcedLevelBranchesPreserveRetailPrecedenceAndClamp() { var firstBsp = new WalkBspNode(); var building = new WalkBuilding { DegradeLevels = [ new(0x01000021u, 11u, 1f, 2f, 3f, firstBsp), new(0x01000022u, 12u, 3f, 4f, 5f, null), new(0u, 13u, 5f, 6f, 7f, null), ], }; Assert.Equal( new WalkBuildingSelection(0x01000021u, firstBsp, 0, 11u), building.Select(float.PositiveInfinity, 50f, 1f, degradesDisabled: true, forcedLevel: 2)); Assert.Equal(1, building.Select(0f, 50f, 0f, forcedLevel: 1).Level); WalkBuildingSelection clamped = building.Select(0f, 50f, 0f, forcedLevel: 99); Assert.Equal(2, clamped.Level); Assert.Equal(13u, clamped.Mode); Assert.Equal(0u, clamped.GfxObjId); } private sealed class DistanceContext(float distance) : TestContext { public override float ViewerDistanceTo(WalkBuilding building) => distance; } // ── T2 (S3 chunk 3 §9.3): the per-cell interleave — DrawLandCell (LC) // fires BEFORE its own cell's building/object turn, cells draw // far-to-near, an out-of-view cell under alwaysDrawObjects keeps its // sort turn (SC) but gets NO LC, and no whole-stage terrain event // exists any more (superseded — S3 §9.1 R4). Real CalcDrawOrder/ // CheckBlocks run (not hand-set and bypassed): a VERTICAL CyPlane // (N=(1,0,0)) at X-threshold 30 carves an honest "one column // out-of-view" result out of the viewer's own 8-side block via // WalkVisibilityMath's OWN corner-vs-plane test — LandCellCheck's // n!=8 branch marks EVERY cell PartiallyInside unconditionally, so an // out-of-view CELL (as opposed to a whole out-of-view BLOCK, which // DrawLandscape's own block-level gate skips entirely, LC and SC // alike) is only reachable at full n==8 resolution — hence the near // block's own SC count is unavoidably 64 (AlwaysDrawObjects fires for // every cell of an admitted block regardless of individual // visibility), so this test asserts STRUCTURALLY (membership + local // order) rather than as one 64-cell literal sequence; T1 separately // pins the exact draw ORDER via LandWalkOrder + CalcDrawOrder against // the real retail transcripts. ──────────────────────────────────────── [Fact] public void DrawLandscape_InterleavesLandCellTurnsWithBuildingAndObjectTurns_FarToNear() { var ctx = new TestContext { // Vertical plane (Z-independent): GetPointLimit's third arm // reduces to "d = x*1 + D < -eps => Outside" at (x, y, 0) — a // pure world-X half-space test, the CY-only arm (ViewCount==0 // below) is the sole plane BlockCheck consults. CyPlane = new WalkPlane(new Vector3(1, 0, 0), -30f), }; var landscape = new WalkLandscape { MidWidth = 2, Blocks = new WalkLandBlock?[4], ViewerBlockX = 0, ViewerBlockY = 0, ViewerCellX = 0, ViewerCellY = 0, }; // The viewer's own block (grid slot 0 = (0,0)): world X/Y in // [0, 192). Threshold 30 excludes the cx=0 column (X in [0,24], both // corners < 30) and admits every other column (cx=1's west corner // straddles at X=24 -> PartiallyInside; cx>=2 is fully >= 30). var nearBlock = new WalkLandBlock { LandblockId = 0x11110000u, SideCellCount = 8, MaxZ = 10f, MinZ = 0f, }; nearBlock.EnsureCellArrays(); // A building at an ordinary in-view cell (cx=3, cy=0), away from // both the excluded column and the closest/farthest cells. var building = new WalkBuilding { PositionCellId = 0x11110019u, GfxObjId = 0 }; nearBlock.CellBuildings[3 * 8 + 0] = building; // The far block (grid slot 3 = (1,1), the diagonal corner — drawn // FIRST since GetBlockOrder puts ring-1 slots ahead of the viewer's // own and DrawLandscape walks that list BACKWARDS). World X/Y in // [192, 384) sits comfortably east of the threshold -> the block // itself is EntirelyInside; its one LOD-1 cell still classifies // PartiallyInside (LandCellCheck's n!=8 branch marks every cell of // a coarse block that way unconditionally) — either way, != Outside, // so it still gets an LC turn, which is all this test needs. var farBlock = new WalkLandBlock { LandblockId = 0x22220000u, SideCellCount = 1, MaxZ = 10f, MinZ = 0f, }; farBlock.EnsureCellArrays(); landscape.Blocks[0] = nearBlock; landscape.Blocks[3] = farBlock; var walk = new RetailFrameWalk(); // AlwaysDrawObjects defaults true (retail default). var recorder = new Recorder(); // ViewCount == 0 exercises the CY-only visibility arm deterministically // (matches Outdoor_frame_emits_landscape_then_buildings_far_to_near's // own convention) — the CyPlane above is the ONLY plane consulted. walk.DrawLandscape(landscape, new WalkPortalView(), ctx, recorder); Assert.Equal("LS", recorder.Combined[0]); // S3 chunk 3 fix round 1 (F6): the "no TERRAIN event" assertion this // line used to make was vacuous — this Recorder's own vocabulary // (Combined) never had a "TERRAIN"-prefixed entry to begin with, so // the assertion could never fail. WalkFrameEventKind has no // TerrainSlice case any more (chunk 3 deleted it outright), and the // driver-level pins in WalkFrameDriverTests (F4: the outdoor-root, // interior-root, and cross-landblock-batching RunFrame tests) are // what actually prove no whole-stage terrain draw survives. const uint farCellId = 0x22220001u; int farLcIndex = recorder.Combined.IndexOf($"LC:{farCellId:x8}"); int farScIndex = recorder.Combined.IndexOf($"SC:{farCellId:x8}"); Assert.True(farLcIndex >= 0 && farScIndex > farLcIndex, "far block: LC before SC"); var outsideIds = new List(); var insideIds = new List(); for (int cx = 0; cx < 8; cx++) for (int cy = 0; cy < 8; cy++) { uint cellId = 0x11110000u | (uint)(cx * 8 + cy + 1); (cx == 0 ? outsideIds : insideIds).Add(cellId); } // Far block draws entirely before ANY near-block content — the // far-to-near block order. int nearFirstIndex = insideIds.Concat(outsideIds) .Select(id => recorder.Combined.IndexOf($"SC:{id:x8}")) .Where(i => i >= 0) .Min(); Assert.True(farScIndex < nearFirstIndex, "far block must draw before the near block"); // The excluded column: SC fires (AlwaysDrawObjects), LC never does. foreach (uint cellId in outsideIds) { Assert.Contains($"SC:{cellId:x8}", recorder.Combined); Assert.DoesNotContain($"LC:{cellId:x8}", recorder.Combined); } // Every other near-block cell: LC before SC (with the one // building's BLD sitting strictly between them for its own cell). Assert.Equal(56, insideIds.Count); foreach (uint cellId in insideIds) { int lc = recorder.Combined.IndexOf($"LC:{cellId:x8}"); int sc = recorder.Combined.IndexOf($"SC:{cellId:x8}"); Assert.True(lc >= 0, $"cell 0x{cellId:x8} must have an LC turn"); Assert.True(sc > lc, $"cell 0x{cellId:x8}: LC must precede SC"); } const uint buildingCellId = 0x11110019u; int bldIndex = recorder.Combined.IndexOf($"BLD:{buildingCellId:x8}"); int buildingLc = recorder.Combined.IndexOf($"LC:{buildingCellId:x8}"); int buildingSc = recorder.Combined.IndexOf($"SC:{buildingCellId:x8}"); Assert.True(bldIndex >= 0, "the building's own cell must emit BLD"); Assert.True(buildingLc < bldIndex && bldIndex < buildingSc, "LC, then BLD, then SC"); } /// S4-c2: RenderDeviceD3D::DrawBlock's own per-land-cell /// FlushAlphaList(0.75f) call — pinned at the exact position spec /// §7 site 2 requires: strictly AFTER that cell's own object-list turn /// (OnLandscapeCellTurn/"SC") and strictly BEFORE the next /// visited cell's terrain turn (OnLandCellTurn/"LC"). Reuses /// 's /// exact two-block far/near setup so the "next cell" crossing is a real /// cross-BLOCK boundary, not a same-block artifact. Mutation check: /// emitting OnSortCellExit BEFORE OnLandscapeCellTurn (the /// wrong side of the object turn) would place SCX before SC for the far /// cell — the first assertion fails; emitting it once per BLOCK instead /// of once per CELL would produce only one SCX total instead of one per /// near-block cell — the count assertion fails. [Fact] public void DrawLandscape_EmitsSortCellExitAfterEachCellsObjectTurnAndBeforeTheNextCellsLandTurn() { var ctx = new TestContext { CyPlane = new WalkPlane(new Vector3(1, 0, 0), -30f), }; var landscape = new WalkLandscape { MidWidth = 2, Blocks = new WalkLandBlock?[4], ViewerBlockX = 0, ViewerBlockY = 0, ViewerCellX = 0, ViewerCellY = 0, }; var nearBlock = new WalkLandBlock { LandblockId = 0x11110000u, SideCellCount = 8, MaxZ = 10f, MinZ = 0f, }; nearBlock.EnsureCellArrays(); var farBlock = new WalkLandBlock { LandblockId = 0x22220000u, SideCellCount = 1, MaxZ = 10f, MinZ = 0f, }; farBlock.EnsureCellArrays(); landscape.Blocks[0] = nearBlock; landscape.Blocks[3] = farBlock; var walk = new RetailFrameWalk(); var recorder = new Recorder(); walk.DrawLandscape(landscape, new WalkPortalView(), ctx, recorder); // The far block's single cell: SCX sits strictly between its own SC // turn and the FIRST cell of the near block (the very next LC the // walk visits, per the far-to-near block order this landscape's // sibling test already proves). const uint farCellId = 0x22220001u; int farSc = recorder.Combined.IndexOf($"SC:{farCellId:x8}"); int farScx = recorder.Combined.IndexOf($"SCX:{farCellId:x8}"); Assert.True(farSc >= 0 && farScx == farSc + 1, "SCX must immediately follow its own cell's SC"); int firstNearLc = -1; for (int i = 0; i < recorder.Combined.Count; i++) { if (recorder.Combined[i].StartsWith("LC:11110", StringComparison.Ordinal)) { firstNearLc = i; break; } } Assert.True(firstNearLc > farScx, "the far cell's SCX must precede the near block's first LC"); // Every near-block cell (visited or not — SC/SCX fire under // AlwaysDrawObjects regardless of LC) gets exactly one SCX, each // immediately after that same cell's own SC. for (int cx = 0; cx < 8; cx++) for (int cy = 0; cy < 8; cy++) { uint cellId = 0x11110000u | (uint)(cx * 8 + cy + 1); int sc = recorder.Combined.IndexOf($"SC:{cellId:x8}"); int scx = recorder.Combined.IndexOf($"SCX:{cellId:x8}"); Assert.True(sc >= 0, $"cell 0x{cellId:x8} must have an SC turn"); Assert.Equal(sc + 1, scx); } // Total SCX count equals the total cell count (1 far + 64 near) — // one per cell, never batched per block. Assert.Equal(65, recorder.Combined.Count(e => e.StartsWith("SCX:", StringComparison.Ordinal))); } [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()); } // ── T4 / R1 (S3 chunk 2): RenderDeviceD3D::Init @0x0059efb0 constructs // TWO PViews — indoor_pview = PView(…, 1) [draw_landscape=true] and // outdoor_pview = PView(…, 0) [draw_landscape=false]. A building // look-in's own DrawCells always runs through the OUTDOOR pview // (RetailFrameWalk.DrawBuilding passes _outdoorPView into // WalkBuildingPortals.DrawPortal), so it can never raise an outside // view regardless of what portals the reached cell has — the mechanism // itself (draw_landscape gating outside_view population) is pinned at // the WalkPView level by // WalkPViewFloodTests.Exit_portal_raises_the_outside_view_only_when_landscape_is_drawn; // this test pins the ASSEMBLY fact that RetailFrameWalk wires the two // PView instances with the correct flags. [Fact] public void RetailFrameWalk_WiresTheOutdoorPViewWithDrawLandscapeFalse_AndTheInteriorPViewTrue() { var walk = new RetailFrameWalk(); Assert.False(walk.OutdoorPView.DrawLandscape); Assert.True(walk.InteriorPView.DrawLandscape); } [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); } }