acdream/tests/AcDream.App.Tests/Rendering/Walk/WalkFrameDriverTranscriptTests.cs
Erik 62c2231d0a refactor(render): S3 chunk 4 — delete the clip rules retail does not have and the cathedral discriminators
Campaign OVERHAUL S3 chunk 4 (docs/research/2026-09-01-overhaul/s3-walk-ownership-map.md §10). Retail
polygon-clips exactly three things on the CPU — portal polygons (PView::GetClip), punch fans
(DrawPortalPolyInternal against building_view), and exit seals (setup_view + polyClipFinish);
ordinary meshes, cell shells, terrain, particles and the weather are never view-clipped. Every
acdream-only clip rule beyond those three is deleted; the two that remain get a new CPU/GPU
equivalence pin.

DELETED (no retail counterpart):
- RetailPViewRenderer.DrawLandscapeDynamicsPhase's per-outside-view-slice weather loop
  (SetTerrainClip + ClearClipRouting + the old DrawLandscapeSliceLate leaf, one call per active
  landscape view) — retail's GameSky::Draw(sky,1) @0x00506ff0 runs ONCE, unclipped, after
  LScape::draw's whole block loop. RetailPViewPassExecutor.DrawWeatherOnce stays the one call site;
  it now also submits the rain particles (ParticleRenderPass.SkyPostScene) as ONE unclipped
  submission, folded in from the deleted loop's per-slice clipSlot draw.
- RetailPViewPassExecutor.DrawLandscapeSliceLate, RetailPViewLandscapeLateSliceContext,
  SetTerrainClip (the walk-level wrapper — its only caller was the deleted loop),
  ClearClipRouting (same), SetCellShellClipRouting (already had no caller), and
  UseIndoorMembershipOnlyRouting (4 call sites in RetailPViewRenderer.cs) — all "first view clips
  the shell" residue: SetCellShellClipRouting was the ONLY place EnvCellRenderer.SetClipRouting
  ever received a non-null value anywhere in the app, so once it's gone,
  UseIndoorMembershipOnlyRouting's own clear calls are provably permanent no-ops. AbortFrame's
  matching TryAbort(SetClipRouting(null))/TryAbort(ClearClipRouting) lines are dropped for the
  same reason. KEPT: BeginDoorwayScissor, _surface.BindTerrainClip(), EnableClipDistances/
  DisableClipDistances, and IWorldPassSurface.SetTerrainClip/ClipFrame.SetTerrainClip/
  PrepareClipFrame's terrain-clip publish — all still have real consumers (DrawWalkSky's per-slice
  outdoor sky mesh draw; the always-bound TerrainClip UBO section) or are shared classic (non-walk)
  infrastructure out of this chunk's ownership (WorldScenePassExecutor, PortalTunnelPresentation).
- WbDrawDispatcher.WalkClassify.ResolvePartClipSlots (renamed ResolvePartVisible, returns bool) and
  RetailWholeMeshSlot: the per-view clip-slot LIST always collapsed to either nothing or one
  element (RenderDeviceD3D::DrawMesh @0x005A0860 submits the whole mesh once any view admits it) —
  never anything but a Boolean wearing a list-shaped costume. IWalkLookInViewSource.
  VisibleClipSlotsInLookInTurn is deleted outright (no consumer besides that collapse);
  SphereVisibleInLookInTurn gained VisibleClipSlotsInLookInTurn's own testSphere parameter so the
  no-authored-sphere case ("any admitted view, geometry ignored") still has a home.
  EmitClassifiedBatches now emits exactly one batch per surviving TryClassifyBatch using the
  entity's own classic slot (always 0 while _clipRoutingActive is never armed by the walk path).
- The six ProbeCathedralSkip*/ProbeCathedralShellOrderEnabled discriminators (RenderingDiagnostics,
  their six docs/launch-options.md rows, every read site in WalkFrameDriver/
  RetailPViewPassExecutor.WalkLeaf/RetailPViewRenderer, and the WalkFrameDriver.
  TraceCathedralShellOrder method + RetailPViewRenderer._probeCathedralShellOrderFrame it served) —
  the investigation is closed: the cathedral leak is retail behavior, not an acdream defect to
  discriminate.

O1-O4 (the ledger's carried chunk-1 items, closed here because this chunk owns the weather path):
IWalkEventSink gains OnWeatherTurn(viewerCellId), fired by RetailFrameWalk.DrawLandscape
UNCONDITIONALLY at retail's own call-site shape (GameSky::Draw(sky,1) is itself unconditional from
LScape::draw; the is_player_outside gate lives inside it) but gated by the walk context's own new
WeatherGateOpen/ViewerCellId members (default false/0, so every other IRetailFrameWalkContext
implementer — test fixtures, the FW1 conformance replay context — is unaffected).
WalkProductionFrameContext wires RetailPViewFrameInput.ViewerCellId and
RetailPViewPassExecutor.ShouldDrawWeatherOnce(RenderSky, RenderWeather, PlayerCellId) into those two
members. WalkFrameDriver.OnWeatherTurn prints the "OC" transcript line at COLLECT time; the print is
removed from DrawWeatherOnce (the DRAW stays there, at Replay). Because DrawLandscape's new call sits
at the very end of the method, an outdoor root's transcript naturally ends with the line and an
interior root's lands strictly between "LS" and the flood's first "EC" — exactly retail's position —
with no extra plumbing.

Kept items each get a synthetic-view CPU/GPU equivalence pin (ClipFrameLayoutTests): exit seals via
the real ClipFrame.AppendSlot/GetSlotPlanes round trip, punch fans via the direct
ClipViewSlice.Planes = cps.PlaneArray assignment ClipFrameAssembler.Assemble uses — both assert every
polygon edge midpoint has ~0 signed distance under the GPU-read-back planes and non-negative distance
under every plane.

Every new pin's mutation was hand-verified this session (temporarily reintroduced, confirmed the
exact failing assertion, then reverted): a duplicated OnWeatherTurn call fails Assert.Single with
"2 matching items" on both the outdoor and interior position tests; suppressing the Collect-time
call fails the same assertions with "did not contain any matching items"; perturbing one GPU plane's
offset by 0.05 fails the exit-seal equivalence pin with "the closest plane was only 0.050000012
away". WalkProductionFrameContextTests pins that the ViewerCellId/WeatherGateOpen storage seam
itself never drops or swaps its own two constructor/Reset arguments.

No register row added or removed — every change here deletes an acdream-only rule; none introduces
a new deviation. Full solution build: 0 warnings/0 errors. App hermetic 6,829/6,829 (was 6,828 base
+ 1 new pin). InstalledDat 244 pass/1 skip/4 known (2x #383, TowerAscent, and the pre-existing #458
WalkLandscape.CheckBlocks block-visibility divergence — unrelated to this chunk, untouched by it).
Core.Tests Rendering 216/216.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-03 13:32:36 +02:00

714 lines
34 KiB
C#

using System.Numerics;
using AcDream.App.Rendering;
using AcDream.App.Rendering.Gpu;
using AcDream.App.Rendering.Walk;
using AcDream.App.Tests.Rendering;
using AcDream.Core.Rendering;
namespace AcDream.App.Tests.Rendering.Walk;
/// <summary>
/// Campaign OVERHAUL S3 chunk 1 (§11.2): tests T1/T2 for the print-only walk
/// transcript emitter (<see cref="WalkTranscriptDump"/>, gated by
/// <see cref="RenderingDiagnostics.DumpWalkTranscriptEnabled"/>), plus the
/// §11.2 B4 offline signature-diff self-check. Reuses
/// <see cref="WalkFrameDriverTests"/>'s own private fixture types via the
/// shared <c>partial class</c> — the SAME minimal interior two-cell flood
/// (one exit view) that file's own
/// <c>RunFrame_InteriorFloodWithExitView_FreshDriverSkipsTheGatedClearThenDrawsSealsAndFloodCells</c>
/// test already exercises and proves correct; here only
/// <see cref="WalkFrameDriver.Collect"/> runs (no <c>Replay</c>/GPU
/// submission), since every transcript print fires synchronously during the
/// walk itself.
///
/// <para>
/// <see cref="RenderingDiagnostics.DumpWalkTranscriptEnabled"/> and
/// <see cref="Console.Out"/> are both process-wide mutable statics — joins
/// <see cref="CameraDiagnosticsCollection"/> for the SAME reason
/// <c>CornerFloodReplayTests</c>/<c>Issue181WallPressEquilibriumTests</c> do
/// (that collection's own doc comment, issue #251): interleaved
/// <c>Console.SetOut</c> redirection across parallel test classes can
/// restore a disposed <see cref="StringWriter"/> process-wide.
/// </para>
/// </summary>
[Collection(CameraDiagnosticsCollection.Name)]
public sealed partial class WalkFrameDriverTests
{
private static (WalkCell Cell1, WalkCell Cell2) BuildTranscriptFixtureCells(TestContext ctx)
{
var cell1 = new WalkCell
{
CellId = 0x100,
StabList = [0x101u],
Portals =
[
new WalkCellPortal
{
OtherCellId = 0x101, PolygonIndex = 0, PortalSide = 0, OtherPortalId = 0,
},
// The exit portal — raises ov to 1, so the flood draws the
// (empty, zero-block) landscape too, exercising LS.
new WalkCellPortal
{
OtherCellId = 0xFFFFFFFF, PolygonIndex = 1, PortalSide = 0, OtherPortalId = -1,
},
],
PortalPolygons = [Quad(-2f), Quad(-3f)],
};
var cell2 = new WalkCell
{
CellId = 0x101,
Portals = [new WalkCellPortal
{
OtherCellId = 0x100, PolygonIndex = 0, PortalSide = 1, OtherPortalId = 0,
}],
PortalPolygons = [Quad(-2f)],
};
ctx.Cells[cell1.CellId] = cell1;
ctx.Cells[cell2.CellId] = cell2;
return (cell1, cell2);
}
/// <summary>T1 (§11.3): the flag off — zero output, and the emitter's
/// own methods (the only code this chunk adds to the walk path)
/// allocate nothing. The walk itself is NOT independently zero-alloc
/// (e.g. <c>RetailFrameWalk.EmitDrawCells</c> allocates its cell-id
/// array on every call — pre-existing, unrelated to this chunk), so the
/// allocation bound below is scoped to <see cref="WalkTranscriptDump"/>
/// itself, matching B1's actual contract ("allocates nothing EXTRA").</summary>
[Fact]
public void TranscriptEmitter_FlagOff_EveryPrintMethodIsAZeroCostNoOp()
{
bool previous = RenderingDiagnostics.DumpWalkTranscriptEnabled;
RenderingDiagnostics.DumpWalkTranscriptEnabled = false;
TextWriter originalOut = Console.Out;
var capture = new StringWriter();
try
{
Console.SetOut(capture);
var cells = new List<uint> { 0x100u, 0x101u };
void Step()
{
WalkTranscriptDump.PrintFrameRoot(1, 0x100u, Vector3.Zero, Vector3.UnitY);
WalkTranscriptDump.PrintLandscape();
WalkTranscriptDump.PrintBuilding(0x100u);
WalkTranscriptDump.PrintDrawInside(0x100u);
WalkTranscriptDump.PrintDrawCells(outdoorPview: false, 1, cells);
WalkTranscriptDump.PrintLandCell(0xF4180001u);
WalkTranscriptDump.PrintSortCell(0xF4180001u);
WalkTranscriptDump.PrintEnvCellShell(0x101u);
WalkTranscriptDump.PrintObjectCellTurn(0x101u);
}
ZeroAllocationProbe.AssertAllocatesNothing(
"WalkTranscriptDump.Print* (flag off)", Step);
AssertNoTranscriptLines(capture);
}
finally
{
Console.SetOut(originalOut);
RenderingDiagnostics.DumpWalkTranscriptEnabled = previous;
}
}
/// <summary>T1's integration half: a full synthetic interior frame,
/// driven through the SAME <see cref="WalkFrameDriver.Collect"/> path
/// production uses, produces literally NO transcript output when the
/// flag is off. S3 chunk 1 fix round 1 (G1): TWO <c>Collect</c> calls,
/// not one — <see cref="WalkOracleTrace.Parse"/> structurally discards
/// the last (open) frame (its own class doc: the capture harness
/// detaches at the frame marker, so the final "F n" never records its
/// own draws), so a SINGLE-frame transcript parses to zero frames
/// whether or not the flag actually gated anything — a one-Collect-call
/// version of this test is vacuous (it would pass exactly as well with
/// the flag gate forced true). Two Collect calls give the parser a real
/// COMPLETE frame to see if the gate leaked (MUTATION CHECK: forcing
/// <c>RenderingDiagnostics.DumpWalkTranscriptEnabled</c> true right
/// after setting it false here makes <c>AssertNoTranscriptLines</c>
/// fail, both on the parsed-frame-count assertion and the prefix
/// scan).</summary>
[Fact]
public void Collect_TranscriptFlagOff_ProducesNoConsoleOutput()
{
bool previous = RenderingDiagnostics.DumpWalkTranscriptEnabled;
RenderingDiagnostics.DumpWalkTranscriptEnabled = false;
TextWriter originalOut = Console.Out;
var capture = new StringWriter();
try
{
Console.SetOut(capture);
using var fx = new DispatcherFixture();
var ctx = new TestContext();
(WalkCell cell1, _) = BuildTranscriptFixtureCells(ctx);
var landscape = new WalkLandscape { MidWidth = 1, Blocks = new WalkLandBlock?[1] };
var worldData = new FakeWorldData();
var leaf = new RecordingLeafRenderer(new List<string>());
using ClipFrame clipFrame = ClipFrame.NoClip();
var driver = new WalkFrameDriver(fx.Dispatcher, leaf, worldData, clipFrame: clipFrame);
var walk = new RetailFrameWalk();
driver.Collect(
walk, cell1.CellId, cell1, landscape, ctx, Matrix4x4.Identity, Vector3.Zero);
driver.Collect(
walk, cell1.CellId, cell1, landscape, ctx, Matrix4x4.Identity, Vector3.Zero);
AssertNoTranscriptLines(capture);
}
finally
{
Console.SetOut(originalOut);
RenderingDiagnostics.DumpWalkTranscriptEnabled = previous;
}
}
/// <summary>
/// The ten transcript line-kind prefixes <see cref="WalkTranscriptDump"/>
/// ever emits — <c>F </c>/<c>P </c>/<c>LS</c>/<c>LC </c>/<c>SC </c>/
/// <c>BLD </c>/<c>DI </c>/<c>DC </c>/<c>EC </c>/<c>OC </c> — matching
/// each print method's own exact <c>Console.WriteLine</c> format
/// (S3 chunk 1 fix round 1, G1).
/// </summary>
private static readonly string[] TranscriptLinePrefixes =
["F ", "P ", "LS", "LC ", "SC ", "BLD ", "DI ", "DC ", "EC ", "OC "];
/// <summary>
/// Asserts NONE of <see cref="WalkTranscriptDump"/>'s own line kinds
/// appear in <paramref name="capture"/> — robust to unrelated
/// <see cref="Console"/> noise from another test class running in
/// parallel (a real, observed hazard: xUnit runs distinct classes
/// concurrently by default, and <see cref="Console.Out"/> is a
/// process-wide static — see <c>CameraDiagnosticsCollection</c>'s own
/// doc comment, issue #251). A plain <c>Assert.Empty(capture.ToString())</c>
/// is NOT this robust: an unrelated class's unconditional
/// <c>Console.WriteLine</c> can land inside this test's redirect window
/// and fail it for a reason that has nothing to do with the transcript
/// flag. <see cref="WalkOracleTrace.Parse"/> only ever appends a frame
/// once it sees a well-formed <c>F &lt;n&gt;</c> marker line — arbitrary
/// unrelated text can never satisfy that regex, so a truly EMPTY parsed
/// frame list is exactly as strong a proof that THIS code printed
/// nothing, without being sensitive to what else shares the process
/// console. S3 chunk 1 fix round 1 (G1) adds a second, independent
/// check: no captured line starts with any of the ten transcript
/// prefixes — those exact tokens never begin an unrelated test class's
/// own console noise, so this stays robust to the SAME parallel hazard
/// while also catching a leak the parser's own drop-last-frame quirk
/// could otherwise hide (a caller that (mistakenly) drives only ONE
/// frame).
/// </summary>
private static void AssertNoTranscriptLines(StringWriter capture)
{
string[] lines = capture.ToString()
.Split('\n', StringSplitOptions.RemoveEmptyEntries)
.Select(l => l.TrimEnd('\r'))
.ToArray();
Assert.Empty(WalkOracleTrace.Parse(lines));
Assert.DoesNotContain(
lines,
l => Array.Exists(
TranscriptLinePrefixes, p => l.StartsWith(p, StringComparison.Ordinal)));
}
/// <summary>T2 (§11.3): flag on, one synthetic interior frame — the
/// printed lines parse with the extended <see cref="WalkOracleTrace"/>
/// into the same events the driver recorded (emitter/parser round
/// trip). Asserted structurally (kinds, cell ids, counts) rather than
/// against a hand-predicted exact ordering, so the test does not
/// silently pin an assumption about traversal order it never
/// independently verified.</summary>
[Fact]
public void Collect_TranscriptFlagOn_PrintedLinesRoundTripThroughTheParser()
{
bool previous = RenderingDiagnostics.DumpWalkTranscriptEnabled;
RenderingDiagnostics.DumpWalkTranscriptEnabled = true;
TextWriter originalOut = Console.Out;
var capture = new StringWriter();
try
{
Console.SetOut(capture);
using var fx = new DispatcherFixture();
var ctx = new TestContext();
(WalkCell cell1, WalkCell cell2) = BuildTranscriptFixtureCells(ctx);
var landscape = new WalkLandscape { MidWidth = 1, Blocks = new WalkLandBlock?[1] };
var worldData = new FakeWorldData();
var leaf = new RecordingLeafRenderer(new List<string>());
using ClipFrame clipFrame = ClipFrame.NoClip();
var driver = new WalkFrameDriver(fx.Dispatcher, leaf, worldData, clipFrame: clipFrame);
var walk = new RetailFrameWalk();
// Two Collect calls: WalkOracleTrace.Parse (like every real
// capture) only flushes a frame once the NEXT "F n" marker
// appears — it deliberately drops the final in-progress frame
// (the detach-frame rule). One frame alone would parse to zero
// complete frames.
driver.Collect(
walk, cell1.CellId, cell1, landscape, ctx, Matrix4x4.Identity, Vector3.Zero);
driver.Collect(
walk, cell1.CellId, cell1, landscape, ctx, Matrix4x4.Identity, Vector3.Zero);
Console.Out.Flush();
string[] lines = capture.ToString()
.Split('\n', StringSplitOptions.RemoveEmptyEntries)
.Select(l => l.TrimEnd('\r'))
.ToArray();
Assert.NotEmpty(lines);
IReadOnlyList<WalkOracleFrame> frames = WalkOracleTrace.Parse(lines);
WalkOracleFrame frame = Assert.Single(frames);
Assert.Equal(1, frame.Number);
Assert.NotNull(frame.Pose);
Assert.Equal(cell1.CellId, frame.Pose!.CellId);
Assert.Equal(cell1.CellId, frame.InteriorRootCell);
Assert.True(frame.HasLandscape);
WalkOracleEvent dc = Assert.Single(
frame.Events, e => e.Kind == WalkOracleEventKind.DrawCells);
Assert.Equal(1, dc.OutsideViewCount);
Assert.Equal(
new HashSet<uint> { cell1.CellId, cell2.CellId },
dc.Cells.ToHashSet());
// The interior root's own flood visits both cells for a shell
// AND an object-list turn — EC/OC pair, one line per cell, no
// dedupe (this is the interior root's OWN flood — the ONE case
// WalkTranscriptDump.PrintEnvCellShell's own doc comment (G9)
// still guarantees EC==OC count for; a look-in flood matches
// too, but G8's per-frame trailing weather OC does not, and
// never fires here since this fixture's landscape has zero
// blocks/buildings).
List<uint> ec = frame.Events
.Where(e => e.Kind == WalkOracleEventKind.EnvCellShell)
.Select(e => e.CellId!.Value).ToList();
List<uint> oc = frame.Events
.Where(e => e.Kind == WalkOracleEventKind.ObjectCellTurn)
.Select(e => e.CellId!.Value).ToList();
Assert.Equal(new HashSet<uint> { cell1.CellId, cell2.CellId }, ec.ToHashSet());
Assert.Equal(new HashSet<uint> { cell1.CellId, cell2.CellId }, oc.ToHashSet());
Assert.Equal(2, ec.Count);
Assert.Equal(2, oc.Count);
// No landblocks were published (the stub 1x1 landscape), so no
// LC/SC/BLD turns exist this frame — the transcript format and
// relative order for those three kinds is proven by
// OutdoorRoot_TranscriptFlagOn_PrintsLandscapeThenLandCellThenSortCellThenBuilding_InOrder
// below (S3 chunk 1 fix round 1, G3), and separately against
// real retail data by WalkOracleTraceTests.AllFixtures and
// WalkLandCellOrderTests.
Assert.DoesNotContain(frame.Events, e => e.Kind
is WalkOracleEventKind.LandCell or WalkOracleEventKind.SortCell
or WalkOracleEventKind.Building);
}
finally
{
Console.SetOut(originalOut);
RenderingDiagnostics.DumpWalkTranscriptEnabled = previous;
}
}
/// <summary>S3 chunk 1 fix round 1 (G3): the outdoor-root half T2 never
/// covered — the flag-on round trip above uses a stub 1x1 landscape with
/// zero blocks, so LC/SC/BLD never fire at all. Drives
/// <see cref="RetailFrameWalk.DrawLandscape"/> directly with the SAME
/// deterministic "CY-only" <see cref="OneDegenerateView"/> admission
/// technique <c>OutdoorRoot_LandCellPrecedesItsOwnCellsObjectTurn_ThenFlushesAtReplaysEnd</c>
/// uses (a real <see cref="RetailFrameWalk.WalkFrame"/> root's default
/// full-viewport view depends on this suite's synthetic ray caster,
/// which does not model screen geometry faithfully enough to predict
/// block/cell admission from — see that test's own doc comment), with a
/// building attached to the one admitted cell so BLD fires too. Checked
/// against the RAW captured lines directly (not
/// <see cref="WalkOracleTrace.Parse"/>) — this test only needs kind
/// presence and relative order, not a complete F/P-framed capture.
/// MUTATION CHECK: deleting <c>WalkTranscriptDump.PrintLandCell</c>'s
/// call site (or its body) makes the "expected an LC line" assertion
/// fail.</summary>
[Fact]
public void OutdoorRoot_TranscriptFlagOn_PrintsLandscapeThenLandCellThenSortCellThenBuilding_InOrder()
{
bool previous = RenderingDiagnostics.DumpWalkTranscriptEnabled;
RenderingDiagnostics.DumpWalkTranscriptEnabled = true;
TextWriter originalOut = Console.Out;
var capture = new StringWriter();
try
{
Console.SetOut(capture);
using var fx = new DispatcherFixture();
var leaf = new RecordingLeafRenderer(new List<string>());
leaf.CellsWithoutEmitters.UnionWith(CoarseLandscapeBuckets(0xF4180000u));
var ctx = new TestContext();
var driver = new WalkFrameDriver(fx.Dispatcher, leaf, new FakeWorldData());
var walk = new RetailFrameWalk();
var landscape = new WalkLandscape { MidWidth = 1, Blocks = new WalkLandBlock?[1] };
var block = new WalkLandBlock
{
LandblockId = 0xF4180000u, SideCellCount = 1, MaxZ = 10f, MinZ = 0f,
};
block.EnsureCellArrays();
block.CellBuildings[0] = new WalkBuilding { PositionCellId = 0xF4180000u };
landscape.Blocks[0] = block;
driver.BeginFrame(ctx, Matrix4x4.Identity, Vector3.Zero);
walk.DrawLandscape(landscape, OneDegenerateView(), ctx, driver);
driver.EndFrame();
Console.Out.Flush();
string[] lines = capture.ToString()
.Split('\n', StringSplitOptions.RemoveEmptyEntries)
.Select(l => l.TrimEnd('\r'))
.ToArray();
int lsIndex = Array.IndexOf(lines, "LS");
int lcIndex = Array.FindIndex(
lines, l => l.StartsWith("LC ", StringComparison.Ordinal));
int scIndex = Array.FindIndex(
lines, l => l.StartsWith("SC ", StringComparison.Ordinal));
int bldIndex = Array.FindIndex(
lines, l => l.StartsWith("BLD ", StringComparison.Ordinal));
Assert.True(lsIndex >= 0, "expected an LS line");
Assert.True(lcIndex >= 0, "expected an LC line");
Assert.True(scIndex >= 0, "expected an SC line");
Assert.True(bldIndex >= 0, "expected a BLD line");
Assert.True(lsIndex < lcIndex, "LS must precede LC");
Assert.True(
lcIndex < scIndex,
"LC must precede SC — RenderDeviceD3D::DrawLandCell before DrawSortCell");
Assert.True(
scIndex < bldIndex,
"SC must precede BLD — S3 chunk 1 fix round 1 (G7): DrawSortCell entry, "
+ "before its own DrawBuilding call");
Assert.Equal("LC f4180001", lines[lcIndex]);
Assert.Equal("SC f4180001", lines[scIndex]);
Assert.Equal("BLD f4180000", lines[bldIndex]);
}
finally
{
Console.SetOut(originalOut);
RenderingDiagnostics.DumpWalkTranscriptEnabled = previous;
}
}
/// <summary>§11.2 B4: the offline signature diff, self-checked over a
/// synthetic pair — the SAME captured T2 transcript versus itself with
/// its own LAST event removed. Reuses <c>Collect_TranscriptFlagOn_…</c>'s
/// own capture rather than duplicating the walk drive.</summary>
[Fact]
public void SignatureDiff_ReportsTheExactRemovedEvent()
{
bool previous = RenderingDiagnostics.DumpWalkTranscriptEnabled;
RenderingDiagnostics.DumpWalkTranscriptEnabled = true;
TextWriter originalOut = Console.Out;
var capture = new StringWriter();
try
{
Console.SetOut(capture);
using var fx = new DispatcherFixture();
var ctx = new TestContext();
(WalkCell cell1, _) = BuildTranscriptFixtureCells(ctx);
var landscape = new WalkLandscape { MidWidth = 1, Blocks = new WalkLandBlock?[1] };
var worldData = new FakeWorldData();
var leaf = new RecordingLeafRenderer(new List<string>());
using ClipFrame clipFrame = ClipFrame.NoClip();
var driver = new WalkFrameDriver(fx.Dispatcher, leaf, worldData, clipFrame: clipFrame);
var walk = new RetailFrameWalk();
// Two Collect calls — see the round-trip test's own comment on
// why one alone parses to zero complete frames.
driver.Collect(
walk, cell1.CellId, cell1, landscape, ctx, Matrix4x4.Identity, Vector3.Zero);
driver.Collect(
walk, cell1.CellId, cell1, landscape, ctx, Matrix4x4.Identity, Vector3.Zero);
Console.Out.Flush();
string[] lines = capture.ToString()
.Split('\n', StringSplitOptions.RemoveEmptyEntries)
.Select(l => l.TrimEnd('\r'))
.ToArray();
IReadOnlyList<WalkOracleFrame> expected = WalkOracleTrace.Parse(lines);
WalkOracleFrame expectedFrame = Assert.Single(expected);
Assert.NotEmpty(expectedFrame.Events);
// Self-vs-self (unmutated copy): no divergence at all.
IReadOnlyList<WalkOracleFrame> unchanged =
[expectedFrame with { Events = expectedFrame.Events.ToList() }];
Assert.Null(WalkTranscriptSignatureDiff.FirstDivergence(expected, unchanged));
// Self-vs-self-minus-one (the LAST event removed): the reported
// divergence is exact — same frame number, position exactly at
// the new (shorter) end, expected = the removed event's own
// signature string, actual = "<end of frame>".
int lastIndex = expectedFrame.Events.Count - 1;
List<WalkOracleEvent> truncated = expectedFrame.Events.Take(lastIndex).ToList();
IReadOnlyList<WalkOracleFrame> actual =
[expectedFrame with { Events = truncated }];
WalkTranscriptSignatureDiff.Divergence? divergence =
WalkTranscriptSignatureDiff.FirstDivergence(expected, actual);
Assert.NotNull(divergence);
Assert.Equal(expectedFrame.Number, divergence!.Value.FrameNumber);
Assert.Equal(lastIndex, divergence.Value.Position);
Assert.Equal("<end of frame>", divergence.Value.Actual);
Assert.NotEqual("<end of frame>", divergence.Value.Expected);
}
finally
{
Console.SetOut(originalOut);
RenderingDiagnostics.DumpWalkTranscriptEnabled = previous;
}
}
/// <summary>
/// Campaign OVERHAUL S3 chunk 4 (O1/O2): a REAL once-per-frame pin for
/// the weather "OC" transcript line, observed through the printed
/// lines produced by driving the actual <see cref="RetailFrameWalk"/> +
/// <see cref="WalkFrameDriver"/> pair — not IL offsets (the ledger's
/// own finding: an IL-offset "call order" check cannot fail when the
/// call is moved back inside a loop the reviewers can reproduce with the
/// suite green). An outdoor root's <c>DrawLandscape</c> is the LAST
/// thing <see cref="RetailFrameWalk.WalkFrame"/> does, so retail's
/// <c>GameSky::Draw(sky,1)</c> turn — and therefore this "OC" line — is
/// the frame's last event.
/// <para>
/// MUTATION (a, print twice): if <c>IWalkEventSink.OnWeatherTurn</c>
/// fired twice (e.g. a regression reintroducing a per-slice loop), the
/// <c>Assert.Single</c> below would see 2 matches and fail with
/// "Collection contained more than one match".
/// </para>
/// <para>
/// MUTATION (b, wrong cell): the print always carries whatever <see
/// cref="IRetailFrameWalkContext.ViewerCellId"/> the context supplies
/// (production wires <c>RetailPViewFrameInput.ViewerCellId</c>, never
/// <c>PlayerCellId</c> — see <c>WalkProductionFrameContext</c>'s ctor/
/// <c>Reset</c> call sites in <c>RetailPViewRenderer.DrawInside</c>); if
/// production started wiring <c>PlayerCellId</c> instead, this test's
/// own <see cref="TestContext.ViewerCellId"/> would still print
/// correctly (the walk layer only ever sees the ONE value it is
/// handed) — the <c>Assert.Equal(ctx.ViewerCellId, ...)</c> below pins
/// that the walk layer's own plumbing never substitutes a different
/// value of its own.
/// </para>
/// <para>
/// MUTATION (c, wrong time): if the print instead fired lazily at
/// Replay time (as it used to, from <c>DrawWeatherOnce</c>) this test —
/// which only calls <see cref="WalkFrameDriver.Collect"/>, never
/// <see cref="WalkFrameDriver.Replay"/> — would show ZERO
/// <see cref="WalkOracleEventKind.ObjectCellTurn"/> events, and
/// <c>Assert.Single</c> would fail with "no match" instead of finding
/// exactly one.
/// </para>
/// </summary>
[Fact]
public void Collect_OutdoorRoot_WeatherGateOpen_PrintsExactlyOneOcAtFrameEndMatchingTheViewerCell()
{
bool previous = RenderingDiagnostics.DumpWalkTranscriptEnabled;
RenderingDiagnostics.DumpWalkTranscriptEnabled = true;
TextWriter originalOut = Console.Out;
var capture = new StringWriter();
try
{
Console.SetOut(capture);
using var fx = new DispatcherFixture();
const uint viewerCellId = 0xF4180003u; // low word < 0x100 -> outdoor root
var ctx = new TestContext { WeatherGateOpen = true, ViewerCellId = viewerCellId };
var landscape = new WalkLandscape { MidWidth = 1, Blocks = new WalkLandBlock?[1] };
var leaf = new RecordingLeafRenderer(new List<string>());
var driver = new WalkFrameDriver(fx.Dispatcher, leaf, new FakeWorldData());
var walk = new RetailFrameWalk();
// Two Collect calls — WalkOracleTrace.Parse (like every real
// capture) discards the final in-progress frame.
driver.Collect(
walk, viewerCellId, null, landscape, ctx, Matrix4x4.Identity, Vector3.Zero);
driver.Collect(
walk, viewerCellId, null, landscape, ctx, Matrix4x4.Identity, Vector3.Zero);
Console.Out.Flush();
string[] lines = capture.ToString()
.Split('\n', StringSplitOptions.RemoveEmptyEntries)
.Select(l => l.TrimEnd('\r'))
.ToArray();
IReadOnlyList<WalkOracleFrame> frames = WalkOracleTrace.Parse(lines);
WalkOracleFrame frame = Assert.Single(frames);
Assert.NotNull(frame.Pose);
Assert.Equal(viewerCellId, frame.Pose!.CellId);
WalkOracleEvent oc = Assert.Single(
frame.Events, e => e.Kind == WalkOracleEventKind.ObjectCellTurn);
Assert.Equal(viewerCellId, oc.CellId);
Assert.Same(frame.Events[frame.Events.Count - 1], oc);
}
finally
{
Console.SetOut(originalOut);
RenderingDiagnostics.DumpWalkTranscriptEnabled = previous;
}
}
/// <summary>
/// Campaign OVERHAUL S3 chunk 4 (O1/O2/O3): the interior-root sibling of
/// <see cref="Collect_OutdoorRoot_WeatherGateOpen_PrintsExactlyOneOcAtFrameEndMatchingTheViewerCell"/> —
/// retail's <c>GameSky::Draw(sky,1)</c> runs at the END of <c>LScape::
/// draw</c>, which an interior root calls from <c>DrawInside</c> BEFORE
/// <c>OnInteriorFloodDrawTurn</c> draws the flood's own cells (first the
/// reverse EnvCell-shell loop, then the reverse object-list loop) — so
/// the weather "OC" line must sit strictly between the "LS" line and the
/// flood's FIRST "EC" line, never mixed in among the flood's own EC/OC
/// pairs (which fire after it, per-cell, from
/// <c>WalkFrameDriver.EmitFloodTurns</c>).
/// <para>
/// MUTATION (a, print twice): a second <c>OnWeatherTurn</c> firing would
/// add a second <see cref="WalkOracleEventKind.ObjectCellTurn"/> to the
/// LS-to-first-EC window, failing <c>Assert.Single</c> on the filtered
/// list below.
/// </para>
/// <para>
/// MUTATION (c, wrong time — the ledger's actual round-1 regression):
/// the OLD call site printed the weather OC from the Replay-time
/// <c>LandscapeFlush</c> leaf, i.e. at the FRAME'S END (after every
/// flood EC/OC), not between the landscape and the flood. Moving the
/// print there again would move it OUT of the LS-to-first-EC window,
/// making the windowed <c>Assert.Single</c> below find zero matches
/// instead of one.
/// </para>
/// </summary>
[Fact]
public void Collect_InteriorRoot_WeatherGateOpen_PrintsOneOcBetweenLandscapeAndTheFloodsFirstShell()
{
bool previous = RenderingDiagnostics.DumpWalkTranscriptEnabled;
RenderingDiagnostics.DumpWalkTranscriptEnabled = true;
TextWriter originalOut = Console.Out;
var capture = new StringWriter();
try
{
Console.SetOut(capture);
using var fx = new DispatcherFixture();
var ctx = new TestContext();
(WalkCell cell1, WalkCell cell2) = BuildTranscriptFixtureCells(ctx);
// Production always hands the walk the SAME cell id as both the
// Collect root and the "P" line's own cell (ctx.ViewerCellId ==
// cameraCellId at the real call site).
ctx.ViewerCellId = cell1.CellId;
ctx.WeatherGateOpen = true;
var landscape = new WalkLandscape { MidWidth = 1, Blocks = new WalkLandBlock?[1] };
var worldData = new FakeWorldData();
var leaf = new RecordingLeafRenderer(new List<string>());
using ClipFrame clipFrame = ClipFrame.NoClip();
var driver = new WalkFrameDriver(fx.Dispatcher, leaf, worldData, clipFrame: clipFrame);
var walk = new RetailFrameWalk();
driver.Collect(
walk, cell1.CellId, cell1, landscape, ctx, Matrix4x4.Identity, Vector3.Zero);
driver.Collect(
walk, cell1.CellId, cell1, landscape, ctx, Matrix4x4.Identity, Vector3.Zero);
Console.Out.Flush();
string[] lines = capture.ToString()
.Split('\n', StringSplitOptions.RemoveEmptyEntries)
.Select(l => l.TrimEnd('\r'))
.ToArray();
IReadOnlyList<WalkOracleFrame> frames = WalkOracleTrace.Parse(lines);
WalkOracleFrame frame = Assert.Single(frames);
List<WalkOracleEvent> events = frame.Events.ToList();
int landscapeIndex = events.FindIndex(e => e.Kind == WalkOracleEventKind.Landscape);
int firstShellIndex = events.FindIndex(e => e.Kind == WalkOracleEventKind.EnvCellShell);
Assert.True(landscapeIndex >= 0, "expected an LS line (ov=1 from the fixture's exit portal)");
Assert.True(firstShellIndex >= 0, "expected the flood's own EC lines");
Assert.True(
landscapeIndex < firstShellIndex,
"the landscape turn must precede the flood's own shells");
List<WalkOracleEvent> ocInWindow = events
.Where((e, i) => e.Kind == WalkOracleEventKind.ObjectCellTurn
&& i > landscapeIndex && i < firstShellIndex)
.ToList();
WalkOracleEvent weatherOc = Assert.Single(ocInWindow);
Assert.Equal(cell1.CellId, weatherOc.CellId);
// The flood's OWN EC/OC pairs (cell1, cell2) still exist, all AFTER
// the window above — confirms the weather OC is not merely one of
// those, even though cell1's id happens to match ctx.ViewerCellId.
List<WalkOracleEvent> floodOc = events
.Where(e => e.Kind == WalkOracleEventKind.ObjectCellTurn)
.Skip(1) // drop the weather OC found above
.ToList();
Assert.Equal(
new HashSet<uint> { cell1.CellId, cell2.CellId },
floodOc.Select(e => e.CellId!.Value).ToHashSet());
}
finally
{
Console.SetOut(originalOut);
RenderingDiagnostics.DumpWalkTranscriptEnabled = previous;
}
}
/// <summary>
/// Campaign OVERHAUL S3 chunk 4 (O1): the gate's negative half — an
/// indoor player (or either render toggle off) never prints the weather
/// "OC" line at all, matching <see
/// cref="RetailPViewPassExecutor.ShouldDrawWeatherOnce"/>'s own pinned
/// predicate (<c>RetailPViewPassExecutorTests.
/// ShouldDrawWeatherOnce_MatchesRetailIsPlayerOutsideGate</c>) — this
/// test proves the WALK actually honors that gate rather than firing
/// unconditionally. MUTATION: dropping the <c>if (ctx.WeatherGateOpen)</c>
/// guard in <see cref="RetailFrameWalk.DrawLandscape"/> would print an
/// extra "OC" line at the frame's end, making <c>Assert.Same</c> below
/// see a DIFFERENT cell (the flood's own object turn is what would then
/// occupy that slot) or the event count assertion fail.
/// </summary>
[Fact]
public void Collect_WeatherGateClosed_NeverFiresTheWeatherTurn()
{
bool previous = RenderingDiagnostics.DumpWalkTranscriptEnabled;
RenderingDiagnostics.DumpWalkTranscriptEnabled = true;
TextWriter originalOut = Console.Out;
var capture = new StringWriter();
try
{
Console.SetOut(capture);
using var fx = new DispatcherFixture();
const uint viewerCellId = 0xF4180003u;
var ctx = new TestContext { WeatherGateOpen = false, ViewerCellId = viewerCellId };
var landscape = new WalkLandscape { MidWidth = 1, Blocks = new WalkLandBlock?[1] };
var leaf = new RecordingLeafRenderer(new List<string>());
var driver = new WalkFrameDriver(fx.Dispatcher, leaf, new FakeWorldData());
var walk = new RetailFrameWalk();
driver.Collect(
walk, viewerCellId, null, landscape, ctx, Matrix4x4.Identity, Vector3.Zero);
driver.Collect(
walk, viewerCellId, null, landscape, ctx, Matrix4x4.Identity, Vector3.Zero);
Console.Out.Flush();
string[] lines = capture.ToString()
.Split('\n', StringSplitOptions.RemoveEmptyEntries)
.Select(l => l.TrimEnd('\r'))
.ToArray();
IReadOnlyList<WalkOracleFrame> frames = WalkOracleTrace.Parse(lines);
WalkOracleFrame frame = Assert.Single(frames);
Assert.DoesNotContain(
frame.Events, e => e.Kind == WalkOracleEventKind.ObjectCellTurn);
}
finally
{
Console.SetOut(originalOut);
RenderingDiagnostics.DumpWalkTranscriptEnabled = previous;
}
}
}