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 partial 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(RetailAlphaFlushSite.DrawBuilding, 0f);
}
public readonly List AlphaPendingAtSortCellExit = new();
public void FlushSortCellExit()
{
if (alpha is null)
{
log.Add("SORTCELLEXIT");
return;
}
AlphaPendingAtSortCellExit.Add(alpha.PendingCount);
log.Add($"SORTCELLEXIT:{alpha.PendingCount}");
alpha.Flush(RetailAlphaFlushSite.SortCellExit, 0.75f);
}
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;
/// S3 chunk 4 (O1/O2/O3): overrides 's default-false/-zero weather-turn
/// members so a test can arm the gate and pin the printed "OC" cell
/// id. Real production values wire RetailPViewFrameInput.ViewerCellId
/// / RetailPViewPassExecutor.ShouldDrawWeatherOnce(...) — see
/// WalkProductionFrameContext.
public uint ViewerCellId { get; set; }
public bool WeatherGateOpen { get; set; }
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);
}
// ── S4-c1 C3 (T3): the depth-alpha packet's truth table over (root kind,
// draw_landscape, outside-view count, previous count) -> the exact
// LFLUSH/stamp/CLEAR/SEALS event subsequence. Chunk 2 already covers
// most of the table as direct Facts, so chunk 1 adds only the rows that
// were genuinely missing rather than re-deriving them:
// - interior, ov==0 (draw_landscape==false), prior==0 ->
// RunFrame_InteriorFloodWithNoExitView_SkipsLandscapeAndNeverFlushesClearsOrSeals
// - interior, ov>0 (draw_landscape==true), prior==0 ->
// RunFrame_InteriorFloodWithExitView_FreshDriverSkipsTheGatedClearThenDrawsSealsAndFloodCells
// and OnInteriorFloodDrawTurn_FirstOvFrameSkipsClear_SecondFrameArmedByFirstsSealsClears
// (its own frame 1)
// - interior, ov>0, prior>0 ->
// OnInteriorFloodDrawTurn_FirstOvFrameSkipsClear_SecondFrameArmedByFirstsSealsClears
// (its own frame 2) — this pair IS T4, the two-consecutive-frames
// latch case, below
// The two rows below — root kind == OUTDOOR (where the entire LFLUSH/
// stamp/CLEAR/SEALS mechanism cannot fire at all, because
// RetailFrameWalk.WalkFrame's outdoor branch never calls DrawInside/
// OnInteriorFloodDrawTurn), and interior/ov==0 immediately AFTER a
// prior-armed nonzero counter (proving the counter is left completely
// UNTOUCHED, not merely "not cleared this frame") — were not yet pinned
// anywhere. ─────────────────────────────────────────────────────────
[Fact]
public void WalkFrame_OutdoorRoot_NeverFiresTheInteriorClearSealMachinery()
{
// Root kind == OUTDOOR: RetailFrameWalk.WalkFrame's
// (cameraCellId & 0xFFFF) < 0x100 branch calls DrawLandscape
// directly and never calls DrawInside — so OnInteriorFloodDrawTurn,
// the sole owner of LFLUSH/stamp/CLEAR/SEALS, never fires at all,
// for ANY outside-view-count/previous-count combination (there is
// no such combination reachable outdoors — this row of the table
// has no ov/prior axis).
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());
var walk = new RetailFrameWalk();
// Same minimal, no-op 1x1 unpublished landscape the interior exit-
// view fixtures use — LScape::draw still runs its full sky/terrain
// turn against it; there's nothing published to iterate for.
var landscape = new WalkLandscape { MidWidth = 1, Blocks = new WalkLandBlock?[1] };
using DrawScope draw = fx.BeginDraw();
driver.RunFrame(
walk, cameraCellId: 0x00000050u, cameraCell: null, landscape: landscape,
ctx, draw.Frame, draw.Pass, Matrix4x4.Identity, cameraWorldPosition: Vector3.Zero);
// MUTATION: add a stray sink.OnInteriorFloodDrawTurn(...) call to
// RetailFrameWalk.WalkFrame's outdoor branch (e.g. a copy-paste from
// the interior branch) — SKY still appears, but LFLUSH/SEALS would
// too, and this fails.
Assert.Contains("SKY", log);
Assert.DoesNotContain("LFLUSH", log);
Assert.DoesNotContain("CLEAR", log);
Assert.DoesNotContain("SEALS", log);
Assert.Equal(0, driver.PortalsDrawnCount);
}
[Fact]
public void OnInteriorFloodDrawTurn_OvZeroAfterAPriorArmedCounter_LeavesTheLatchCompletelyUntouched()
{
// Interior root, ov==0 immediately after an EARLIER ov>0 frame armed
// the counter: S3 §8.1 R3 gates the ENTIRE outside_view.view_count>0
// block — including the read-then-zero decision itself — so ov==0
// must leave the counter EXACTLY as an earlier frame left it, not
// merely "not cleared this frame" (a read-then-zero-back-to-the-
// same-nonzero-value mistake would also leave PortalsDrawnCount
// looking untouched from the OUTSIDE — this test's real target is
// that no clear/seal machinery runs at all, proven by the empty log
// alongside the unchanged counter).
using var fx = new DispatcherFixture();
var log = new List();
var leaf = new RecordingLeafRenderer(log);
var ctx = new TestContext();
const uint cellId = 0xF4180310u;
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();
// Frame 1: an ordinary ov>0 flood arms the counter.
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.Equal(1, driver.PortalsDrawnCount);
log.Clear();
// Frame 2: ov==0 — retail's whole clear/seal gate is skipped by
// construction (no landscape turn either, matching DrawInside's own
// ov==0 shape).
driver.BeginFrame(ctx, Matrix4x4.Identity, Vector3.Zero);
sink.OnInteriorFloodDrawTurn([cellId], outsideViewCount: 0);
driver.EndFrame();
driver.Replay(draw.Frame, draw.Pass);
// The ov==0 flood still draws its OWN cell's shell + contents (R1's
// "straight to the flood's own cells" tail) — the four LFLUSH/
// stamp/CLEAR/SEALS tokens are what must be absent, not the whole
// log; see RunFrame_InteriorFloodWithNoExitView_... above for the
// same non-empty-but-gate-free shape.
Assert.DoesNotContain("SKY", log);
Assert.DoesNotContain("LFLUSH", log);
Assert.DoesNotContain("CLEAR", log);
Assert.DoesNotContain("SEALS", log);
// MUTATION: move the `int armed = PortalsDrawnCount; PortalsDrawnCount
// = 0;` read-then-zero in WalkFrameDriver.OnInteriorFloodDrawTurn
// outside the `if (outsideViewCount > 0)` gate — the counter reads
// back as 0 here instead of the untouched 1, and this fails.
Assert.Equal(1, driver.PortalsDrawnCount);
}
// ── S4-c1 C3 (T4): the same fixture as
// OnInteriorFloodDrawTurn_FirstOvFrameSkipsClear_SecondFrameArmedByFirstsSealsClears
// above already proves the "frame 1 seals N>0 -> frame 2 clears" half of
// the two-consecutive-frames latch, and
// OnInteriorFloodDrawTurn_FloodWithNoExitPortal_NeverClearsAcrossFrames
// already proves "frame 1 seals 0 -> frame 2 does not clear" (repeated
// across three consecutive ov>0 frames, which subsumes the two-frame
// case). No further T4 test is added — see the commit body for the
// pre-existing-coverage inventory. ────────────────────────────────────
// ── 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);
}
// ── S4-c1 C3 (T5): the ABOVE test proves one look-in is isolated from
// the root latch; this extends it to MULTIPLE look-ins — two in the
// SAME frame (two buildings' own portal passes), then a third in a
// LATER, separate frame — since retail's DrawCells re-entry (R1) has no
// per-call state of its own that could accumulate across repeats. ────
[Fact]
public void MultipleLookIns_WithinOneFrameAndAcrossFrames_NeverTouchTheRootLatch()
{
using var fx = new DispatcherFixture();
var log = new List();
var leaf = new RecordingLeafRenderer(log);
var ctx = new TestContext();
const uint rootCellId = 0xF4180330u;
const uint lookInCellIdA = 0xF4180331u;
const uint lookInCellIdB = 0xF4180332u;
var rootCell = new WalkCell { CellId = rootCellId };
rootCell.PushView();
WalkCopyView.AppendFullViewportQuad(
rootCell.TopView, ctx.Rays, ctx.WorldViewpoint, ctx.ViewportWidth, ctx.ViewportHeight);
ctx.Cells[rootCellId] = rootCell;
foreach (uint lookInId in new[] { lookInCellIdA, lookInCellIdB })
{
var lookInCell = new WalkCell { CellId = lookInId };
lookInCell.PushView();
WalkCopyView.AppendFullViewportQuad(
lookInCell.TopView, ctx.Rays, ctx.WorldViewpoint, ctx.ViewportWidth, ctx.ViewportHeight);
ctx.Cells[lookInId] = 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();
// TWO look-ins in the SAME frame.
driver.BeginFrame(ctx, Matrix4x4.Identity, Vector3.Zero);
sink.OnBuildingTurn(new WalkBuilding());
sink.Emit(WalkEvent.DrawCells(outsideViewCount: 0, [lookInCellIdA]));
sink.OnBuildingTurn(new WalkBuilding());
sink.Emit(WalkEvent.DrawCells(outsideViewCount: 0, [lookInCellIdB]));
driver.EndFrame();
driver.Replay(draw.Frame, draw.Pass);
Assert.DoesNotContain("LFLUSH", log);
Assert.DoesNotContain("CLEAR", log);
Assert.DoesNotContain("SEALS", log);
// MUTATION (verified during S4-c1's own implementation, then
// reverted): a bug that only mishandles a REPEAT look-in call within
// one frame (e.g. resetting PortalsDrawnCount on the second
// HandleDrawCellsTurn(LookInStatic) call) leaves this exact
// assertion at 0 instead of 1, while
// LookInDrawCells_NeitherArmsNorConsumesThePortalsDrawnCounter above
// — which calls DrawCells exactly once — stays green throughout;
// that gap is this test's whole reason to exist over the single-
// look-in fact.
Assert.Equal(1, driver.PortalsDrawnCount);
log.Clear();
// A THIRD look-in in a LATER, separate frame.
driver.BeginFrame(ctx, Matrix4x4.Identity, Vector3.Zero);
sink.OnBuildingTurn(new WalkBuilding());
sink.Emit(WalkEvent.DrawCells(outsideViewCount: 0, [lookInCellIdA]));
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));
// S3 chunk 4 fix round 1 (K7): restores the pin the deleted
// VisibleClipSlotsInLookInTurn used to prove — a real look-in slice
// is packed into its own GPU clip slot, never left on the reserved
// no-clip slot 0 (the exit-seal KEEP reads planes through the SAME
// slot mechanism, _clipFrame.GetSlotPlanes(slice.ClipSlot)).
uint clipSlot = driver.LookInSliceClipSlotAt(0);
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));
}
// ── S4-c1 C1/T2 (quantifier corrected at fix round 1 F1): DrawPortalPoly
// Internal's degenerate-input guard, ported at the punch-fan producer
// (WalkFrameDriver.OnPunchGeometry — the handler that owns the LOCAL
// polygon before TransformToWorld). The polygon is dropped before any
// transform, event, or counter effect ONLY when EVERY local vertex lies
// on the SAME +/-12 plane; a polygon with just one vertex there — or at
// 11.999, just inside — is ordinary and punches normally. ────────────
[Fact]
public void OnPunchGeometry_RejectsOnlyWhenEveryVertexSharesOnePlane_ButPunchesAnyOtherShape()
{
using var fx = new DispatcherFixture();
var log = new List();
var leaf = new RecordingLeafRenderer(log);
var driver = new WalkFrameDriver(fx.Dispatcher, leaf, new FakeWorldData());
IWalkEventSink sink = driver;
var building = new WalkBuilding { PositionCellId = 0xA9B40040u };
using DrawScope draw = fx.BeginDraw();
driver.BeginFrame(new TestContext(), Matrix4x4.Identity, Vector3.Zero);
// Degenerate: EVERY vertex sits exactly on the local x == +12
// boundary (degenerate onto the plane) — the only shape retail
// actually rejects.
// MUTATION: restore the any-vertex form (any single vertex ==
// +/-12 rejects the whole polygon) — the "one vertex only" case
// below starts failing (wrongly rejected) instead.
sink.OnPunchGeometry(
building,
new WalkPolygon
{
Vertices = [new(12f, -3f, 3f), new(12f, 0f, 3f), new(12f, 5f, 3f)],
Plane = new WalkPlane(Vector3.UnitZ, -3f),
},
activeViewIndex: 0);
// Admitted: only ONE vertex sits on x == +12; the other two do not
// share that plane. Retail draws this polygon (punched, counted).
sink.OnPunchGeometry(
building,
new WalkPolygon
{
Vertices = [new(0f, 0f, 3f), new(12f, 0f, 3f), new(5f, 5f, 3f)],
Plane = new WalkPlane(Vector3.UnitZ, -3f),
},
activeViewIndex: 0);
// Admitted: the nearest-boundary vertex is 11.999, not 12 — an
// ordinary polygon that must punch exactly like any other.
sink.OnPunchGeometry(
building,
new WalkPolygon
{
Vertices = [new(0f, 0f, 3f), new(11.999f, 0f, 3f), new(5f, 5f, 3f)],
Plane = new WalkPlane(Vector3.UnitZ, -3f),
},
activeViewIndex: 0);
// Admitted (S4-c1 fix round 2, R2-3): every vertex sits on SOME
// +/-12 plane, but not all on the SAME one — vertex 1's y=0 clears
// the y=+12 accumulator, vertex 2's x=0 clears the x=+12
// accumulator, so neither of the four per-plane accumulators
// survives to the end of the loop. This is the case that tells
// retail's real quantifier ("every vertex on the SAME plane") apart
// from the weaker "every vertex on SOME plane" (per-vertex OR
// across the four planes, ANDed across vertices) — every vertex
// here individually touches a plane, yet retail still draws it.
// MUTATION: replace the four independent per-plane accumulators
// with a single per-vertex "is this vertex on ANY of the four
// planes" test ANDed across vertices — this case starts failing
// (wrongly rejected): every vertex here IS on some plane, so the
// per-vertex-OR form rejects it.
sink.OnPunchGeometry(
building,
new WalkPolygon
{
Vertices = [new(12f, 0f, 3f), new(0f, 12f, 3f), new(12f, 5f, 3f)],
Plane = new WalkPlane(Vector3.UnitZ, -3f),
},
activeViewIndex: 0);
driver.EndFrame();
driver.Replay(draw.Frame, draw.Pass);
// Exactly THREE punches reached the leaf — the all-on-plane polygon
// produced no PunchFan event at all (not a punch that draws zero
// vertices; no event, full stop); the other three (one-vertex-on-
// plane, just-inside, split-across-two-planes) are ordinary and all
// punched.
Assert.Equal(3, leaf.Punches.Count);
Assert.Equal(new Vector3(12f, 0f, 3f), leaf.Punches[0].Vertices[1]);
Assert.Equal(new Vector3(11.999f, 0f, 3f), leaf.Punches[1].Vertices[1]);
Assert.Equal(new Vector3(0f, 12f, 3f), leaf.Punches[2].Vertices[1]);
Assert.Equal(3, log.Count(entry => entry == "PUNCH:3@v0"));
}
[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);
// S4-c2: RenderDeviceD3D::DrawBlock's own FlushAlphaList(0.75f) call
// fires once per admitted land-block cell, after this cell's object
// turn (here: the particle turn) — see OnSortCellExit.
Assert.Equal(
new[] { "SKY", "LANDCELL:f4180000:1:0", "PARTICLES:f4180001", "SORTCELLEXIT" }, log);
Assert.Equal(new (uint LandblockId, int SideCellCount, int CellIndex)[] { (0xF4180000u, 1, 0) },
Assert.Single(leaf.LandCellBatches));
}
///
/// S4-c2 fix round 1 (M5): the ONE live pin exercising the valve THIS
/// CHUNK added, through 's own
/// WalkFrameEventKind.SortCellExit arm (not
/// called directly —
/// RetailAlphaQueueTests already has that boundary). The queue is
/// preloaded directly (a dummy source, never real mesh content — this
/// pin proves the VALVE's threshold behavior, not classification, which
/// is covered elsewhere) with entries sitting in ALPHA when the walk's
/// own DrawBlock-equivalent land-cell turn reaches its
/// FlushAlphaList(0.75f) call. Mutation check: forcing
/// 's no-op comparison to
/// <= instead of < (S4-c1/c2's own boundary mutation)
/// makes the 2250 case read PendingCount == 2250 (a no-op)
/// instead of 0 — the first assertion below fails against that
/// mutation.
///
[Theory]
[InlineData(2250, 0)]
[InlineData(2249, 2249)]
public void SortCellExit_ValveDrainsThroughReplayAtTheExactBoundary(
int preloadedCount, int expectedPendingAfter)
{
using var fx = new DispatcherFixture();
var log = new List();
var leaf = new RecordingLeafRenderer(log, fx.AlphaQueue);
leaf.CellsWithoutEmitters.UnionWith(CoarseLandscapeBuckets(0xF4180000u));
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;
var dummySource = new DummyAlphaSource();
using DrawScope draw = fx.BeginDraw(beginAlpha: true);
for (int i = 0; i < preloadedCount; i++)
{
Assert.True(fx.AlphaQueue.TryAppend(RetailAlphaList.Alpha, dummySource, i, false));
}
driver.BeginFrame(ctx, Matrix4x4.Identity, Vector3.Zero);
walk.DrawLandscape(landscape, OneDegenerateView(), ctx, driver);
driver.EndFrame();
driver.Replay(draw.Frame, draw.Pass);
Assert.Equal(new[] { preloadedCount }, leaf.AlphaPendingAtSortCellExit);
Assert.Equal(expectedPendingAfter, fx.AlphaQueue.PendingCount);
}
private sealed class DummyAlphaSource : IRetailAlphaDrawSource
{
public void PrepareAlphaDraws(ReadOnlySpan tokens)
{
}
public void DrawPreparedAlphaBatch(int firstPreparedDraw, int drawCount)
{
}
public void ResetAlphaSubmissions()
{
}
}
// ── 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);
// S4-c2: this cell's own object turn submits no particle (every
// covered bucket is in CellsWithoutEmitters), so SORTCELLEXIT lands
// immediately after the LandCell terrain turn — DrawBlock's own
// FlushAlphaList(0.75f), before LandscapeFlush (PView::DrawCells'
// own FlushAlphaList(0f)).
Assert.Equal(
new[]
{
"SKY", "LANDCELL:f4180000:1:0", "SORTCELLEXIT", "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;
}
// S4-c1 fix round 1 F3: DrawScope/DispatcherFixture widened to internal
// so WalkTraceConformanceTests.AlphaDepthTranscriptTests.cs can drive a
// REAL WbDrawDispatcher for the depth-event transcript gate without
// duplicating this entire fixture (RecordingGpuDevice + WbMeshAdapter +
// TextureCache + EntitySpawnAdapter wiring) a second time.
internal 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();
}
}
internal 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()
{
}
}
}
}