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. [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); AssertNoTranscriptLines(capture); } finally { Console.SetOut(originalOut); RenderingDiagnostics.DumpWalkTranscriptEnabled = previous; } } /// /// 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. /// 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)); } /// 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 (WalkTranscriptDump.PrintEnvCellShell's own doc // comment: EC/OC counts are always exactly equal per pose). 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 for // those three kinds is proven 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; } } /// §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; } } }