Files
psxrecomp/tools/tests/test_cd_fork.py
T
Alex Vanderveen 36ba6bd950 cd_fork: check the disc's own sectors against what landed in RAM
The two table_watch timelines agree byte-for-byte up to one moment
(baseline fingerprints identical), then fork: entering the scene, native
receives two ch3 DMA loads (74 then 339 words) leaving 216 raw colour
words in the effect's table, while DuckStation's region goes to the
correct 5-colour table -- and both sides hold that state for the whole
~1220-frame effect before converging again (216->254->129 vs 5->252->129).

psx-runtime's cd_read_log records every ch3 load with LBA, dest and
size. This finds the loads that intersect the table and reads those very
sectors from the ISO. Disc holds the many-colour data -> native
delivered what was asked and the divergence is upstream (why the game
requested it, or where the DMA pointed). Disc holds something else ->
the CD stack is delivering wrong sector data, and that is the bug.

The log dies with the process: capture after playing the scene, before
restarting.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-24 12:55:58 -04:00

62 lines
2.1 KiB
Python

"""Tests for cd_fork's load filtering and sector slicing."""
import io
import os
import sys
import tempfile
import unittest
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import cd_fork as cf # noqa: E402
def load(lba, dest, size):
return {"lba": str(lba), "dest": f"0x{dest:08X}", "size": str(size)}
class OverlapTest(unittest.TestCase):
LO, HI = 0x0E25F0, 0x0E2C60
def test_load_inside_region_matches(self):
hits = cf.overlapping([load(1000, 0x800E2600, 296)], self.LO, self.HI)
self.assertEqual(len(hits), 1)
self.assertEqual(hits[0]["lba"], 1000)
def test_load_straddling_start_matches(self):
hits = cf.overlapping([load(1, 0x800E2000, 0x700)], self.LO, self.HI)
self.assertEqual(len(hits), 1)
def test_load_elsewhere_ignored(self):
hits = cf.overlapping([load(1, 0x80100000, 4096)], self.LO, self.HI)
self.assertEqual(hits, [])
def test_kseg_bits_stripped(self):
hits = cf.overlapping([load(1, 0x000E2600, 8)], self.LO, self.HI)
self.assertEqual(len(hits), 1)
class SectorTest(unittest.TestCase):
def test_mode2_user_data_extracted(self):
with tempfile.NamedTemporaryFile(suffix=".bin", delete=False) as f:
# two raw sectors: header junk + recognisable user data
for k in range(2):
f.write(bytes([0xEE] * cf.MODE2_HEADER))
f.write(bytes([k] * cf.DATA_BYTES))
f.write(bytes([0xDD] * (cf.RAW_SECTOR - cf.MODE2_HEADER
- cf.DATA_BYTES)))
path = f.name
try:
blob = cf.sector_bytes(path, 0, 2)
self.assertEqual(blob[:4], b"\x00\x00\x00\x00")
self.assertEqual(blob[cf.DATA_BYTES:cf.DATA_BYTES + 4],
b"\x01\x01\x01\x01")
self.assertEqual(len(blob), 2 * cf.DATA_BYTES)
finally:
os.unlink(path)
class DistinctTest(unittest.TestCase):
def test_masks_command_byte(self):
blob = b"\x01\x02\x03\x38" + b"\x01\x02\x03\x00"
self.assertEqual(cf.distinct_colours(blob), 1)