using AcDream.App.Rendering; using AcDream.Core.Meshing; namespace AcDream.App.Tests.Rendering; /// /// S4-c2: 's port of retail /// D3DPolyRender::DrawMesh @0x0059d4a0's branch table (spec §4). /// public sealed class RetailAlphaMeshRouterTests { /// /// 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. /// public static IEnumerable 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, }; } /// /// / /// are boxed as (not the internal enum types) /// because [Theory] methods must be and a /// public method cannot expose an parameter /// type — / /// stay internal (this assembly's whole alpha-queue surface is internal). /// [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); } } /// /// S4-c2 fix round 1 (A7): this was documented as an "INDEPENDENTLY- /// shaped reference derivation (a switch-driven truth table, not a copy /// of 's own if-chain)" — false; /// 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 /// 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. /// [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}"); } /// 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). 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); } /// /// '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. /// [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)); /// 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. [Fact] public void ConstructSubsetMask_AlphaFamilyWinsOverClipMapAndTranslucent() { byte mask = RetailAlphaMeshRouter.ConstructSubsetMask( hasAlphaFamilyBit: true, hasClipMapBit: true, hasTranslucentBit: true, hasPositiveStippling: false); Assert.Equal(RetailAlphaMeshRouter.MaskAlphaFamily, mask); } /// ClipMap wins over Translucent when no alpha-family bit is /// present. Mutation check: checking Translucent before ClipMap would /// return 0x04 here instead of 0x08. [Fact] public void ConstructSubsetMask_ClipMapWinsOverTranslucentWithoutAlphaFamily() { byte mask = RetailAlphaMeshRouter.ConstructSubsetMask( hasAlphaFamilyBit: false, hasClipMapBit: true, hasTranslucentBit: true, hasPositiveStippling: false); Assert.Equal(RetailAlphaMeshRouter.MaskClipMap, mask); } /// 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. [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); } }