namespace AcDream.App.Tests.Rendering.Walk; /// /// S3 chunk 1 (§11.2 B4): the offline transcript signature diff. Given an /// acdream transcript (printed by the production /// ACDREAM_DUMP_WALK_TRANSCRIPT=1 emitter — WalkTranscriptDump) /// and a retail capture (or any two -parsed /// transcripts), reports the FIRST divergent event and its position, frame /// by frame, at the DI/DC/BLD/LS/LC/SC/EC/OC level. A test-side comparison /// utility only — no runner, no dashboard, no new tool under /// tools/ ([[feedback-evidence-infrastructure-sink]]); the S3 review /// and G3 consume its report by hand. /// public static class WalkTranscriptSignatureDiff { /// The first point two transcripts disagree, or /// when every frame both sides share matches /// exactly. is the retail-side frame's /// own number (matching the "F n" line); /// is the zero-based index into that frame's DI/DC/BLD/LS/LC/SC/EC/OC /// event signature. "<end of frame>"/"<missing /// frame>" mark a length mismatch rather than a content /// mismatch. public readonly record struct Divergence( int FrameNumber, int Position, string Expected, string Actual); /// Diffs two raw transcripts (e.g. one file read as lines each) /// — the file-to-file form B4 names directly. public static Divergence? FirstDivergence( IEnumerable expectedLines, IEnumerable actualLines) => FirstDivergence( WalkOracleTrace.Parse(expectedLines), WalkOracleTrace.Parse(actualLines)); /// Diffs two already-parsed frame lists — the form the /// synthetic self-test below uses directly, without a round trip /// through a temp file. public static Divergence? FirstDivergence( IReadOnlyList expected, IReadOnlyList actual) { int frameCount = Math.Min(expected.Count, actual.Count); for (int f = 0; f < frameCount; f++) { IReadOnlyList e = Signature(expected[f]); IReadOnlyList a = Signature(actual[f]); int shared = Math.Min(e.Count, a.Count); for (int i = 0; i < shared; i++) { if (!string.Equals(e[i], a[i], StringComparison.Ordinal)) return new Divergence(expected[f].Number, i, e[i], a[i]); } if (e.Count != a.Count) { return new Divergence( expected[f].Number, shared, shared < e.Count ? e[shared] : "", shared < a.Count ? a[shared] : ""); } } if (expected.Count != actual.Count) { int frameNumber = frameCount < expected.Count ? expected[frameCount].Number : actual[frameCount].Number; return new Divergence( frameNumber, 0, frameCount < expected.Count ? "" : "", frameCount < actual.Count ? "" : ""); } return null; } /// One frame's DI/DC/BLD/LS/LC/SC/EC/OC turns, in transcript /// order — the full eight-kind vocabulary B4 names (unlike /// , /// which deliberately stays at the pre-S3-chunk-3 four-kind level for /// the B3 still-fixture rows). private static IReadOnlyList Signature(WalkOracleFrame frame) => frame.Events.Select(e => e.Kind switch { WalkOracleEventKind.Landscape => "LS", WalkOracleEventKind.Building => $"BLD:{e.CellId!.Value:x8}", WalkOracleEventKind.DrawInside => $"DI:{e.CellId!.Value:x8}", WalkOracleEventKind.DrawCells => $"DC:ov={e.OutsideViewCount}:{string.Join(',', e.Cells.Select(c => c.ToString("x8")))}", WalkOracleEventKind.LandCell => $"LC:{e.CellId!.Value:x8}", WalkOracleEventKind.SortCell => $"SC:{e.CellId!.Value:x8}", WalkOracleEventKind.EnvCellShell => $"EC:{e.CellId!.Value:x8}", WalkOracleEventKind.ObjectCellTurn => $"OC:{e.CellId!.Value:x8}", _ => "?", }).ToList(); }