From cf4add4ea6118b03e913ba43b6409fdac47eceef Mon Sep 17 00:00:00 2001 From: Alex Vanderveen Date: Sat, 22 Aug 2026 00:44:32 -0400 Subject: [PATCH] fingerprint: per-column coverage gate, and don't name the window edge Both fixes come from the first real run against a live pair. A column where one side records thousands of writes and the other records ZERO sailed through the global coverage check, which only compares total write counts. It then reported a confident "FIRST DIVERGENCE" for scratchpad, where the truth was that the oracle had never observed a single scratchpad write (0 against psx-runtime's 719264). That is a coverage hole, and calling it a divergence would send someone looking for a guest fault that does not exist. Each column now checks its own counts and says NOT COMPARABLE instead. Divergence on the very first frame of the window is not a finding either: it means the fork happened BEFORE the window, and the frame named is just wherever the window happened to start. It now says so and suggests widening. The global ratio band was 0.5x..2.0x, and a live pair sat at 1.98x -- the oracle's CPU-store hook does not see DMA-written RAM -- squeaking through as "comparable". Tightened to 0.67x..1.5x. Working, verified live: the oracle records fingerprints in interpreter mode, and the guest cycle clocks agree to 0.31% drift across a 150-frame window, which is what makes cycle-based alignment a sharp test rather than a guess. Still open: the oracle sees no scratchpad writes at all, and its main-RAM write count runs well under psx-runtime's. Until that coverage gap closes the tool will keep refusing to name a divergence frame, which is the correct behaviour. Co-Authored-By: Claude Opus 5 (1M context) --- tools/fingerprint_diff.py | 38 +++++++++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/tools/fingerprint_diff.py b/tools/fingerprint_diff.py index 20cc71e9..d2ba4d27 100644 --- a/tools/fingerprint_diff.py +++ b/tools/fingerprint_diff.py @@ -136,6 +136,11 @@ def coverage_verdict(a, b, frames): if mid < 0.5 or mid > 2.0: return False, (f"write counts differ by {mid:.2f}x — the two are not " f"observing the same set of writes (DMA coverage?)") + # 2.0 was too loose: a live pair sat at 1.98x -- the oracle's CPU-store hook + # not seeing DMA-written RAM -- and squeaked through as "comparable". + if mid < 0.67 or mid > 1.5: + return False, (f"write counts differ by {mid:.2f}x — the two are not " + f"observing the same set of writes (DMA coverage?)") if hi / max(lo, 1e-9) > 4.0: return False, (f"write-count ratio drifts {lo:.2f}x..{hi:.2f}x across " f"frames — coverage is not comparable") @@ -241,6 +246,22 @@ def main(argv=None): print(f"\ncomparing frames {frames[0]}..{frames[-1]}") found = False for key, cnt, label in COLUMNS: + # Per-column coverage. The global check compares total write counts, so + # a column where one side records thousands and the other records ZERO + # sails straight through it -- and then reports a "first divergence" + # that is really "this emulator never watched this at all". Seen live: + # the oracle logged 0 scratchpad writes against psx-runtime's 6916. + if cnt: + ca = sum(int(na[f].get(cnt, 0)) for f in frames) + cb = sum(int(ob[f].get(cnt, 0)) for f in frames) + if (ca == 0) != (cb == 0): + blind = "oracle" if cb == 0 else "psx-runtime" + print(f" {label:<18} NOT COMPARABLE — {blind} recorded no " + f"writes here at all (other side: " + f"{max(ca, cb)}). This column is a coverage hole, not a " + f"divergence.") + continue + first = None for f in frames: if na[f].get(key) != ob[f].get(key): @@ -249,10 +270,21 @@ def main(argv=None): if first is None: print(f" {label:<18} identical across all {len(frames)} frames") continue + + # Divergence on the very first frame compared means the fork happened + # BEFORE the window, not at its edge. Naming that frame would point at + # an arbitrary boundary -- the frame the window happened to start on. + if first == frames[0]: + print(f" {label:<18} already divergent at the START of the window " + f"(frame {first}) — the fork is EARLIER. Widen with --count, " + f"or restart both and compare from boot.") + found = True + continue + found = True - prev = frames[frames.index(first) - 1] if frames.index(first) else None - print(f" {label:<18} FIRST DIVERGENCE at frame {first}" - + (f" (last agreeing: {prev})" if prev is not None else "")) + prev = frames[frames.index(first) - 1] + print(f" {label:<18} FIRST DIVERGENCE at frame {first} " + f"(last agreeing: {prev})") print(f" psx-runtime {na[first].get(key)}" + (f" writes={na[first].get(cnt)}" if cnt else "")) print(f" oracle {ob[first].get(key)}"