using System.Numerics; using AcDream.App.Rendering; using AcDream.App.Rendering.Gpu; using AcDream.App.Rendering.Walk; using AcDream.App.Tests.Rendering; using AcDream.Core.Rendering; namespace AcDream.App.Tests.Rendering.Walk; /// /// Campaign OVERHAUL S3 chunk 1 (§11.2): tests T1/T2 for the print-only walk /// transcript emitter (, gated by /// ), plus the /// §11.2 B4 offline signature-diff self-check. Reuses /// 's own private fixture types via the /// shared partial class — the SAME minimal interior two-cell flood /// (one exit view) that file's own /// RunFrame_InteriorFloodWithExitView_FreshDriverSkipsTheGatedClearThenDrawsSealsAndFloodCells /// test already exercises and proves correct; here only /// runs (no Replay/GPU /// submission), since every transcript print fires synchronously during the /// walk itself. /// /// /// and /// are both process-wide mutable statics — joins /// for the SAME reason /// CornerFloodReplayTests/Issue181WallPressEquilibriumTests do /// (that collection's own doc comment, issue #251): interleaved /// Console.SetOut redirection across parallel test classes can /// restore a disposed process-wide. /// /// [Collection(CameraDiagnosticsCollection.Name)] public sealed partial class WalkFrameDriverTests { private static (WalkCell Cell1, WalkCell Cell2) BuildTranscriptFixtureCells(TestContext ctx) { var cell1 = new WalkCell { CellId = 0x100, StabList = [0x101u], Portals = [ new WalkCellPortal { OtherCellId = 0x101, PolygonIndex = 0, PortalSide = 0, OtherPortalId = 0, }, // The exit portal — raises ov to 1, so the flood draws the // (empty, zero-block) landscape too, exercising LS. 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; return (cell1, cell2); } /// T1 (§11.3): the flag off — zero output, and the emitter's /// own methods (the only code this chunk adds to the walk path) /// allocate nothing. The walk itself is NOT independently zero-alloc /// (e.g. RetailFrameWalk.EmitDrawCells allocates its cell-id /// array on every call — pre-existing, unrelated to this chunk), so the /// allocation bound below is scoped to /// itself, matching B1's actual contract ("allocates nothing EXTRA"). [Fact] public void TranscriptEmitter_FlagOff_EveryPrintMethodIsAZeroCostNoOp() { bool previous = RenderingDiagnostics.DumpWalkTranscriptEnabled; RenderingDiagnostics.DumpWalkTranscriptEnabled = false; TextWriter originalOut = Console.Out; var capture = new StringWriter(); try { Console.SetOut(capture); var cells = new List { 0x100u, 0x101u }; void Step() { WalkTranscriptDump.PrintFrameRoot(1, 0x100u, Vector3.Zero, Vector3.UnitY); WalkTranscriptDump.PrintLandscape(); WalkTranscriptDump.PrintBuilding(0x100u); WalkTranscriptDump.PrintDrawInside(0x100u); WalkTranscriptDump.PrintDrawCells(outdoorPview: false, 1, cells); WalkTranscriptDump.PrintLandCell(0xF4180001u); WalkTranscriptDump.PrintSortCell(0xF4180001u); WalkTranscriptDump.PrintEnvCellShell(0x101u); WalkTranscriptDump.PrintObjectCellTurn(0x101u); } ZeroAllocationProbe.AssertAllocatesNothing( "WalkTranscriptDump.Print* (flag off)", Step); AssertNoTranscriptLines(capture); } finally { Console.SetOut(originalOut); RenderingDiagnostics.DumpWalkTranscriptEnabled = previous; } } /// T1's integration half: a full synthetic interior frame, /// driven through the SAME path /// production uses, produces literally NO transcript output when the /// flag is off. S3 chunk 1 fix round 1 (G1): TWO Collect calls, /// not one — structurally discards /// the last (open) frame (its own class doc: the capture harness /// detaches at the frame marker, so the final "F n" never records its /// own draws), so a SINGLE-frame transcript parses to zero frames /// whether or not the flag actually gated anything — a one-Collect-call /// version of this test is vacuous (it would pass exactly as well with /// the flag gate forced true). Two Collect calls give the parser a real /// COMPLETE frame to see if the gate leaked (MUTATION CHECK: forcing /// RenderingDiagnostics.DumpWalkTranscriptEnabled true right /// after setting it false here makes AssertNoTranscriptLines /// fail, both on the parsed-frame-count assertion and the prefix /// scan). [Fact] public void Collect_TranscriptFlagOff_ProducesNoConsoleOutput() { bool previous = RenderingDiagnostics.DumpWalkTranscriptEnabled; RenderingDiagnostics.DumpWalkTranscriptEnabled = false; TextWriter originalOut = Console.Out; var capture = new StringWriter(); try { Console.SetOut(capture); using var fx = new DispatcherFixture(); var ctx = new TestContext(); (WalkCell cell1, _) = BuildTranscriptFixtureCells(ctx); var landscape = new WalkLandscape { MidWidth = 1, Blocks = new WalkLandBlock?[1] }; var worldData = new FakeWorldData(); var leaf = new RecordingLeafRenderer(new List()); using ClipFrame clipFrame = ClipFrame.NoClip(); var driver = new WalkFrameDriver(fx.Dispatcher, leaf, worldData, clipFrame: clipFrame); var walk = new RetailFrameWalk(); driver.Collect( walk, cell1.CellId, cell1, landscape, ctx, Matrix4x4.Identity, Vector3.Zero); driver.Collect( walk, cell1.CellId, cell1, landscape, ctx, Matrix4x4.Identity, Vector3.Zero); AssertNoTranscriptLines(capture); } finally { Console.SetOut(originalOut); RenderingDiagnostics.DumpWalkTranscriptEnabled = previous; } } /// /// The ten transcript line-kind prefixes /// ever emits — F /P /LS/LC /SC / /// BLD /DI /DC /EC /OC — matching /// each print method's own exact Console.WriteLine format /// (S3 chunk 1 fix round 1, G1). /// private static readonly string[] TranscriptLinePrefixes = ["F ", "P ", "LS", "LC ", "SC ", "BLD ", "DI ", "DC ", "EC ", "OC "]; /// /// Asserts NONE of 's own line kinds /// appear in — robust to unrelated /// noise from another test class running in /// parallel (a real, observed hazard: xUnit runs distinct classes /// concurrently by default, and is a /// process-wide static — see CameraDiagnosticsCollection's own /// doc comment, issue #251). A plain Assert.Empty(capture.ToString()) /// is NOT this robust: an unrelated class's unconditional /// Console.WriteLine can land inside this test's redirect window /// and fail it for a reason that has nothing to do with the transcript /// flag. only ever appends a frame /// once it sees a well-formed F <n> marker line — arbitrary /// unrelated text can never satisfy that regex, so a truly EMPTY parsed /// frame list is exactly as strong a proof that THIS code printed /// nothing, without being sensitive to what else shares the process /// console. S3 chunk 1 fix round 1 (G1) adds a second, independent /// check: no captured line starts with any of the ten transcript /// prefixes — those exact tokens never begin an unrelated test class's /// own console noise, so this stays robust to the SAME parallel hazard /// while also catching a leak the parser's own drop-last-frame quirk /// could otherwise hide (a caller that (mistakenly) drives only ONE /// frame). /// private static void AssertNoTranscriptLines(StringWriter capture) { string[] lines = capture.ToString() .Split('\n', StringSplitOptions.RemoveEmptyEntries) .Select(l => l.TrimEnd('\r')) .ToArray(); Assert.Empty(WalkOracleTrace.Parse(lines)); Assert.DoesNotContain( lines, l => Array.Exists( TranscriptLinePrefixes, p => l.StartsWith(p, StringComparison.Ordinal))); } /// T2 (§11.3): flag on, one synthetic interior frame — the /// printed lines parse with the extended /// into the same events the driver recorded (emitter/parser round /// trip). Asserted structurally (kinds, cell ids, counts) rather than /// against a hand-predicted exact ordering, so the test does not /// silently pin an assumption about traversal order it never /// independently verified. [Fact] public void Collect_TranscriptFlagOn_PrintedLinesRoundTripThroughTheParser() { bool previous = RenderingDiagnostics.DumpWalkTranscriptEnabled; RenderingDiagnostics.DumpWalkTranscriptEnabled = true; TextWriter originalOut = Console.Out; var capture = new StringWriter(); try { Console.SetOut(capture); using var fx = new DispatcherFixture(); var ctx = new TestContext(); (WalkCell cell1, WalkCell cell2) = BuildTranscriptFixtureCells(ctx); var landscape = new WalkLandscape { MidWidth = 1, Blocks = new WalkLandBlock?[1] }; var worldData = new FakeWorldData(); var leaf = new RecordingLeafRenderer(new List()); using ClipFrame clipFrame = ClipFrame.NoClip(); var driver = new WalkFrameDriver(fx.Dispatcher, leaf, worldData, clipFrame: clipFrame); var walk = new RetailFrameWalk(); // Two Collect calls: WalkOracleTrace.Parse (like every real // capture) only flushes a frame once the NEXT "F n" marker // appears — it deliberately drops the final in-progress frame // (the detach-frame rule). One frame alone would parse to zero // complete frames. driver.Collect( walk, cell1.CellId, cell1, landscape, ctx, Matrix4x4.Identity, Vector3.Zero); driver.Collect( walk, cell1.CellId, cell1, landscape, ctx, Matrix4x4.Identity, Vector3.Zero); Console.Out.Flush(); string[] lines = capture.ToString() .Split('\n', StringSplitOptions.RemoveEmptyEntries) .Select(l => l.TrimEnd('\r')) .ToArray(); Assert.NotEmpty(lines); IReadOnlyList frames = WalkOracleTrace.Parse(lines); WalkOracleFrame frame = Assert.Single(frames); Assert.Equal(1, frame.Number); Assert.NotNull(frame.Pose); Assert.Equal(cell1.CellId, frame.Pose!.CellId); Assert.Equal(cell1.CellId, frame.InteriorRootCell); Assert.True(frame.HasLandscape); WalkOracleEvent dc = Assert.Single( frame.Events, e => e.Kind == WalkOracleEventKind.DrawCells); Assert.Equal(1, dc.OutsideViewCount); Assert.Equal( new HashSet { cell1.CellId, cell2.CellId }, dc.Cells.ToHashSet()); // The interior root's own flood visits both cells for a shell // AND an object-list turn — EC/OC pair, one line per cell, no // dedupe (this is the interior root's OWN flood — the ONE case // WalkTranscriptDump.PrintEnvCellShell's own doc comment (G9) // still guarantees EC==OC count for; a look-in flood matches // too, but G8's per-frame trailing weather OC does not, and // never fires here since this fixture's landscape has zero // blocks/buildings). List ec = frame.Events .Where(e => e.Kind == WalkOracleEventKind.EnvCellShell) .Select(e => e.CellId!.Value).ToList(); List oc = frame.Events .Where(e => e.Kind == WalkOracleEventKind.ObjectCellTurn) .Select(e => e.CellId!.Value).ToList(); Assert.Equal(new HashSet { cell1.CellId, cell2.CellId }, ec.ToHashSet()); Assert.Equal(new HashSet { cell1.CellId, cell2.CellId }, oc.ToHashSet()); Assert.Equal(2, ec.Count); Assert.Equal(2, oc.Count); // No landblocks were published (the stub 1x1 landscape), so no // LC/SC/BLD turns exist this frame — the transcript format and // relative order for those three kinds is proven by // OutdoorRoot_TranscriptFlagOn_PrintsLandscapeThenLandCellThenSortCellThenBuilding_InOrder // below (S3 chunk 1 fix round 1, G3), and separately against // real retail data by WalkOracleTraceTests.AllFixtures and // WalkLandCellOrderTests. Assert.DoesNotContain(frame.Events, e => e.Kind is WalkOracleEventKind.LandCell or WalkOracleEventKind.SortCell or WalkOracleEventKind.Building); } finally { Console.SetOut(originalOut); RenderingDiagnostics.DumpWalkTranscriptEnabled = previous; } } /// S3 chunk 1 fix round 1 (G3): the outdoor-root half T2 never /// covered — the flag-on round trip above uses a stub 1x1 landscape with /// zero blocks, so LC/SC/BLD never fire at all. Drives /// directly with the SAME /// deterministic "CY-only" admission /// technique OutdoorRoot_LandCellPrecedesItsOwnCellsObjectTurn_ThenFlushesAtReplaysEnd /// uses (a real root's default /// full-viewport view depends on this suite's synthetic ray caster, /// which does not model screen geometry faithfully enough to predict /// block/cell admission from — see that test's own doc comment), with a /// building attached to the one admitted cell so BLD fires too. Checked /// against the RAW captured lines directly (not /// ) — this test only needs kind /// presence and relative order, not a complete F/P-framed capture. /// MUTATION CHECK: deleting WalkTranscriptDump.PrintLandCell's /// call site (or its body) makes the "expected an LC line" assertion /// fail. [Fact] public void OutdoorRoot_TranscriptFlagOn_PrintsLandscapeThenLandCellThenSortCellThenBuilding_InOrder() { bool previous = RenderingDiagnostics.DumpWalkTranscriptEnabled; RenderingDiagnostics.DumpWalkTranscriptEnabled = true; TextWriter originalOut = Console.Out; var capture = new StringWriter(); try { Console.SetOut(capture); using var fx = new DispatcherFixture(); var leaf = new RecordingLeafRenderer(new List()); 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(); block.CellBuildings[0] = new WalkBuilding { PositionCellId = 0xF4180000u }; landscape.Blocks[0] = block; driver.BeginFrame(ctx, Matrix4x4.Identity, Vector3.Zero); walk.DrawLandscape(landscape, OneDegenerateView(), ctx, driver); driver.EndFrame(); Console.Out.Flush(); string[] lines = capture.ToString() .Split('\n', StringSplitOptions.RemoveEmptyEntries) .Select(l => l.TrimEnd('\r')) .ToArray(); int lsIndex = Array.IndexOf(lines, "LS"); int lcIndex = Array.FindIndex( lines, l => l.StartsWith("LC ", StringComparison.Ordinal)); int scIndex = Array.FindIndex( lines, l => l.StartsWith("SC ", StringComparison.Ordinal)); int bldIndex = Array.FindIndex( lines, l => l.StartsWith("BLD ", StringComparison.Ordinal)); Assert.True(lsIndex >= 0, "expected an LS line"); Assert.True(lcIndex >= 0, "expected an LC line"); Assert.True(scIndex >= 0, "expected an SC line"); Assert.True(bldIndex >= 0, "expected a BLD line"); Assert.True(lsIndex < lcIndex, "LS must precede LC"); Assert.True( lcIndex < scIndex, "LC must precede SC — RenderDeviceD3D::DrawLandCell before DrawSortCell"); Assert.True( scIndex < bldIndex, "SC must precede BLD — S3 chunk 1 fix round 1 (G7): DrawSortCell entry, " + "before its own DrawBuilding call"); Assert.Equal("LC f4180001", lines[lcIndex]); Assert.Equal("SC f4180001", lines[scIndex]); Assert.Equal("BLD f4180000", lines[bldIndex]); } finally { Console.SetOut(originalOut); RenderingDiagnostics.DumpWalkTranscriptEnabled = previous; } } /// §11.2 B4: the offline signature diff, self-checked over a /// synthetic pair — the SAME captured T2 transcript versus itself with /// its own LAST event removed. Reuses Collect_TranscriptFlagOn_…'s /// own capture rather than duplicating the walk drive. [Fact] public void SignatureDiff_ReportsTheExactRemovedEvent() { bool previous = RenderingDiagnostics.DumpWalkTranscriptEnabled; RenderingDiagnostics.DumpWalkTranscriptEnabled = true; TextWriter originalOut = Console.Out; var capture = new StringWriter(); try { Console.SetOut(capture); using var fx = new DispatcherFixture(); var ctx = new TestContext(); (WalkCell cell1, _) = BuildTranscriptFixtureCells(ctx); var landscape = new WalkLandscape { MidWidth = 1, Blocks = new WalkLandBlock?[1] }; var worldData = new FakeWorldData(); var leaf = new RecordingLeafRenderer(new List()); using ClipFrame clipFrame = ClipFrame.NoClip(); var driver = new WalkFrameDriver(fx.Dispatcher, leaf, worldData, clipFrame: clipFrame); var walk = new RetailFrameWalk(); // Two Collect calls — see the round-trip test's own comment on // why one alone parses to zero complete frames. driver.Collect( walk, cell1.CellId, cell1, landscape, ctx, Matrix4x4.Identity, Vector3.Zero); driver.Collect( walk, cell1.CellId, cell1, landscape, ctx, Matrix4x4.Identity, Vector3.Zero); Console.Out.Flush(); string[] lines = capture.ToString() .Split('\n', StringSplitOptions.RemoveEmptyEntries) .Select(l => l.TrimEnd('\r')) .ToArray(); IReadOnlyList expected = WalkOracleTrace.Parse(lines); WalkOracleFrame expectedFrame = Assert.Single(expected); Assert.NotEmpty(expectedFrame.Events); // Self-vs-self (unmutated copy): no divergence at all. IReadOnlyList unchanged = [expectedFrame with { Events = expectedFrame.Events.ToList() }]; Assert.Null(WalkTranscriptSignatureDiff.FirstDivergence(expected, unchanged)); // Self-vs-self-minus-one (the LAST event removed): the reported // divergence is exact — same frame number, position exactly at // the new (shorter) end, expected = the removed event's own // signature string, actual = "". int lastIndex = expectedFrame.Events.Count - 1; List truncated = expectedFrame.Events.Take(lastIndex).ToList(); IReadOnlyList actual = [expectedFrame with { Events = truncated }]; WalkTranscriptSignatureDiff.Divergence? divergence = WalkTranscriptSignatureDiff.FirstDivergence(expected, actual); Assert.NotNull(divergence); Assert.Equal(expectedFrame.Number, divergence!.Value.FrameNumber); Assert.Equal(lastIndex, divergence.Value.Position); Assert.Equal("", divergence.Value.Actual); Assert.NotEqual("", divergence.Value.Expected); } finally { Console.SetOut(originalOut); RenderingDiagnostics.DumpWalkTranscriptEnabled = previous; } } }