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;
foreach (string line in lines)
{
Match frameMatch = FramePattern.Match(line);
if (frameMatch.Success)
{
if (current is not null)
frames.Add(new WalkOracleFrame(currentNumber, current));
currentNumber = int.Parse(
frameMatch.Groups[1].Value, CultureInfo.InvariantCulture);
current = new List();
continue;
}
if (current is null)
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 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);
}
public sealed record WalkOracleFrame(int Number, IReadOnlyList Events)
{
/// 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);
}