using System.Collections.Concurrent; using System.Collections.ObjectModel; using System.Diagnostics.CodeAnalysis; using System.Numerics; using System.Reflection; using AcDream.App.Rendering; using AcDream.App.Rendering.Gpu; using AcDream.App.Rendering.Gpu.Vk; using AcDream.App.Rendering.Scene; using AcDream.App.Rendering.Wb; using AcDream.App.Rendering.Walk; using AcDream.App.Tests.Rendering.Gpu; using AcDream.Content; using AcDream.Core.Meshing; using AcDream.Core.World; using DatReaderWriter; using DatReaderWriter.DBObjs; using DatReaderWriter.Enums; using DatReaderWriter.Lib.IO; using Microsoft.Extensions.Logging.Abstractions; namespace AcDream.App.Tests.Rendering.Walk; /// /// Campaign FW stage FW3.2b-1: 's headless /// referee suite — proves that driving with the /// driver as its produces retail's own turn /// order (all shells then all contents in reverse flood order, /// flush-before-every-leaf-action, /// content-before-punch) through the REAL /// onto a — never a mock of the submission /// path itself. No production wiring is exercised (WorldSceneRenderer /// still does not construct this driver); every world-data/leaf-renderer /// dependency here is a synthetic fake per plan §FW3.2b-1. /// public sealed class WalkFrameDriverTests { // ── Shared ordered log: BOTH the fake leaf renderer and the fake trace // write into ONE list, so a single sequence assertion proves the FULL // interleave (stream flushes interleaved with sky/terrain/shell/punch/ // alpha-barrier), not just each half in isolation. ───────────────────── private sealed class RecordingLeafRenderer( List log, RetailAlphaQueue? alpha = null) : IWalkFrameLeafRenderer { public readonly List Punches = new(); public readonly List Shells = new(); public readonly List AlphaPendingAtBarrier = new(); public readonly List<(uint LandblockId, int SideCellCount, int CellIndex)[]> LandCellBatches = new(); /// S3 chunk 3 fix round 1 (F2/F4c): cell ids /// reports as having NO /// renderable emitter. Empty by default so EVERY pre-existing pin in /// this file keeps its old "every StaticParticles/CellParticles turn /// submits unconditionally" behavior unchanged; a test proving the /// new "genuinely empty turn submits nothing and does not flush" /// rule opts specific cells OUT via this set. public readonly HashSet CellsWithoutEmitters = new(); /// S3 chunk 2: the exit-seal polygon count this fake /// reports back to the driver (B2 — /// returns the submitted count so the driver can re-arm its /// persistent PortalsDrawnCount for the NEXT ov>0 /// frame's clear decision, R4). Defaults to 1 so a test that /// doesn't care about the re-arm mechanism sees an ordinary /// "some seal fired" outcome; a test proving "flood with no exit /// portal never clears" sets this to 0. public int SealPolygonsSubmitted = 1; public void DrawSky() => log.Add("SKY"); public void DrawLandCellBatch( IReadOnlyList<(uint LandblockId, int SideCellCount, int CellIndex)> cells) { LandCellBatches.Add(cells.ToArray()); log.Add("LANDCELL:" + string.Join( ',', cells.Select(c => $"{c.LandblockId:x8}:{c.SideCellCount}:{c.CellIndex}"))); } public bool HasRenderableEmittersInCell(uint cellId) => !CellsWithoutEmitters.Contains(cellId); public void DrawCellShell(uint cellId) { Shells.Add(cellId); log.Add($"SHELL:{cellId:x8}"); } public void FlushLandscape() => log.Add("LFLUSH"); public void ClearInteriorDepth() => log.Add("CLEAR"); public int DrawExitSeals() { log.Add("SEALS"); return SealPolygonsSubmitted; } public void DrawPunchFan(WalkPolygon worldPolygon, int activeViewIndex) { Punches.Add(worldPolygon); log.Add($"PUNCH:{worldPolygon.Vertices.Length}@v{activeViewIndex}"); } public void AlphaBarrier() { if (alpha is null) { log.Add("ALPHA"); return; } AlphaPendingAtBarrier.Add(alpha.PendingCount); log.Add($"ALPHA:{alpha.PendingCount}"); alpha.Flush(); } public void DrawStaticParticles(uint cellId) => log.Add($"PARTICLES:{cellId:x8}"); public void DrawCellParticles(uint cellId) => log.Add($"CELL-PARTICLES:{cellId:x8}"); } private sealed class RecordingTrace(List log) : IWalkFrameDriverTrace { public void OnFlush(int commandCount, IReadOnlyList stages) => log.Add($"FLUSH:{commandCount}:{string.Join(',', stages.Distinct())}"); } private sealed class FakeWorldData : IWalkFrameWorldData { public readonly Dictionary CellStaticsByCell = new(); public readonly Dictionary CellDynamicsByCell = new(); public readonly Dictionary OutdoorStaticsByCell = new(); public readonly Dictionary OutdoorDynamicsByCell = new(); public readonly Dictionary ShellByBuilding = new(); public readonly Dictionary WorldTransformByBuilding = new(); public WalkFrameStaticRecords GetCellStatics(uint cellId) => CellStaticsByCell.GetValueOrDefault(cellId, WalkFrameStaticRecords.Empty); public WalkFrameStaticRecords GetCellDynamics(uint cellId) => CellDynamicsByCell.GetValueOrDefault(cellId, WalkFrameStaticRecords.Empty); public WalkFrameStaticRecords GetOutdoorStatics(uint cellId) => OutdoorStaticsByCell.GetValueOrDefault(cellId, WalkFrameStaticRecords.Empty); public WalkFrameStaticRecords GetOutdoorDynamics(uint cellId) => OutdoorDynamicsByCell.GetValueOrDefault(cellId, WalkFrameStaticRecords.Empty); public WalkFrameStaticRecords GetBuildingShellStatics(WalkBuilding building) => ShellByBuilding.GetValueOrDefault(building, WalkFrameStaticRecords.Empty); public Matrix4x4 GetBuildingWorldTransform(WalkBuilding building) => WorldTransformByBuilding.GetValueOrDefault(building, Matrix4x4.Identity); } // ── The walk-level test context (interior flood + building portal pass) ─ 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(); public readonly Dictionary ViewerDistances = new(); private readonly Matrix4x4 _viewProj; private static readonly Vector2[] RootQuad = [ new(0, 480), new(640, 480), new(640, 0), new(0, 0), ]; 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) => ViewerDistances.GetValueOrDefault(building, 0f); public IWalkFrameContext CellContext => this; 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) { Span projected = stackalloc WalkScreenPoint[polygon.Vertices.Length]; for (int i = 0; i < polygon.Vertices.Length; i++) projected[i] = WalkScreenClip.TransformToScreen( polygon.Vertices[i], _viewProj, ViewportWidth, ViewportHeight); if (side != 0) projected.Reverse(); return WalkScreenClip.ClipAgainstView(projected, RootQuad, output); } } 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), }; // ── Deliverable: RunFrame drives an interior two-cell flood using // PView::DrawCells' exact two reverse loops: ALL shells far-to-near, // then ALL object cells far-to-near. ───────────────────────────────── // ── Deliverable (S3 chunk 2, superseding the 2026-08-30 decomp // correction): PView::DrawCells @0x005a4840's actual DRAW order for an // interior root's OWN flood is NOT the order its DrawInside/DrawCells // EVENTS fire in (breakpoint-entry order, matching the FW0 oracle // traces) — retail draws LScape::draw FIRST (pc:432719, only when exit // views survived), then the landscape flush + device-stamp advance, // then a GATED depth clear (pc:432731-432732 — R4: read-then-zero // portalsDrawnCount @0x005a489c-0x005a489e, clear iff nonzero), then // the exit-portal seals (pc:432785-432786), and ONLY THEN the flood's // own cells far-to-near. This case has a surviving exit view (ov=1): DC // records the flood list (no draw), the landscape turn runs (sky, // terrain), THEN flush, THEN — since this is a FRESH driver's very // first ov>0 flood, so portalsDrawnCount starts at 0 (R4's // "first-frame no-clear quirk") — the clear is SKIPPED, then seals, // then all shells and all contents in reverse order. ────────────────── [Fact] public void RunFrame_InteriorFloodWithExitView_FreshDriverSkipsTheGatedClearThenDrawsSealsAndFloodCells() { using var fx = new DispatcherFixture(); var log = new List(); const ulong gfxObjA = 0x0200_0001UL; const ulong gfxObjB = 0x0200_0002UL; InjectRenderData(fx.Manager, gfxObjA, MakeFlatMesh( MakeBatch(0x08100001u, TranslucencyKind.Opaque, 0, 0, 3, 1))); InjectRenderData(fx.Manager, gfxObjB, MakeFlatMesh( MakeBatch(0x08100002u, TranslucencyKind.Opaque, 3, 4, 3, 2))); var ctx = new TestContext(); var cell1 = new WalkCell { CellId = 0x100, StabList = [0x101u], Portals = [ new WalkCellPortal { OtherCellId = 0x101, PolygonIndex = 0, PortalSide = 0, OtherPortalId = 0, }, // The exit portal (retail's "world beyond the door") — this // is what raises ov to 1 and makes DrawInside run the // landscape turn before clear+seals+the flood cells. new WalkCellPortal { OtherCellId = 0xFFFFFFFF, PolygonIndex = 1, PortalSide = 0, OtherPortalId = -1, }, ], PortalPolygons = [Quad(-2f), Quad(-3f)], }; var cell2 = new WalkCell { CellId = 0x101, Portals = [new WalkCellPortal { OtherCellId = 0x100, PolygonIndex = 0, PortalSide = 1, OtherPortalId = 0, }], PortalPolygons = [Quad(-2f)], }; ctx.Cells[cell1.CellId] = cell1; ctx.Cells[cell2.CellId] = cell2; var worldData = new FakeWorldData(); worldData.CellStaticsByCell[0x100] = new WalkFrameStaticRecords( new[] { MakeRecord(101, 0, Vector3.Zero, [new MeshRef((uint)gfxObjA, Matrix4x4.Identity)]) }, 0x8C04u); worldData.CellStaticsByCell[0x101] = new WalkFrameStaticRecords( new[] { MakeRecord(102, 0, Vector3.Zero, [new MeshRef((uint)gfxObjB, Matrix4x4.Identity)]) }, 0x8C04u); var leaf = new RecordingLeafRenderer(log); var trace = new RecordingTrace(log); using ClipFrame clipFrame = ClipFrame.NoClip(); var driver = new WalkFrameDriver( fx.Dispatcher, leaf, worldData, trace, clipFrame); var walk = new RetailFrameWalk(); // A minimal, no-op landscape (1x1 window, the one slot unpublished) // — matches RetailFrameWalkTests' own exit-view fixture. LScape::draw // still runs its full turn (sky/terrain) against it; there's simply // nothing published to iterate blocks/cells/buildings for. var landscape = new WalkLandscape { MidWidth = 1, Blocks = new WalkLandBlock?[1] }; using DrawScope draw = fx.BeginDraw(); driver.RunFrame( walk, cameraCellId: cell1.CellId, cameraCell: cell1, landscape: landscape, ctx, draw.Frame, draw.Pass, Matrix4x4.Identity, cameraWorldPosition: Vector3.Zero); Assert.Equal( new[] { // No CLEAR: R4's first-frame no-clear quirk — this driver's // PortalsDrawnCount starts at 0, so the read-then-zero // decision sees "not armed" even though ov=1. "SKY", "LFLUSH", "SEALS", "SHELL:00000101", "SHELL:00000100", "FLUSH:1:CellStatic", "CELL-PARTICLES:00000101", "FLUSH:1:CellStatic", "CELL-PARTICLES:00000100", }, log); Assert.DoesNotContain("CLEAR", log); // This frame's own seals (RecordingLeafRenderer's default // SealPolygonsSubmitted) armed the counter for a NEXT ov>0 frame. Assert.Equal(1, driver.PortalsDrawnCount); List mdiCalls = [.. fx.Device.Calls.OfType()]; Assert.Equal(2, mdiCalls.Count); Assert.All(mdiCalls, c => Assert.Equal(1u, c.DrawCount)); // Nothing dropped: every populated record reached exactly one indirect draw. Assert.Equal(2, mdiCalls.Sum(c => (int)c.DrawCount)); // Exit seals replay through the exact portal_view slices captured by // this walk. The legacy PortalVisibilityFrame is not consulted. Assert.Equal(1, driver.InteriorFloodViewSliceCountAt(0)); Assert.Equal(1, driver.InteriorFloodViewSliceCountAt(1)); Assert.Equal(4, driver.InteriorFloodViewClipPlanesAt(0, 0).Length); Assert.Equal(4, driver.InteriorFloodViewClipPlanesAt(1, 0).Length); Assert.True(clipFrame.SlotCount >= 3); } [Fact] public void InteriorFloodAfterLandscape_RearmsRetailPartStampForPostClearCellRepaint() { using var fx = new DispatcherFixture(); var log = new List(); const ulong gfxObj = 0x0200_0021UL; const uint outdoorCellId = 0xF4180009u; const uint interiorCellId = 0xF4180112u; InjectRenderData(fx.Manager, gfxObj, MakeFlatMesh( MakeBatch(0x08100021u, TranslucencyKind.Opaque, 0, 0, 3, 1))); RenderProjectionRecord sharedPart = MakeRecord( 0x4F41806Cu, 0, Vector3.Zero, [new MeshRef((uint)gfxObj, Matrix4x4.Identity)]); var worldData = new FakeWorldData(); worldData.OutdoorStaticsByCell[outdoorCellId] = new WalkFrameStaticRecords(new[] { sharedPart }, 0xF418u); worldData.CellStaticsByCell[interiorCellId] = new WalkFrameStaticRecords(new[] { sharedPart }, 0xF418u); var ctx = new TestContext(); var interiorCell = new WalkCell { CellId = interiorCellId }; interiorCell.PushView(); WalkCopyView.AppendFullViewportQuad( interiorCell.TopView, ctx.Rays, ctx.WorldViewpoint, ctx.ViewportWidth, ctx.ViewportHeight); ctx.Cells[interiorCellId] = interiorCell; var driver = new WalkFrameDriver( fx.Dispatcher, new RecordingLeafRenderer(log), worldData, new RecordingTrace(log)); IWalkEventSink sink = driver; using DrawScope draw = fx.BeginDraw(); driver.BeginFrame(ctx, Matrix4x4.Identity, Vector3.Zero); sink.Emit(WalkEvent.Landscape(activeViewCount: 1)); var landscapeViews = new WalkPortalView(); WalkCopyView.AppendFullViewportQuad( landscapeViews, ctx.Rays, ctx.WorldViewpoint, ctx.ViewportWidth, ctx.ViewportHeight); sink.OnLandscapeViews(landscapeViews); sink.OnLandscapeCellTurn(outdoorCellId); // A landscape turn ran (activeViewCount: 1 above), so this models // retail's ov>0 case. sink.OnInteriorFloodDrawTurn([interiorCellId], outsideViewCount: 1); driver.EndFrame(); driver.Replay(draw.Frame, draw.Pass); List mdiCalls = [.. fx.Device.Calls.OfType()]; Assert.Equal(2, mdiCalls.Count); Assert.All(mdiCalls, call => Assert.Equal(1u, call.DrawCount)); Assert.Equal(2, mdiCalls.Sum(call => (int)call.DrawCount)); Assert.Equal(2, log.Count(entry => entry == "FLUSH:1:OutdoorStatic" || entry == "FLUSH:1:CellStatic")); } // ── Deliverable (T1, flipped for S3 chunk 2): the ov==0 interior case — // no exit view survives, so DrawInside never runs the landscape turn, // and PView::DrawCells' whole landscape-flush/stamp/clear/seal turn // (S3 §8.1 R3: all four gated on outside_view.view_count > 0) never // runs either — straight to the flood's own cells after the // (draw-nothing) DC event. ────────────────────────────────────────── [Fact] public void RunFrame_InteriorFloodWithNoExitView_SkipsLandscapeAndNeverFlushesClearsOrSeals() { using var fx = new DispatcherFixture(); var log = new List(); const ulong gfxObjA = 0x0200_0003UL; const ulong gfxObjB = 0x0200_0004UL; InjectRenderData(fx.Manager, gfxObjA, MakeFlatMesh( MakeBatch(0x08100003u, TranslucencyKind.Opaque, 0, 0, 3, 1))); InjectRenderData(fx.Manager, gfxObjB, MakeFlatMesh( MakeBatch(0x08100004u, TranslucencyKind.Opaque, 3, 4, 3, 2))); var ctx = new TestContext(); var cell1 = new WalkCell { CellId = 0x100, StabList = [0x101u], Portals = [new WalkCellPortal { OtherCellId = 0x101, PolygonIndex = 0, PortalSide = 0, OtherPortalId = 0, }], PortalPolygons = [Quad(-2f)], }; var cell2 = new WalkCell { CellId = 0x101, Portals = [new WalkCellPortal { OtherCellId = 0x100, PolygonIndex = 0, PortalSide = 1, OtherPortalId = 0, }], PortalPolygons = [Quad(-2f)], }; ctx.Cells[cell1.CellId] = cell1; ctx.Cells[cell2.CellId] = cell2; var worldData = new FakeWorldData(); worldData.CellStaticsByCell[0x100] = new WalkFrameStaticRecords( new[] { MakeRecord(101, 0, Vector3.Zero, [new MeshRef((uint)gfxObjA, Matrix4x4.Identity)]) }, 0x8C04u); worldData.CellStaticsByCell[0x101] = new WalkFrameStaticRecords( new[] { MakeRecord(102, 0, Vector3.Zero, [new MeshRef((uint)gfxObjB, Matrix4x4.Identity)]) }, 0x8C04u); var leaf = new RecordingLeafRenderer(log); var trace = new RecordingTrace(log); var driver = new WalkFrameDriver(fx.Dispatcher, leaf, worldData, trace); var walk = new RetailFrameWalk(); using DrawScope draw = fx.BeginDraw(); driver.RunFrame( walk, cameraCellId: cell1.CellId, cameraCell: cell1, landscape: new WalkLandscape(), ctx, draw.Frame, draw.Pass, Matrix4x4.Identity, cameraWorldPosition: Vector3.Zero); // No SKY/TERRAIN — ov==0 means DrawInside never calls DrawLandscape // at all. T1 (flipped): LFLUSH/CLEAR/SEALS and the device-stamp // advance are ALSO absent now — S3 §8.1 R3 gates all four on // outside_view.view_count > 0, and it is 0 here. Assert.Equal( new[] { "SHELL:00000101", "SHELL:00000100", "FLUSH:1:CellStatic", "CELL-PARTICLES:00000101", "FLUSH:1:CellStatic", "CELL-PARTICLES:00000100", }, log); Assert.Equal(0, driver.PortalsDrawnCount); List mdiCalls = [.. fx.Device.Calls.OfType()]; Assert.Equal(2, mdiCalls.Count); Assert.All(mdiCalls, c => Assert.Equal(1u, c.DrawCount)); // Nothing dropped: every populated record reached exactly one indirect draw. Assert.Equal(2, mdiCalls.Sum(c => (int)c.DrawCount)); } // ── T2 (S3 chunk 2, R4): a fresh driver's first ov>0 interior flood // skips the gated clear (PortalsDrawnCount starts at 0); that same // frame's own exit seals arm the counter at Replay, so the driver's // SECOND ov>0 flood — even with a different flood cell, since the // counter is driver-scoped, not cell-scoped — sees it nonzero and // clears. ──────────────────────────────────────────────────────────── [Fact] public void OnInteriorFloodDrawTurn_FirstOvFrameSkipsClear_SecondFrameArmedByFirstsSealsClears() { using var fx = new DispatcherFixture(); var log = new List(); var leaf = new RecordingLeafRenderer(log); var ctx = new TestContext(); const uint cellId = 0xF4180200u; var cell = new WalkCell { CellId = cellId }; cell.PushView(); WalkCopyView.AppendFullViewportQuad( cell.TopView, ctx.Rays, ctx.WorldViewpoint, ctx.ViewportWidth, ctx.ViewportHeight); ctx.Cells[cellId] = cell; var driver = new WalkFrameDriver(fx.Dispatcher, leaf, new FakeWorldData()); IWalkEventSink sink = driver; using DrawScope draw = fx.BeginDraw(); // R4: PortalsDrawnCount starts at 0 for a fresh driver — the // 0x005a489c-0x005a489e read-then-zero decision sees "not armed" on // this driver's very first ov>0 interior-root flood, so the gated // depth clear (pc:432731-432732) is skipped even though a landscape // turn just ran. driver.BeginFrame(ctx, Matrix4x4.Identity, Vector3.Zero); sink.Emit(WalkEvent.Landscape(activeViewCount: 1)); var views1 = new WalkPortalView(); WalkCopyView.AppendFullViewportQuad( views1, ctx.Rays, ctx.WorldViewpoint, ctx.ViewportWidth, ctx.ViewportHeight); sink.OnLandscapeViews(views1); sink.OnInteriorFloodDrawTurn([cellId], outsideViewCount: 1); driver.EndFrame(); driver.Replay(draw.Frame, draw.Pass); Assert.Equal( new[] { "SKY", "LFLUSH", "SEALS", "SHELL:f4180200", "CELL-PARTICLES:f4180200", }, log); Assert.DoesNotContain("CLEAR", log); Assert.Equal(1, driver.PortalsDrawnCount); log.Clear(); // Frame 1's exit seals reported SealPolygonsSubmitted (default 1) at // Replay, arming PortalsDrawnCount for THIS frame's read-then-zero. driver.BeginFrame(ctx, Matrix4x4.Identity, Vector3.Zero); sink.Emit(WalkEvent.Landscape(activeViewCount: 1)); var views2 = new WalkPortalView(); WalkCopyView.AppendFullViewportQuad( views2, ctx.Rays, ctx.WorldViewpoint, ctx.ViewportWidth, ctx.ViewportHeight); sink.OnLandscapeViews(views2); sink.OnInteriorFloodDrawTurn([cellId], outsideViewCount: 1); driver.EndFrame(); driver.Replay(draw.Frame, draw.Pass); Assert.Equal( new[] { "SKY", "LFLUSH", "CLEAR", "SEALS", "SHELL:f4180200", "CELL-PARTICLES:f4180200", }, log); } // ── T2 (S3 chunk 2, R4): a driver whose flood never reaches an exit // portal (DrawExitSeals reports 0 submitted every turn) never clears, // across any number of ov>0 frames — retail's counter is fed ONLY by // actually-submitted seal fans. ───────────────────────────────────── [Fact] public void OnInteriorFloodDrawTurn_FloodWithNoExitPortal_NeverClearsAcrossFrames() { using var fx = new DispatcherFixture(); var log = new List(); var leaf = new RecordingLeafRenderer(log) { SealPolygonsSubmitted = 0 }; var ctx = new TestContext(); const uint cellId = 0xF4180201u; var cell = new WalkCell { CellId = cellId }; cell.PushView(); WalkCopyView.AppendFullViewportQuad( cell.TopView, ctx.Rays, ctx.WorldViewpoint, ctx.ViewportWidth, ctx.ViewportHeight); ctx.Cells[cellId] = cell; var driver = new WalkFrameDriver(fx.Dispatcher, leaf, new FakeWorldData()); IWalkEventSink sink = driver; using DrawScope draw = fx.BeginDraw(); for (int frame = 0; frame < 3; frame++) { driver.BeginFrame(ctx, Matrix4x4.Identity, Vector3.Zero); sink.Emit(WalkEvent.Landscape(activeViewCount: 1)); var views = new WalkPortalView(); WalkCopyView.AppendFullViewportQuad( views, ctx.Rays, ctx.WorldViewpoint, ctx.ViewportWidth, ctx.ViewportHeight); sink.OnLandscapeViews(views); sink.OnInteriorFloodDrawTurn([cellId], outsideViewCount: 1); driver.EndFrame(); driver.Replay(draw.Frame, draw.Pass); } Assert.DoesNotContain("CLEAR", log); Assert.Equal(3, log.Count(entry => entry == "SEALS")); Assert.Equal(0, driver.PortalsDrawnCount); } // ── T3 (S3 chunk 2, R1): a building look-in's own DrawCells re-enters // with ov==0 unconditionally and neither ARMS nor CONSUMES the // persistent PortalsDrawnCount counter — retail calls DrawCells // re-entrantly there with no clear/seal step at all. ──────────────── [Fact] public void LookInDrawCells_NeitherArmsNorConsumesThePortalsDrawnCounter() { using var fx = new DispatcherFixture(); var log = new List(); var leaf = new RecordingLeafRenderer(log); var ctx = new TestContext(); const uint rootCellId = 0xF4180301u; const uint lookInCellId = 0xF4180302u; var rootCell = new WalkCell { CellId = rootCellId }; rootCell.PushView(); WalkCopyView.AppendFullViewportQuad( rootCell.TopView, ctx.Rays, ctx.WorldViewpoint, ctx.ViewportWidth, ctx.ViewportHeight); ctx.Cells[rootCellId] = rootCell; var lookInCell = new WalkCell { CellId = lookInCellId }; lookInCell.PushView(); WalkCopyView.AppendFullViewportQuad( lookInCell.TopView, ctx.Rays, ctx.WorldViewpoint, ctx.ViewportWidth, ctx.ViewportHeight); ctx.Cells[lookInCellId] = lookInCell; var driver = new WalkFrameDriver(fx.Dispatcher, leaf, new FakeWorldData()); IWalkEventSink sink = driver; using DrawScope draw = fx.BeginDraw(); // Arm PortalsDrawnCount with one throwaway ov>0 interior-root flood. driver.BeginFrame(ctx, Matrix4x4.Identity, Vector3.Zero); sink.Emit(WalkEvent.Landscape(activeViewCount: 1)); var rootViews = new WalkPortalView(); WalkCopyView.AppendFullViewportQuad( rootViews, ctx.Rays, ctx.WorldViewpoint, ctx.ViewportWidth, ctx.ViewportHeight); sink.OnLandscapeViews(rootViews); sink.OnInteriorFloodDrawTurn([rootCellId], outsideViewCount: 1); driver.EndFrame(); driver.Replay(draw.Frame, draw.Pass); Assert.Equal(1, driver.PortalsDrawnCount); log.Clear(); // A building look-in's own DrawCells re-enters with ov==0 // unconditionally (R1) — no LFLUSH/clear/seal step at all ("DrawCells // re-entrantly there with no clear/seal step"), so it must leave the // already-armed counter alone. driver.BeginFrame(ctx, Matrix4x4.Identity, Vector3.Zero); sink.OnBuildingTurn(new WalkBuilding()); sink.Emit(WalkEvent.DrawCells(outsideViewCount: 0, [lookInCellId])); driver.EndFrame(); driver.Replay(draw.Frame, draw.Pass); Assert.DoesNotContain("LFLUSH", log); Assert.DoesNotContain("CLEAR", log); Assert.DoesNotContain("SEALS", log); Assert.Equal(1, driver.PortalsDrawnCount); } // ── Deliverable: a building turn's alpha barrier precedes its portal // pass (retail RenderDeviceD3D::DrawBuilding @0x0059f2a0: // FlushAlphaList(0f) -> CPhysicsPart::Draw(parts,1) [the portal walk] // -> CPhysicsPart::Draw(parts,0) [the shell] @0x0059f30b-0x0059f345); // the punch pass runs with nothing of THIS building's own queued yet // (the shell is not appended until the whole portal pass completes); // the look-in DC turn draws shell-then-contents exactly like an ordinary // interior flood; the building's own shell content is appended and // flushed only AFTER the portal pass, at frame end; and the punch // polygon reaches the leaf renderer transformed building-local -> // world. ───────────────────────────────────────────────────────────── [Fact] public void BeginEndFrame_BuildingTurnWithPunchAndLookIn_OrdersAlphaBarrierPortalPassThenShell() { using var fx = new DispatcherFixture(); var log = new List(); const ulong shellGfxObj = 0x0200_0010UL; const ulong interiorGfxObj = 0x0200_0011UL; const ulong dynamicGfxObj = 0x0200_0012UL; InjectRenderData(fx.Manager, shellGfxObj, MakeFlatMesh( MakeBatch(0x08100010u, TranslucencyKind.Opaque, 0, 0, 3, 1))); InjectRenderData(fx.Manager, interiorGfxObj, MakeFlatMesh( MakeBatch(0x08100011u, TranslucencyKind.Opaque, 3, 4, 3, 2))); InjectRenderData(fx.Manager, dynamicGfxObj, MakeFlatMesh( MakeBatch(0x08100012u, TranslucencyKind.Opaque, 6, 8, 3, 3))); var ctx = new TestContext(); var interior = new WalkCell { CellId = 0x104, Portals = [new WalkCellPortal { OtherCellId = 0xFFFFFFFF, PolygonIndex = 0, PortalSide = 1, OtherPortalId = 0, }], PortalPolygons = [Quad(-2f)], }; ctx.Cells[interior.CellId] = interior; var building = new WalkBuilding { PositionCellId = 0xA9B4000Fu, Portals = [ new WalkBldPortal { PortalSide = 0, OtherCellId = 0x104, OtherPortalId = 0, StabList = [0x104u], }, ], // Viewpoint (0,0,0) is on the NEGATIVE side of this splitting // plane (d=-5): the single PORT node's side==1 arm emits its // portal exactly once per pass (WalkBuildingPortals.Walk). DrawingBsp = new WalkBspNode { SplittingPlane = new WalkPlane(new Vector3(1, 0, 0), -5f), InPortals = [new WalkPortalRef { PortalIndex = 0, Polygon = Quad(-2f) }], }, }; ctx.ViewerDistances[building] = 12.5f; var worldData = new FakeWorldData(); worldData.ShellByBuilding[building] = new WalkFrameStaticRecords( new[] { MakeRecord(201, 0, Vector3.Zero, [new MeshRef((uint)shellGfxObj, Matrix4x4.Identity)]) }, 0x8C04u); worldData.CellStaticsByCell[0x104] = new WalkFrameStaticRecords( new[] { MakeRecord(202, 0, Vector3.Zero, [new MeshRef((uint)interiorGfxObj, Matrix4x4.Identity)]) }, 0x8C04u); worldData.CellDynamicsByCell[0x104] = new WalkFrameStaticRecords( new[] { MakeRecord(203, 0, Vector3.Zero, [new MeshRef((uint)dynamicGfxObj, Matrix4x4.Identity)], parentCellId: 0x104) }, 0x8C04u); Matrix4x4 buildingWorld = Matrix4x4.CreateTranslation(10f, 0f, 0f); worldData.WorldTransformByBuilding[building] = buildingWorld; var leaf = new RecordingLeafRenderer(log); var trace = new RecordingTrace(log); using ClipFrame clipFrame = ClipFrame.NoClip(); var driver = new WalkFrameDriver( fx.Dispatcher, leaf, worldData, trace, clipFrame); var walk = new RetailFrameWalk(); var activeView = new WalkPortalView(); activeView.ResetForPush(); WalkCopyView.AppendFullViewportQuad( activeView, ctx.Rays, ctx.WorldViewpoint, ctx.ViewportWidth, ctx.ViewportHeight); Assert.Equal(1, activeView.ViewCount); using DrawScope draw = fx.BeginDraw(); driver.BeginFrame(ctx, Matrix4x4.Identity, Vector3.Zero); walk.DrawBuilding(building, activeView, ctx, driver); driver.EndFrame(); driver.Replay(draw.Frame, draw.Pass); Assert.Equal( // Campaign OVERHAUL S2 chunk 6: a building's shell fires no // particle turn of its own any more — interior emitters draw at // their own cell's CellParticles turn instead. new[] { "ALPHA", "PUNCH:4@v0", "SHELL:00000104", "FLUSH:1:LookInStatic", "FLUSH:1:Dynamic", "CELL-PARTICLES:00000104", "FLUSH:1:BuildingShell", }, log); Assert.Equal([0x104u], driver.LookInCellTurns); Assert.Equal([0x104u], driver.LookInCells); Assert.Collection( leaf.Shells, shell => Assert.Equal(0x104u, shell)); WalkPortalView capturedView = ctx.Cells[0x104].PortalViews[0]; WalkViewPoly capturedPoly = Assert.Single(capturedView.View.Polys); Vector2 capturedCenter = Vector2.Zero; for (int edge = 0; edge < capturedPoly.VertexCount; edge++) { capturedCenter += capturedView.View.Vertices[ capturedPoly.VertexIndex + edge].Point; } capturedCenter /= capturedPoly.VertexCount; Vector3 insideCone = ctx.Rays.RayThrough(capturedCenter.X, capturedCenter.Y); Assert.True(driver.SphereVisibleInLookInTurn( 0, in insideCone, 0.1f)); Assert.False(driver.SphereVisibleInLookInTurn( 0, new Vector3(10_000f, 0f, 10f), 0.1f)); uint clipSlot = Assert.Single(driver.VisibleClipSlotsInLookInTurn( 0, in insideCone, 0.1f, testSphere: true)); Assert.NotEqual(0u, clipSlot); Assert.Equal(2, clipFrame.SlotCount); // The punch polygon reached the leaf renderer in WORLD space: the // building-local Quad(-2f) vertex (-0.5,-0.5,-2) translates by // (10,0,0) under the caller-supplied building world transform. WalkPolygon punch = Assert.Single(leaf.Punches); Assert.Equal(new Vector3(9.5f, -0.5f, -2f), punch.Vertices[0]); List mdiCalls = [.. fx.Device.Calls.OfType()]; Assert.Equal(3, mdiCalls.Count); Assert.Equal(3, mdiCalls.Sum(c => (int)c.DrawCount)); } [Fact] public void RepeatedFloodTurns_DrawEnvCellShellWholeOncePerRetailFrameStamp() { using var fx = new DispatcherFixture(); var log = new List(); var leaf = new RecordingLeafRenderer(log); var ctx = new TestContext(); const uint cellId = 0xF4180112u; var cell = new WalkCell { CellId = cellId }; cell.PushView(); WalkCopyView.AppendFullViewportQuad( cell.TopView, ctx.Rays, ctx.WorldViewpoint, ctx.ViewportWidth, ctx.ViewportHeight); ctx.Cells[cellId] = cell; var driver = new WalkFrameDriver( fx.Dispatcher, leaf, new FakeWorldData()); IWalkEventSink sink = driver; using DrawScope draw = fx.BeginDraw(); driver.BeginFrame(ctx, Matrix4x4.Identity, Vector3.Zero); // Neither call models a surviving exit view (ov==0 both times — no // WalkEvent.Landscape turn ran in this synthetic double-entry // scenario), so per S3 §8.1 R3 the device-stamp advance never fires // from either call: this isolates the frame-stamp shell dedup from // the landscape-flush/clear/seal gate entirely. sink.OnInteriorFloodDrawTurn([cellId], outsideViewCount: 0); sink.OnInteriorFloodDrawTurn([cellId], outsideViewCount: 0); driver.EndFrame(); driver.Replay(draw.Frame, draw.Pass); Assert.Equal([cellId], leaf.Shells); Assert.Equal(1, log.Count(entry => entry == "SHELL:f4180112")); Assert.Equal(0, log.Count(entry => entry == "LFLUSH")); Assert.Equal(0, log.Count(entry => entry == "CLEAR")); Assert.Equal(0, log.Count(entry => entry == "SEALS")); Assert.Equal(0, driver.PortalsDrawnCount); } // Campaign OVERHAUL S2 chunk 6 pin — the portal-haze bug this chunk // fixes: a hidden/suspended owner (a portalling player materializing // into its arrival cell) publishes NO registry rows there, so // GetCellStatics/GetCellDynamics report empty records for that cell — // yet retail's CPhysicsObj::add_particle_shadow_to_cell (0x00514a70) // still draws that owner's emitter at the cell's own object-list turn, // because particle draw membership is the emitter's OWN cell, never a // registry/owner lookup. The walk must fire the cell's particle turn // regardless of whether any static/dynamic record names an owner there. [Fact] public void EmitCellContentsTurn_FiresCellParticlesEvenWhenTheCellHasNoStaticOrDynamicRecords() { using var fx = new DispatcherFixture(); var log = new List(); var leaf = new RecordingLeafRenderer(log); var ctx = new TestContext(); const uint arrivalCellId = 0x8A020141u; var arrivalCell = new WalkCell { CellId = arrivalCellId }; ctx.Cells[arrivalCellId] = arrivalCell; // FakeWorldData has NO entry at all for the arrival cell — // GetCellStatics/GetCellDynamics both fall back to // WalkFrameStaticRecords.Empty, exactly the "hidden owner, no // registry rows" scenario. var driver = new WalkFrameDriver(fx.Dispatcher, leaf, new FakeWorldData()); IWalkEventSink sink = driver; using DrawScope draw = fx.BeginDraw(); driver.BeginFrame(ctx, Matrix4x4.Identity, Vector3.Zero); // No landscape turn modeled here — ov==0. sink.OnInteriorFloodDrawTurn([arrivalCellId], outsideViewCount: 0); driver.EndFrame(); driver.Replay(draw.Frame, draw.Pass); Assert.Contains("CELL-PARTICLES:8a020141", log); } [Fact] public void LandscapeStampBoundary_RearmsWholeShellForPostClearRootRepaint() { using var fx = new DispatcherFixture(); var log = new List(); var ctx = new TestContext(); const uint cellId = 0xF4180112u; var cell = new WalkCell { CellId = cellId }; cell.PushView(); WalkCopyView.AppendFullViewportQuad( cell.TopView, ctx.Rays, ctx.WorldViewpoint, ctx.ViewportWidth, ctx.ViewportHeight); ctx.Cells[cellId] = cell; var driver = new WalkFrameDriver( fx.Dispatcher, new RecordingLeafRenderer(new List()), new FakeWorldData()); IWalkEventSink sink = driver; using DrawScope draw = fx.BeginDraw(); // R4 (the "first-frame no-clear quirk", pinned explicitly by // RunFrame_InteriorFloodWithExitView_FreshDriverSkipsTheGatedClear...): // PortalsDrawnCount starts at 0 for a fresh driver, so ITS first // ov>0 flood would skip the clear. Prime the counter with one // throwaway ov>0 flood (this driver's own exit seals arm it) so the // documented scenario below models a STEADY-STATE interior frame, // where the clear actually fires. driver.BeginFrame(ctx, Matrix4x4.Identity, Vector3.Zero); sink.Emit(WalkEvent.Landscape(activeViewCount: 1)); var primerViews = new WalkPortalView(); WalkCopyView.AppendFullViewportQuad( primerViews, ctx.Rays, ctx.WorldViewpoint, ctx.ViewportWidth, ctx.ViewportHeight); sink.OnLandscapeViews(primerViews); sink.OnInteriorFloodDrawTurn([cellId], outsideViewCount: 1); driver.EndFrame(); driver.Replay(draw.Frame, draw.Pass); Assert.Equal(1, driver.PortalsDrawnCount); var leaf = new RecordingLeafRenderer(log); driver.RebindFrame(leaf, clipFrame: null); driver.BeginFrame(ctx, Matrix4x4.Identity, Vector3.Zero); // LScape::draw has begun. A building look-in reached this cell before // PView::DrawCells advances m_nFrameStamp and clears interior depth. sink.Emit(WalkEvent.Landscape(activeViewCount: 1)); var landscapeViews = new WalkPortalView(); WalkCopyView.AppendFullViewportQuad( landscapeViews, ctx.Rays, ctx.WorldViewpoint, ctx.ViewportWidth, ctx.ViewportHeight); sink.OnLandscapeViews(landscapeViews); sink.OnBuildingTurn(new WalkBuilding()); sink.Emit(WalkEvent.DrawCells(outsideViewCount: 0, [cellId])); // The same shell must draw again after the retail stamp increment and // full depth clear; otherwise the pre-clear color survives unpaired // with depth and bleeds through the root's walls. ov=1 (the // landscape turn above ran), and PortalsDrawnCount is armed from // the primer frame's own seals, so the clear actually fires here. sink.OnInteriorFloodDrawTurn([cellId], outsideViewCount: 1); driver.EndFrame(); driver.Replay(draw.Frame, draw.Pass); Assert.Equal([cellId, cellId], leaf.Shells); Assert.Equal(2, log.Count(entry => entry == "SHELL:f4180112")); Assert.Contains("CLEAR", log); Assert.True( log.IndexOf("SHELL:f4180112") < log.IndexOf("CLEAR"), "The look-in shell must precede the interior clear."); Assert.True( log.LastIndexOf("SHELL:f4180112") > log.IndexOf("SEALS"), "The rearmed root shell must repaint after the clear and seals."); } // ── Fail-loud: a DrawCells turn with no preceding DrawInside/Building // turn is a walk/driver desync, not a silent skip. ───────────────────── [Fact] public void Emit_DrawCellsBeforeAnyDrawInsideOrBuildingTurn_ThrowsRatherThanSilentlyDropping() { using var fx = new DispatcherFixture(); var log = new List(); var ctx = new TestContext(); var driver = new WalkFrameDriver(fx.Dispatcher, new RecordingLeafRenderer(log), new FakeWorldData()); using DrawScope draw = fx.BeginDraw(); driver.BeginFrame(ctx, Matrix4x4.Identity, Vector3.Zero); Assert.Throws( () => ((IWalkEventSink)driver).Emit(WalkEvent.DrawCells(0, [0x100u]))); } // ── Fail-loud: BeginFrame is not re-entrant. ──────────────────────────── [Fact] public void BeginFrame_CalledWhileAFrameIsAlreadyOpen_Throws() { using var fx = new DispatcherFixture(); var log = new List(); var ctx = new TestContext(); var driver = new WalkFrameDriver(fx.Dispatcher, new RecordingLeafRenderer(log), new FakeWorldData()); using DrawScope draw = fx.BeginDraw(); driver.BeginFrame(ctx, Matrix4x4.Identity, Vector3.Zero); Assert.Throws( () => driver.BeginFrame( ctx, Matrix4x4.Identity, Vector3.Zero)); driver.EndFrame(); // EndFrame cleared the open-frame guard: BeginFrame is usable again. driver.BeginFrame(ctx, Matrix4x4.Identity, Vector3.Zero); driver.EndFrame(); } // ── Campaign FW3.4a fail-loud: Replay without a completed Collect (no // BeginFrame/EndFrame at all, or BeginFrame with no matching EndFrame) // has nothing recorded to draw — throw rather than silently drawing // nothing, which would look like an empty frame instead of a misuse. ── [Fact] public void Replay_WithNoPrecedingCollect_Throws() { using var fx = new DispatcherFixture(); var log = new List(); var driver = new WalkFrameDriver(fx.Dispatcher, new RecordingLeafRenderer(log), new FakeWorldData()); using DrawScope draw = fx.BeginDraw(); Assert.Throws(() => driver.Replay(draw.Frame, draw.Pass)); } [Fact] public void Replay_WhileCollectIsStillOpen_Throws() { using var fx = new DispatcherFixture(); var log = new List(); var ctx = new TestContext(); var driver = new WalkFrameDriver(fx.Dispatcher, new RecordingLeafRenderer(log), new FakeWorldData()); using DrawScope draw = fx.BeginDraw(); driver.BeginFrame(ctx, Matrix4x4.Identity, Vector3.Zero); Assert.Throws(() => driver.Replay(draw.Frame, draw.Pass)); } // ── Deliverable: Collect + Replay called as the SPLIT PAIR (never // RunFrame) — the shape RetailPViewRenderer uses, since it must run other // frame work (PrepareCellBatches/BuildAndBorrow) between the two. Proves // the pair alone — with no GPU work happening until Replay — reproduces // the same turn order RunFrame's combined call would. ────────────────── [Fact] public void CollectThenReplay_AsSeparateCalls_PerformsNoGpuWorkUntilReplay() { using var fx = new DispatcherFixture(); var log = new List(); var ctx = new TestContext(); var driver = new WalkFrameDriver( fx.Dispatcher, new RecordingLeafRenderer(log), new FakeWorldData(), new RecordingTrace(log)); var walk = new RetailFrameWalk(); // Outdoor root (camera cell low word < 0x100): a minimal, no-op // landscape — LScape::draw still runs its sky/terrain turn against // it even though nothing is published to walk cells/buildings for. var landscape = new WalkLandscape { MidWidth = 1, Blocks = new WalkLandBlock?[1] }; using DrawScope draw = fx.BeginDraw(); driver.Collect( walk, cameraCellId: 0u, cameraCell: null, landscape, ctx, Matrix4x4.Identity, cameraWorldPosition: Vector3.Zero); // No GPU calls at all yet — Collect is CPU-only. Assert.Empty(log); Assert.Empty(fx.Device.Calls); driver.Replay(draw.Frame, draw.Pass); Assert.Equal(new[] { "SKY" }, log); } [Fact] public void RebindFrame_ReusesTheDriverAndRoutesTheNextFrameToTheNewLeaf() { using var fx = new DispatcherFixture(); var firstLog = new List(); var secondLog = new List(); var ctx = new TestContext(); var driver = new WalkFrameDriver( fx.Dispatcher, new RecordingLeafRenderer(firstLog), new FakeWorldData()); var walk = new RetailFrameWalk(); var landscape = new WalkLandscape { MidWidth = 1, Blocks = new WalkLandBlock?[1] }; using DrawScope draw = fx.BeginDraw(); driver.Collect( walk, 0u, null, landscape, ctx, Matrix4x4.Identity, Vector3.Zero); driver.Replay(draw.Frame, draw.Pass); driver.RebindFrame(new RecordingLeafRenderer(secondLog), clipFrame: null); driver.Collect( walk, 0u, null, landscape, ctx, Matrix4x4.Identity, Vector3.Zero); driver.Replay(draw.Frame, draw.Pass); Assert.Equal(new[] { "SKY" }, firstLog); Assert.Equal(new[] { "SKY" }, secondLog); } [Fact] public void RebindFrame_DiscardsAnIncompletePriorFrameAndRecovers() { using var fx = new DispatcherFixture(); var log = new List(); var ctx = new TestContext(); var driver = new WalkFrameDriver( fx.Dispatcher, new RecordingLeafRenderer(new List()), new FakeWorldData()); var walk = new RetailFrameWalk(); var landscape = new WalkLandscape { MidWidth = 1, Blocks = new WalkLandBlock?[1] }; driver.BeginFrame(ctx, Matrix4x4.Identity, Vector3.Zero); driver.RebindFrame(new RecordingLeafRenderer(log), clipFrame: null); using DrawScope draw = fx.BeginDraw(); driver.Collect( walk, 0u, null, landscape, ctx, Matrix4x4.Identity, Vector3.Zero); driver.Replay(draw.Frame, draw.Pass); Assert.Equal(new[] { "SKY" }, log); } // ── Deliverable: an outdoor landscape-cell turn with no building appends // straight to the stream (no shell call — outdoor cells have no EnvCell // shell), and the accumulated content flushes at frame end. ─────────── [Fact] public void OnLandscapeCellTurn_AppendsOutdoorStaticsWithNoShellCallAndFlushesAtFrameEnd() { using var fx = new DispatcherFixture(); var log = new List(); const ulong gfxObj = 0x0200_0020UL; InjectRenderData(fx.Manager, gfxObj, MakeFlatMesh( MakeBatch(0x08100020u, TranslucencyKind.Opaque, 0, 0, 3, 1))); var ctx = new TestContext(); var worldData = new FakeWorldData(); worldData.OutdoorStaticsByCell[0x8C040005u] = new WalkFrameStaticRecords( new[] { MakeRecord(301, 0, Vector3.Zero, [new MeshRef((uint)gfxObj, Matrix4x4.Identity)]) }, 0x8C04u); worldData.OutdoorDynamicsByCell[0x8C040005u] = new WalkFrameStaticRecords( new[] { MakeRecord(302, 0x50000001, Vector3.Zero, [new MeshRef((uint)gfxObj, Matrix4x4.Identity)]) }, 0x8C04u); var driver = new WalkFrameDriver( fx.Dispatcher, new RecordingLeafRenderer(log), worldData, new RecordingTrace(log)); using DrawScope draw = fx.BeginDraw(); driver.BeginFrame(ctx, Matrix4x4.Identity, Vector3.Zero); var activeViews = new WalkPortalView(); WalkCopyView.AppendFullViewportQuad( activeViews, ctx.Rays, ctx.WorldViewpoint, ctx.ViewportWidth, ctx.ViewportHeight); ((IWalkEventSink)driver).OnLandscapeViews(activeViews); ((IWalkEventSink)driver).OnLandscapeCellTurn(0x8C040005u); Assert.Empty(log); // no GPU work at Collect time; nothing recorded to the log yet driver.EndFrame(); Assert.Empty(log); // still nothing — EndFrame closes Collect, it does not Replay driver.Replay(draw.Frame, draw.Pass); // FW4 #132 positional invariant: the cell's emitters submit at its // own landscape turn, after its meshes flush. Campaign OVERHAUL S2 // chunk 6: the leaf draws by CELL, not by owner set. Assert.Equal( new[] { "FLUSH:2:OutdoorStatic,Dynamic", "PARTICLES:8c040005" }, log); var visibleCells = new HashSet { 0xDEAD_BEEFu }; driver.CopyVisibleCellsTo(visibleCells); Assert.Equal(new[] { 0x8C040005u }, visibleCells); GpuRecordedMultiDrawIndirect[] mdi = fx.Device.Calls.OfType().ToArray(); Assert.Equal(2, mdi.Length); Assert.Equal(2u, mdi.Aggregate(0u, static (sum, call) => sum + call.DrawCount)); } // Campaign OVERHAUL S2 chunk 6: the mesh-dedup shape (a shadow alias's // record entering two landscape cells' dictionaries) is a MESH-only // concern — retail's per-part render membership is whole-object per // cell. Particles are NOT part of that alias: an emitter owns exactly // one shadow in its own current cell (add_particle_shadow_to_cell // 0x00514a70), so each VISITED landscape cell fires its own independent // StaticParticles turn with no cross-cell dedupe needed or performed. [Fact] public void OnLandscapeCellTurn_MultiCellShadowAlias_DrawsMeshOnceButParticlesPerVisitedCell() { using var fx = new DispatcherFixture(); var log = new List(); const ulong gfxObj = 0x0200_0021UL; InjectRenderData(fx.Manager, gfxObj, MakeFlatMesh( MakeBatch(0x08100021u, TranslucencyKind.Opaque, 0, 0, 3, 1))); var ctx = new TestContext(); var worldData = new FakeWorldData(); RenderProjectionRecord shadowAlias = MakeRecord( 303, 0x50000002, Vector3.Zero, [new MeshRef((uint)gfxObj, Matrix4x4.Identity)]); WalkFrameStaticRecords aliases = new( new[] { shadowAlias }, 0xF07Fu); worldData.OutdoorDynamicsByCell[0xF07F0040u] = aliases; worldData.OutdoorDynamicsByCell[0xF0800001u] = aliases; var driver = new WalkFrameDriver( fx.Dispatcher, new RecordingLeafRenderer(log), worldData, new RecordingTrace(log)); using DrawScope draw = fx.BeginDraw(); driver.BeginFrame(ctx, Matrix4x4.Identity, Vector3.Zero); var activeViews = new WalkPortalView(); WalkCopyView.AppendFullViewportQuad( activeViews, ctx.Rays, ctx.WorldViewpoint, ctx.ViewportWidth, ctx.ViewportHeight); ((IWalkEventSink)driver).OnLandscapeViews(activeViews); ((IWalkEventSink)driver).OnLandscapeCellTurn(0xF07F0040u); ((IWalkEventSink)driver).OnLandscapeCellTurn(0xF0800001u); driver.EndFrame(); driver.Replay(draw.Frame, draw.Pass); Assert.Equal( new[] { "FLUSH:1:Dynamic", "PARTICLES:f07f0040", "PARTICLES:f0800001" }, log); GpuRecordedMultiDrawIndirect[] mdi = fx.Device.Calls.OfType().ToArray(); Assert.Single(mdi); Assert.Equal(1u, mdi[0].DrawCount); } [Fact] public void CoarseLandscapeCellTurn_ExpandsToEveryCoveredAuthoritativeOwnerCell() { using var fx = new DispatcherFixture(); const ulong gfxObj = 0x0200_0022UL; InjectRenderData(fx.Manager, gfxObj, MakeFlatMesh( MakeBatch(0x08100022u, TranslucencyKind.Opaque, 0, 0, 3, 1))); var worldData = new FakeWorldData(); worldData.OutdoorStaticsByCell[0xE43D0040u] = new WalkFrameStaticRecords( new[] { MakeRecord(304, 0, Vector3.Zero, [new MeshRef((uint)gfxObj, Matrix4x4.Identity)]) }, 0xE43Du); var driver = new WalkFrameDriver( fx.Dispatcher, new RecordingLeafRenderer(new List()), worldData); var ctx = new TestContext(); using DrawScope draw = fx.BeginDraw(); driver.BeginFrame(ctx, Matrix4x4.Identity, Vector3.Zero); var activeViews = new WalkPortalView(); WalkCopyView.AppendFullViewportQuad( activeViews, ctx.Rays, ctx.WorldViewpoint, ctx.ViewportWidth, ctx.ViewportHeight); ((IWalkEventSink)driver).OnLandscapeViews(activeViews); // A 2x2 coarse grid's cell 3 covers authoritative x/y cells 4..7, // including original cell 64. The old cellIndex+1 lookup only asked // for ids 1..4 and silently lost this record. ((IWalkEventSink)driver).OnLandscapeCellTurn(0xE43DFFFFu, 2, 3); driver.EndFrame(); driver.Replay(draw.Frame, draw.Pass); Assert.Contains(0xE43D0040u, driver.VisitedLandscapeCellIds); GpuRecordedMultiDrawIndirect call = Assert.Single( fx.Device.Calls.OfType()); Assert.Equal(1u, call.DrawCount); } [Fact] public void Collect_DoesNotExposeNearAlphaToEarlierBuildingBarrier() { using var fx = new DispatcherFixture(); const ulong gfxObj = 0x0200_0023UL; InjectRenderData(fx.Manager, gfxObj, MakeFlatMesh( MakeBatch(0x08100023u, TranslucencyKind.AlphaBlend, 0, 0, 3, 1))); var worldData = new FakeWorldData(); worldData.OutdoorStaticsByCell[0xE43D0001u] = new WalkFrameStaticRecords( new[] { MakeRecord(305, 0, new Vector3(0, 0, -20), [new MeshRef((uint)gfxObj, Matrix4x4.Identity)]) }, 0xE43Du); worldData.OutdoorStaticsByCell[0xE43D0002u] = new WalkFrameStaticRecords( new[] { MakeRecord(306, 0, new Vector3(0, 0, -2), [new MeshRef((uint)gfxObj, Matrix4x4.Identity)]) }, 0xE43Du); var log = new List(); var leaf = new RecordingLeafRenderer(log, fx.AlphaQueue); var driver = new WalkFrameDriver(fx.Dispatcher, leaf, worldData); var ctx = new TestContext(); using DrawScope draw = fx.BeginDraw(beginAlpha: true); driver.BeginFrame(ctx, Matrix4x4.Identity, Vector3.Zero); var activeViews = new WalkPortalView(); WalkCopyView.AppendFullViewportQuad( activeViews, ctx.Rays, ctx.WorldViewpoint, ctx.ViewportWidth, ctx.ViewportHeight); ((IWalkEventSink)driver).OnLandscapeViews(activeViews); ((IWalkEventSink)driver).OnLandscapeCellTurn(0xE43D0001u); ((IWalkEventSink)driver).OnBuildingTurn(new WalkBuilding()); ((IWalkEventSink)driver).OnLandscapeCellTurn(0xE43D0002u); driver.EndFrame(); Assert.Equal(0, fx.AlphaQueue.PendingCount); driver.Replay(draw.Frame, draw.Pass); Assert.Equal(new[] { 1 }, leaf.AlphaPendingAtBarrier); Assert.Equal(1, fx.AlphaQueue.PendingCount); } // ── F4(a) (S3 chunk 3 fix round 1 §9.6): an outdoor-root sequence — the // LandCell terrain turn precedes its own cell's object-list turn, and // (since nothing else intervenes) the whole frame's terrain stays one // PENDING batch until Replay's own end. Drives RetailFrameWalk. // DrawLandscape directly with a ZERO-view WalkPortalView — the SAME // deterministic "CY-only" admission technique RetailFrameWalkTests' // own outdoor tests use (a real WalkFrame root's 1-view default quad // depends on the production ray-caster's screen geometry, which this // suite's synthetic Caster does not model faithfully enough to predict // block/cell admission from). A side=1 block's own object-list turn // (WalkFrameDriver.OnLandscapeCellTurn's coarse-cell expansion) fires a // StaticParticles turn for every one of the underlying 64 owner // buckets; marking them all "no emitters" keeps this test's sequence // to exactly SKY + one LANDCELL, the same way a genuinely empty turn // stays silent (F2). ──────────────────────────────────────────────── private static IEnumerable CoarseLandscapeBuckets(uint landblockPrefix) { for (int x = 0; x < 8; x++) for (int y = 0; y < 8; y++) yield return landblockPrefix | (uint)(x * 8 + y + 1); } /// A ONE-view whose single polygon /// has ZERO vertices — WalkLandscape.CheckBlocks reads only /// poly.VertexCount to build its edge-plane list, so this reaches /// the SAME permissive edgeCount == 0 ("CY-only") admission test /// RetailFrameWalkTests' own outdoor fixtures use via a bare /// zero-VIEW — but with ViewCount == 1, /// satisfying 's fail-loud /// "a Landscape turn needs at least one active view" guard, which a /// driver-level test (unlike a bare IWalkEventSink recorder) must /// pass through RetailFrameWalk.DrawLandscape's real /// Emit(WalkEvent.Landscape(...)) call. private static WalkPortalView OneDegenerateView() { var view = new WalkPortalView { ViewCount = 1 }; view.View.Polys.Add(new WalkViewPoly(0, 0, 0, 0, 0, 0)); return view; } [Fact] public void OutdoorRoot_LandCellPrecedesItsOwnCellsObjectTurn_ThenFlushesAtReplaysEnd() { using var fx = new DispatcherFixture(); var log = new List(); var leaf = new RecordingLeafRenderer(log); // Every covered bucket but ONE reports no renderable emitter, so the // block's object turns submit nothing (no flush) except bucket // 0xF4180001, whose particle turn is a real submission — it must land // AFTER the coarse cell's own terrain (retail DrawLandCell → DrawSortCell). leaf.CellsWithoutEmitters.UnionWith(CoarseLandscapeBuckets(0xF4180000u)); leaf.CellsWithoutEmitters.Remove(0xF4180001u); var ctx = new TestContext(); var driver = new WalkFrameDriver(fx.Dispatcher, leaf, new FakeWorldData()); var walk = new RetailFrameWalk(); var landscape = new WalkLandscape { MidWidth = 1, Blocks = new WalkLandBlock?[1] }; var block = new WalkLandBlock { LandblockId = 0xF4180000u, SideCellCount = 1, MaxZ = 10f, MinZ = 0f, }; block.EnsureCellArrays(); landscape.Blocks[0] = block; using DrawScope draw = fx.BeginDraw(); driver.BeginFrame(ctx, Matrix4x4.Identity, Vector3.Zero); walk.DrawLandscape(landscape, OneDegenerateView(), ctx, driver); driver.EndFrame(); driver.Replay(draw.Frame, draw.Pass); Assert.Equal(new[] { "SKY", "LANDCELL:f4180000:1:0", "PARTICLES:f4180001" }, log); Assert.Equal(new (uint LandblockId, int SideCellCount, int CellIndex)[] { (0xF4180000u, 1, 0) }, Assert.Single(leaf.LandCellBatches)); } // ── F4(b) (S3 chunk 3 fix round 1 §9.6): an interior root with one // surviving exit view — the SAME fixture as // RunFrame_InteriorFloodWithExitView_FreshDriverSkipsTheGatedClearThenDrawsSealsAndFloodCells // above, with one populated land block added to the (previously // unpublished) landscape. Sequence: SKY, LANDCELL…, LFLUSH, SEALS, // SHELL… — the LandCell terrain turn(s) drawn through the interior // root's own exit view precede the landscape-flush/seal/flood-cell // turns, exactly like the outdoor case above. ──────────────────────── [Fact] public void InteriorRootWithExitView_DrawsLandCellThroughTheExitViewBeforeFlushSealsAndFlood() { using var fx = new DispatcherFixture(); var log = new List(); const ulong gfxObjA = 0x0200_0025UL; const ulong gfxObjB = 0x0200_0026UL; InjectRenderData(fx.Manager, gfxObjA, MakeFlatMesh( MakeBatch(0x08100025u, TranslucencyKind.Opaque, 0, 0, 3, 1))); InjectRenderData(fx.Manager, gfxObjB, MakeFlatMesh( MakeBatch(0x08100026u, TranslucencyKind.Opaque, 3, 4, 3, 2))); var ctx = new TestContext(); var cell1 = new WalkCell { CellId = 0x100, StabList = [0x101u], Portals = [ new WalkCellPortal { OtherCellId = 0x101, PolygonIndex = 0, PortalSide = 0, OtherPortalId = 0, }, // The exit portal (retail's "world beyond the door") — this // is what raises ov to 1 and drives the landscape (and now // its own LandCell turns) before clear+seals+the flood cells. new WalkCellPortal { OtherCellId = 0xFFFFFFFF, PolygonIndex = 1, PortalSide = 0, OtherPortalId = -1, }, ], PortalPolygons = [Quad(-2f), Quad(-3f)], }; var cell2 = new WalkCell { CellId = 0x101, Portals = [new WalkCellPortal { OtherCellId = 0x100, PolygonIndex = 0, PortalSide = 1, OtherPortalId = 0, }], PortalPolygons = [Quad(-2f)], }; ctx.Cells[cell1.CellId] = cell1; ctx.Cells[cell2.CellId] = cell2; var worldData = new FakeWorldData(); worldData.CellStaticsByCell[0x100] = new WalkFrameStaticRecords( new[] { MakeRecord(101, 0, Vector3.Zero, [new MeshRef((uint)gfxObjA, Matrix4x4.Identity)]) }, 0x8C04u); worldData.CellStaticsByCell[0x101] = new WalkFrameStaticRecords( new[] { MakeRecord(102, 0, Vector3.Zero, [new MeshRef((uint)gfxObjB, Matrix4x4.Identity)]) }, 0x8C04u); var leaf = new RecordingLeafRenderer(log); leaf.CellsWithoutEmitters.UnionWith(CoarseLandscapeBuckets(0xF4180000u)); var trace = new RecordingTrace(log); using ClipFrame clipFrame = ClipFrame.NoClip(); var driver = new WalkFrameDriver( fx.Dispatcher, leaf, worldData, trace, clipFrame); var walk = new RetailFrameWalk(); // The same 1x1 landscape RunFrame_InteriorFloodWithExitView... uses, // but with a real block published in its one slot so the exit view // has something to admit. var landscape = new WalkLandscape { MidWidth = 1, Blocks = new WalkLandBlock?[1] }; var block = new WalkLandBlock { LandblockId = 0xF4180000u, SideCellCount = 1, MaxZ = 10f, MinZ = 0f, }; block.EnsureCellArrays(); landscape.Blocks[0] = block; using DrawScope draw = fx.BeginDraw(); driver.RunFrame( walk, cameraCellId: cell1.CellId, cameraCell: cell1, landscape: landscape, ctx, draw.Frame, draw.Pass, Matrix4x4.Identity, cameraWorldPosition: Vector3.Zero); Assert.Equal( new[] { "SKY", "LANDCELL:f4180000:1:0", "LFLUSH", "SEALS", "SHELL:00000101", "SHELL:00000100", "FLUSH:1:CellStatic", "CELL-PARTICLES:00000101", "FLUSH:1:CellStatic", "CELL-PARTICLES:00000100", }, log); } // ── T4 (S3 chunk 3 §9.3), re-expressed for fix round 1's F2 rule // (§9.6 F4c), then again for fix round 2's F10 rule (§9.7 F12): the // batch is a single PENDING list Replay keeps across the WHOLE frame. // Two LandCell turns of DIFFERENT landblocks merge into ONE // DrawLandCellBatch call across EITHER of two things that used to (or, // for a real StreamMark, still might look like they should) split it: // an intervening EMPTY particle turn (F2, unchanged) AND an intervening // StreamMark from a cell that DOES grow the stream but has no // renderable emitter of its own (F10, new — proves the split doesn't // come back just because content was appended). A cell with BOTH real // stream content AND a renderable emitter still splits, because its // StaticParticles turn itself is a flush point (F10) — the split just // no longer comes from the StreamMark that precedes it. A building's // own alpha barrier and its portal pass's punch fan each split too // (F10: both remain flush points). ───────────────────────────────────── [Fact] public void OnLandCellTurn_MergesAcrossLandblocksOverStreamMarksAndEmptyParticleTurns_RealFlushPointsSplit() { using var fx = new DispatcherFixture(); const ulong gfxObj = 0x0200_0024UL; InjectRenderData(fx.Manager, gfxObj, MakeFlatMesh( MakeBatch(0x08100024u, TranslucencyKind.Opaque, 0, 0, 3, 1))); var worldData = new FakeWorldData(); // Has REAL stream content (so its own turn records a genuine // StreamMark event) but NO renderable emitter — the F10 case: the // StreamMark must not split the pending terrain batch even though // the ordered stream visibly grew. worldData.OutdoorStaticsByCell[0xAAAA0002u] = new WalkFrameStaticRecords( new[] { MakeRecord(311, 0, Vector3.Zero, [new MeshRef((uint)gfxObj, Matrix4x4.Identity)]) }, 0xAAAAu); // Has real stream content AND a renderable emitter (default) — its // StaticParticles turn is itself a flush point. worldData.OutdoorStaticsByCell[0xBBBB0002u] = new WalkFrameStaticRecords( new[] { MakeRecord(310, 0, Vector3.Zero, [new MeshRef((uint)gfxObj, Matrix4x4.Identity)]) }, 0xBBBBu); var log = new List(); var leaf = new RecordingLeafRenderer(log); // The 0xAAAA0001 particle turn has no world-data records AND is // marked without emitters -> a genuinely empty turn (F2, unchanged). // 0xAAAA0002 has world-data records (grows the stream, so Collect // records a real StreamMark) but is ALSO marked without emitters -> // the F10 case. 0xBBBB0002 keeps the default "has emitters". leaf.CellsWithoutEmitters.Add(0xAAAA0001u); leaf.CellsWithoutEmitters.Add(0xAAAA0002u); var ctx = new TestContext(); var driver = new WalkFrameDriver(fx.Dispatcher, leaf, worldData, new RecordingTrace(log)); IWalkEventSink sink = driver; using DrawScope draw = fx.BeginDraw(); driver.BeginFrame(ctx, Matrix4x4.Identity, Vector3.Zero); // A REAL full-viewport landscape view: with an empty view the classifier // admits nothing, the stream never grows, and no StreamMark ever fires // (the round-2 review caught exactly that vacuity). var landscapeViews = new WalkPortalView(); WalkCopyView.AppendFullViewportQuad( landscapeViews, ctx.Rays, ctx.WorldViewpoint, ctx.ViewportWidth, ctx.ViewportHeight); sink.OnLandscapeViews(landscapeViews); sink.OnLandCellTurn(0xF4180000u, 8, 0); // landblock A sink.OnLandscapeCellTurn(0xAAAA0001u); // empty particle turn: no growth, no emitters -> no submit, no flush (F2) sink.OnLandCellTurn(0xF3180000u, 8, 0); // landblock B, DIFFERENT -> still merges (F2) sink.OnLandscapeCellTurn(0xAAAA0002u); // grows the stream (a real StreamMark fires) but no emitters -> the StreamMark does NOT flush (F10), and the particle turn doesn't either sink.OnLandCellTurn(0xF2180001u, 8, 0); // landblock C -> still merges straight across that StreamMark (F10) sink.OnLandscapeCellTurn(0xBBBB0002u); // grows the stream too, but HAS emitters (default) -> its StaticParticles turn flushes (F10), not the StreamMark ahead of it sink.OnLandCellTurn(0xF3180000u, 8, 1); // new batch, started after that flush sink.OnBuildingTurn(new WalkBuilding()); // the building's own alpha barrier flushes (F10) sink.OnLandCellTurn(0xF2180000u, 8, 0); // new batch sink.OnPunchGeometry(new WalkBuilding(), Quad(0f), 0); // the portal pass's punch fan flushes (F10) sink.OnLandCellTurn(0xF1180000u, 8, 0); // final batch, flushed at Replay's own end driver.EndFrame(); driver.Replay(draw.Frame, draw.Pass); Assert.Equal( new[] { "FLUSH:1:OutdoorStatic", // 0xAAAA0002's statics: the StreamMark draws its range but does NOT flush the terrain batch (F10) "FLUSH:1:OutdoorStatic", // 0xBBBB0002's statics: same "LANDCELL:f4180000:8:0,f3180000:8:0,f2180001:8:0", "PARTICLES:bbbb0002", "LANDCELL:f3180000:8:1", "ALPHA", "LANDCELL:f2180000:8:0", "PUNCH:4@v0", "LANDCELL:f1180000:8:0", }, log); Assert.DoesNotContain("PARTICLES:aaaa0001", log); Assert.DoesNotContain("PARTICLES:aaaa0002", log); // Two genuine StreamMarks fired (the ordered stream grew twice) — the // F10 rule is exercised, not assumed. Assert.Equal(2, log.Count(entry => entry.StartsWith("FLUSH:", StringComparison.Ordinal))); Assert.Equal(4, leaf.LandCellBatches.Count); Assert.Equal( new (uint LandblockId, int SideCellCount, int CellIndex)[] { (0xF4180000u, 8, 0), (0xF3180000u, 8, 0), (0xF2180001u, 8, 0), }, leaf.LandCellBatches[0]); Assert.Equal( new (uint LandblockId, int SideCellCount, int CellIndex)[] { (0xF3180000u, 8, 1) }, leaf.LandCellBatches[1]); Assert.Equal( new (uint LandblockId, int SideCellCount, int CellIndex)[] { (0xF2180000u, 8, 0) }, leaf.LandCellBatches[2]); Assert.Equal( new (uint LandblockId, int SideCellCount, int CellIndex)[] { (0xF1180000u, 8, 0) }, leaf.LandCellBatches[3]); } // ── New punch-order pin (S3 chunk 3 fix round 2, §9.7 F12): a // PunchFan flushes the pending terrain batch (F10 — PunchFan's // DEPTHTEST_ALWAYS + write is exactly the ordering this chunk exists to // preserve, §9.1 R4). LandCell(far), a building turn whose portal pass // emits a PunchFan, LandCell(near) -> the leaf log reads // LANDCELL(far batch), PUNCH, LANDCELL(near batch): the far terrain is // flushed and drawn BEFORE the punch stamps its far-Z, and the near // terrain starts a NEW batch drawn strictly AFTER it — so the nearer // terrain still overwrites the punched depth, exactly as retail's own // interleave requires. ────────────────────────────────────────────────── [Fact] public void OnPunchGeometry_FlushesPendingTerrainBatch_FarTerrainBeforePunchNearTerrainAfter() { using var fx = new DispatcherFixture(); var log = new List(); var leaf = new RecordingLeafRenderer(log); var ctx = new TestContext(); var driver = new WalkFrameDriver(fx.Dispatcher, leaf, new FakeWorldData()); IWalkEventSink sink = driver; using DrawScope draw = fx.BeginDraw(); driver.BeginFrame(ctx, Matrix4x4.Identity, Vector3.Zero); sink.OnLandscapeViews(new WalkPortalView()); sink.OnLandCellTurn(0xF4180000u, 8, 0); // far land cell sink.OnPunchGeometry(new WalkBuilding(), Quad(0f), 0); // the building's far-Z punch sink.OnLandCellTurn(0xF4180000u, 8, 1); // near land cell (same landblock — the point is ORDER, not the landblock key) driver.EndFrame(); driver.Replay(draw.Frame, draw.Pass); Assert.Equal( new[] { "LANDCELL:f4180000:8:0", "PUNCH:4@v0", "LANDCELL:f4180000:8:1", }, log); } // ── Fixture (mirrors WalkStaticStreamPopulatorTests' DispatcherFixture — // FW3.2a's own referee) ───────────────────────────────────────────────── private static RenderProjectionRecord MakeRecord( uint localEntityId, uint serverGuid, Vector3 position, IReadOnlyList meshRefs, bool isBuildingShell = false, uint parentCellId = 0u) => new( Id: RenderProjectionId.FromRaw(localEntityId), ProjectionClass: RenderProjectionClass.OutdoorStatic, OwnerIncarnation: RenderOwnerIncarnation.FromRaw(1), Transform: new RenderTransform(Matrix4x4.CreateTranslation(position)), PreviousTransform: default, MeshSet: default, Material: default, Residency: default, Bounds: default, Flags: RenderProjectionFlags.Draw, DegradeState: default, SortKey: new RenderSortKey(0), DirtyMask: default, Source: new RenderSourceMetadata( LocalEntityId: localEntityId, ServerGuid: serverGuid, SourceId: 0, ParentCellId: parentCellId, EffectCellId: 0, BuildingShellAnchorCellId: 0, TransformFingerprint: default, GeometryFingerprint: default, AppearanceFingerprint: default), EntityPayload: new RenderEntityPayload( MeshRefs: meshRefs, PaletteOverride: null, IsBuildingShell: isBuildingShell)); private static ObjectRenderBatch MakeBatch( uint surfaceId, TranslucencyKind translucency, uint firstIndex, int baseVertex, int indexCount, uint textureSlotIndex, uint textureLayer = 0, CullMode cullMode = CullMode.CounterClockwise) => new() { Key = new TextureKey { SurfaceId = surfaceId, IsSolid = false }, Translucency = translucency, FirstIndex = firstIndex, BaseVertex = (uint)baseVertex, IndexCount = indexCount, TextureSlot = new GpuTextureSlot(textureSlotIndex), TextureIndex = (int)textureLayer, }; private static ObjectRenderData MakeFlatMesh(params ObjectRenderBatch[] batches) => new() { Batches = new List(batches) }; private static void InjectRenderData(ObjectMeshManager manager, ulong id, ObjectRenderData data) { FieldInfo field = typeof(ObjectMeshManager).GetField( "_renderData", BindingFlags.NonPublic | BindingFlags.Instance) ?? throw new InvalidOperationException( "ObjectMeshManager._renderData field not found — test relies on this exact name."); var dict = (ConcurrentDictionary)field.GetValue(manager)!; dict[id] = data; } private readonly struct DrawScope : IDisposable { private readonly IDisposable _publication; private readonly IGpuPassEncoder _pass; private readonly RetailAlphaQueue? _alpha; public DrawScope( IGpuFrame frame, IGpuPassEncoder pass, IDisposable publication, RetailAlphaQueue? alpha = null) { Frame = frame; _pass = pass; _publication = publication; _alpha = alpha; } public IGpuFrame Frame { get; } public IGpuPassEncoder Pass => _pass; public void Dispose() { if (_alpha?.IsCollecting == true) _alpha.EndFrame(); _publication.Dispose(); _pass.Dispose(); } } private sealed class DispatcherFixture : IDisposable { private readonly WbMeshAdapter _meshAdapter; private readonly TextureCache _textures; public DispatcherFixture() { Device = new RecordingGpuDevice(); FrameLifetime = new GpuDeviceFrameLifetime(Device); Scope = new VulkanWorldPassScope(sampleCount: 1); _textures = new TextureCache(Device, new NoopDatReaderWriter()); _meshAdapter = new WbMeshAdapter( Device, new NoopDatReaderWriter(), new NullPreparedAssetSource(), NullLogger.Instance, Device.Retirement); var entitySpawnAdapter = new EntitySpawnAdapter( _textures, _ => throw new NotSupportedException("Not exercised by these tests.")); Dispatcher = new WbDrawDispatcher( Device, FrameLifetime, Scope, _textures, _meshAdapter, entitySpawnAdapter, new EntityClassificationCache(), new AcDream.Core.Rendering.TranslucencyFadeManager(), alphaQueue: AlphaQueue); } public RecordingGpuDevice Device { get; } public GpuDeviceFrameLifetime FrameLifetime { get; } public VulkanWorldPassScope Scope { get; } public WbDrawDispatcher Dispatcher { get; } public RetailAlphaQueue AlphaQueue { get; } = new(); public ObjectMeshManager Manager => _meshAdapter.MeshManager!; public DrawScope BeginDraw(bool beginAlpha = false) { if (beginAlpha) { Dispatcher.BeginFrame(frameSlot: 0); AlphaQueue.BeginFrame(); } FrameLifetime.BeginFrame(); IGpuFrame frame = FrameLifetime.CurrentFrame!; IGpuPassEncoder pass = frame.BeginPass( GpuPassDescription.BackbufferClear( "fw3-2b-1-walk-frame-driver-test", Vector4.Zero, sampleCount: 1)); IDisposable publication = Scope.Publish(pass); Device.Clear(); return new DrawScope( frame, pass, publication, beginAlpha ? AlphaQueue : null); } public void Dispose() { if (AlphaQueue.IsCollecting) AlphaQueue.AbortFrame(); Dispatcher.Dispose(); _meshAdapter.Dispose(); _textures.Dispose(); Device.Dispose(); } } private sealed class NullPreparedAssetSource : IPreparedAssetSource { public PreparedAssetSourceStats Stats => default; public CacheStats DecodedTextureCacheStats => default; public PreparedAssetPresence Probe( AcDream.Content.Pak.PakAssetType type, uint sourceFileId) => PreparedAssetPresence.Missing; public PreparedAssetReadResult Read( in PreparedAssetRequest request, CancellationToken cancellationToken = default) => PreparedAssetReadResult.Missing; public void Dispose() { } } private sealed class NoopDatReaderWriter : IDatReaderWriter { private readonly StubDatabase _portal = new(); private readonly StubDatabase _highRes = new(); private readonly StubDatabase _language = new(); private readonly StubDatabase _cell = new(); public string SourceDirectory => string.Empty; public IDatDatabase Portal => _portal; public IDatDatabase Cell => _cell; public ReadOnlyDictionary CellRegions { get; } = new(new Dictionary()); public IDatDatabase HighRes => _highRes; public IDatDatabase Language => _language; public IDatDatabase Local => _language; public ReadOnlyDictionary RegionFileMap { get; } = new(new Dictionary()); public int PortalIteration => 0; public int CellIteration => 0; public int HighResIteration => 0; public int LanguageIteration => 0; public bool TryGetFileBytes( uint regionId, uint fileId, ref byte[] bytes, out int bytesRead) { bytesRead = 0; return false; } public IEnumerable GetAllIdsOfType() where T : IDBObj => Array.Empty(); public IEnumerable ResolveId(uint id) => Array.Empty(); public bool TrySave(T obj, int iteration = 0) where T : IDBObj => throw new NotSupportedException(); public bool TrySave( uint regionId, T obj, int iteration = 0) where T : IDBObj => throw new NotSupportedException(); [return: MaybeNull] public T Get(uint fileId) where T : IDBObj => default; public bool TryGet( uint fileId, [MaybeNullWhen(false)] out T value) where T : IDBObj { value = default; return false; } public void Dispose() { } private sealed class StubDatabase : IDatDatabase { public DatDatabase Db => throw new NotSupportedException(); public int Iteration => 0; public IEnumerable GetAllIdsOfType() where T : IDBObj => Array.Empty(); public bool TryGet( uint fileId, [MaybeNullWhen(false)] out T value) where T : IDBObj { value = default; return false; } public bool TryGetFileBytes( uint fileId, [MaybeNullWhen(false)] out byte[] value) { value = null; return false; } public bool TryGetFileBytes( uint fileId, ref byte[] bytes, out int bytesRead) { bytesRead = 0; return false; } public bool TrySave(T obj, int iteration = 0) where T : IDBObj => throw new NotSupportedException(); public void Dispose() { } } } }