acdream/tests/AcDream.App.Tests/Rendering/RetailPViewPassExecutorTests.cs
Erik 89c4494d05 fix(render): S3 review round 1 — punch fans clipped by their own view, seal pin through the producer, dead per-cell clip machinery deleted, EC/OC transcript exact
F1 (BLOCKING, punch-fan view alignment). ClipFrameAssembler.
ReassembleOutsideViewFromWalk now keeps exactly ONE outside-view slice per
walk view, index-aligned with RetailFrameWalk.DrawBuilding's own
ActiveViewIndex (retail building_view = portal_view_num @0x0059f3bf): a
collapsed view (ClipPlaneSet.IsNothingVisible — retail polyClipFinish under
a degenerate view leaves <3 vertices, 0x59BDBC-0x59BDBF) now appends its
own ClipViewSlice.NothingVisible=true slot instead of being skipped, which
used to shift every later view's slice down by one. DrawWalkPunchFan draws
NOTHING for a NothingVisible slice and throws ArgumentOutOfRangeException
when activeViewIndex is out of range (fail-loud; never draws unclipped).
New tests: WalkOutsideViewReassemblyTests.
FirstViewCollapses_SecondSurvives_SlicesStayIndexAligned and
PunchLeaf_UsesIndexAlignedSlice_DrawsNothingForCollapsed_ThrowsOutOfRange.
MUTATION M1 (revert the append-nothing-on-skip fix, restoring the old
skip): both new tests fail with
  Assert.Equal() Failure: Values differ
  Expected: 2
  Actual:   1
MUTATION M2 (restore the old ": default" unclipped fallback in
DrawWalkPunchFan): the punch-leaf test fails with
  Assert.Equal() Failure: Values differ
  Expected: 0
  Actual:   1
(both restored; verified clean afterward).

F2 (major, exit-seal pin through the real producer + both overflow cases).
The exit-seal CPU/GPU equivalence pin moved from ClipFrameLayoutTests (which
drove ClipFrame.AppendSlot(ClipPlaneSet) via the zero-caller
ClipFrameAssembler.Assemble) to the new
tests/.../Walk/WalkFrameDriverClipSealTests.cs, which drives
WalkFrameDriver.BeginFrame + the real IWalkEventSink.OnInteriorFloodDrawTurn
hook — the SAME path RetailPViewPassExecutor.DrawExitPortalMask resolves
through via WalkFrameDriver.InteriorFloodViewClipPlanesAt. The new pin
compares WalkFrameDriver's captured planes EXACTLY (not just geometrically)
against ClipPlaneSet.From's independent CCW-normalized-perpendicular
computation, since AppendClipSlot's per-edge formula is bit-identical to
ClipPlaneSet's own. Two overflow cases added: a 9-vertex portal view exit-seals
to exactly the 4 conservative AABB planes containing every source vertex
(over-include, never under-include — AppendClipSlot's fallback for a view too
complex for the 8-plane budget); a 9-vertex OUTSIDE view (the punch-fan
sibling, WalkOutsideViewReassemblyTests.
NineVertexOutsideView_PunchSliceHasZeroPlanes_DrawsUnclipped_NotNothingVisible)
produces a zero-plane, NOT-NothingVisible slice — draws fully unclipped,
distinct from F1's "draw nothing" state.
MUTATION M1 (flip the winding selection, `ccw = area2 < 0f`):
  Assert.Equal() Failure: Values are not within 4 decimal places
  Expected: 0.9191 (rounded from 0.919144988)
  Actual:   0.8944 (rounded from 0.89442724)
MUTATION M2 (drop the normalize from the per-edge plane formula):
  Assert.Equal() Failure: Values are not within 4 decimal places
  Expected: 0.9191 (rounded from 0.919144988)
  Actual:   0.7 (rounded from 0.700000048)
MUTATION M3 (delete the >8-plane AABB overflow branch — the per-edge loop
then indexes a fixed 8-plane stackalloc with a 9-edge polygon):
  System.IndexOutOfRangeException : Index was outside the bounds of the array.
    at WalkFrameDriver.AppendClipSlot(...) line 1915
    at WalkFrameDriver.CaptureViews(...) -> CaptureCellViews -> CaptureCellViewRoute
    -> EmitFloodTurns -> IWalkEventSink.OnInteriorFloodDrawTurn
(all three restored; verified clean afterward).

F3 (minor/major). RetailPViewPassExecutor.cs's false bracket comment
(neither KEEP clip — exit seals nor punch fans — ever called Enable/
DisableClipDistances; the six DisableClipDistances() call sites all sat
around the UNCLIPPED particle/weather draws, whose bodies are no-ops on the
only backend) is rewritten to the truth; the six no-op calls and the
private DisableClipDistances() wrapper are deleted.
IWorldPassSurface.EnableClipDistances/DisableClipDistances stay on the
INTERFACE — WorldScenePassExecutor (the separate flat-world path) still
calls them.

F4a (EC transcript exactness). WalkFrameDriver.EmitFloodTurns now prints one
"EC" line per LIVE VIEW of the flood cell (retail's setup_view/DrawEnvCell
loop, PView::DrawCells @0x005a4ab1-0x005a4acc, fires once per live portal_view
slice, not once per cell) via the captured route's own SliceCount — read
directly off _lookInTurns[viewRouteIndex] rather than through
InteriorFloodViewSliceCountAt's flood-index indirection, because this same
loop also serves a building's LOOK-IN flood (WalkDrawStage.LookInStatic),
whose cells never populate InteriorFloodCells (the interior ROOT flood only)
— indexing through that accessor threw ArgumentOutOfRangeException on a real
look-in turn during verification, fixed before landing. The actual CellShell
DRAW event is unchanged (the frame stamp still dedupes it to one submission).

F4b (transcript exactness, trailing weather OC).
WalkTraceConformanceTests.Recorder now implements IWalkEventSink.
OnWeatherTurn (the interface's silent no-op default previously left the
replay side of Signature8 blind to the weather turn's own "OC" line), and
WalkTraceReplayContext.Signature8(WalkOracleFrame) no longer excludes the
trailing per-frame weather ObjectCellTurn — every ObjectCellTurn now reads
literally, on both sides. Implementing OnWeatherTurn alone left
Still_fixture_first_frame_reproduces_exactly(terrace-edge.walk) diverging
ONLY on the newly-un-excluded trailing OC (EXPECTED ...OC:f4180104|
OC:f418000b vs ACTUAL ...OC:f4180104 — a length-12 tail, nothing else
differs across a 16.6k-char signature) because
WalkTraceReplayContext.WeatherGateOpen had always defaulted to false (the
harness never needed to fire the walk's weather hook before). Per the
review round's own instruction not to weaken the pin, the harness itself is
fixed instead: WalkTraceReplayContext now implements ViewerCellId (= the
harness's own WalkFrame cameraCellId argument, matching production's
RetailPViewFrameInput.ViewerCellId invariant) and WeatherGateOpen
(retail's own SmartBox::is_player_outside gate, (cellId & 0xFFFF) < 0x100,
with no App-level render-toggle concept to AND against). MUTATION (delete
Recorder.OnWeatherTurn): Still_fixture_first_frame_reproduces_exactly
(terrace-edge.walk) fails with EXPECTED ending "...OC:f4180104|OC:f418000b"
vs ACTUAL ending "...OC:f4180104" (the trailing OC missing) — restored;
verified clean afterward.
Fixture-row status (InstalledDat lane, WalkTraceConformanceTests, 14 rows):
13 pass, 1 fails — Oh_doorway_still_first_frame_diff, the PRE-EXISTING
[Trait("Status","KnownFailure")] #458 row (a documented block-plane
precision boundary at token index 165, LC/SC content, upstream of any
EC/OC — unaffected by and unrelated to this round's OC change). Every OTHER
row, including every eight-kind (Signature8) row that now compares the
weather OC literally, passes exactly.

F5 (major, dead per-cell clip machinery). WbDrawDispatcher.SetClipRouting
(the only writer of the per-instance routing-active flag) had ZERO
production callers; EnvCellRenderer.SetClipRouting was called only with
null (WorldScenePassExecutor's BeginFrame/AbortFrame) — no path could ever
arm the per-cell clip-region table, on the walk path OR the flat path (the
S3 landing-hygiene AD-17 correction's "live for the flat path" clause is
itself corrected here: dormant in every path, deleted). Deleted:
WbDrawDispatcher's SetClipRouting/ClearClipRouting and their four backing
fields; EnvCellRenderer's SetClipRouting and its backing field (its
RenderModernMDIInternal write now unconditionally clears instanceClipSlot);
the ProbeClipRouteEnabled-gated per-frame [clip-route-disp] dispatch probe
and its three backing fields (the OTHER two ACDREAM_PROBE_CLIPROUTE
producers, [clip-route] and [clip-route-scis] in WorldRenderDiagnostics.cs,
are untouched — a different, still-live mechanism); ResolveEntitySlot,
IsIndoorCellId and the ClipSlotCull sentinel; the ACDREAM_CLIP_DEBUG probe
(RenderingDiagnostics.ClipDebugNoShellTrim) and its docs/launch-options.md
row, since the "clip shells" branch it toggled between is gone.
ResolveSlotForFrame is simplified to a parameterless
`(uint Slot, bool Culled) ResolveSlotForFrame() => (0u, false)` per the
review's own framing ("it becomes slot 0, never culled") rather than
deleted outright, since its callers (WbDrawDispatcher.cs and
WbDrawDispatcher.WalkClassify.cs) still want the same two-value shape.
tests/.../Wb/WbDrawDispatcherClipSlotTests.cs (12 tests, all exercising the
deleted routing arm via ResolveEntitySlot/the active ResolveSlotForFrame
branch) is replaced with one pin on the new parameterless behavior.
Shaders: mesh_modern.vert, mesh_atmospheric.vert, mesh_detail.vert,
particle.vert and particle_mesh.vert each lose their CellClip struct +
binding=2 ClipRegionBuf fetch, gl_ClipDistance write loop, and gl_PerVertex
redeclaration (portal_depth.vert's own, separate gl_PerVertex/gl_ClipDistance
pair — the KEEP mechanism, its planes handed through the TerrainClip UBO at
binding=2 in the UBO namespace — is untouched; verified it never read the
deleted SSBO). Recompiled via tools/compile-shaders.ps1 (glslc backend
detected; managed shaderc path ran); 24/24 pairs compiled. Re-pinned in
VulkanShaderManifestTests.RetailOracleSpirvSha256 (the only three of the
five edited shaders that carry a byte-exact retail-oracle pin):
  mesh_modern.vert.spv    = f9ed4ee7140ccd136130559dbea68545f733f6022f52b085b6df4bcb787223c6
  particle.vert.spv       = 95ce6ecf834930a92da5c5fe9aef513b38b5ba104704b98c1606af71fe17eaf3
  particle_mesh.vert.spv  = 043482b97c2ed036511692f89c75a0a6c298aba48cb519e5e3aff7fe7ba6371b
(mesh_atmospheric.vert.spv / mesh_detail.vert.spv changed too but carry no
retail-oracle pin, so no re-pin was needed for them.)
tests/.../ParticleBindlessInstanceTests.cs's
Assert.Contains("clipRegions[aClipSlot]", vertex) — a real breakage the
grep sweep caught — is deleted with a note; the surrounding aClipSlot
attribute-declaration assertion stays.
Vertex-layout residue kept THIS round, fed 0 by the CPU, unread by any
shader (S5's instance-buffer-layout revisit): instanceClipSlot[] (binding=3
SSBO) in mesh_modern.vert/mesh_atmospheric.vert/mesh_detail.vert; aClipSlot
(a per-vertex attribute) in particle.vert/particle_mesh.vert.
Grep sweep — "SetClipRouting|_clipRoutingActive|ClearClipRouting|
BeginScissor|NdcScissorRect|SetTerrainClip|BindTerrainClip|TerrainBytes|
ScissorNdcAabb|UseScissorFallback" over src/ and tests/: EMPTY (also swept
and rewrote several PRE-EXISTING prose mentions of BeginScissor/
ClearClipRouting in RetailPViewPassExecutor.cs, RetailPViewRenderer.cs,
WorldPassSurface.cs and RetailPViewPassExecutorTests.cs that predate this
commit's own work, purely to satisfy the literal sweep). "clipRegions|
instanceClipSlot|aClipSlot": every remaining match is the vertex-layout
residue named above, or a comment/assertion describing that same residue.

Deviations from a fully literal reading of F5 (recorded here since none of
them cross the mandatory automated gates, all of which pass):
- The C#-side ClipRegions SSBO publish/bind pipeline
  (RhiWorldPassSurface.PrepareClipFrame's publish, WorldFrameSections.
  ClipRegions, and ClipFrame's internal std430 byte-packing —
  RegionBytes/RegionBytesForTest/CellClipStrideBytes) is NOT deleted this
  round; it is left as documented dead residue (verified zero shader
  readers) rather than rewritten. The mandatory grep sweep targets the
  shader-source identifier `clipRegions` (lowercase), which is fully clean;
  the C# publish machinery sits behind ~8 files (ClipFrame.cs,
  WorldPassSurface.cs, WbDrawDispatcher.Rhi.cs, WorldPassScope.cs,
  GpuBindingModel.cs, EnvCellRenderer.Rhi.cs, ParticleRenderer.Rhi.cs,
  WbDrawDispatcher.OrderedStream.cs, plus the ClipFrameLayoutTests std430
  pins) and CellClipPlanesOffset/MaxPlanes are the SAME constants
  portal_depth.vert's KEEP mechanism uses for its own, unrelated UBO layout
  — a full rewrite carried materially higher regression risk than this
  round's mandatory scope justified. ClipFrame.cs and WorldPassSurface.cs
  now both name this residue explicitly in their doc comments for a future
  round to finish.
- SetClipRegionSsbo/_sharedClipRegionSsbo in WbDrawDispatcher.cs (a
  pre-existing, already fully dead GL-era write-only field/method with zero
  callers, unrelated to per-instance clip-slot routing) is left untouched —
  outside F5's named scope and outside the mandatory grep sweep.
- WalkTraceReplayContext.ViewerCellId/WeatherGateOpen (F4b, detailed above)
  is an addition beyond the literal §12 text, made to keep the InstalledDat
  lane at exactly the four established known failures without weakening
  the newly-un-excluded OC pin.

Gates: dotnet build tests/AcDream.App.Tests/AcDream.App.Tests.csproj -c
Release and dotnet build src/AcDream.App/AcDream.App.csproj -c Release both
0 warnings/0 errors; dotnet build AcDream.slnx -c Release 0 warnings/0
errors; hermetic lane 6815/6815 passed; InstalledDat lane 243 passed / 4
failed / 1 skipped (exactly the four known failures: two #383 layout tests,
TowerAscentReplayTests, Oh_doorway_still_first_frame_diff #458);
VulkanShaderDescriptorContractTests/VulkanShaderManifestTests/
RenderPackSpirvValidatorTests/SkyVertexLayoutTests 34/34 passed.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-03 20:15:29 +02:00

432 lines
22 KiB
C#

using System.Reflection;
using System.Reflection.Emit;
using AcDream.App.Composition;
using AcDream.App.Rendering;
using AcDream.App.Rendering.Sky;
using AcDream.App.Rendering.Walk;
using AcDream.App.Tests.Architecture;
namespace AcDream.App.Tests.Rendering;
public sealed class RetailPViewPassExecutorTests
{
[Fact]
public void Extracted_contracts_retain_no_window_callbacks_or_visibility_owner()
{
Assert.DoesNotContain(
typeof(RetailPViewFrameInput).GetProperties(),
property => typeof(Delegate).IsAssignableFrom(property.PropertyType));
FieldInfo[] fields = typeof(RetailPViewPassExecutor).GetFields(
BindingFlags.Instance | BindingFlags.NonPublic);
Assert.DoesNotContain(fields, field => field.FieldType == typeof(GameWindow));
Assert.DoesNotContain(fields, field => field.FieldType == typeof(CellVisibility));
Assert.DoesNotContain(fields, field => field.FieldType == typeof(RetailPViewFrameInput));
Assert.DoesNotContain(fields, field => field.FieldType == typeof(RetailPViewFrameResult));
Assert.DoesNotContain(fields, field => field.FieldType == typeof(ClipFrameAssembly));
Assert.DoesNotContain(
fields,
field => typeof(Delegate).IsAssignableFrom(field.FieldType));
}
[Fact]
public void Concrete_executor_accumulates_walk_terrain_batch_timing()
{
// S3 chunk 3 fix round 1 (F3): the walk leaf no longer brackets
// itself with Begin()/Complete() (that stopwatch-restart pair would
// push one timing SAMPLE per batch, not one per frame) — it times
// itself with a raw Stopwatch.GetTimestamp() delta and hands the
// elapsed ticks to AccumulateWalkBatch, which only accumulates.
MethodInfo landscape = typeof(RetailPViewPassExecutor).GetMethod(
"DrawWalkLandCellBatch",
BindingFlags.Instance | BindingFlags.NonPublic)!;
IReadOnlyList<CompiledCall> landscapeCalls = CompiledCallGraph.Read(landscape);
int terrainDraw = RequiredCallIndex(
landscapeCalls,
typeof(TerrainModernRenderer),
nameof(TerrainModernRenderer.DrawLandCells));
int accumulate = RequiredCallIndex(
landscapeCalls,
typeof(TerrainDrawDiagnosticsController),
nameof(TerrainDrawDiagnosticsController.AccumulateWalkBatch));
Assert.True(terrainDraw < accumulate);
Assert.DoesNotContain(
landscapeCalls,
call => call.Target.DeclaringType == typeof(TerrainDrawDiagnosticsController)
&& call.Target.Name == nameof(TerrainDrawDiagnosticsController.Begin));
Assert.DoesNotContain(
landscapeCalls,
call => call.Target.DeclaringType == typeof(TerrainDrawDiagnosticsController)
&& call.Target.Name == nameof(TerrainDrawDiagnosticsController.Complete));
}
[Fact]
public void Concrete_executor_pushes_the_walk_terrain_frame_sample_at_replay_end()
{
// S3 chunk 3 fix round 1 (F3): DrawWalkDrivenStatics is the ONE call
// site of driver.Replay in production — CompleteWalkTerrainFrame
// must run immediately after it, so the frame's sample is pushed
// exactly once, at "the end of the walk replay".
MethodInfo drawWalkDrivenStatics = typeof(RetailPViewRenderer).GetMethod(
"DrawWalkDrivenStatics",
BindingFlags.Instance | BindingFlags.NonPublic)!;
IReadOnlyList<CompiledCall> calls = CompiledCallGraph.Read(drawWalkDrivenStatics);
int replay = RequiredCallIndex(
calls,
typeof(AcDream.App.Rendering.Walk.WalkFrameDriver),
nameof(AcDream.App.Rendering.Walk.WalkFrameDriver.Replay));
int completeWalkFrame = RequiredCallIndex(
calls,
typeof(RetailPViewPassExecutor),
nameof(RetailPViewPassExecutor.CompleteWalkTerrainFrame));
Assert.True(replay < completeWalkFrame);
}
[Fact]
public void Frame_composition_constructs_one_walk_executor()
{
MethodInfo compose = typeof(FrameRootCompositionPhase).GetMethod(
"ComposeCore",
BindingFlags.Instance | BindingFlags.NonPublic)!;
IReadOnlyList<CompiledCall> calls = CompiledCallGraph.Read(compose);
int executor = RequiredCallIndex(
calls,
typeof(RetailPViewPassExecutor),
".ctor");
int renderer = RequiredCallIndex(
calls,
typeof(WorldScenePViewRenderer),
".ctor");
Assert.True(executor < renderer);
Assert.Single(
calls,
call => call.Target.DeclaringType == typeof(RetailPViewPassExecutor)
&& call.Target.Name == ".ctor");
Assert.Single(
calls,
call => call.Target.DeclaringType == typeof(WorldScenePViewRenderer)
&& call.Target.Name == ".ctor");
}
/// <summary>
/// S3 chunk 1 fix round 2 (§11.6 H1): the weather MESH draw + its OC
/// print moved OUT of <see cref="RetailPViewPassExecutor.DrawWeatherOnce"/>
/// entirely — S3 chunk 4 (O3) relocated the print to
/// <c>WalkFrameDriver.OnWeatherTurn</c>, fired at Collect time by
/// <c>RetailFrameWalk.DrawLandscape</c> (see the real transcript pins in
/// <c>WalkFrameDriverTranscriptTests</c>: <c>Collect_OutdoorRoot_...</c>
/// and <c>Collect_InteriorRoot_...</c>). This method now draws the
/// weather MESH and the rain PARTICLES only — the former per-outside-
/// view-slice loop that used to run before this call (the walk's own
/// screen-space terrain-clip writer + its per-frame clip-routing reset
/// call + the old <c>DrawLandscapeSliceLate</c> leaf) is deleted outright (§10.2): retail
/// draws the weather mesh and its
/// rain particles ONCE, unclipped, never once per doorway aperture.
/// MUTATION: re-inlining a
/// <c>WalkTranscriptDump.PrintObjectCellTurn</c> call back into this
/// method makes the <c>Assert.DoesNotContain</c> below fail; deleting
/// either the mesh or the particle call makes the matching
/// <c>Assert.Single</c> fail (zero matches instead of one).
/// </summary>
[Fact]
public void DrawWeatherOnce_DrawsTheWeatherMeshAndParticlesButNeverPrints()
{
MethodInfo method = typeof(RetailPViewPassExecutor).GetMethod(
nameof(RetailPViewPassExecutor.DrawWeatherOnce),
BindingFlags.Instance | BindingFlags.Public)!;
IReadOnlyList<CompiledCall> calls = CompiledCallGraph.Read(method);
Assert.Single(
calls,
call => call.Target.DeclaringType == typeof(SkyRenderer)
&& call.Target.Name == nameof(SkyRenderer.RenderWeather));
Assert.Single(
calls,
call => call.Target.DeclaringType == typeof(ParticleRenderer)
&& call.Target.Name == nameof(ParticleRenderer.Draw));
Assert.DoesNotContain(
calls,
call => call.Target.DeclaringType == typeof(WalkTranscriptDump));
}
/// <summary>
/// S3 chunk 4 (§10.2): the former per-outside-view-slice loop
/// (the walk's own screen-space terrain-clip writer + its per-frame
/// clip-routing reset call + the old <c>DrawLandscapeSliceLate</c> leaf,
/// one call per active landscape view) is deleted — <c>DrawLandscapeDynamicsPhase</c> now calls
/// <see cref="RetailPViewPassExecutor.DrawWeatherOnce"/> exactly once,
/// conditional on <see cref="AcDream.App.Rendering.Walk.WalkFrameDriver.WeatherTurnFired"/>
/// (see <see cref="DrawLandscapeDynamicsPhase_GatesDrawWeatherOnceOnWalkDriverWeatherTurnFired"/>
/// — the L1 pin), with no loop of any kind around it. This
/// <c>Assert.Single</c> alone proved insufficient at fix round 1 (K1):
/// it counts DISTINCT call-site offsets, so it stays green even with a
/// <c>foreach</c> wrapped around the one call site (the exact round-1
/// regression this file's review caught) — see
/// <see cref="DrawLandscapeDynamicsPhase_DrawWeatherOnceCallSiteHasNoEnclosingBackwardBranch"/>
/// for the pin that actually rules that out. Kept as a cheap first-line
/// check: MUTATION: adding a second, textually distinct call site (e.g.
/// a duplicated call, not a loop) makes <c>Assert.Single</c> fail.
/// </summary>
[Fact]
public void DrawLandscapeDynamicsPhase_CallsDrawWeatherOnceExactlyOnce()
{
MethodInfo method = typeof(RetailPViewRenderer).GetMethod(
"DrawLandscapeDynamicsPhase",
BindingFlags.Instance | BindingFlags.NonPublic)!;
IReadOnlyList<CompiledCall> calls = CompiledCallGraph.Read(method);
Assert.Single(
calls,
call => call.Target.DeclaringType == typeof(RetailPViewPassExecutor)
&& call.Target.Name == nameof(RetailPViewPassExecutor.DrawWeatherOnce));
}
/// <summary>
/// S3 chunk 4 fix round 1 (K1, blocking): the real loop-shape pin.
/// <see cref="DrawLandscapeDynamicsPhase_CallsDrawWeatherOnceExactlyOnce"/>'s
/// <c>Assert.Single</c> over call-site offsets still passes when the ONE
/// call site sits inside a <c>foreach</c> — an IL-offset ORDER pin has
/// now failed three times to be discriminating for this exact class of
/// regression, so this asks the LOOP-SHAPE question directly: does any
/// BACKWARD branch (a branch whose target offset is lower than its own
/// offset — the shape every C# loop compiles to, whether
/// <c>for</c>/<c>foreach</c>/<c>while</c>) enclose the
/// <c>DrawWeatherOnce</c> call's own IL offset? A call sitting strictly
/// between a backward branch's target and its own offset is inside that
/// loop's body and can run more than once per method invocation; a call
/// outside every backward branch's span cannot. MUTATION: wrap the call
/// in <c>foreach (var slice in clipAssembly.OutsideViewSlices)</c> —
/// the compiled <c>foreach</c> emits a backward branch (the
/// condition-check jump back to the loop body) whose span now contains
/// the call's offset, so this test fails; restore the single
/// unconditional call to make it pass again. Note for reviewers (added
/// at S3 landing hygiene, H4, matching <see cref="DrawWalkSky_RenderSkyCallSiteHasNoEnclosingBackwardBranch"/>'s
/// own note): <see cref="CompiledCallGraph.ReadBranches"/> reads only
/// <c>br</c>/<c>brtrue</c>/<c>brfalse</c>-family single-target branches —
/// it does not decode a compiled <c>switch</c> jump table, but no C# loop
/// construct (<c>for</c>/<c>foreach</c>/<c>while</c>/<c>do</c>) ever
/// compiles to one, so this pin's blind spot is not a loop shape it
/// could miss.
/// </summary>
[Fact]
public void DrawLandscapeDynamicsPhase_DrawWeatherOnceCallSiteHasNoEnclosingBackwardBranch()
{
MethodInfo method = typeof(RetailPViewRenderer).GetMethod(
"DrawLandscapeDynamicsPhase",
BindingFlags.Instance | BindingFlags.NonPublic)!;
IReadOnlyList<CompiledCall> calls = CompiledCallGraph.Read(method);
int callIndex = RequiredCallIndex(
calls,
typeof(RetailPViewPassExecutor),
nameof(RetailPViewPassExecutor.DrawWeatherOnce));
int callOffset = calls[callIndex].Offset;
IReadOnlyList<CompiledBranch> branches = CompiledCallGraph.ReadBranches(method);
Assert.DoesNotContain(
branches,
branch => branch.TargetOffset < branch.Offset
&& branch.TargetOffset <= callOffset
&& callOffset < branch.Offset);
}
/// <summary>
/// S3 chunk 4 fix round 2 (L1, BLOCKING). Round 1's three-lens review
/// found that neither existing pin above actually looks at the
/// production CONDITION gating <c>DrawWeatherOnce</c>: restoring the
/// pre-fix gate <c>if (clipAssembly.OutsideViewSlices.Length != 0)</c> —
/// the exact regression K2 was supposed to close — leaves both green,
/// because both only ask "is the call site shaped correctly", never
/// "does the call site read <c>WalkFrameDriver.WeatherTurnFired</c>".
/// This pin reads the compiled condition directly: with <c>c</c> the
/// index of the <c>DrawWeatherOnce</c> call, (a) <c>calls[c-1]</c> must
/// be the <c>WeatherTurnFired</c> getter — the LAST call before the draw
/// — and (b) exactly one branch must sit strictly between that getter
/// call and the draw call, be a <c>brfalse</c>/<c>brfalse.s</c>, and
/// jump FORWARD past the draw call — the compiled shape of
/// <c>if (walkDriver.WeatherTurnFired) passes.DrawWeatherOnce(ctx);</c>
/// and nothing else (an inverted test, an unconditional call, or a
/// different condition entirely all fail one of the two checks).
/// MUTATION M1 (restores the pre-fix regression): change the gate back
/// to <c>if (clipAssembly.OutsideViewSlices.Length != 0)</c> — check (a)
/// fails because <c>calls[c-1]</c> is no longer the
/// <c>WeatherTurnFired</c> getter. MUTATION M2 (drops the gate
/// entirely): make the call unconditional — check (b) fails because no
/// branch sits between the getter call and the draw call (in fact the
/// getter call itself disappears with the gate, so check (a) fails
/// first). MUTATION M3 (inverts the condition): change the gate to
/// <c>if (!walkDriver.WeatherTurnFired)</c> — <c>calls[c-1]</c> is still
/// the getter (check (a) passes), but the compiler emits a
/// <c>brtrue</c>/<c>brtrue.s</c> to skip the draw instead of a
/// <c>brfalse</c>/<c>brfalse.s</c>, so check (b)'s opcode filter finds
/// nothing and <c>Assert.Single</c> fails on zero matches.
/// </summary>
[Fact]
public void DrawLandscapeDynamicsPhase_GatesDrawWeatherOnceOnWalkDriverWeatherTurnFired()
{
MethodInfo method = typeof(RetailPViewRenderer).GetMethod(
"DrawLandscapeDynamicsPhase",
BindingFlags.Instance | BindingFlags.NonPublic)!;
IReadOnlyList<CompiledCall> calls = CompiledCallGraph.Read(method);
int callIndex = RequiredCallIndex(
calls,
typeof(RetailPViewPassExecutor),
nameof(RetailPViewPassExecutor.DrawWeatherOnce));
Assert.True(callIndex > 0, "Expected a call before DrawWeatherOnce — the gate condition.");
CompiledCall condition = calls[callIndex - 1];
Assert.Equal(typeof(AcDream.App.Rendering.Walk.WalkFrameDriver), condition.Target.DeclaringType);
Assert.Equal("get_WeatherTurnFired", condition.Target.Name);
int conditionOffset = condition.Offset;
int drawOffset = calls[callIndex].Offset;
IReadOnlyList<CompiledBranch> branches = CompiledCallGraph.ReadBranches(method);
Assert.Single(
branches,
branch => branch.Offset > conditionOffset
&& branch.Offset < drawOffset
&& (branch.OpCode == OpCodes.Brfalse || branch.OpCode == OpCodes.Brfalse_S)
&& branch.TargetOffset > drawOffset);
}
/// <summary>
/// S3 landing hygiene (H5): strengthens the L1 pin above against a
/// conjoined gate the post-hoc three-lens review found it does not
/// reject. L1's check (b) only looks at branches STRICTLY BETWEEN the
/// <c>WeatherTurnFired</c> getter call and the <c>DrawWeatherOnce</c>
/// call — so <c>if (clipAssembly.OutsideViewSlices.Length != 0 &amp;&amp;
/// walkDriver.WeatherTurnFired) passes.DrawWeatherOnce(ctx);</c> still
/// passes it: the compiler evaluates <c>OutsideViewSlices.Length != 0</c>
/// FIRST, so ITS OWN <c>brfalse</c> lands BEFORE the getter call's
/// offset — outside L1's window — while <c>calls[c-1]</c> is still the
/// getter (the added condition reads <c>OutsideViewSlices</c>, a
/// property getter, then <c>.Length</c>, a non-call <c>ldlen</c>, so no
/// OTHER call intervenes before the getter). This pin widens the window
/// to start at the call immediately before the whole gate —
/// <see cref="RetailPViewPassExecutor.DrawUnattachedSceneParticles"/>,
/// the outdoor-emitters call the method's own comment names as the last
/// call before the gate — and counts EVERY branch in that wider window
/// with <c>Assert.Single</c>: production has exactly one, the forward
/// <c>brfalse</c>/<c>brfalse.s</c> right after the getter. A conjoined
/// gate's extra, earlier condition adds a second branch this wider
/// window catches but L1's narrower one cannot.
/// MUTATION: change the gate to <c>if
/// (clipAssembly.OutsideViewSlices.Length != 0 &amp;&amp;
/// walkDriver.WeatherTurnFired)</c> — the branch count in the window
/// goes from 1 to 2 and <c>Assert.Single</c> fails (see the commit body
/// for the exact recorded failure text).
/// </summary>
[Fact]
public void DrawLandscapeDynamicsPhase_ExactlyOneBranchGuardsDrawWeatherOnce()
{
MethodInfo method = typeof(RetailPViewRenderer).GetMethod(
"DrawLandscapeDynamicsPhase",
BindingFlags.Instance | BindingFlags.NonPublic)!;
IReadOnlyList<CompiledCall> calls = CompiledCallGraph.Read(method);
int particlesIndex = RequiredCallIndex(
calls,
typeof(RetailPViewPassExecutor),
nameof(RetailPViewPassExecutor.DrawUnattachedSceneParticles));
int drawIndex = RequiredCallIndex(
calls,
typeof(RetailPViewPassExecutor),
nameof(RetailPViewPassExecutor.DrawWeatherOnce));
int particlesOffset = calls[particlesIndex].Offset;
int drawOffset = calls[drawIndex].Offset;
IReadOnlyList<CompiledBranch> branches = CompiledCallGraph.ReadBranches(method);
CompiledBranch onlyGuard = Assert.Single(
branches,
branch => branch.Offset > particlesOffset && branch.Offset < drawOffset);
Assert.True(
onlyGuard.OpCode == OpCodes.Brfalse || onlyGuard.OpCode == OpCodes.Brfalse_S,
$"Expected the sole branch between DrawUnattachedSceneParticles and "
+ $"DrawWeatherOnce to be a brfalse, was {onlyGuard.OpCode}.");
Assert.True(
onlyGuard.TargetOffset > drawOffset,
"Expected the guard branch to skip forward past DrawWeatherOnce.");
}
/// <summary>
/// S3 chunk 4 fix round 2 (L8): the identical loop-shape question K1
/// asked of <see cref="RetailPViewPassExecutor.DrawWeatherOnce"/>'s call
/// site, applied to <see cref="RetailPViewPassExecutor.DrawWalkSky"/>'s
/// own <see cref="SkyRenderer.RenderSky"/> call — retail draws the sky
/// dome exactly once per frame too (K4's own doc comment on
/// <c>DrawWalkSky</c>), so nothing may wrap this call in a loop either.
/// Note for reviewers: <see cref="CompiledCallGraph.ReadBranches"/> reads
/// only <c>br</c>/<c>brtrue</c>/<c>brfalse</c>-family single-target
/// branches — it does not decode a compiled <c>switch</c> jump table,
/// but no C# loop construct (<c>for</c>/<c>foreach</c>/<c>while</c>/
/// <c>do</c>) ever compiles to one, so this pin's blind spot is not a
/// loop shape it could miss. MUTATION: wrapping the call in
/// <c>for (int i = 0; i &lt; 2; i++) { _sky?.RenderSky(...); }</c> makes
/// this fail; restoring the single unconditional call makes it pass
/// again.
/// </summary>
[Fact]
public void DrawWalkSky_RenderSkyCallSiteHasNoEnclosingBackwardBranch()
{
MethodInfo method = typeof(RetailPViewPassExecutor).GetMethod(
"DrawWalkSky",
BindingFlags.Instance | BindingFlags.NonPublic)!;
IReadOnlyList<CompiledCall> calls = CompiledCallGraph.Read(method);
int callIndex = RequiredCallIndex(
calls,
typeof(SkyRenderer),
nameof(SkyRenderer.RenderSky));
int callOffset = calls[callIndex].Offset;
IReadOnlyList<CompiledBranch> branches = CompiledCallGraph.ReadBranches(method);
Assert.DoesNotContain(
branches,
branch => branch.TargetOffset < branch.Offset
&& branch.TargetOffset <= callOffset
&& callOffset < branch.Offset);
}
/// <summary>
/// S3 chunk 1 fix round 2 (§11.6 H1): <see
/// cref="RetailPViewPassExecutor.ShouldDrawWeatherOnce"/> is
/// <see cref="DrawWeatherOnce_DrawsTheWeatherMeshAndPrintsExactlyOnce"/>'s
/// gate, extracted as a pure predicate so this suite can pin "no OC line
/// while the player stands indoors" without a live GL/DAT
/// <see cref="SkyRenderer"/> — combined with the two structural tests
/// above (moved out of the loop; drawn/printed exactly once per call),
/// this proves both halves of the spec's pin: an outdoor root (or an
/// interior root with several exit-view slices) prints exactly one OC
/// line, and an indoor player prints none. Retail's own check:
/// <c>SmartBox::is_player_outside</c> @0x00451e80,
/// <c>(player objcell_id &amp; 0xFFFF) &lt; 0x100</c>. MUTATION: negating
/// the <c>&lt; 0x100</c> comparison (or dropping either bool AND) makes
/// one of the four rows below fail.
/// </summary>
[Theory]
[InlineData(true, true, 0xF4180003u, true)] // outdoor root, player outside a land cell -> draws
[InlineData(true, true, 0xA9B40100u, false)] // player indoors (local id >= 0x100) -> no draw
[InlineData(false, true, 0xF4180003u, false)] // RenderSky off -> no draw
[InlineData(true, false, 0xF4180003u, false)] // RenderWeather off -> no draw
public void ShouldDrawWeatherOnce_MatchesRetailIsPlayerOutsideGate(
bool renderSky, bool renderWeather, uint playerCellId, bool expected)
{
Assert.Equal(
expected,
RetailPViewPassExecutor.ShouldDrawWeatherOnce(renderSky, renderWeather, playerCellId));
}
private static int RequiredCallIndex(
IReadOnlyList<CompiledCall> calls,
Type declaringType,
string methodName)
{
int index = CompiledCallGraph.IndexOf(calls, declaringType, methodName);
Assert.True(
index >= 0,
$"Expected call to {declaringType.Name}.{methodName}.");
return index;
}
}