Round 2 (9ba8f013e) passed the retail and production lenses and every gate and mutation was reproduced by all three; the gate-honesty lens failed it on comment truth only: the terrace-edge Fact repeated round 1's false 'PC already matched' sentence, the sixth Fact claimed a 12/4 guard split and a preamble seed the gate does not evaluate, and WalkVisibilityMath's scan note cited a pin that does not exist and a callers argument that was not the real justification. A third implementer round is barred by the campaign rule, so the lead corrected the sentences at landing. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
388 lines
21 KiB
C#
388 lines
21 KiB
C#
using System.Linq;
|
|
using System.Numerics;
|
|
using AcDream.App.Rendering.Gpu;
|
|
using AcDream.App.Rendering.Walk;
|
|
using DatReaderWriter;
|
|
|
|
namespace AcDream.App.Tests.Rendering.Walk;
|
|
|
|
/// <summary>
|
|
/// S4-c1 fix round 1, F3: the automated depth-event transcript gate.
|
|
/// Replays each OH alpha/depth capture's frame 2 through the SAME ported
|
|
/// walk (<see cref="RetailFrameWalk"/> + <see cref="WalkFrameDriver"/>,
|
|
/// <see cref="WalkTraceReplayContext"/> + <see cref="WalkLandscapeDatBuilder"/>
|
|
/// — the identical replay harness <see cref="WalkTraceConformanceTests"/>
|
|
/// already uses) with a RECORDING sink/leaf pair that reproduces retail's
|
|
/// own PM (mode, counterBefore) and PC (ov, counter, forceClear) sequences,
|
|
/// then compares them against <see cref="WalkAlphaDepthTrace"/>'s parse of
|
|
/// the real capture, exactly (pointers ignored).
|
|
///
|
|
/// <para><b>Why two observation points, not one.</b> Retail's PM line fires
|
|
/// at <c>DrawPortalPolyInternal</c>'s function ENTRY — before the four-plane
|
|
/// boundary guard (S4-c1 F1) has run — so a PM line is emitted for every
|
|
/// ATTEMPT, guard-rejected or not. A building punch's guard decision runs
|
|
/// INSIDE <see cref="WalkFrameDriver.OnPunchGeometry"/> (a Collect-time
|
|
/// hook): a rejected punch never reaches <see cref="IWalkFrameLeafRenderer.DrawPunchFan"/>
|
|
/// at all, so <see cref="AlphaDepthCollectSink"/> intercepts
|
|
/// <c>OnPunchGeometry</c> ITSELF, at Collect time, before the guard runs —
|
|
/// reading <see cref="WalkFrameDriver.PortalsDrawnCount"/> there gives the
|
|
/// PRE-reset value retail's own punches observe (Collect completes entirely
|
|
/// before Replay starts, so reading the counter at Replay time would already
|
|
/// see the root's own post-reset value — the wrong number, confirmed against
|
|
/// holtburg-doorway-still's own counterBefore=0002 punches immediately
|
|
/// followed by counterBefore=0000/0001 seals in the SAME turn). Exit seals
|
|
/// have no such Collect-time per-portal hook at all (<see
|
|
/// cref="IWalkFrameLeafRenderer.DrawExitSeals"/> takes no cell/portal
|
|
/// argument — <c>RetailPViewPassExecutor.DrawPortalDepthWrite</c> owns the
|
|
/// real per-portal loop entirely at Replay time), so <see cref="AlphaDepthLeaf"/>
|
|
/// reproduces that SAME loop (S4-c1 F1's guard, F2's count-before-clip order)
|
|
/// directly over the walk's own <see cref="WalkFrameDriver.InteriorFloodCells"/>
|
|
/// / <see cref="WalkCell.Portals"/> / <see cref="WalkCell.PortalPolygons"/> —
|
|
/// the identical LOCAL portal-polygon data the production seal path reads,
|
|
/// at Replay time, which is also where retail's own exit-seal loop runs
|
|
/// (established: punches always precede the single per-root ExitSeals
|
|
/// Collect-time event, so simple list concatenation reproduces the exact
|
|
/// interleaved PM order without needing a shared Collect/Replay timeline).
|
|
/// </para>
|
|
///
|
|
/// <para><b>Why the counter is SEEDED, not primed by a throwaway pass
|
|
/// (S4-c1 fix round 2, R2-2).</b> Retail's <c>portalsDrawnCount</c> is a
|
|
/// PERSISTENT session global (wo(008719b4)), unaffected by any reset
|
|
/// between captured frames — a running client carries whatever value the
|
|
/// counter last settled at into every subsequent capture. Each fixture's
|
|
/// own FIRST PM or PC line (wherever it falls — including the "F 1"
|
|
/// preamble text a cdb session prints before the first parsed frame
|
|
/// marker, which is where terrace-edge's and holtburg-doorway-still's own
|
|
/// first samples live) already carries that pre-capture value:
|
|
/// cathedral-arrival 0, cathedral-leak 0, foundry-deep 1,
|
|
/// holtburg-doorway-still 2, terrace-edge 2, cathedral-stair-arch 8 (see
|
|
/// <see cref="WalkAlphaDepthTrace.LoadInitialCounter"/>'s own doc comment).
|
|
/// Fix round 1 tried to reach a matching steady state by running a
|
|
/// throwaway "priming" Collect+Replay pass first and discarding its
|
|
/// output — this works only for a fixture whose captured frames themselves
|
|
/// contain enough seal activity to climb from a cold
|
|
/// <c>PortalsDrawnCount=0</c> start up to the needed value (holtburg-
|
|
/// doorway-still, foundry-deep); terrace-edge's own capture never runs a
|
|
/// qualifying interior turn at all (every PC line reads <c>ov=0</c>), so no
|
|
/// number of priming passes from a cold start can ever reach its
|
|
/// <c>counterBefore=2</c> — the value has to come from BEFORE the capture,
|
|
/// not from replaying the capture against itself. Seeding
|
|
/// <see cref="WalkFrameDriver.PortalsDrawnCount"/> directly from the
|
|
/// fixture's own first sample before a SINGLE Collect+Replay pass fixes
|
|
/// this for every pose, terrace-edge included, without changing what a
|
|
/// pose with real seal activity computes (a fixture's own first sample
|
|
/// IS the steady-state value fix round 1's priming pass converged to,
|
|
/// since a steady state is by definition unchanged by one more pass).</para>
|
|
/// </summary>
|
|
public sealed partial class WalkTraceConformanceTests
|
|
{
|
|
/// <summary>Collect-time <see cref="IWalkEventSink"/> decorator: forwards
|
|
/// every hook to the real <see cref="WalkFrameDriver"/> unchanged (so its
|
|
/// own event collection stays complete and correct), while separately
|
|
/// recording the two families this gate needs — see this file's own
|
|
/// class doc comment for why punches must be captured HERE rather than
|
|
/// at Replay.</summary>
|
|
private sealed class AlphaDepthCollectSink : IWalkEventSink
|
|
{
|
|
private readonly WalkFrameDriver _driver;
|
|
private readonly IWalkEventSink _inner;
|
|
|
|
public AlphaDepthCollectSink(WalkFrameDriver driver)
|
|
{
|
|
_driver = driver;
|
|
_inner = driver;
|
|
}
|
|
|
|
public readonly List<(int Mode, int CounterBefore)> Punches = new();
|
|
public readonly List<(int Ov, int Counter, int ForceClear)> PcEvents = new();
|
|
|
|
public void Emit(in WalkEvent walkEvent)
|
|
{
|
|
// PView::DrawCells @0x005a4840's own PC print fires at function
|
|
// ENTRY for every call — the root's own turn AND every building
|
|
// look-in's re-entrant call alike (oh1-depth-lifecycle.md: the
|
|
// breakpoint doesn't distinguish). WalkEventKind.DrawCells's
|
|
// Emit call is the walk's own "this DrawCells call happened"
|
|
// signal at that SAME entry-order point (its own doc comment:
|
|
// "sits at breakpoint-ENTRY order"); forceClear (oh1: no write
|
|
// site found anywhere in the binary) is always 0.
|
|
if (walkEvent.Kind == WalkEventKind.DrawCells)
|
|
PcEvents.Add((walkEvent.OutsideViewCount, _driver.PortalsDrawnCount, 0));
|
|
_inner.Emit(walkEvent);
|
|
}
|
|
|
|
public void OnLandscapeViews(WalkPortalView activeViews) =>
|
|
_inner.OnLandscapeViews(activeViews);
|
|
|
|
public void OnLandCellTurn(uint landblockId, int sideCellCount, int cellIndex) =>
|
|
_inner.OnLandCellTurn(landblockId, sideCellCount, cellIndex);
|
|
|
|
public void OnSortCellTurn(uint landblockId, int sideCellCount, int cellIndex) =>
|
|
_inner.OnSortCellTurn(landblockId, sideCellCount, cellIndex);
|
|
|
|
public void OnLandscapeCellTurn(uint cellId) =>
|
|
_inner.OnLandscapeCellTurn(cellId);
|
|
|
|
public void OnLandscapeCellTurn(uint landblockId, int sideCellCount, int cellIndex) =>
|
|
_inner.OnLandscapeCellTurn(landblockId, sideCellCount, cellIndex);
|
|
|
|
public void OnBuildingTurn(WalkBuilding building) =>
|
|
_inner.OnBuildingTurn(building);
|
|
|
|
public void OnBuildingShellTurn(WalkBuilding building) =>
|
|
_inner.OnBuildingShellTurn(building);
|
|
|
|
public void OnPunchGeometry(
|
|
WalkBuilding building, WalkPolygon polygon, int activeViewIndex)
|
|
{
|
|
// Far-Z punches never touch portalsDrawnCount (R2); this reads
|
|
// it purely as a snapshot for the transcript, BEFORE the SAME
|
|
// interior root's own OnInteriorFloodDrawTurn reset — see class
|
|
// doc comment.
|
|
Punches.Add((1, _driver.PortalsDrawnCount));
|
|
_inner.OnPunchGeometry(building, polygon, activeViewIndex);
|
|
}
|
|
|
|
public void OnInteriorFloodDrawTurn(IReadOnlyList<uint> cells, int outsideViewCount) =>
|
|
_inner.OnInteriorFloodDrawTurn(cells, outsideViewCount);
|
|
|
|
public void OnWeatherTurn(uint viewerCellId) =>
|
|
_inner.OnWeatherTurn(viewerCellId);
|
|
}
|
|
|
|
/// <summary>Replay-time <see cref="IWalkFrameLeafRenderer"/>: every
|
|
/// member besides <see cref="DrawExitSeals"/> is a plain no-op (this
|
|
/// gate cares only about the depth/count mechanism, not mesh content).
|
|
/// <see cref="DrawExitSeals"/> reproduces
|
|
/// <c>RetailPViewPassExecutor.DrawPortalDepthWrite</c>'s real per-portal
|
|
/// loop — S4-c1 F1's guard, F2's count-before-clip order — directly over
|
|
/// the walk's own flood/cell data (no fake GPU clip needed: F2
|
|
/// established the count happens unconditionally once the guard
|
|
/// passes).</summary>
|
|
private sealed class AlphaDepthLeaf : IWalkFrameLeafRenderer
|
|
{
|
|
private readonly Dictionary<uint, WalkCell> _cells;
|
|
|
|
public AlphaDepthLeaf(Dictionary<uint, WalkCell> cells) => _cells = cells;
|
|
|
|
/// <summary>Set once, immediately after construction — the leaf and
|
|
/// its owning <see cref="WalkFrameDriver"/> are mutually referential
|
|
/// (the driver's constructor requires the leaf), so this cannot be a
|
|
/// constructor parameter.</summary>
|
|
public WalkFrameDriver Driver { get; set; } = null!;
|
|
|
|
public readonly List<(int Mode, int CounterBefore)> Seals = new();
|
|
|
|
public void DrawSky() { }
|
|
public void DrawLandCellBatch(
|
|
IReadOnlyList<(uint LandblockId, int SideCellCount, int CellIndex)> cells) { }
|
|
public bool HasRenderableEmittersInCell(uint cellId) => false;
|
|
public void DrawCellShell(uint cellId) { }
|
|
public void DrawStaticParticles(uint cellId) { }
|
|
public void DrawCellParticles(uint cellId) { }
|
|
public void ClearInteriorDepth() { }
|
|
public void FlushLandscape() { }
|
|
public void DrawPunchFan(WalkPolygon worldPolygon, int activeViewIndex) { }
|
|
public void AlphaBarrier() { }
|
|
|
|
public int DrawExitSeals()
|
|
{
|
|
int accepted = 0;
|
|
List<uint> flood = Driver.InteriorFloodCells;
|
|
// Reverse order — the SAME direction DrawWalkExitPortalMasks
|
|
// walks driver.InteriorFloodCells in production
|
|
// (RetailPViewRenderer.cs).
|
|
for (int i = flood.Count - 1; i >= 0; i--)
|
|
{
|
|
if (!_cells.TryGetValue(flood[i], out WalkCell? cell))
|
|
continue;
|
|
int sliceCount = Driver.InteriorFloodViewSliceCountAt(i);
|
|
for (int slice = 0; slice < sliceCount; slice++)
|
|
{
|
|
for (int p = 0; p < cell.Portals.Length; p++)
|
|
{
|
|
WalkCellPortal portal = cell.Portals[p];
|
|
// Walk-side exit sentinel is 32-bit 0xFFFFFFFF
|
|
// (WalkCellPortal's own doc comment) — the render-
|
|
// side CellPortalInfo's 16-bit 0xFFFF is a DIFFERENT
|
|
// representation of the same fact.
|
|
if (portal.OtherCellId != 0xFFFFFFFFu)
|
|
continue;
|
|
if ((uint)portal.PolygonIndex >= (uint)cell.PortalPolygons.Length)
|
|
continue;
|
|
WalkPolygon poly = cell.PortalPolygons[portal.PolygonIndex];
|
|
|
|
// F2: count happens BEFORE clip (unconditionally,
|
|
// once the guard passes) — counterBefore is the
|
|
// running total BEFORE this specific portal's own
|
|
// decision.
|
|
Seals.Add((0, Driver.PortalsDrawnCount + accepted));
|
|
if (!WalkVisibilityMath.IsRejectedByPortalPolygonBoundaryGuard(
|
|
poly.Vertices))
|
|
{
|
|
accepted++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return accepted;
|
|
}
|
|
}
|
|
|
|
/// <summary>Minimal <see cref="IWalkFrameWorldData"/>: this gate never
|
|
/// inspects mesh content, only the depth/count event sequence, so every
|
|
/// query returns the shared empty record.</summary>
|
|
private sealed class EmptyAlphaDepthWorldData : IWalkFrameWorldData
|
|
{
|
|
public WalkFrameStaticRecords GetCellStatics(uint cellId) => WalkFrameStaticRecords.Empty;
|
|
public WalkFrameStaticRecords GetCellDynamics(uint cellId) => WalkFrameStaticRecords.Empty;
|
|
public WalkFrameStaticRecords GetOutdoorStatics(uint cellId) => WalkFrameStaticRecords.Empty;
|
|
public WalkFrameStaticRecords GetOutdoorDynamics(uint cellId) => WalkFrameStaticRecords.Empty;
|
|
public WalkFrameStaticRecords GetBuildingShellStatics(WalkBuilding building) =>
|
|
WalkFrameStaticRecords.Empty;
|
|
public Matrix4x4 GetBuildingWorldTransform(WalkBuilding building) => Matrix4x4.Identity;
|
|
}
|
|
|
|
/// <summary>Seeds <see cref="WalkFrameDriver.PortalsDrawnCount"/> from
|
|
/// <paramref name="fixtureName"/>'s own first observed sample (S4-c1 fix
|
|
/// round 2, R2-2 — see class doc comment), runs ONE Collect+Replay pass,
|
|
/// and asserts BOTH the PM and PC sequences against
|
|
/// <paramref name="fixtureName"/>'s captured frame 2
|
|
/// (<see cref="WalkAlphaDepthTrace"/>'s <c>frames[1]</c>, matching every
|
|
/// other OH-capture row's own frame-2 convention in this file).
|
|
/// <para>R2-1 (gate honesty): both diffs are computed BEFORE either is
|
|
/// asserted, so a PM mismatch can never prevent the PC comparison from
|
|
/// running (or vice versa) — a written claim that one sequence
|
|
/// "matches" is only ever one this method actually evaluated.</para>
|
|
/// </summary>
|
|
private void RunAlphaDepthTranscriptGate(string fixtureName)
|
|
{
|
|
IReadOnlyList<WalkOracleFrame> poseFrames = WalkOracleTrace.Load(OhCaptureRoot, fixtureName);
|
|
Assert.NotEmpty(poseFrames);
|
|
Assert.True(poseFrames.Count > 1, $"{fixtureName}: need a captured frame 2.");
|
|
WalkOracleFrame poseFrame = poseFrames[1];
|
|
Assert.NotNull(poseFrame.Pose);
|
|
WalkOraclePose pose = poseFrame.Pose!;
|
|
|
|
IReadOnlyList<WalkAlphaDepthFrame> depthFrames = WalkAlphaDepthTrace.Load(OhCaptureRoot, fixtureName);
|
|
Assert.True(depthFrames.Count > 1, $"{fixtureName}: need a captured frame 2's PM/PC content.");
|
|
WalkAlphaDepthFrame expected = depthFrames[1];
|
|
int initialCounter = WalkAlphaDepthTrace.LoadInitialCounter(OhCaptureRoot, fixtureName);
|
|
|
|
using DatCollection dats = OpenDats();
|
|
WalkLandscapeDatBuilder.BuiltWorld world =
|
|
WalkLandscapeDatBuilder.Build(dats, pose.CellId, pose.Origin);
|
|
var ctx = new WalkTraceReplayContext(pose, world.Cells) { Buildings = world.Buildings };
|
|
WalkCell? camera = (pose.CellId & 0xFFFFu) >= 0x100
|
|
? Assert.Contains(pose.CellId, world.Cells)
|
|
: null;
|
|
|
|
using var fx = new WalkFrameDriverTests.DispatcherFixture();
|
|
var worldData = new EmptyAlphaDepthWorldData();
|
|
var leaf = new AlphaDepthLeaf(world.Cells);
|
|
var driver = new WalkFrameDriver(fx.Dispatcher, leaf, worldData);
|
|
leaf.Driver = driver;
|
|
var sink = new AlphaDepthCollectSink(driver);
|
|
var walk = new RetailFrameWalk();
|
|
|
|
// R2-2: seed the persistent counter from the fixture's own
|
|
// pre-capture value instead of priming a throwaway first pass.
|
|
driver.PortalsDrawnCount = initialCounter;
|
|
|
|
using (WalkFrameDriverTests.DrawScope draw = fx.BeginDraw())
|
|
{
|
|
driver.BeginFrame(ctx, Matrix4x4.Identity, pose.Origin);
|
|
walk.WalkFrame(pose.CellId, camera, world.Landscape, ctx, sink);
|
|
driver.EndFrame();
|
|
driver.Replay(draw.Frame, draw.Pass);
|
|
}
|
|
// DrawScope.Dispose ends only the pass/publication — the frame
|
|
// itself (RecordingGpuDevice's "one open frame" invariant) is
|
|
// ended separately, same order WalkOutsideViewReassemblyTests'
|
|
// own multi-draw loop uses.
|
|
fx.FrameLifetime.EndFrame();
|
|
|
|
var actualPm = new List<(int Mode, int CounterBefore)>(sink.Punches);
|
|
actualPm.AddRange(leaf.Seals);
|
|
IReadOnlyList<(int Ov, int Counter, int ForceClear)> actualPc = sink.PcEvents;
|
|
|
|
bool pmMatches = expected.PmEvents.SequenceEqual(actualPm);
|
|
bool pcMatches = expected.PcEvents.SequenceEqual(actualPc);
|
|
if (!pmMatches || !pcMatches)
|
|
{
|
|
Assert.Fail(
|
|
$"{fixtureName}: PM {(pmMatches ? "matches" : "DIVERGES")} " +
|
|
$"— expected {FormatPm(expected.PmEvents)}, actual {FormatPm(actualPm)}; " +
|
|
$"PC {(pcMatches ? "matches" : "DIVERGES")} " +
|
|
$"— expected {FormatPc(expected.PcEvents)}, actual {FormatPc(actualPc)}");
|
|
}
|
|
}
|
|
|
|
private static string FormatPm(IReadOnlyList<(int Mode, int CounterBefore)> events) =>
|
|
"[" + string.Join(", ", events.Select(e => $"(mode={e.Mode},counterBefore={e.CounterBefore})")) + "]";
|
|
|
|
private static string FormatPc(IReadOnlyList<(int Ov, int Counter, int ForceClear)> events) =>
|
|
"[" + string.Join(", ", events.Select(e => $"(ov={e.Ov},counter={e.Counter},fc={e.ForceClear})")) + "]";
|
|
|
|
[Fact]
|
|
public void AlphaDepthTranscript_CathedralArrival_MatchesRetailFrame2()
|
|
=> RunAlphaDepthTranscriptGate("cathedral-arrival.alphadepth");
|
|
|
|
[Fact]
|
|
public void AlphaDepthTranscript_FoundryDeep_MatchesRetailFrame2()
|
|
=> RunAlphaDepthTranscriptGate("foundry-deep.alphadepth");
|
|
|
|
[Fact]
|
|
public void AlphaDepthTranscript_HoltburgDoorwayStill_MatchesRetailFrame2()
|
|
=> RunAlphaDepthTranscriptGate("holtburg-doorway-still.alphadepth");
|
|
|
|
/// <summary>
|
|
/// S4-c1 fix round 2 (R2-2): seeding <see cref="WalkFrameDriver.PortalsDrawnCount"/>
|
|
/// from terrace-edge's own first observed sample (<c>counterBefore=2</c>,
|
|
/// carried in the file's pre-"F 1" preamble text — see
|
|
/// <see cref="WalkAlphaDepthTrace.LoadInitialCounter"/>'s own doc
|
|
/// comment) closes fix round 1's KnownFailure. Under a cold <c>0</c>
|
|
/// BOTH sequences diverged in the counter field — PM expected
|
|
/// <c>(1,2),(1,2)</c> and PC expected <c>(0,2,0),(0,2,0)</c> against a
|
|
/// cold <c>(1,0)</c>/<c>(0,0,0)</c> — round 1's write-up wrongly called
|
|
/// the PC side a match, and round 2's gate-honesty lens caught the same
|
|
/// sentence re-worded here (lead correction at landing). Seeded, both
|
|
/// sequences match exactly; this pose never runs a qualifying interior
|
|
/// turn, so PC stays <c>ov=0</c> with the seeded counter.
|
|
/// </summary>
|
|
[Fact]
|
|
public void AlphaDepthTranscript_TerraceEdge_MatchesRetailFrame2()
|
|
=> RunAlphaDepthTranscriptGate("terrace-edge.alphadepth");
|
|
|
|
/// <summary>
|
|
/// S4-c1 fix round 2, R2-4: the sixth pose (#464 artifact pose, root
|
|
/// <c>0xF4180114</c>, seven outside views). Sixteen building punches —
|
|
/// all <c>mode=1</c>, all <c>counterBefore=0008</c> (far-Z punches never
|
|
/// touch the counter) — then eight true-depth exit seals with
|
|
/// <c>counterBefore</c> running <c>0..7</c> against the root's own
|
|
/// <c>ov=7</c> read-then-zero. This gate compares only the
|
|
/// <c>(mode, counterBefore)</c> and <c>(ov, counter, forceClear)</c>
|
|
/// tuples: it does NOT observe which of the sixteen punches the ±12
|
|
/// boundary guard rejects (the sink records every attempt before the
|
|
/// guard runs; the <c>pmverts</c> capture and the DAT, not this Fact,
|
|
/// show twelve of them lying on local <c>y=12.000</c> — S4 packet §9).
|
|
/// The seed <c>8</c> is the first PM/PC sample
|
|
/// <see cref="WalkAlphaDepthTrace.LoadInitialCounter"/> finds in the
|
|
/// file, which for this fixture lies inside frame 1 (this capture has no
|
|
/// pre-"F 1" preamble sample); with the priming pass gone, frame 2's PC
|
|
/// <c>counter</c> equals that seed by construction in steady state.
|
|
/// </summary>
|
|
[Fact]
|
|
public void AlphaDepthTranscript_CathedralStairArch_MatchesRetailFrame2()
|
|
=> RunAlphaDepthTranscriptGate("cathedral-stair-arch.alphadepth");
|
|
|
|
/// <summary>Cathedral-leak carries the exact same steady state (counter
|
|
/// pinned at 0, every exit portal degenerate-onto-plane rejected) as
|
|
/// cathedral-arrival; listed here (S4-c1 fix round 1, F3) because the
|
|
/// original spec's own fixture count (four) undercounts the five
|
|
/// <c>*.alphadepth.log</c> files that were already present in
|
|
/// <c>oh-capture/</c> at that round — see that round's commit body.</summary>
|
|
[Fact]
|
|
public void AlphaDepthTranscript_CathedralLeak_MatchesRetailFrame2()
|
|
=> RunAlphaDepthTranscriptGate("cathedral-leak.alphadepth");
|
|
}
|