mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-28 10:59:16 -04:00
651acb42e5
The segment-level run came back with 10,619 checked and 74,111 skipped, and the weak-coverage guard correctly refused to call it a result. But "74,111" on its own does not distinguish the two situations that produce it, and they call for opposite responses: something is wrong, or this mode structurally cannot cover this game. Here it is the second. 99% of the skips are segments containing an interrupt, which cannot be replayed — so in an interrupt-heavy game segment granularity skips almost everything by construction. The tool now names the dominant reason when one accounts for 60% or more, explains it, and in this case says outright to use block granularity instead. That is not hypothetical: the block-level run over the same game covered 5,679,898 blocks with ZERO skips. Same instrument, same question, and the two modes differ by four orders of magnitude in what they actually compared. No single reason above 60% names no cause at all, since inventing one would be worse than staying quiet. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
141 lines
5.9 KiB
Python
141 lines
5.9 KiB
Python
#!/usr/bin/env python3
|
|
"""Reading a lockstep result, in particular when it has said nothing.
|
|
|
|
"found: false" is produced by two completely different situations: the compiled
|
|
code matched everywhere it was compared, and nothing was compared at all. The
|
|
segment comparator skips whatever it cannot replay — interrupts, overflow,
|
|
unhandled ops, conflicts — so a run that skipped everything is indistinguishable
|
|
from a clean one unless the counters are read alongside the verdict.
|
|
|
|
Reporting that as "no divergence" would be the worst kind of wrong: a pass that
|
|
was never earned, on the one instrument that needs no frame alignment and no
|
|
cross-emulator addresses to be trusted.
|
|
"""
|
|
|
|
import importlib.util
|
|
import io
|
|
import sys
|
|
import unittest
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
sys.path.insert(0, str(ROOT))
|
|
|
|
|
|
def _load(name):
|
|
spec = importlib.util.spec_from_file_location(name, ROOT / f"{name}.py")
|
|
mod = importlib.util.module_from_spec(spec)
|
|
assert spec.loader is not None
|
|
spec.loader.exec_module(mod)
|
|
return mod
|
|
|
|
|
|
LS = _load("lockstep_check")
|
|
|
|
|
|
def run(doc, func=False):
|
|
buf = io.StringIO()
|
|
v = LS.summarise(doc, func, out=buf)
|
|
return v, buf.getvalue()
|
|
|
|
|
|
class TestVerdicts(unittest.TestCase):
|
|
def test_nothing_checked_is_inconclusive_not_clean(self):
|
|
v, txt = run({"found": 0, "blocks_checked": 0, "window": [10, 130]})
|
|
self.assertEqual(v, "inconclusive")
|
|
self.assertIn("NOT a clean result", txt)
|
|
|
|
def test_checked_and_quiet_is_clean(self):
|
|
v, txt = run({"found": 0, "blocks_checked": 4211, "window": [10, 130]})
|
|
self.assertEqual(v, "clean")
|
|
self.assertIn("4211", txt)
|
|
|
|
def test_more_skipped_than_checked_is_weak(self):
|
|
v, txt = run({"found": 0, "segments_checked": 40, "window": [0, 60],
|
|
"skipped_irq": 30, "skipped_unhandled": 25}, func=True)
|
|
self.assertEqual(v, "weak")
|
|
self.assertIn("skipped", txt)
|
|
|
|
def test_a_divergence_reports_both_values(self):
|
|
v, txt = run({"found": 1, "div_kind": "write-val", "blocks_checked": 900,
|
|
"window": [0, 120], "frame": 41230,
|
|
"block": "0x80068440", "pc": "0x8006844C",
|
|
"addr": "0x1F800264", "reg": -1,
|
|
"interp_expected": "0x000000F8",
|
|
"compiled_actual": "0x0000001B",
|
|
"trace": ["W4:1F800264=000000F8"]})
|
|
self.assertEqual(v, "diverged")
|
|
self.assertIn("0x8006844C", txt)
|
|
self.assertIn("0x000000F8", txt)
|
|
self.assertIn("0x0000001B", txt)
|
|
|
|
def test_each_kind_is_explained_in_words(self):
|
|
# A bare "kind: read-addr" tells the reader nothing about where to look.
|
|
for k in ("reg", "hi", "lo", "write-val", "read-addr", "write-addr"):
|
|
self.assertIn(k, LS.MEANING, f"{k} has no explanation")
|
|
|
|
def test_the_document_type_survives_a_clean_run(self):
|
|
# The engine calls the divergence type "kind" too, and merging its reply
|
|
# as-is overwrote the document type with "none" — after which the Studio
|
|
# rejected its own output as a foreign file.
|
|
import json as _j, subprocess, sys as _s
|
|
self.assertEqual(LS.KIND, "psx-lockstep")
|
|
|
|
def test_comparator_limits_are_not_reported_as_bugs(self):
|
|
# path-cap and unsupported mean the comparison gave up, not that the
|
|
# compiled code is wrong. Reading them as findings sends you hunting
|
|
# a bug that was never claimed.
|
|
for k in ("path-cap", "unsupported"):
|
|
v, txt = run({"found": 1, "div_kind": k, "blocks_checked": 10,
|
|
"window": [0, 5], "reg": -1})
|
|
self.assertIn("limit of the comparator", txt)
|
|
|
|
def test_the_window_is_always_shown(self):
|
|
_, txt = run({"found": 0, "blocks_checked": 5, "window": [77, 197]})
|
|
self.assertIn("77..197", txt)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|
|
|
|
|
|
class TestSkipGuidance(unittest.TestCase):
|
|
"""A skip count is a number; what it implies is the useful part.
|
|
|
|
Measured on a real run: 10,619 segments checked against 74,111 skipped, 99%
|
|
of them because the segment contained an interrupt. That means SEGMENT
|
|
granularity cannot cover this game — not that anything is broken. The two
|
|
call for opposite responses, and 74,111 on its own does not say which.
|
|
"""
|
|
|
|
def test_irq_dominated_skips_recommend_block_granularity(self):
|
|
v, txt = run({"found": 0, "segments_checked": 10619, "window": [2572, 2692],
|
|
"skipped_irq": 73489, "skipped_conflict": 300,
|
|
"skipped_disabled": 322}, func=True)
|
|
self.assertEqual(v, "weak")
|
|
self.assertIn("BLOCK granularity", txt)
|
|
self.assertIn("99%", txt)
|
|
|
|
def test_the_ratio_is_stated_not_just_the_counts(self):
|
|
_, txt = run({"found": 0, "segments_checked": 100, "window": [0, 10],
|
|
"skipped_irq": 700}, func=True)
|
|
self.assertIn("7.0x", txt)
|
|
|
|
def test_block_mode_does_not_suggest_itself(self):
|
|
# The advice only makes sense when you are NOT already using blocks.
|
|
_, txt = run({"found": 0, "blocks_checked": 100, "window": [0, 10],
|
|
"skipped_irq": 700}, func=False)
|
|
self.assertNotIn("BLOCK granularity", txt)
|
|
|
|
def test_a_mixed_skip_profile_names_no_single_cause(self):
|
|
# No reason above 60% means there is no one thing to point at, and
|
|
# inventing one would be worse than staying quiet.
|
|
_, txt = run({"found": 0, "segments_checked": 10, "window": [0, 10],
|
|
"skipped_irq": 30, "skipped_conflict": 30,
|
|
"skipped_disabled": 30}, func=True)
|
|
self.assertNotIn("mostly", txt)
|
|
|
|
def test_a_clean_run_with_no_skips_says_nothing_about_skipping(self):
|
|
_, txt = run({"found": 0, "blocks_checked": 5679898, "window": [9275, 9395]})
|
|
self.assertNotIn("skipped", txt)
|