From 751a2bf2aa236abc4564b263e506f0645649ece1 Mon Sep 17 00:00:00 2001 From: Alex Vanderveen Date: Sat, 22 Aug 2026 22:22:56 -0400 Subject: [PATCH] colour_inputs: report WHAT differs in the region, not just how much MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The run now says region_static: true with region_churn_bytes: 0 and 2747 of 23088 bytes differing. By the tool's own criterion that is a real divergence — the region is not mid-write — and the report still only carried a number. A count is compatible with several completely different causes: psx-runtime has not written the data at all, psx-runtime is writing where the oracle does not, or both hold values that disagree. Those point in different directions, and "2747" cannot be read to tell them apart. The report now carries the differing runs with both sides' bytes side by side, coalesced so that a 4-byte entry differing in three of its bytes reads as one finding rather than three, plus a plain statement of the shape: * zeros on one side -> that side has not written this * data on both -> a genuine disagreement, and whether it falls on 4-byte boundaries (whole entries) or not (stray bytes) Also worth recording against the earlier reading: with the effect OFF the same region is byte-identical and completely static across eight samples. So this difference appears with the effect, which is what makes it interesting rather than ambient. Co-Authored-By: Claude Opus 5 (1M context) --- tools/colour_inputs.py | 64 +++++++++++++++++++++++++++++++ tools/tests/test_colour_inputs.py | 54 ++++++++++++++++++++++++++ 2 files changed, 118 insertions(+) diff --git a/tools/colour_inputs.py b/tools/colour_inputs.py index d3003cbb..315ae57d 100644 --- a/tools/colour_inputs.py +++ b/tools/colour_inputs.py @@ -58,6 +58,60 @@ SRC_OFFSET = -12 # lwr $t6,-12($s4): the source word's base DEFAULT_SPAN = 48 # enough to see the table around it +def diff_clusters(a, b, base, gap=8, limit=12): + """Where two buffers differ, coalesced into runs. + + A count is not a finding. "2747 of 23088 bytes differ" says nothing about + whether the two hold the same data shifted, one holds zeros where the other + holds values, or a handful of entries changed -- and those point at + completely different causes. Runs separated by fewer than `gap` matching + bytes are merged, because a differing 4-byte entry usually differs in only + some of its bytes and reporting it as three findings obscures it. + """ + runs = [] + i = 0 + n = min(len(a), len(b)) + while i < n: + if a[i] == b[i]: + i += 1 + continue + start = i + last = i + while i < n: + if a[i] != b[i]: + last = i + elif i - last >= gap: + break + i += 1 + runs.append((start, last)) + if len(runs) >= limit * 4: + break + return [{"addr": f"0x{base + s:08X}", "length": e - s + 1, + "native": a[s:min(e + 1, s + 16)].hex(" "), + "oracle": b[s:min(e + 1, s + 16)].hex(" ")} + for s, e in runs[:limit]] + + +def describe_difference(clusters): + """What KIND of difference is this? The shape narrows the cause.""" + if not clusters: + return "" + nat_zero = sum(1 for c in clusters + if set(c["native"].split()) <= {"00"}) + orc_zero = sum(1 for c in clusters + if set(c["oracle"].split()) <= {"00"}) + if nat_zero == len(clusters): + return ("psx-runtime holds ZEROS everywhere the oracle holds data — it " + "has not written this, rather than written it differently.") + if orc_zero == len(clusters): + return ("the oracle holds zeros where psx-runtime holds data — " + "psx-runtime is writing somewhere the oracle does not.") + aligned = all(int(c["addr"], 16) % 4 == 0 for c in clusters) + return ("both sides hold data and it differs" + + (", on 4-byte boundaries — whole entries, not stray bytes" + if aligned else ", not aligned to entry boundaries")) + + def region_bounds(a, b, pad=0x2000, limit=0x200000): """A span covering both pointers, so the comparison is phase-independent. @@ -351,6 +405,10 @@ def main(argv=None): nd = sum(1 for x, y in zip(ra_, rb_) if x != y) doc["region_differing_bytes"] = nd doc["region_bytes"] = len(ra_) + if nd: + cl = diff_clusters(ra_, rb_, lo) + doc["region_clusters"] = cl + doc["region_shape"] = describe_difference(cl) if nd == 0: doc["verdict"] = "region-matches" print(f"\nVERDICT: the whole {len(ra_)}-byte region is " @@ -381,6 +439,12 @@ def main(argv=None): f"region is not changing — so this is a real data " f"divergence, independent of where either pointer " f"happened to be.") + if doc.get("region_shape"): + print(f"\n {doc['region_shape']}") + for c in doc.get("region_clusters", [])[:6]: + print(f" {c['addr']} ({c['length']} bytes)") + print(f" psx-runtime {c['native']}") + print(f" oracle {c['oracle']}") return _finish(doc, args, 0) same = a == b diff --git a/tools/tests/test_colour_inputs.py b/tools/tests/test_colour_inputs.py index b3e5eb4a..81e3e2b4 100644 --- a/tools/tests/test_colour_inputs.py +++ b/tools/tests/test_colour_inputs.py @@ -284,3 +284,57 @@ class TestPhaseIndependentRegion(unittest.TestCase): self.assertGreater(hi - lo, 0x1000, "a zero-width region would compare nothing and " "report a match") + + +class TestDifferenceShape(unittest.TestCase): + """A count is not a finding. + + "2747 of 23088 bytes differ" is compatible with several completely + different causes: one side has not written the data at all, one is writing + where the other does not, or both hold values that disagree. Each points + somewhere else, so the shape has to be reported alongside the number. + """ + + def test_zeros_on_one_side_are_called_out_as_not_written(self): + a = bytes(8) + b = bytes.fromhex("f8505000f8f8b000") + cl = CI.diff_clusters(a, b, 0xE0000) + self.assertIn("has not written this", CI.describe_difference(cl)) + + def test_zeros_on_the_other_side_are_distinguished(self): + a = bytes.fromhex("f8505000f8f8b000") + b = bytes(8) + self.assertIn("oracle holds zeros", + CI.describe_difference(CI.diff_clusters(a, b, 0))) + + def test_both_holding_data_is_reported_as_a_real_disagreement(self): + a = bytes.fromhex("f8505000") + b = bytes.fromhex("08080800") + self.assertIn("both sides hold data", + CI.describe_difference(CI.diff_clusters(a, b, 0))) + + def test_entry_alignment_is_noted(self): + # Differences on 4-byte boundaries mean whole colour entries changed, + # not stray bytes — which separates a data difference from corruption. + a = bytes.fromhex("00000000" + "f8505000") + b = bytes.fromhex("00000000" + "08080800") + self.assertIn("4-byte boundaries", + CI.describe_difference(CI.diff_clusters(a, b, 0))) + + def test_nearby_differing_bytes_become_one_cluster(self): + # A 4-byte entry usually differs in only some of its bytes; reporting + # that as three findings buries it. + a = bytes.fromhex("f8005000") + b = bytes.fromhex("08ff0800") + cl = CI.diff_clusters(a, b, 0) + self.assertEqual(len(cl), 1) + + def test_distant_differences_stay_separate(self): + a = bytes.fromhex("ff" + "00" * 20 + "ff") + b = bytes.fromhex("00" + "00" * 20 + "00") + self.assertEqual(len(CI.diff_clusters(a, b, 0)), 2) + + def test_identical_buffers_have_no_clusters(self): + a = bytes.fromhex("f8505000") + self.assertEqual(CI.diff_clusters(a, a, 0), []) + self.assertEqual(CI.describe_difference([]), "")