From 9f4c0f95e32dd89144c7c7aea18f876d2bab0bfd Mon Sep 17 00:00:00 2001 From: Erik Date: Sun, 30 Aug 2026 09:14:55 +0200 Subject: [PATCH] test(render) Campaign FW0: walk-oracle replay helper + fixture goldens WalkOracleTrace parses the FW0 retail traces (frames of LS/BLD/DI/DC events, truncated detach frame dropped) and loads fixtures from the research directory. Nineteen tests pin the load-bearing shapes: the far building drawn every terrace-edge frame, the cathedral roster cull, the stable doorway root, the one-frame walkout handover, camera-cell rooting (porch-cam), the foundry landscape drop, and bit-identical stationary frames. FW1 conformance builds on these. Co-Authored-By: Claude Fable 5 --- .../Rendering/Walk/WalkOracleTrace.cs | 150 +++++++++++++++ .../Rendering/Walk/WalkOracleTraceTests.cs | 171 ++++++++++++++++++ 2 files changed, 321 insertions(+) create mode 100644 tests/AcDream.App.Tests/Rendering/Walk/WalkOracleTrace.cs create mode 100644 tests/AcDream.App.Tests/Rendering/Walk/WalkOracleTraceTests.cs diff --git a/tests/AcDream.App.Tests/Rendering/Walk/WalkOracleTrace.cs b/tests/AcDream.App.Tests/Rendering/Walk/WalkOracleTrace.cs new file mode 100644 index 00000000..d4af658a --- /dev/null +++ b/tests/AcDream.App.Tests/Rendering/Walk/WalkOracleTrace.cs @@ -0,0 +1,150 @@ +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); +} diff --git a/tests/AcDream.App.Tests/Rendering/Walk/WalkOracleTraceTests.cs b/tests/AcDream.App.Tests/Rendering/Walk/WalkOracleTraceTests.cs new file mode 100644 index 00000000..c066c4a0 --- /dev/null +++ b/tests/AcDream.App.Tests/Rendering/Walk/WalkOracleTraceTests.cs @@ -0,0 +1,171 @@ +namespace AcDream.App.Tests.Rendering.Walk; + +/// +/// Integrity tests for the FW0 walk-oracle fixtures: every checked-in +/// trace parses, and the load-bearing shapes recorded in the fixture +/// README hold. FW1's conformance suite builds on these parsed frames; +/// if a fixture is edited or recaptured, these goldens catch drift. +/// +public sealed class WalkOracleTraceTests +{ + private const uint FarBuilding = 0xF518002Eu; + + public static readonly TheoryData AllFixtures = new() + { + "terrace-center", + "terrace-edge", + "cathedral-arrival", + "holtburg-doorway-still", + "holtburg-walkout", + "holtburg-street-porchcam", + "holtburg-street-outdoor", + "holtburg-walkabout", + "foundry-entry", + "foundry-deep", + }; + + [Theory] + [MemberData(nameof(AllFixtures))] + public void Fixture_parses_with_complete_frames(string name) + { + IReadOnlyList frames = WalkOracleTrace.Load(name); + + Assert.NotEmpty(frames); + // Frame numbers are contiguous from 1; the truncated final frame is dropped. + Assert.Equal(1, frames[0].Number); + Assert.Equal(frames.Count, frames[^1].Number); + Assert.All(frames, f => Assert.NotEmpty(f.Events)); + } + + [Fact] + public void Terrace_edge_draws_the_far_building_every_outdoor_frame() + { + // The #456 acceptance oracle: retail HIDES the vista by depth cover, + // not by omission — 0xF518002E is submitted every single frame. + IReadOnlyList frames = WalkOracleTrace.Load("terrace-edge"); + + Assert.All(frames, f => + { + Assert.Null(f.InteriorRootCell); + Assert.True(f.HasLandscape); + Assert.Contains(FarBuilding, f.Buildings); + }); + } + + [Fact] + public void Cathedral_arrival_roots_interior_and_culls_the_far_building() + { + IReadOnlyList frames = WalkOracleTrace.Load("cathedral-arrival"); + + Assert.All(frames, f => + { + Assert.Equal(0xF4180106u, f.InteriorRootCell); + Assert.True(f.HasLandscape); // drawn THROUGH the exit view + Assert.DoesNotContain(FarBuilding, f.Buildings); + }); + } + + [Fact] + public void Doorway_root_is_stable_across_every_frame() + { + IReadOnlyList frames = WalkOracleTrace.Load("holtburg-doorway-still"); + + Assert.All(frames, f => Assert.Equal(0xA9B4013Fu, f.InteriorRootCell)); + } + + [Fact] + public void Walkout_hands_over_between_interior_cells_in_one_frame() + { + IReadOnlyList frames = WalkOracleTrace.Load("holtburg-walkout"); + + uint?[] roots = frames.Select(f => f.InteriorRootCell).Distinct().ToArray(); + Assert.Equal(new uint?[] { 0xA9B4013Fu, 0xA9B40150u }, roots); + // Exactly one handover: the root sequence is two contiguous runs. + int transitions = frames.Zip(frames.Skip(1)) + .Count(pair => pair.First.InteriorRootCell != pair.Second.InteriorRootCell); + Assert.Equal(1, transitions); + } + + [Fact] + public void Street_porchcam_roots_at_the_camera_cell_not_the_player() + { + // The player stood in the street; the chase camera sat inside the + // cottage porch — and retail rooted the frame at the CAMERA's cell. + IReadOnlyList frames = WalkOracleTrace.Load("holtburg-street-porchcam"); + + Assert.All(frames, f => Assert.Equal(0xA9B40150u, f.InteriorRootCell)); + } + + [Fact] + public void Street_outdoor_never_enters_an_interior_root() + { + IReadOnlyList frames = WalkOracleTrace.Load("holtburg-street-outdoor"); + + Assert.All(frames, f => + { + Assert.Null(f.InteriorRootCell); + Assert.True(f.HasLandscape); + }); + } + + [Fact] + public void Foundry_entry_flips_outdoor_to_interior_and_drops_the_landscape() + { + IReadOnlyList frames = WalkOracleTrace.Load("foundry-entry"); + + WalkOracleFrame flip = frames.First(f => f.InteriorRootCell is not null); + Assert.Equal(0xA9B40178u, flip.InteriorRootCell); + // The frame before the flip is fully outdoor; the flip frame itself + // draws NO landscape and NO buildings — the pure-interior shape. + WalkOracleFrame before = frames[flip.Number - 2]; + Assert.Null(before.InteriorRootCell); + Assert.True(before.HasLandscape); + Assert.False(flip.HasLandscape); + Assert.Empty(flip.Buildings); + } + + [Fact] + public void Foundry_deep_draws_the_town_through_the_surviving_chain() + { + IReadOnlyList frames = WalkOracleTrace.Load("foundry-deep"); + + Assert.All(frames, f => + { + Assert.Equal(0xA9B40176u, f.InteriorRootCell); + Assert.True(f.HasLandscape); + Assert.NotEmpty(f.Buildings); + }); + } + + [Fact] + public void Stationary_frames_repeat_their_event_sequence_exactly() + { + // README finding 6: while the camera is still, the whole-frame + // sequence repeats bit-for-bit. Assert it on the terrace fixture. + IReadOnlyList frames = WalkOracleTrace.Load("terrace-center"); + + WalkOracleFrame first = frames[0]; + Assert.All(frames.Skip(1), f => + { + Assert.Equal(first.Events.Count, f.Events.Count); + for (int i = 0; i < first.Events.Count; i++) + Assert.Equal(first.Events[i], f.Events[i], WalkOracleEventComparer.Instance); + }); + } + + private sealed class WalkOracleEventComparer : IEqualityComparer + { + public static readonly WalkOracleEventComparer Instance = new(); + + public bool Equals(WalkOracleEvent? x, WalkOracleEvent? y) + => x is not null && y is not null + && x.Kind == y.Kind + && x.CellId == y.CellId + && x.OutsideViewCount == y.OutsideViewCount + && x.DeclaredCellCount == y.DeclaredCellCount + && x.Cells.SequenceEqual(y.Cells); + + public int GetHashCode(WalkOracleEvent obj) + => HashCode.Combine(obj.Kind, obj.CellId, obj.Cells.Count); + } +}