using System.Globalization; using System.Text.RegularExpressions; namespace AcDream.App.Tests.Rendering.Walk; /// /// S4-c1 fix round 1, F3: parser for the OH alpha/depth captures /// (docs/research/2026-09-01-overhaul/oh-capture/*.alphadepth.log — /// tools/walk-oracle/oh/oh-capture-alpha-depth.cdb.template documents the /// line formats). Reads the two lines this round's gate cares about: /// /// /// PM poly=<ptr> mode=<0|1> counterBefore=<hex> — /// one per D3DPolyRender::DrawPortalPolyInternal @0x0059bc90 ENTRY /// (mode 1 = far-Z building punch, mode 0 = true-depth exit seal). The /// breakpoint is at function ENTRY, before the boundary guard runs inside /// the function — a PM line is emitted for every ATTEMPT, guard-rejected /// or not; counterBefore is the persistent /// portalsDrawnCount global sampled before THIS call's own possible /// increment. /// PC ov=<n> counter=<hex> fc=<0|1> — one per /// PView::DrawCells @0x005a4840 ENTRY (root turn AND every building /// look-in's own re-entrant call alike — the breakpoint doesn't /// distinguish). counter is portalsDrawnCount sampled at /// THIS call's entry, before its own possible read-then-zero. /// /// /// Frame delimiting mirrors exactly (same /// F <n> marker, same "events between F_n and F_(n+1) belong to /// frame n" rule, same final-frame drop) so a caller can parse the SAME /// capture file with both parsers and get position-consistent frame /// numbering — for the pose, this type for /// the PM/PC sequences. /// public static class WalkAlphaDepthTrace { public static IReadOnlyList Parse(IEnumerable lines) { var frames = new List(); List<(int Mode, int CounterBefore)>? currentPm = null; List<(int Ov, int Counter, int ForceClear)>? currentPc = null; int currentNumber = 0; foreach (string line in lines) { Match frameMatch = FramePattern.Match(line); if (frameMatch.Success) { if (currentPm is not null) { frames.Add(new WalkAlphaDepthFrame(currentNumber, currentPm, currentPc!)); } currentNumber = int.Parse( frameMatch.Groups[1].Value, CultureInfo.InvariantCulture); currentPm = new List<(int, int)>(); currentPc = new List<(int, int, int)>(); continue; } if (currentPm is null) continue; Match pm = PmPattern.Match(line); if (pm.Success) { currentPm.Add(( int.Parse(pm.Groups[1].Value, CultureInfo.InvariantCulture), int.Parse(pm.Groups[2].Value, NumberStyles.HexNumber, CultureInfo.InvariantCulture))); continue; } Match pc = PcPattern.Match(line); if (pc.Success) { currentPc!.Add(( int.Parse(pc.Groups[1].Value, CultureInfo.InvariantCulture), int.Parse(pc.Groups[2].Value, NumberStyles.HexNumber, CultureInfo.InvariantCulture), int.Parse(pc.Groups[3].Value, CultureInfo.InvariantCulture))); continue; } // Anything else (FL/AM lines, cdb chrome) is out of this // parser's scope — ignored, matching WalkOracleTrace's own // catch-all. } // The last STARTED frame is deliberately never appended — the // truncated detach frame, same rule as WalkOracleTrace. return frames; } public static IReadOnlyList Load(string root, string fixtureName) { string repoRoot = FindRepositoryRoot(); string path = Path.Combine( repoRoot, Path.Combine(root.Split('/')), fixtureName + ".log"); return Parse(File.ReadLines(path)); } private static string FindRepositoryRoot() { DirectoryInfo? dir = new(AppContext.BaseDirectory); while (dir is not null) { if (File.Exists(Path.Combine(dir.FullName, "AcDream.slnx"))) return dir.FullName; dir = dir.Parent; } throw new InvalidOperationException( "AcDream.slnx not found above the test base directory; walk-oracle fixtures unavailable."); } private static readonly Regex FramePattern = new(@"^F (\d+)\s*$", RegexOptions.Compiled); private static readonly Regex PmPattern = new( @"^PM poly=[0-9a-f]+ mode=([01]) counterBefore=([0-9a-f]{4})\s*$", RegexOptions.Compiled); private static readonly Regex PcPattern = new( @"^PC ov=(\d+) counter=([0-9a-f]{4}) fc=([01])\s*$", RegexOptions.Compiled); } /// One capture frame's PM (mode, counterBefore) and PC (ov, /// counter, forceClear) sequences, in the exact order the retail trace /// recorded them. Pointers are intentionally not carried — S4-c1 F3's own /// comparison ignores them. public sealed record WalkAlphaDepthFrame( int Number, IReadOnlyList<(int Mode, int CounterBefore)> PmEvents, IReadOnlyList<(int Ov, int Counter, int ForceClear)> PcEvents);