using System.Globalization; using System.Text.RegularExpressions; namespace AcDream.App.Tests.Rendering.Walk; /// /// Parser for the FW0 retail walk-oracle traces /// (docs/research/2026-08-30-fw-walk-oracle/ — captured live from the /// PDB-paired 2013 retail client on 2026-08-30; format documented in that /// directory's README). These traces are Campaign FW's conformance /// fixtures: FW1's RetailFrameWalk must reproduce each frame's event /// sequence position-for-position. /// /// The final frame of every capture is dropped: the harness detaches at /// the frame MARKER, so the last frame never records its draws and would /// read as a false "outdoor, no landscape" frame. /// public static class WalkOracleTrace { public static IReadOnlyList Parse(IEnumerable lines) { var frames = new List(); List? current = null; int currentNumber = 0; WalkOraclePose? currentPose = null; foreach (string line in lines) { Match frameMatch = FramePattern.Match(line); if (frameMatch.Success) { if (current is not null) frames.Add(new WalkOracleFrame(currentNumber, current, currentPose)); currentNumber = int.Parse( frameMatch.Groups[1].Value, CultureInfo.InvariantCulture); current = new List(); currentPose = null; continue; } if (current is null) continue; Match poseMatch = PosePattern.Match(line); if (poseMatch.Success) { // P — raw IEEE-754 dwords // dumped from Render::viewer_pos @0x0081ef00 at the frame marker. uint[] raw = new uint[8]; for (int i = 0; i < 8; i++) raw[i] = uint.Parse( poseMatch.Groups[i + 1].Value, NumberStyles.HexNumber, CultureInfo.InvariantCulture); currentPose = new WalkOraclePose( raw[0], new System.Numerics.Vector3( BitConverter.Int32BitsToSingle((int)raw[1]), BitConverter.Int32BitsToSingle((int)raw[2]), BitConverter.Int32BitsToSingle((int)raw[3])), BitConverter.Int32BitsToSingle((int)raw[4]), BitConverter.Int32BitsToSingle((int)raw[5]), BitConverter.Int32BitsToSingle((int)raw[6]), BitConverter.Int32BitsToSingle((int)raw[7])); continue; } if (line == "LS") { current.Add(WalkOracleEvent.Landscape()); continue; } Match buildingMatch = BuildingPattern.Match(line); if (buildingMatch.Success) { current.Add(WalkOracleEvent.Building(ParseId(buildingMatch.Groups[1].Value))); continue; } Match insideMatch = DrawInsidePattern.Match(line); if (insideMatch.Success) { current.Add(WalkOracleEvent.DrawInside(ParseId(insideMatch.Groups[1].Value))); continue; } Match cellsMatch = DrawCellsPattern.Match(line); if (cellsMatch.Success) { uint[] cells = cellsMatch.Groups[3].Value .Split(' ', StringSplitOptions.RemoveEmptyEntries) .Select(ParseId) .ToArray(); current.Add(WalkOracleEvent.DrawCells( outsideViewCount: int.Parse( cellsMatch.Groups[1].Value, CultureInfo.InvariantCulture), declaredCount: int.Parse( cellsMatch.Groups[2].Value, CultureInfo.InvariantCulture), cells)); } // Anything else is cdb chrome (banner, prompts, symbol notes) — ignored. } // The last STARTED frame (still in `current`) is deliberately never // appended — that is the truncated detach frame (see class doc). return frames; } public static IReadOnlyList Load(string fixtureName) { string root = FindRepositoryRoot(); string path = Path.Combine( root, "docs", "research", "2026-08-30-fw-walk-oracle", 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 uint ParseId(string hex) => uint.Parse(hex, NumberStyles.HexNumber, CultureInfo.InvariantCulture); private static readonly Regex FramePattern = new(@"^F (\d+)\s*$", RegexOptions.Compiled); private static readonly Regex PosePattern = new( @"^P ([0-9a-f]{8}) ([0-9a-f]{8}) ([0-9a-f]{8}) ([0-9a-f]{8}) ([0-9a-f]{8}) ([0-9a-f]{8}) ([0-9a-f]{8}) ([0-9a-f]{8})\s*$", RegexOptions.Compiled); private static readonly Regex BuildingPattern = new(@"^BLD ([0-9a-f]{8})\s*$", RegexOptions.Compiled); private static readonly Regex DrawInsidePattern = new(@"^DI ([0-9a-f]{8})\s*$", RegexOptions.Compiled); private static readonly Regex DrawCellsPattern = new( @"^DC pv=[0-9a-f]{8} ov=(\d+) n=(\d+):((?: [0-9a-f]{8})*)\s*$", RegexOptions.Compiled); } /// The camera pose dumped at the frame marker (raw dwords from /// Render::viewer_pos @0x0081ef00): the camera's cell id, world /// origin, and the Frame quaternion's four raw components (q0..q3 in /// storage order — axis convention resolved by the replay runner). /// Present only in the pose-stamped capture round. public sealed record WalkOraclePose( uint CellId, System.Numerics.Vector3 Origin, float Q0, float Q1, float Q2, float Q3); public sealed record WalkOracleFrame( int Number, IReadOnlyList Events, WalkOraclePose? Pose = null) { /// The frame's root: the first DrawInside cell, or null when outdoor-rooted. public uint? InteriorRootCell => Events.FirstOrDefault(e => e.Kind == WalkOracleEventKind.DrawInside)?.CellId; public bool HasLandscape => Events.Any(e => e.Kind == WalkOracleEventKind.Landscape); public IEnumerable Buildings => Events.Where(e => e.Kind == WalkOracleEventKind.Building).Select(e => e.CellId!.Value); } public enum WalkOracleEventKind { Landscape, Building, DrawInside, DrawCells, } public sealed record WalkOracleEvent( WalkOracleEventKind Kind, uint? CellId, int OutsideViewCount, int DeclaredCellCount, IReadOnlyList Cells) { public static WalkOracleEvent Landscape() => new(WalkOracleEventKind.Landscape, null, 0, 0, Array.Empty()); public static WalkOracleEvent Building(uint cellId) => new(WalkOracleEventKind.Building, cellId, 0, 0, Array.Empty()); public static WalkOracleEvent DrawInside(uint cellId) => new(WalkOracleEventKind.DrawInside, cellId, 0, 0, Array.Empty()); public static WalkOracleEvent DrawCells( int outsideViewCount, int declaredCount, IReadOnlyList cells) => new(WalkOracleEventKind.DrawCells, null, outsideViewCount, declaredCount, cells); }