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 <noreply@anthropic.com>
This commit is contained in:
parent
71b11817ad
commit
9f4c0f95e3
2 changed files with 321 additions and 0 deletions
150
tests/AcDream.App.Tests/Rendering/Walk/WalkOracleTrace.cs
Normal file
150
tests/AcDream.App.Tests/Rendering/Walk/WalkOracleTrace.cs
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
using System.Globalization;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace AcDream.App.Tests.Rendering.Walk;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public static class WalkOracleTrace
|
||||
{
|
||||
public static IReadOnlyList<WalkOracleFrame> Parse(IEnumerable<string> lines)
|
||||
{
|
||||
var frames = new List<WalkOracleFrame>();
|
||||
List<WalkOracleEvent>? 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<WalkOracleEvent>();
|
||||
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<WalkOracleFrame> 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<WalkOracleEvent> Events)
|
||||
{
|
||||
/// <summary>The frame's root: the first DrawInside cell, or null when outdoor-rooted.</summary>
|
||||
public uint? InteriorRootCell
|
||||
=> Events.FirstOrDefault(e => e.Kind == WalkOracleEventKind.DrawInside)?.CellId;
|
||||
|
||||
public bool HasLandscape => Events.Any(e => e.Kind == WalkOracleEventKind.Landscape);
|
||||
|
||||
public IEnumerable<uint> 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<uint> Cells)
|
||||
{
|
||||
public static WalkOracleEvent Landscape()
|
||||
=> new(WalkOracleEventKind.Landscape, null, 0, 0, Array.Empty<uint>());
|
||||
|
||||
public static WalkOracleEvent Building(uint cellId)
|
||||
=> new(WalkOracleEventKind.Building, cellId, 0, 0, Array.Empty<uint>());
|
||||
|
||||
public static WalkOracleEvent DrawInside(uint cellId)
|
||||
=> new(WalkOracleEventKind.DrawInside, cellId, 0, 0, Array.Empty<uint>());
|
||||
|
||||
public static WalkOracleEvent DrawCells(
|
||||
int outsideViewCount, int declaredCount, IReadOnlyList<uint> cells)
|
||||
=> new(WalkOracleEventKind.DrawCells, null, outsideViewCount, declaredCount, cells);
|
||||
}
|
||||
171
tests/AcDream.App.Tests/Rendering/Walk/WalkOracleTraceTests.cs
Normal file
171
tests/AcDream.App.Tests/Rendering/Walk/WalkOracleTraceTests.cs
Normal file
|
|
@ -0,0 +1,171 @@
|
|||
namespace AcDream.App.Tests.Rendering.Walk;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public sealed class WalkOracleTraceTests
|
||||
{
|
||||
private const uint FarBuilding = 0xF518002Eu;
|
||||
|
||||
public static readonly TheoryData<string> 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<WalkOracleFrame> 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<WalkOracleFrame> 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<WalkOracleFrame> 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<WalkOracleFrame> 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<WalkOracleFrame> 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<WalkOracleFrame> 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<WalkOracleFrame> 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<WalkOracleFrame> 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<WalkOracleFrame> 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<WalkOracleFrame> 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<WalkOracleEvent>
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue