diff --git a/docs/reviews/2026-08-18-r3-test-truth-ledger.md b/docs/reviews/2026-08-18-r3-test-truth-ledger.md index 77712980..f3abe94a 100644 --- a/docs/reviews/2026-08-18-r3-test-truth-ledger.md +++ b/docs/reviews/2026-08-18-r3-test-truth-ledger.md @@ -43,7 +43,9 @@ inventory refreshed for Batch G reports: | Syntax signal | Current count | |---|---:| | Tracked/parsed C# test files | 1,256 / 1,256 | -| Attributed test methods (not expanded theory cases) | 11,450 | +| Attributed test methods (not expanded theory cases) | 11,447 | +| Exact duplicate-data rows | 0 | +| Reviewed body-equivalent groups / methods | 11 / 27 | | Methods containing at least one empty `return;` | 138 | | Empty-return sites | 143 | | Direct / same-file-helper prerequisite-return candidates | 2 / 1 | @@ -140,6 +142,7 @@ identifiable in the lane report. | T-017 Avalonia ownership | reopened and closed in batch D | The full gate exposed the same compositor ownership class between six newer `MainWindowViewTests` facts. Their six named assertion phases now run in one owned Avalonia application session; 25 fresh-process stress iterations and the complete gate pass. | | T-018 stderr observer race | resolved in R2 | Live reader shares writes/deletes; 25 focused repetitions plus the complete gate. | | T-019 remaining explicit waits | classified in batch H; nine fixed-delay negative oracles need cleanup | Five of the 38 methods are deterministic cancellation stubs, not wall-clock tests. Preserve four intentional real-time contracts and bounded integration polling; replace the nine tests that infer “still blocked” from a fixed delay with observable synchronization state. | +| T-020 exact duplicate bodies/data | resolved in batch K | Four redundant executions were removed. The remaining 11 body-equivalent groups are intentionally split theories with disjoint, meaningfully named datasets; the inventory enforces zero repeated rows within a theory or across body-equivalent theories. | ## Preserved rationale for removals in batch A @@ -538,3 +541,41 @@ Verification before the complete gate: The no-retry complete hermetic Release gate passed 14,391/14,391 with zero skips or failures across all 12 test assemblies. + +## Batch K duplicate-body and theory-row truth + +The inventory now fingerprints every attributed method body and compares +`InlineData` case-sensitively both within a theory and across theories with an +identical body. Case sensitivity prevents legitimate inputs such as `iris` / +`IRIS`, `Sunny` / `SUNNY`, and `0x` / `0X` from being mislabeled as duplicates. + +The initial 14 body-equivalent groups contained four redundant executions: + +- `VulkanCapabilityReportContractTests.TheReportFileNameIsTheOneTheWorkflowPathNames` + duplicated the report-name assertion in `VulkanCapabilityGateTests` and did + not inspect the workflow it named. The retained fact is now + `TheReportFileNameRemainsStableForDiagnosticsAutomation`. +- `MotionInterpreterDoMotionFamilyTests.StopCompletely_NullPhysicsObj_Returns8` + was byte-equivalent to the more descriptive + `MotionInterpreterTests.StopCompletely_NullPhysicsObj_ReturnsNoPhysicsObject`. +- `CastSpellTests.ParseMagicUpdateSpell_RoundTrip` duplicated the same parser + contract under `GameEventDispatcherTests`, the parser's durable owner. +- retail motion row `(0x0150, 0x10000150u)` appeared in both the recall-command + theory and the generic anchor theory. It remains in the specifically named + recall contract. + +The other 11 groups are not useless duplicates. They deliberately reuse a +small assertion body while separating disjoint datasets by meaning: command +resolution, command argument shapes, valid chat tags, retail string escapes, +weenie error-message evidence, help text, invalid chargen ranges, ACE/retail +motion tables, character-option oracle sources, and environment-sound gaps. +Combining those rows would erase useful failure names without reducing the +assertion mechanism. + +After cleanup the inventory reports 11,447 attributed methods, zero duplicate +rows, and 11 reviewed body-equivalent groups containing 27 methods. Verification +for the changed owners passes 46 App, 154 Core, and 32 Core.Net cases. The +complete 44-project Release build reports zero warnings and zero errors. The +no-retry complete hermetic Release gate passed 14,387/14,387 with zero skips or +failures across all 12 test assemblies. The four-case reduction is exactly the +three duplicate facts and one duplicate theory row removed above. diff --git a/tests/AcDream.App.Tests/Rendering/Gpu/Vk/VulkanCapabilityGateTests.cs b/tests/AcDream.App.Tests/Rendering/Gpu/Vk/VulkanCapabilityGateTests.cs index 0937eaf2..ccab0552 100644 --- a/tests/AcDream.App.Tests/Rendering/Gpu/Vk/VulkanCapabilityGateTests.cs +++ b/tests/AcDream.App.Tests/Rendering/Gpu/Vk/VulkanCapabilityGateTests.cs @@ -499,7 +499,7 @@ public sealed class VulkanCapabilityGateTests } [Fact] - public void TheReportFileNameSitsBesideTheGlOne() + public void TheReportFileNameRemainsStableForDiagnosticsAutomation() { Assert.Equal( "graphical-capabilities-vulkan.json", diff --git a/tests/AcDream.App.Tests/Rendering/Gpu/Vk/VulkanCapabilityReportContractTests.cs b/tests/AcDream.App.Tests/Rendering/Gpu/Vk/VulkanCapabilityReportContractTests.cs index 3919e588..bd899c4e 100644 --- a/tests/AcDream.App.Tests/Rendering/Gpu/Vk/VulkanCapabilityReportContractTests.cs +++ b/tests/AcDream.App.Tests/Rendering/Gpu/Vk/VulkanCapabilityReportContractTests.cs @@ -177,14 +177,4 @@ public sealed class VulkanCapabilityReportContractTests Assert.Contains("timelineSemaphore", message, StringComparison.Ordinal); } - /// - /// The report file name is what the workflow's VULKAN_REPORT path ends in. - /// - [Fact] - public void TheReportFileNameIsTheOneTheWorkflowPathNames() - { - Assert.Equal( - "graphical-capabilities-vulkan.json", - VulkanCapabilityGuard.ReportFileName); - } } diff --git a/tests/AcDream.Core.Net.Tests/Messages/CastSpellTests.cs b/tests/AcDream.Core.Net.Tests/Messages/CastSpellTests.cs index de2875ca..fbf13dc8 100644 --- a/tests/AcDream.Core.Net.Tests/Messages/CastSpellTests.cs +++ b/tests/AcDream.Core.Net.Tests/Messages/CastSpellTests.cs @@ -38,15 +38,6 @@ public sealed class CastSpellTests BinaryPrimitives.ReadUInt32LittleEndian(body.AsSpan(16))); } - [Fact] - public void ParseMagicUpdateSpell_RoundTrip() - { - byte[] payload = new byte[4]; - BinaryPrimitives.WriteUInt32LittleEndian(payload, 0x123403E1u); - var parsed = GameEvents.ParseMagicUpdateSpell(payload); - Assert.Equal(0x123403E1u, parsed); - } - [Fact] public void ParseMagicUpdateEnchantment_RoundTrip() { diff --git a/tests/AcDream.Core.Tests/Physics/MotionCommandCatalogTests.cs b/tests/AcDream.Core.Tests/Physics/MotionCommandCatalogTests.cs index 7610cd8b..a1b8b44e 100644 --- a/tests/AcDream.Core.Tests/Physics/MotionCommandCatalogTests.cs +++ b/tests/AcDream.Core.Tests/Physics/MotionCommandCatalogTests.cs @@ -101,7 +101,6 @@ public class MotionCommandCatalogTests [InlineData(0x0005, 0x45000005u)] [InlineData(0x0007, 0x44000007u)] [InlineData(0x000D, 0x6500000Du)] - [InlineData(0x0150, 0x10000150u)] [InlineData(0x0153, 0x09000153u)] // NOT LifestoneRecall in 2013 numbering — anchor only. public void Retail2013_AnchorValuesMatchExtractedTable(ushort wire, uint expected) { diff --git a/tests/AcDream.Core.Tests/Physics/MotionInterpreterDoMotionFamilyTests.cs b/tests/AcDream.Core.Tests/Physics/MotionInterpreterDoMotionFamilyTests.cs index 34d814d3..11050540 100644 --- a/tests/AcDream.Core.Tests/Physics/MotionInterpreterDoMotionFamilyTests.cs +++ b/tests/AcDream.Core.Tests/Physics/MotionInterpreterDoMotionFamilyTests.cs @@ -429,16 +429,6 @@ public sealed class MotionInterpreterDoMotionFamilyTests // StopCompletely — 0x00527e40 @305208 (A9 verbatim quirk) // ========================================================================= - [Fact] - public void StopCompletely_NullPhysicsObj_Returns8() - { - var interp = new MotionInterpreter(); - - var result = interp.StopCompletely(); - - Assert.Equal(WeenieError.NoPhysicsObject, result); - } - [Fact] public void StopCompletely_ZeroesForwardSidestepTurnCommands_OnBothStates() { diff --git a/tools/audit-test-inventory.ps1 b/tools/audit-test-inventory.ps1 index fcfd5795..73b70ca9 100644 --- a/tools/audit-test-inventory.ps1 +++ b/tools/audit-test-inventory.ps1 @@ -224,6 +224,24 @@ function Get-WaitSites { return @($sites) } +function Get-BodyFingerprint { + param([Parameter(Mandatory)]$Method) + + $bodyNode = if ($null -ne $Method.Body) { + $Method.Body + } else { + $Method.ExpressionBody + } + if ($null -eq $bodyNode) { + return $null + } + + $bodyText = $bodyNode.ToString().Replace("`r`n", "`n") + $bytes = [Text.Encoding]::UTF8.GetBytes($bodyText) + return [Convert]::ToHexString( + [Security.Cryptography.SHA256]::HashData($bytes)) +} + function Test-RecursiveSignal { param( [Parameter(Mandatory)][string]$MethodKey, @@ -386,6 +404,16 @@ foreach ($relativePath in $trackedFiles) { } } + $inlineDataRows = @($methodAttributes | Where-Object { + (Get-ShortAttributeName $_) -eq 'InlineData' + } | ForEach-Object { + if ($null -eq $_.ArgumentList) { '()' } else { $_.ArgumentList.ToString() } + }) + $duplicateInlineDataRows = @($inlineDataRows | + Group-Object -CaseSensitive | + Where-Object { $_.Count -gt 1 } | + ForEach-Object { $_.Name }) + $bodyText = $method.ToString() $waitSites = @(Get-WaitSites $tree $method) $environmentVariables = @([regex]::Matches( @@ -402,6 +430,9 @@ foreach ($relativePath in $trackedFiles) { Attributes = $testAttributes Traits = @($traits) StaticSkip = $staticSkip + BodyFingerprint = Get-BodyFingerprint $method + InlineDataRows = $inlineDataRows + DuplicateInlineDataRows = $duplicateInlineDataRows EmptyReturns = @($emptyReturns) HelperPrerequisiteReturns = @($helperPrerequisiteReturns) PrerequisiteReturnCandidate = @($emptyReturns | Where-Object { @@ -430,6 +461,54 @@ foreach ($relativePath in $trackedFiles) { } $orderedRecords = @($records | Sort-Object Path, Line, Method) +$duplicateBodyGroups = @($orderedRecords | + Where-Object { $null -ne $_.BodyFingerprint } | + Group-Object { $_.BodyFingerprint } | + Where-Object { $_.Count -gt 1 } | + ForEach-Object { + [ordered]@{ + Fingerprint = $_.Name + Count = $_.Count + Tests = @($_.Group | ForEach-Object { + [ordered]@{ + Path = $_.Path + Line = $_.Line + Class = $_.Class + Method = $_.Method + } + }) + } + }) +$duplicateDataRowsAcrossEquivalentBodies = [Collections.Generic.List[object]]::new() +foreach ($bodyGroup in $duplicateBodyGroups) { + $occurrences = @($bodyGroup.Tests | ForEach-Object { + $test = $_ + $record = $orderedRecords | Where-Object { + $_.Path -eq $test.Path -and + $_.Line -eq $test.Line -and + $_.Class -eq $test.Class -and + $_.Method -eq $test.Method + } | Select-Object -First 1 + foreach ($row in @($record.InlineDataRows)) { + [ordered]@{ + Row = $row + Path = $test.Path + Line = $test.Line + Class = $test.Class + Method = $test.Method + } + } + }) + foreach ($rowGroup in @($occurrences | + Group-Object { $_.Row } -CaseSensitive | + Where-Object { $_.Count -gt 1 })) { + $duplicateDataRowsAcrossEquivalentBodies.Add([ordered]@{ + BodyFingerprint = $bodyGroup.Fingerprint + Row = $rowGroup.Name + Occurrences = @($rowGroup.Group) + }) + } +} $workingTreeStatus = @(& git -C $repoRoot status --short --untracked-files=no) $summary = [ordered]@{ GeneratedUtc = [DateTime]::UtcNow.ToString('O') @@ -441,6 +520,14 @@ $summary = [ordered]@{ ParsedCSharpFiles = $parsedFileCount AttributedTestMethods = $orderedRecords.Count StaticSkipMethods = @($orderedRecords | Where-Object { $null -ne $_.StaticSkip }).Count + DuplicateInlineDataMethods = @($orderedRecords | Where-Object { + $_.DuplicateInlineDataRows.Count -gt 0 + }).Count + ExactBodyDuplicateGroups = $duplicateBodyGroups.Count + MethodsInExactBodyDuplicateGroups = @($duplicateBodyGroups | + ForEach-Object { $_.Tests }).Count + DuplicateDataRowsAcrossEquivalentBodies = + $duplicateDataRowsAcrossEquivalentBodies.Count EmptyReturnMethods = @($orderedRecords | Where-Object { $_.EmptyReturns.Count -gt 0 }).Count EmptyReturnSites = @($orderedRecords | ForEach-Object { $_.EmptyReturns }).Count PrerequisiteReturnCandidates = @($orderedRecords | Where-Object { @@ -512,6 +599,9 @@ $summary = [ordered]@{ $report = [ordered]@{ Summary = $summary + ExactBodyDuplicateGroups = $duplicateBodyGroups + DuplicateDataRowsAcrossEquivalentBodies = + @($duplicateDataRowsAcrossEquivalentBodies) Tests = $orderedRecords } $outputDirectory = Split-Path -Parent $resolvedOutput