mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-28 19:07:47 -04:00
df88fc61aa
vertex_taps settled the load side: per frame the routine loads ONE source pair -- (248,136,8) -- and one scale, and the 'second word' is the mult product itself (0x5160 = 84*248, matching $s6=84 exactly). Loads, products, scale, and all four unaligned helpers verified correct; the dirty-RAM interpreter's tables are correct too, and the probe firing at all proves this code is compiled anyway. Yet the DMA'd buffer carries ~151 colours in one frame. The one mechanism that reconciles that with everything measured -- and with the hard-edged pie sectors on screen -- is amortisation: the game patching a SLICE of the 256 colour words per frame, cycling, while $s6 falls 4 per frame. Stale slices at different scales ARE brightness bands. The write trace records frame and value per write, so this counts how many of the 256 colour words are written each frame. ~256 kills the hypothesis; a fraction confirms it and predicts the resident bands. If confirmed, the game code being identical on DuckStation means the true divergence is whatever drives $s6, and the next measurement is the oracle's $s6 timeline. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
74 lines
2.6 KiB
Python
74 lines
2.6 KiB
Python
"""Tests for slice_check's address derivation and per-frame grouping."""
|
|
import os
|
|
import sys
|
|
import unittest
|
|
|
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
|
|
import slice_check as sc # noqa: E402
|
|
|
|
|
|
def quad(src, op="PolyG4+semi", stp=1, semi=True):
|
|
return {"op_name": op, "stp": stp, "semi": semi, "src": f"0x{src:08X}"}
|
|
|
|
|
|
class ColourAddrTest(unittest.TestCase):
|
|
def test_four_colour_words_per_quad(self):
|
|
a = sc.colour_addrs({"prims": [quad(0x0010D078)]})
|
|
self.assertEqual(a, {0x10D078, 0x10D080, 0x10D088, 0x10D090})
|
|
|
|
def test_other_classes_excluded(self):
|
|
d = {"prims": [quad(0x0010D078, op="PolyF4+semi", stp=2)]}
|
|
self.assertEqual(sc.colour_addrs(d), set())
|
|
|
|
def test_opaque_excluded(self):
|
|
d = {"prims": [quad(0x0010D078, semi=False)]}
|
|
self.assertEqual(sc.colour_addrs(d), set())
|
|
|
|
|
|
class PerFrameTest(unittest.TestCase):
|
|
ADDRS = {0x10D078, 0x10D080}
|
|
|
|
def entry(self, frame, addr, val):
|
|
return {"frame": frame, "addr": f"0x{addr:08X}", "new": f"0x{val:08X}"}
|
|
|
|
def test_groups_by_frame(self):
|
|
es = [self.entry(10, 0x10D078, 0x3A0011), self.entry(11, 0x10D080, 5)]
|
|
fr = sc.per_frame(es, self.ADDRS, 0x10D078, 0x10D090)
|
|
self.assertEqual(len(fr[10]["addrs"]), 1)
|
|
self.assertEqual(len(fr[11]["addrs"]), 1)
|
|
|
|
def test_other_buffer_copy_folds_in(self):
|
|
import packet_writers as pw
|
|
es = [self.entry(10, 0x10D078 + pw.BUFFER_STRIDE, 7)]
|
|
fr = sc.per_frame(es, self.ADDRS, 0x10D078, 0x10D090)
|
|
self.assertIn(0x10D078, fr[10]["addrs"])
|
|
|
|
def test_command_byte_masked_from_values(self):
|
|
es = [self.entry(10, 0x10D078, 0x3A005160),
|
|
self.entry(10, 0x10D080, 0x00005160)]
|
|
fr = sc.per_frame(es, self.ADDRS, 0x10D078, 0x10D090)
|
|
self.assertEqual(fr[10]["vals"], {0x005160})
|
|
|
|
def test_unrelated_addresses_ignored(self):
|
|
es = [self.entry(10, 0x080000, 7)]
|
|
self.assertEqual(len(sc.per_frame(es, self.ADDRS, 0x10D078,
|
|
0x10D090)), 0)
|
|
|
|
|
|
class VerdictTest(unittest.TestCase):
|
|
def test_full_rewrite_kills_the_hypothesis(self):
|
|
v, why = sc.verdict_of([250, 256, 251], 256)
|
|
self.assertEqual(v, "full-rewrite")
|
|
self.assertIn("dead", why)
|
|
|
|
def test_slice_confirms_and_predicts_residency(self):
|
|
v, why = sc.verdict_of([16, 16, 16], 256)
|
|
self.assertEqual(v, "amortised-slice")
|
|
self.assertIn("~16", why)
|
|
self.assertIn("$s6", why)
|
|
|
|
def test_no_writes_refuses(self):
|
|
v, _ = sc.verdict_of([], 256)
|
|
self.assertEqual(v, "no-writes")
|