Exclude deferred EnvCell subsets from the opaque turn, preserve exact CLIP/ALPHA fixed-state groups through leaf replay, and use retail's row-3 override, blend, depth, and texture-class alpha references. Keep the existing building-detail sentinel distinct from the two CLIP references.
Bound rejected source payload, replace no-op allocation proofs with actual EnvCell and particle RHI paths, rebuild checked-in SPIR-V, and correct AP-238/AP-240 plus the S4-c2 evidence record.
Gates: Release 0W/0E; hermetic 16735/0/0; InstalledDat 255 pass/10 known fail/1 skip; shaders 32/32; focused 239/239; allocation 2/2 at 0 B. The evidence/comment repair re-ran Release 0W/0E, shaders 32/32, affected 37/37, and allocation 2/2; mesh_detail.vert.spv remained byte-identical at SHA-256 5346247ab7d606046943e19b28888c814e08dc6cb27cd9750096ac055457eb57.
Mutation proof, with each mutation restored after its named first failure:
1. Restoring the opaque predicate to !IsAdditive fails WholeLeaf_MixedCellDrawsOpaqueAtTurnThenClipAndAlphaAtDrain first at draw count: expected 1, actual 3.
2. Selecting _alphaPipeline for CLIP fails WholeLeaf_ClipDrainBindsExactStateAndTextureClassReference first at the bind sequence: expected [envcell-clip], actual [envcell-alpha].
3. Disabling CLIP depth write fails that production Theory first at Assert.True(clipPipeline.Depth.Write): expected true, actual false.
4. Swapping palette/DDS references fails the DDS row first: expected 0.784313738, actual 0.392156869; the palette row reports the inverse.
5. Mutating mesh_modern.frag from < to <= fails ClipShaders_UseGreaterEqualForThePerRangeReference first at Assert.Contains("if (color.a < alphaCutoff) discard;"): the required source spelling is absent.
6. Restoring row-3 OverrideClipmap=true fails the real-interface clip Theory first at Assert.False: expected false, actual true.
7. Deleting failed-append rollback fails the flush/end/abort rejection-storm rows first at the bounded pending-count assertion: expected 0, actual 9000.
8. Selecting _transparentDetailPipeline for a ClipMap detail contribution fails the leaf detail pin first at the bind sequence: expected second bind envcell-retail-detail-clip, actual envcell-retail-detail-alpha.
9. Resetting detail ParamB to zero fails the same detail pin first at the second pushed reference: expected 0.784313738, actual 0.
10. Classifying CLIP with exact mask equality excludes legal 0x09 and fails WholeLeaf_PositiveStippleClipMaskUsesClipPipelineAndDdsReference first at pipeline: expected envcell-clip, actual envcell-alpha.
11. Mapping the new blend to SRC_ALPHA/INVSRCALPHA fails AllRetailBlendModesAreRepresentable first at the tuple: expected (One, OneMinusSrcAlpha), actual (SrcAlpha, OneMinusSrcAlpha).
12. Restoring mesh_detail.vert's uParamB > 0.5 category predicate fails ClipShaders_UseGreaterEqualForThePerRangeReference first because vDetailCategory = uParamB == 1.0 is absent.
13. Treating every positive detail uParamB as a cutoff fails that source pin first because isRetailClipReference(uParamB) ? uParamB : 0.05 is absent.
14. Adding arbitrary 0.5 as an accepted reference to either mesh_modern.frag or mesh_detail.frag fails that source pin first at Assert.DoesNotContain("value - 0.5"); both mutations were run and reversed independently.
Retail: D3DPolyRender::SetSurface @ 0x0059c4d0; paired binary @ 0x0059c72a, 0x0059c747/0x0059c74f, 0x0059c821, 0x0059c838, 0x0059c866.
293 lines
14 KiB
C#
293 lines
14 KiB
C#
using AcDream.App.Rendering;
|
||
using AcDream.Core.Meshing;
|
||
|
||
namespace AcDream.App.Tests.Rendering;
|
||
|
||
/// <summary>
|
||
/// S4-c2: <see cref="RetailAlphaMeshRouter"/>'s port of retail
|
||
/// <c>D3DPolyRender::DrawMesh</c> @0x0059d4a0's branch table (spec §4).
|
||
/// </summary>
|
||
public sealed class RetailAlphaMeshRouterTests
|
||
{
|
||
/// <summary>
|
||
/// Hand-traced literal cases against the Ghidra-verified decomp — each
|
||
/// one differentiates exactly one row boundary, with the mutation each
|
||
/// guards against spelled out. delayMask is fixed at the real default
|
||
/// (0x0E) throughout, matching the contract's fixed axis.
|
||
/// </summary>
|
||
public static IEnumerable<object[]> RowBoundaryCases()
|
||
{
|
||
// Row 1a: sky wins outright even over a mask that would otherwise
|
||
// hit row 3. Mutation: dropping the sky check entirely returns
|
||
// Append(Alpha) instead.
|
||
yield return new object[]
|
||
{
|
||
true, false, false, (byte)0x02, false,
|
||
(int)RetailAlphaMeshAction.Immediate, (int)RetailAlphaList.Alpha, false,
|
||
};
|
||
|
||
// Row 1c: detailSurfaceActive wins outright even with multipass=true
|
||
// and a clip-bearing mask that would otherwise hit row 2. Mutation:
|
||
// dropping the detail check returns AppendClipAndImmediate instead.
|
||
yield return new object[]
|
||
{
|
||
false, true, true, (byte)0x08, false,
|
||
(int)RetailAlphaMeshAction.Immediate, (int)RetailAlphaList.Alpha, false,
|
||
};
|
||
|
||
// Row 2a: multipass + clip bit (exact 0x08) -> CLIP,
|
||
// overrideClipmap=true, ALSO immediate. Mutation: an `==` (exact
|
||
// mask match) instead of a bit test would still pass THIS case...
|
||
yield return new object[]
|
||
{
|
||
false, false, true, (byte)0x08, false,
|
||
(int)RetailAlphaMeshAction.AppendClipAndImmediate, (int)RetailAlphaList.Clip, true,
|
||
};
|
||
|
||
// ...but Row 2b (0x09 = clip bit + stipple bit) exposes an `==`
|
||
// mutation: it would fall through to row 3 (Append, no immediate
|
||
// pass) instead of also matching row 2's bit test.
|
||
yield return new object[]
|
||
{
|
||
false, false, true, (byte)0x09, false,
|
||
(int)RetailAlphaMeshAction.AppendClipAndImmediate, (int)RetailAlphaList.Clip, true,
|
||
};
|
||
|
||
// Row 2c: multipass=true but mask has NO clip bit (0x02) -> must
|
||
// fall through to row 3, not row 2. Mutation: a row-2 test that
|
||
// ignores the clip-bit requirement (fires on multipass alone) would
|
||
// return AppendClipAndImmediate here instead of plain Append(Alpha).
|
||
yield return new object[]
|
||
{
|
||
false, false, true, (byte)0x02, false,
|
||
(int)RetailAlphaMeshAction.Append, (int)RetailAlphaList.Alpha, false,
|
||
};
|
||
|
||
// Row 3a: alpha-family mask -> ALPHA, no immediate pass. Mutation:
|
||
// swapping the CLIP/ALPHA selection (always ALPHA regardless of bit
|
||
// 0x08) would still pass this one — paired with 3b below.
|
||
yield return new object[]
|
||
{
|
||
false, false, false, (byte)0x02, false,
|
||
(int)RetailAlphaMeshAction.Append, (int)RetailAlphaList.Alpha, false,
|
||
};
|
||
|
||
// Row 3b: clip-map mask -> CLIP (bit 0x08 selects the list even
|
||
// without multipass). Mutation: always routing row 3 to ALPHA
|
||
// regardless of the clip bit fails THIS case (expects Clip).
|
||
yield return new object[]
|
||
{
|
||
false, false, false, (byte)0x08, false,
|
||
(int)RetailAlphaMeshAction.Append, (int)RetailAlphaList.Clip, false,
|
||
};
|
||
|
||
// Row 3c: translucent-only mask (0x04, no clip bit) -> ALPHA.
|
||
yield return new object[]
|
||
{
|
||
false, false, false, (byte)0x04, false,
|
||
(int)RetailAlphaMeshAction.Append, (int)RetailAlphaList.Alpha, false,
|
||
};
|
||
|
||
// Row-3-inert-stipple: mask 0x01 (stipple bit ONLY, no alpha/clip/
|
||
// translucent bit) does NOT intersect the fixed default delay mask
|
||
// 0x0E (bit 0 is absent from 0x0E) — falls through to row 4.
|
||
// Mutation: including bit 0 in the effective delay test (treating
|
||
// 0x0E as if it were 0x0F) would make this case ALSO match row 3
|
||
// (Append) instead of falling to row 4's material-fallback decision.
|
||
yield return new object[]
|
||
{
|
||
false, false, false, (byte)0x01, true,
|
||
(int)RetailAlphaMeshAction.Append, (int)RetailAlphaList.Alpha, false,
|
||
};
|
||
|
||
// Row 4a: plain mask (0x00), material has_alpha true -> ALPHA
|
||
// fallback. Mutation: requiring the TRANSLUCENT bit (0x04) to
|
||
// ALREADY be set on the mask itself (rather than just checking the
|
||
// DELAY mask's own bit 0x04, which is fixed on regardless of the
|
||
// subset's mask) would make this case fall to row 5 (Immediate)
|
||
// instead.
|
||
yield return new object[]
|
||
{
|
||
false, false, false, (byte)0x00, true,
|
||
(int)RetailAlphaMeshAction.Append, (int)RetailAlphaList.Alpha, false,
|
||
};
|
||
|
||
// Row 4 vs 5: plain mask, material has_alpha FALSE -> row 4's own
|
||
// condition fails -> row 5 Immediate. Mutation: dropping the
|
||
// materialHasAlpha check from row 4 (treating it as "always append
|
||
// when mask is plain") would return Append here instead.
|
||
yield return new object[]
|
||
{
|
||
false, false, false, (byte)0x00, false,
|
||
(int)RetailAlphaMeshAction.Immediate, (int)RetailAlphaList.Alpha, false,
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// <paramref name="expectedActionRaw"/>/<paramref name="expectedListRaw"/>
|
||
/// are boxed as <see langword="int"/> (not the internal enum types)
|
||
/// because <c>[Theory]</c> methods must be <see langword="public"/> and a
|
||
/// public method cannot expose an <see langword="internal"/> parameter
|
||
/// type — <see cref="RetailAlphaMeshAction"/>/<see cref="RetailAlphaList"/>
|
||
/// stay internal (this assembly's whole alpha-queue surface is internal).
|
||
/// </summary>
|
||
[Theory]
|
||
[MemberData(nameof(RowBoundaryCases))]
|
||
public void Route_MatchesTheHandTracedDecompBoundary(
|
||
bool sky, bool detail, bool multipass, byte mask, bool hasAlpha,
|
||
int expectedActionRaw, int expectedListRaw,
|
||
bool expectedOverrideClipmap)
|
||
{
|
||
var expectedAction = (RetailAlphaMeshAction)expectedActionRaw;
|
||
var expectedList = (RetailAlphaList)expectedListRaw;
|
||
RetailAlphaMeshDecision decision = RetailAlphaMeshRouter.Route(
|
||
sky, RetailAlphaMeshRouter.DefaultDelayMask, detail, multipass, mask, hasAlpha);
|
||
|
||
Assert.Equal(expectedAction, decision.Action);
|
||
if (expectedAction != RetailAlphaMeshAction.Immediate)
|
||
{
|
||
Assert.Equal(expectedList, decision.List);
|
||
Assert.Equal(expectedOverrideClipmap, decision.OverrideClipmap);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// S4-c2 fix round 1 (A7): this was documented as an "INDEPENDENTLY-
|
||
/// shaped reference derivation (a switch-driven truth table, not a copy
|
||
/// of <see cref="RetailAlphaMeshRouter.Route"/>'s own if-chain)" — false;
|
||
/// <see cref="RestatedBranchTableRoute"/> below is the SAME five-row
|
||
/// if-chain shape with different local variable names, not a switch or
|
||
/// lookup table. It is renamed and redocumented honestly rather than
|
||
/// rewritten into a literal 160-row table (spec §4's five rows over ten
|
||
/// masks × sky × detail × multipass × hasAlpha) — the per-row
|
||
/// <see cref="RowBoundaryCases"/> Theory above already carries one
|
||
/// mutation text per row boundary; this brute-force sweep's real value
|
||
/// is catching a copy-paste/off-by-one divergence BETWEEN the two
|
||
/// restatements (a typo in one that the other doesn't share), not an
|
||
/// independent verification of the spec itself. The claim that "each of
|
||
/// the five rows' conditions was flipped in turn and this test failed
|
||
/// every time" is deleted — that hand-verification was never re-run and
|
||
/// is not reproduced in this round's evidence.
|
||
/// </summary>
|
||
[Fact]
|
||
public void Route_MatchesRestatedBranchTableAcrossEveryCell()
|
||
{
|
||
byte[] masks = [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09];
|
||
bool[] bothBools = [false, true];
|
||
int mismatches = 0;
|
||
var firstMismatch = "";
|
||
|
||
foreach (byte mask in masks)
|
||
foreach (bool sky in bothBools)
|
||
foreach (bool detail in bothBools)
|
||
foreach (bool multipass in bothBools)
|
||
foreach (bool hasAlpha in bothBools)
|
||
{
|
||
RetailAlphaMeshDecision actual = RetailAlphaMeshRouter.Route(
|
||
sky, RetailAlphaMeshRouter.DefaultDelayMask, detail, multipass, mask, hasAlpha);
|
||
RetailAlphaMeshDecision expected = RestatedBranchTableRoute(
|
||
sky, RetailAlphaMeshRouter.DefaultDelayMask, detail, multipass, mask, hasAlpha);
|
||
|
||
bool matches = actual.Action == expected.Action
|
||
&& (actual.Action == RetailAlphaMeshAction.Immediate
|
||
|| (actual.List == expected.List
|
||
&& actual.OverrideClipmap == expected.OverrideClipmap));
|
||
if (!matches && mismatches++ == 0)
|
||
{
|
||
firstMismatch =
|
||
$"sky={sky} detail={detail} multipass={multipass} mask=0x{mask:x2} "
|
||
+ $"hasAlpha={hasAlpha}: expected {expected}, got {actual}";
|
||
}
|
||
}
|
||
|
||
Assert.True(mismatches == 0, $"{mismatches} mismatches; first: {firstMismatch}");
|
||
}
|
||
|
||
/// <summary>Re-derivation of spec §4's table in independently-named
|
||
/// local variables (NOT a switch/lookup shape — see this Fact's own doc
|
||
/// comment above, A7).</summary>
|
||
private static RetailAlphaMeshDecision RestatedBranchTableRoute(
|
||
bool sky, byte delayMask, bool detail, bool multipass, byte mask, bool hasAlpha)
|
||
{
|
||
bool row1 = sky || delayMask == 0 || detail;
|
||
if (row1)
|
||
return new RetailAlphaMeshDecision(RetailAlphaMeshAction.Immediate, default, false);
|
||
|
||
bool clipBit = (mask & 0b1000) == 0b1000;
|
||
bool row2 = multipass switch
|
||
{
|
||
true => clipBit,
|
||
false => false,
|
||
};
|
||
if (row2)
|
||
return new RetailAlphaMeshDecision(RetailAlphaMeshAction.AppendClipAndImmediate, RetailAlphaList.Clip, true);
|
||
|
||
int intersect = delayMask & mask;
|
||
bool row3 = intersect != 0;
|
||
if (row3)
|
||
{
|
||
RetailAlphaList list = clipBit ? RetailAlphaList.Clip : RetailAlphaList.Alpha;
|
||
return new RetailAlphaMeshDecision(RetailAlphaMeshAction.Append, list, false);
|
||
}
|
||
|
||
bool delayHasTranslucentBit = (delayMask & 0b0100) == 0b0100;
|
||
bool row4 = delayHasTranslucentBit && hasAlpha;
|
||
if (row4)
|
||
return new RetailAlphaMeshDecision(RetailAlphaMeshAction.Append, RetailAlphaList.Alpha, false);
|
||
|
||
return new RetailAlphaMeshDecision(RetailAlphaMeshAction.Immediate, default, false);
|
||
}
|
||
|
||
/// <summary>
|
||
/// <see cref="RetailAlphaMeshRouter.MaskFromTranslucencyKind"/>'s
|
||
/// complete mapping. Ordinary Wb callers pre-filter opaque/ClipMap and
|
||
/// retain AP-239's earlier subset-mask reconstruction distinction. Mesh
|
||
/// particles, however, genuinely call this method without that ordinary-
|
||
/// Wb pre-filter, including for opaque-classified batches.
|
||
/// </summary>
|
||
[Theory]
|
||
[InlineData(TranslucencyKind.AlphaBlend, RetailAlphaMeshRouter.MaskAlphaFamily)]
|
||
[InlineData(TranslucencyKind.Additive, RetailAlphaMeshRouter.MaskAlphaFamily)]
|
||
[InlineData(TranslucencyKind.InvAlpha, RetailAlphaMeshRouter.MaskAlphaFamily)]
|
||
[InlineData(TranslucencyKind.ClipMap, RetailAlphaMeshRouter.MaskClipMap)]
|
||
[InlineData(TranslucencyKind.Opaque, (byte)0)]
|
||
public void MaskFromTranslucencyKind_MapsEveryKind(TranslucencyKind kind, byte expectedMask)
|
||
=> Assert.Equal(expectedMask, RetailAlphaMeshRouter.MaskFromTranslucencyKind(kind));
|
||
|
||
/// <summary>Spec §2's priority chain: alpha-family bits win outright over
|
||
/// ClipMap and Translucent even when both are also present. Mutation
|
||
/// check: checking ClipMap before the alpha-family union would return
|
||
/// 0x08 here instead of 0x02.</summary>
|
||
[Fact]
|
||
public void ConstructSubsetMask_AlphaFamilyWinsOverClipMapAndTranslucent()
|
||
{
|
||
byte mask = RetailAlphaMeshRouter.ConstructSubsetMask(
|
||
hasAlphaFamilyBit: true, hasClipMapBit: true, hasTranslucentBit: true,
|
||
hasPositiveStippling: false);
|
||
Assert.Equal(RetailAlphaMeshRouter.MaskAlphaFamily, mask);
|
||
}
|
||
|
||
/// <summary>ClipMap wins over Translucent when no alpha-family bit is
|
||
/// present. Mutation check: checking Translucent before ClipMap would
|
||
/// return 0x04 here instead of 0x08.</summary>
|
||
[Fact]
|
||
public void ConstructSubsetMask_ClipMapWinsOverTranslucentWithoutAlphaFamily()
|
||
{
|
||
byte mask = RetailAlphaMeshRouter.ConstructSubsetMask(
|
||
hasAlphaFamilyBit: false, hasClipMapBit: true, hasTranslucentBit: true,
|
||
hasPositiveStippling: false);
|
||
Assert.Equal(RetailAlphaMeshRouter.MaskClipMap, mask);
|
||
}
|
||
|
||
/// <summary>Positive stippling ORs bit 0x01 onto whichever base mask
|
||
/// applies. Mutation check: forgetting the OR (or using an exclusive
|
||
/// select) would leave the base mask value (0x04) instead of 0x05.</summary>
|
||
[Fact]
|
||
public void ConstructSubsetMask_PositiveStipplingOrsIntoTheBaseMask()
|
||
{
|
||
byte mask = RetailAlphaMeshRouter.ConstructSubsetMask(
|
||
hasAlphaFamilyBit: false, hasClipMapBit: false, hasTranslucentBit: true,
|
||
hasPositiveStippling: true);
|
||
Assert.Equal((byte)(RetailAlphaMeshRouter.MaskTranslucent | RetailAlphaMeshRouter.MaskPositiveStipple), mask);
|
||
}
|
||
}
|