feat(render): S4 chunk 1 — retail far-punch bits, the ±12 local-input reject, the depth truth table and cross-frame latch pins
S4-c1 per docs/research/2026-09-01-overhaul/s4-depth-alpha-packet.md §6.
S3 chunk 2 already landed the persistent portalsDrawnCount latch, the
gated clear, the exit-seal counting, and the look-in isolation — this
chunk covers only what §1/§2 of the packet name as still owed: C0-C3.
C0 — far-punch depth constant (R1: DrawPortalPolyInternal @0x0059bc90's
tail). portal_depth.vert's punch branch carried the decimal 0.99999988,
which reinterprets as bits 0x3F7FFFFE — fifteen ULPs FARTHER from the
camera than retail's real constant, bits 0x3F7FFFEF. Now writes
`uintBitsToFloat(0x3F7FFFEFu)` so the exact bits survive the GLSL/SPIR-V
compiler instead of trusting a decimal literal to round-trip unchanged.
Recompiled via tools/compile-shaders.ps1 (glslc 1.4.350.0 backend
recorded, managed shaderc path used); portal_depth.vert.spv's SHA-256
re-pinned in VulkanShaderManifestTests
(51c60d0924d62c61548efcf5f9e7672a121b1b68ca0a06755e32f1a4d73a8acf,
was 4ac1c452e7ac0d08a32f67fb03f21229af2d1605baa81f407240a3626251dfd7).
T1 (new Fact PortalDepthVert_FarPunchConstant_MatchesRetailExactBits in
VulkanShaderManifestTests.cs): a SOURCE pin — reads portal_depth.vert's
punch line and reinterprets whatever literal it carries (uintBitsToFloat
hex or a plain decimal) as raw bits, asserts == 0x3F7FFFEF. Verified
against the PRE-CHANGE source by hand-reverting the line to
`clipPos.z = clipPos.w * 0.99999988;` and re-running just this test:
Assert.Equal() Failure: Values differ
Expected: 1065353199
Actual: 1065353214
(1065353199 = 0x3F7FFFEF, 1065353214 = 0x3F7FFFFE). Line restored and
the test re-confirmed green afterward. MUTATION: any other literal fails
the same way.
C1 — the ±12 local-input reject (R2: 0x59BCD6-0x59BD28 then
0x59BD40-0x59BD66). The Ghidra arbitration table in
oh1-depth-lifecycle.md governs over the pseudo-C's own nested-if reading
of the four x87 FCOM results (BinaryNinja's `test ah, 0x44` condition
synthesis is FPU-flag-ambiguous and reads backward at face value — see
feedback_bn_decomp_field_names.md on decompiler flag mush as an artifact
class, not semantics): the table's row says "whole poly on any
local-input x/y == +/-12 boundary is rejected before count/clip" — taken
as written, not re-derived from the pseudo-C's literal branch nesting.
Ported as one shared predicate,
WalkVisibilityMath.IsRejectedByPortalPolygonBoundaryGuard(ReadOnlySpan
<Vector3>): true iff any vertex's X or Y is exactly +12f/-12f (retail
tests LOCAL x/y before xformStart, the world transform). Wired at BOTH
producers that own the LOCAL polygon before it leaves cell/building
space:
- WalkFrameDriver.OnPunchGeometry (the walk's punch-event producer,
IWalkEventSink.OnPunchGeometry) — checked on the building-local
WalkPolygon.Vertices before TransformToWorld; a hit returns before
MarkIfGrown/any event append (retail's reject -> transform -> clip
-> count order).
- RetailPViewPassExecutor.DrawPortalDepthWrite (the exit-seal
enumeration behind DrawExitPortalMask, the sole caller) — checked on
cell.PortalPolygons[index]'s local vertices before the
Vector3.Transform loop; a hit `continue`s with no `submitted++`.
T2 (three layers):
1. WalkVisibilityMathTests.cs — direct unit tests of the predicate:
Boundary_guard_rejects_a_polygon_with_one_vertex_exactly_on_plus_minus_12
(Theory, x/y == +-12 each), Boundary_guard_admits_a_polygon_whose_
nearest_vertex_is_just_inside_12 (Theory, x/y == +-11.999),
Boundary_guard_rejects_the_whole_polygon_even_when_only_one_of_
several_vertices_hits_it, Boundary_guard_ignores_the_vertical_z_
component, Boundary_guard_admits_the_empty_polygon.
2. WalkFrameDriverTests.OnPunchGeometry_RejectsWholePolygonOnExact
PlusMinus12LocalVertex_ButPunchesJustInside — functional: feeds
OnPunchGeometry a polygon with a vertex at x=12 (no PunchFan/no
"PUNCH:" log line) then one at x=11.999 (punches normally,
leaf.Punches has exactly one entry, log has exactly one "PUNCH:3@v0").
3. RetailPViewPassExecutorTests.DrawPortalDepthWrite_RejectsDegenerate
LocalPolygons_BeforeTransformOrSubmission — a real functional test of
DrawPortalDepthWrite needs a live PortalDepthMaskRenderer the suite
has no fake for, so this is a compiled-call-graph pin (this file's
established pattern for exactly this situation): the guard call
precedes both the Vector3.Transform loop and
PortalDepthMaskRenderer.DrawDepthFan by IL offset, gated by a
conditional branch immediately after it.
MUTATION texts, all verified live during this session then reverted:
- OnPunchGeometry_RejectsWholePolygon... with the C1 guard deleted from
OnPunchGeometry:
Assert.Single() Failure: The collection contained 2 items
Collection: [WalkPolygon { Plane = WalkPlane { Normal = <0, 0, 1>, D = -3 }, Vertices = [<0, 0, 3>, <12, 0, 3>, <5, 5, 3>] }, WalkPolygon { Plane = WalkPlane { Normal = <0, 0, 1>, D = -3 }, Vertices = [<0, 0, 3>, <11.999, 0, 3>, <5, 5, 3>] }]
- DrawPortalDepthWrite_RejectsDegenerateLocalPolygons... with the C1
guard deleted from DrawPortalDepthWrite:
Expected call to WalkVisibilityMath.IsRejectedByPortalPolygonBoundaryGuard.
- Boundary_guard_admits_a_polygon_whose_nearest_vertex_is_just_inside_12
with the predicate widened to `MathF.Abs(x) >= 11.99f ||
MathF.Abs(y) >= 11.99f` (all four rows):
Assert.False() Failure
Expected: False
Actual: True
- Boundary_guard_rejects_a_polygon_with_one_vertex_exactly_on_plus_
minus_12 with the predicate narrowed to strict `x > 12f || x < -12f
|| y > 12f || y < -12f` (all four rows):
Assert.True() Failure
Expected: True
Actual: False
- Boundary_guard_rejects_the_whole_polygon_even_when_only_one_of_
several_vertices_hits_it with the guard checking only
localVertices[0] instead of looping every vertex:
Assert.True() Failure
Expected: True
Actual: False
C2 — no pipeline change for R3 (depth ALWAYS/write/no-cull, color writes
ENABLED with a zero-alpha SRCALPHA/INVSRCALPHA blend). acdream's
PortalDepthMaskRenderer.Rhi.cs:92,100 sets ColorWrite=false alongside
Blend=None; portal_depth.frag writes no color output at all. Provably
pixel-identical (retail's blend collapses to dst'=dst when srcAlpha is
fixed at 0, for any RGB) and the write mask is the SAFER mechanism going
forward (structurally blocks any future accidental color write,
independent of an authored zero-alpha invariant). Added register row
AD-119 to docs/architecture/retail-divergence-register.md (the next free
id after AD-118), citing DrawPortalPolyInternal @0x0059bc90 and
PortalDepthMaskRenderer.Rhi.cs; section 2's active-row count and running
header note updated (90 -> 91).
C3 — the truth table + cross-frame latch tests. The (root kind,
draw_landscape, outside-view count, previous count) table's cells are
mostly already covered by S3 chunk 2's own tests — this chunk adds only
the genuinely missing rows/cases, and leaves every existing test
untouched:
Pre-existing coverage (named, not reproduced):
- interior, ov==0, prior==0 ->
RunFrame_InteriorFloodWithNoExitView_SkipsLandscapeAndNeverFlushesClearsOrSeals
- interior, ov>0, prior==0 ->
RunFrame_InteriorFloodWithExitView_FreshDriverSkipsTheGatedClearThenDrawsSealsAndFloodCells
and OnInteriorFloodDrawTurn_FirstOvFrameSkipsClear_SecondFrameArmedByFirstsSealsClears
(its own frame 1)
- interior, ov>0, prior>0 (T4's "frame 1 seals N>0 -> frame 2
clears" half) ->
OnInteriorFloodDrawTurn_FirstOvFrameSkipsClear_SecondFrameArmedByFirstsSealsClears
(its own frame 2)
- T4's "frame 1 seals 0 -> frame 2 does not clear" half (repeated
across three consecutive ov>0 frames, subsuming the two-frame
case) -> OnInteriorFloodDrawTurn_FloodWithNoExitPortal_NeverClearsAcrossFrames
- one look-in isolated from the root latch ->
LookInDrawCells_NeitherArmsNorConsumesThePortalsDrawnCounter
No further T4 test was added — the two existing facts above already
prove both halves of the two-consecutive-frames latch case exactly.
New rows added this chunk:
- WalkFrame_OutdoorRoot_NeverFiresTheInteriorClearSealMachinery: root
kind == OUTDOOR. RetailFrameWalk.WalkFrame's outdoor branch
((cameraCellId & 0xFFFF) < 0x100) calls DrawLandscape directly and
never calls DrawInside/OnInteriorFloodDrawTurn at all, so the whole
LFLUSH/stamp/CLEAR/SEALS mechanism structurally cannot fire —
driven end-to-end through RunFrame with an outdoor cameraCellId,
asserting SKY present, LFLUSH/CLEAR/SEALS absent, counter stays 0.
MUTATION (verified, then reverted): added a stray
`sink.OnInteriorFloodDrawTurn([], 1);` call to WalkFrame's outdoor
branch:
Assert.DoesNotContain() Failure: Item found in collection
↓ (pos 1)
Collection: ["SKY", "LFLUSH", "SEALS"]
Found: "LFLUSH"
- OnInteriorFloodDrawTurn_OvZeroAfterAPriorArmedCounter_LeavesTheLatch
CompletelyUntouched: interior, ov==0 immediately after an EARLIER
ov>0 frame armed the counter — proves the counter is left EXACTLY
as an earlier frame left it (not merely "not cleared this frame"),
since S3 §8.1 R3 gates the ENTIRE outside_view.view_count>0 block,
including the read-then-zero decision itself, on ov>0.
MUTATION (verified, then reverted): moved
`int armed = PortalsDrawnCount; PortalsDrawnCount = 0;` out of the
`if (outsideViewCount > 0)` gate in
WalkFrameDriver.OnInteriorFloodDrawTurn (unconditional
read-then-zero every call):
Assert.Equal() Failure: Values differ
Expected: 1
Actual: 0
(every OTHER WalkFrameDriverTests fact stayed green under this same
mutation — this new test is the only one that catches it).
- MultipleLookIns_WithinOneFrameAndAcrossFrames_NeverTouchTheRootLatch
(T5): extends the single-look-in fact to TWO look-ins in one frame
then a THIRD in a later frame. MUTATION (verified, then reverted):
a `_mutationLookInCalls` counter in HandleDrawCellsTurn's
LookInStatic branch that resets PortalsDrawnCount on the SECOND
look-in call:
Assert.Equal() Failure: Values differ
Expected: 1
Actual: 0
— while LookInDrawCells_NeitherArmsNorConsumesThePortalsDrawnCounter
(one look-in only) stayed green under the identical mutation,
confirming this test's incremental value over the existing single-
look-in fact.
Gates: dotnet build (App.Tests and App) 0 warnings/0 errors; hermetic
lane 6832/6832 passed; InstalledDat lane against
C:/Users/erikn/Documents/Asheron's Call — exactly the four known
failures (TowerAscentReplayTests.TowerAscent_StaircaseStaysConeVisible_
EveryStep, LayoutImporterMediaBearingChildSweepTests.
MainGameUiAndChatInput_MediaBearingChildrenNowBuildAsRealWidgets and
LayoutImporterInvisibleSweepTests.EveryAuthoredInvisibleWidget_
StartsHiddenAcrossAllLayouts — both #383 — and
WalkTraceConformanceTests.Oh_doorway_still_first_frame_diff #458),
243 passed / 1 skipped / 4 failed / 248 total, no new failures; shader
tests (VulkanShaderDescriptorContractTests/VulkanShaderManifestTests/
RenderPackSpirvValidatorTests/SkyVertexLayoutTests) 35/35; register
tests (Divergence|Register filter) 52/52.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
2712a8b221
commit
d1e3e64f61
11 changed files with 564 additions and 4 deletions
|
|
@ -610,6 +610,141 @@ public sealed partial class WalkFrameDriverTests
|
|||
Assert.Equal(0, driver.PortalsDrawnCount);
|
||||
}
|
||||
|
||||
// ── S4-c1 C3 (T3): the depth-alpha packet's truth table over (root kind,
|
||||
// draw_landscape, outside-view count, previous count) -> the exact
|
||||
// LFLUSH/stamp/CLEAR/SEALS event subsequence. Chunk 2 already covers
|
||||
// most of the table as direct Facts, so chunk 1 adds only the rows that
|
||||
// were genuinely missing rather than re-deriving them:
|
||||
// - interior, ov==0 (draw_landscape==false), prior==0 ->
|
||||
// RunFrame_InteriorFloodWithNoExitView_SkipsLandscapeAndNeverFlushesClearsOrSeals
|
||||
// - interior, ov>0 (draw_landscape==true), prior==0 ->
|
||||
// RunFrame_InteriorFloodWithExitView_FreshDriverSkipsTheGatedClearThenDrawsSealsAndFloodCells
|
||||
// and OnInteriorFloodDrawTurn_FirstOvFrameSkipsClear_SecondFrameArmedByFirstsSealsClears
|
||||
// (its own frame 1)
|
||||
// - interior, ov>0, prior>0 ->
|
||||
// OnInteriorFloodDrawTurn_FirstOvFrameSkipsClear_SecondFrameArmedByFirstsSealsClears
|
||||
// (its own frame 2) — this pair IS T4, the two-consecutive-frames
|
||||
// latch case, below
|
||||
// The two rows below — root kind == OUTDOOR (where the entire LFLUSH/
|
||||
// stamp/CLEAR/SEALS mechanism cannot fire at all, because
|
||||
// RetailFrameWalk.WalkFrame's outdoor branch never calls DrawInside/
|
||||
// OnInteriorFloodDrawTurn), and interior/ov==0 immediately AFTER a
|
||||
// prior-armed nonzero counter (proving the counter is left completely
|
||||
// UNTOUCHED, not merely "not cleared this frame") — were not yet pinned
|
||||
// anywhere. ─────────────────────────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public void WalkFrame_OutdoorRoot_NeverFiresTheInteriorClearSealMachinery()
|
||||
{
|
||||
// Root kind == OUTDOOR: RetailFrameWalk.WalkFrame's
|
||||
// (cameraCellId & 0xFFFF) < 0x100 branch calls DrawLandscape
|
||||
// directly and never calls DrawInside — so OnInteriorFloodDrawTurn,
|
||||
// the sole owner of LFLUSH/stamp/CLEAR/SEALS, never fires at all,
|
||||
// for ANY outside-view-count/previous-count combination (there is
|
||||
// no such combination reachable outdoors — this row of the table
|
||||
// has no ov/prior axis).
|
||||
using var fx = new DispatcherFixture();
|
||||
var log = new List<string>();
|
||||
var leaf = new RecordingLeafRenderer(log);
|
||||
var ctx = new TestContext();
|
||||
var driver = new WalkFrameDriver(fx.Dispatcher, leaf, new FakeWorldData());
|
||||
var walk = new RetailFrameWalk();
|
||||
// Same minimal, no-op 1x1 unpublished landscape the interior exit-
|
||||
// view fixtures use — LScape::draw still runs its full sky/terrain
|
||||
// turn against it; there's nothing published to iterate for.
|
||||
var landscape = new WalkLandscape { MidWidth = 1, Blocks = new WalkLandBlock?[1] };
|
||||
|
||||
using DrawScope draw = fx.BeginDraw();
|
||||
driver.RunFrame(
|
||||
walk, cameraCellId: 0x00000050u, cameraCell: null, landscape: landscape,
|
||||
ctx, draw.Frame, draw.Pass, Matrix4x4.Identity, cameraWorldPosition: Vector3.Zero);
|
||||
|
||||
// MUTATION: add a stray sink.OnInteriorFloodDrawTurn(...) call to
|
||||
// RetailFrameWalk.WalkFrame's outdoor branch (e.g. a copy-paste from
|
||||
// the interior branch) — SKY still appears, but LFLUSH/SEALS would
|
||||
// too, and this fails.
|
||||
Assert.Contains("SKY", log);
|
||||
Assert.DoesNotContain("LFLUSH", log);
|
||||
Assert.DoesNotContain("CLEAR", log);
|
||||
Assert.DoesNotContain("SEALS", log);
|
||||
Assert.Equal(0, driver.PortalsDrawnCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OnInteriorFloodDrawTurn_OvZeroAfterAPriorArmedCounter_LeavesTheLatchCompletelyUntouched()
|
||||
{
|
||||
// Interior root, ov==0 immediately after an EARLIER ov>0 frame armed
|
||||
// the counter: S3 §8.1 R3 gates the ENTIRE outside_view.view_count>0
|
||||
// block — including the read-then-zero decision itself — so ov==0
|
||||
// must leave the counter EXACTLY as an earlier frame left it, not
|
||||
// merely "not cleared this frame" (a read-then-zero-back-to-the-
|
||||
// same-nonzero-value mistake would also leave PortalsDrawnCount
|
||||
// looking untouched from the OUTSIDE — this test's real target is
|
||||
// that no clear/seal machinery runs at all, proven by the empty log
|
||||
// alongside the unchanged counter).
|
||||
using var fx = new DispatcherFixture();
|
||||
var log = new List<string>();
|
||||
var leaf = new RecordingLeafRenderer(log);
|
||||
var ctx = new TestContext();
|
||||
const uint cellId = 0xF4180310u;
|
||||
var cell = new WalkCell { CellId = cellId };
|
||||
cell.PushView();
|
||||
WalkCopyView.AppendFullViewportQuad(
|
||||
cell.TopView, ctx.Rays, ctx.WorldViewpoint, ctx.ViewportWidth, ctx.ViewportHeight);
|
||||
ctx.Cells[cellId] = cell;
|
||||
|
||||
var driver = new WalkFrameDriver(fx.Dispatcher, leaf, new FakeWorldData());
|
||||
IWalkEventSink sink = driver;
|
||||
|
||||
using DrawScope draw = fx.BeginDraw();
|
||||
|
||||
// Frame 1: an ordinary ov>0 flood arms the counter.
|
||||
driver.BeginFrame(ctx, Matrix4x4.Identity, Vector3.Zero);
|
||||
sink.Emit(WalkEvent.Landscape(activeViewCount: 1));
|
||||
var views = new WalkPortalView();
|
||||
WalkCopyView.AppendFullViewportQuad(
|
||||
views, ctx.Rays, ctx.WorldViewpoint, ctx.ViewportWidth, ctx.ViewportHeight);
|
||||
sink.OnLandscapeViews(views);
|
||||
sink.OnInteriorFloodDrawTurn([cellId], outsideViewCount: 1);
|
||||
driver.EndFrame();
|
||||
driver.Replay(draw.Frame, draw.Pass);
|
||||
Assert.Equal(1, driver.PortalsDrawnCount);
|
||||
log.Clear();
|
||||
|
||||
// Frame 2: ov==0 — retail's whole clear/seal gate is skipped by
|
||||
// construction (no landscape turn either, matching DrawInside's own
|
||||
// ov==0 shape).
|
||||
driver.BeginFrame(ctx, Matrix4x4.Identity, Vector3.Zero);
|
||||
sink.OnInteriorFloodDrawTurn([cellId], outsideViewCount: 0);
|
||||
driver.EndFrame();
|
||||
driver.Replay(draw.Frame, draw.Pass);
|
||||
|
||||
// The ov==0 flood still draws its OWN cell's shell + contents (R1's
|
||||
// "straight to the flood's own cells" tail) — the four LFLUSH/
|
||||
// stamp/CLEAR/SEALS tokens are what must be absent, not the whole
|
||||
// log; see RunFrame_InteriorFloodWithNoExitView_... above for the
|
||||
// same non-empty-but-gate-free shape.
|
||||
Assert.DoesNotContain("SKY", log);
|
||||
Assert.DoesNotContain("LFLUSH", log);
|
||||
Assert.DoesNotContain("CLEAR", log);
|
||||
Assert.DoesNotContain("SEALS", log);
|
||||
// MUTATION: move the `int armed = PortalsDrawnCount; PortalsDrawnCount
|
||||
// = 0;` read-then-zero in WalkFrameDriver.OnInteriorFloodDrawTurn
|
||||
// outside the `if (outsideViewCount > 0)` gate — the counter reads
|
||||
// back as 0 here instead of the untouched 1, and this fails.
|
||||
Assert.Equal(1, driver.PortalsDrawnCount);
|
||||
}
|
||||
|
||||
// ── S4-c1 C3 (T4): the same fixture as
|
||||
// OnInteriorFloodDrawTurn_FirstOvFrameSkipsClear_SecondFrameArmedByFirstsSealsClears
|
||||
// above already proves the "frame 1 seals N>0 -> frame 2 clears" half of
|
||||
// the two-consecutive-frames latch, and
|
||||
// OnInteriorFloodDrawTurn_FloodWithNoExitPortal_NeverClearsAcrossFrames
|
||||
// already proves "frame 1 seals 0 -> frame 2 does not clear" (repeated
|
||||
// across three consecutive ov>0 frames, which subsumes the two-frame
|
||||
// case). No further T4 test is added — see the commit body for the
|
||||
// pre-existing-coverage inventory. ────────────────────────────────────
|
||||
|
||||
// ── T3 (S3 chunk 2, R1): a building look-in's own DrawCells re-enters
|
||||
// with ov==0 unconditionally and neither ARMS nor CONSUMES the
|
||||
// persistent PortalsDrawnCount counter — retail calls DrawCells
|
||||
|
|
@ -669,6 +804,91 @@ public sealed partial class WalkFrameDriverTests
|
|||
Assert.Equal(1, driver.PortalsDrawnCount);
|
||||
}
|
||||
|
||||
// ── S4-c1 C3 (T5): the ABOVE test proves one look-in is isolated from
|
||||
// the root latch; this extends it to MULTIPLE look-ins — two in the
|
||||
// SAME frame (two buildings' own portal passes), then a third in a
|
||||
// LATER, separate frame — since retail's DrawCells re-entry (R1) has no
|
||||
// per-call state of its own that could accumulate across repeats. ────
|
||||
|
||||
[Fact]
|
||||
public void MultipleLookIns_WithinOneFrameAndAcrossFrames_NeverTouchTheRootLatch()
|
||||
{
|
||||
using var fx = new DispatcherFixture();
|
||||
var log = new List<string>();
|
||||
var leaf = new RecordingLeafRenderer(log);
|
||||
var ctx = new TestContext();
|
||||
const uint rootCellId = 0xF4180330u;
|
||||
const uint lookInCellIdA = 0xF4180331u;
|
||||
const uint lookInCellIdB = 0xF4180332u;
|
||||
var rootCell = new WalkCell { CellId = rootCellId };
|
||||
rootCell.PushView();
|
||||
WalkCopyView.AppendFullViewportQuad(
|
||||
rootCell.TopView, ctx.Rays, ctx.WorldViewpoint, ctx.ViewportWidth, ctx.ViewportHeight);
|
||||
ctx.Cells[rootCellId] = rootCell;
|
||||
foreach (uint lookInId in new[] { lookInCellIdA, lookInCellIdB })
|
||||
{
|
||||
var lookInCell = new WalkCell { CellId = lookInId };
|
||||
lookInCell.PushView();
|
||||
WalkCopyView.AppendFullViewportQuad(
|
||||
lookInCell.TopView, ctx.Rays, ctx.WorldViewpoint, ctx.ViewportWidth, ctx.ViewportHeight);
|
||||
ctx.Cells[lookInId] = lookInCell;
|
||||
}
|
||||
|
||||
var driver = new WalkFrameDriver(fx.Dispatcher, leaf, new FakeWorldData());
|
||||
IWalkEventSink sink = driver;
|
||||
|
||||
using DrawScope draw = fx.BeginDraw();
|
||||
|
||||
// Arm PortalsDrawnCount with one throwaway ov>0 interior-root flood.
|
||||
driver.BeginFrame(ctx, Matrix4x4.Identity, Vector3.Zero);
|
||||
sink.Emit(WalkEvent.Landscape(activeViewCount: 1));
|
||||
var rootViews = new WalkPortalView();
|
||||
WalkCopyView.AppendFullViewportQuad(
|
||||
rootViews, ctx.Rays, ctx.WorldViewpoint, ctx.ViewportWidth, ctx.ViewportHeight);
|
||||
sink.OnLandscapeViews(rootViews);
|
||||
sink.OnInteriorFloodDrawTurn([rootCellId], outsideViewCount: 1);
|
||||
driver.EndFrame();
|
||||
driver.Replay(draw.Frame, draw.Pass);
|
||||
Assert.Equal(1, driver.PortalsDrawnCount);
|
||||
log.Clear();
|
||||
|
||||
// TWO look-ins in the SAME frame.
|
||||
driver.BeginFrame(ctx, Matrix4x4.Identity, Vector3.Zero);
|
||||
sink.OnBuildingTurn(new WalkBuilding());
|
||||
sink.Emit(WalkEvent.DrawCells(outsideViewCount: 0, [lookInCellIdA]));
|
||||
sink.OnBuildingTurn(new WalkBuilding());
|
||||
sink.Emit(WalkEvent.DrawCells(outsideViewCount: 0, [lookInCellIdB]));
|
||||
driver.EndFrame();
|
||||
driver.Replay(draw.Frame, draw.Pass);
|
||||
|
||||
Assert.DoesNotContain("LFLUSH", log);
|
||||
Assert.DoesNotContain("CLEAR", log);
|
||||
Assert.DoesNotContain("SEALS", log);
|
||||
// MUTATION (verified during S4-c1's own implementation, then
|
||||
// reverted): a bug that only mishandles a REPEAT look-in call within
|
||||
// one frame (e.g. resetting PortalsDrawnCount on the second
|
||||
// HandleDrawCellsTurn(LookInStatic) call) leaves this exact
|
||||
// assertion at 0 instead of 1, while
|
||||
// LookInDrawCells_NeitherArmsNorConsumesThePortalsDrawnCounter above
|
||||
// — which calls DrawCells exactly once — stays green throughout;
|
||||
// that gap is this test's whole reason to exist over the single-
|
||||
// look-in fact.
|
||||
Assert.Equal(1, driver.PortalsDrawnCount);
|
||||
log.Clear();
|
||||
|
||||
// A THIRD look-in in a LATER, separate frame.
|
||||
driver.BeginFrame(ctx, Matrix4x4.Identity, Vector3.Zero);
|
||||
sink.OnBuildingTurn(new WalkBuilding());
|
||||
sink.Emit(WalkEvent.DrawCells(outsideViewCount: 0, [lookInCellIdA]));
|
||||
driver.EndFrame();
|
||||
driver.Replay(draw.Frame, draw.Pass);
|
||||
|
||||
Assert.DoesNotContain("LFLUSH", log);
|
||||
Assert.DoesNotContain("CLEAR", log);
|
||||
Assert.DoesNotContain("SEALS", log);
|
||||
Assert.Equal(1, driver.PortalsDrawnCount);
|
||||
}
|
||||
|
||||
// ── Deliverable: a building turn's alpha barrier precedes its portal
|
||||
// pass (retail RenderDeviceD3D::DrawBuilding @0x0059f2a0:
|
||||
// FlushAlphaList(0f) -> CPhysicsPart::Draw(parts,1) [the portal walk]
|
||||
|
|
@ -812,6 +1032,63 @@ public sealed partial class WalkFrameDriverTests
|
|||
Assert.Equal(3, mdiCalls.Sum(c => (int)c.DrawCount));
|
||||
}
|
||||
|
||||
// ── S4-c1 C1/T2: DrawPortalPolyInternal's degenerate-input guard, ported
|
||||
// at the punch-fan producer (WalkFrameDriver.OnPunchGeometry — the
|
||||
// handler that owns the LOCAL polygon before TransformToWorld). A source
|
||||
// vertex with local x/y exactly +/-12 drops the WHOLE polygon before any
|
||||
// transform, event, or counter effect; the same shape at 11.999 is an
|
||||
// ordinary polygon and punches normally. ─────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public void OnPunchGeometry_RejectsWholePolygonOnExactPlusMinus12LocalVertex_ButPunchesJustInside()
|
||||
{
|
||||
using var fx = new DispatcherFixture();
|
||||
var log = new List<string>();
|
||||
var leaf = new RecordingLeafRenderer(log);
|
||||
var driver = new WalkFrameDriver(fx.Dispatcher, leaf, new FakeWorldData());
|
||||
IWalkEventSink sink = driver;
|
||||
var building = new WalkBuilding { PositionCellId = 0xA9B40040u };
|
||||
|
||||
using DrawScope draw = fx.BeginDraw();
|
||||
driver.BeginFrame(new TestContext(), Matrix4x4.Identity, Vector3.Zero);
|
||||
|
||||
// Degenerate: one vertex sits exactly on the local x == +12 boundary.
|
||||
// MUTATION: relax the guard's exact equality to a tolerance/
|
||||
// inequality (e.g. x >= 12f) and this test's second assertion group
|
||||
// (the admitted 11.999 polygon) starts failing instead — 11.999 is a
|
||||
// real, non-degenerate local coordinate a fifth of a millimeter
|
||||
// (retail units) inside the exact boundary.
|
||||
sink.OnPunchGeometry(
|
||||
building,
|
||||
new WalkPolygon
|
||||
{
|
||||
Vertices = [new(0f, 0f, 3f), new(12f, 0f, 3f), new(5f, 5f, 3f)],
|
||||
Plane = new WalkPlane(Vector3.UnitZ, -3f),
|
||||
},
|
||||
activeViewIndex: 0);
|
||||
|
||||
// Admitted: the nearest-boundary vertex is 11.999, not 12 — an
|
||||
// ordinary polygon that must punch exactly like any other.
|
||||
sink.OnPunchGeometry(
|
||||
building,
|
||||
new WalkPolygon
|
||||
{
|
||||
Vertices = [new(0f, 0f, 3f), new(11.999f, 0f, 3f), new(5f, 5f, 3f)],
|
||||
Plane = new WalkPlane(Vector3.UnitZ, -3f),
|
||||
},
|
||||
activeViewIndex: 0);
|
||||
|
||||
driver.EndFrame();
|
||||
driver.Replay(draw.Frame, draw.Pass);
|
||||
|
||||
// Exactly ONE punch reached the leaf — the rejected polygon produced
|
||||
// no PunchFan event at all (not a punch that draws zero vertices; no
|
||||
// event, full stop).
|
||||
WalkPolygon punched = Assert.Single(leaf.Punches);
|
||||
Assert.Equal(new Vector3(11.999f, 0f, 3f), punched.Vertices[1]);
|
||||
Assert.Equal(1, log.Count(entry => entry == "PUNCH:3@v0"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RepeatedFloodTurns_DrawEnvCellShellWholeOncePerRetailFrameStamp()
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue