mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-26 02:11:36 -04:00
255d05e8e4
frame_fingerprint's wr column is an ordered hash over main-RAM writes. DMA and device writes (MDEC-out, CD, SPU, GPU->RAM) are recorded in host device-service order. Native shards flush cycles at every store barrier, while batched interpreted and static code services devices after the block. So identical guest writes gave a different wr forever after. - ws: an order-independent sum of a splitmix64 term per (addr, val) over main RAM. Together with wc it is the main-RAM judge column. wr and pc stay as ordered locators. - qc: fingerprint-eligible writes that FMV-quiet kept out. If a straddled write falls into a quiet frame in one run only, the cumulative columns never agree again, and qc is where that shows. - The accumulator moves to runtime/include/frame_fingerprint.h so the semantics are unit-tested (frame_fingerprint_test). - The JSON buffer is sized from the real maximum entry length (284 B), which the old 224 B per entry could overflow. docs/TCP_COMMANDS.md documents the column roles, the one-frame snapshot straddle and the FMV-quiet caveat (set PSX_DEBUG_FMV_QUIET=0 for A/B identity runs). Existing columns keep their meaning. Nothing outside debug-tools builds changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
124 lines
5.4 KiB
C
124 lines
5.4 KiB
C
/* frame_fingerprint.h: the judge columns (wc/ws, sp, mmio, qc) must agree for
|
|
* two runs that performed the same guest writes, however a backend interleaved
|
|
* device writes with CPU stores, and must still tell different writes apart.
|
|
* The ordered wr column is the locator that is allowed to differ.
|
|
*
|
|
* The streams below model the R4 (SLUS-00797) case that motivated ws: a native
|
|
* overlay shard flushes cycles at every store barrier, so MDEC-out DMA words
|
|
* land between its CPU stores, while batched interpreted/static code services
|
|
* the same DMA after the block. */
|
|
#include <assert.h>
|
|
#include <stdint.h>
|
|
|
|
#include "frame_fingerprint.h"
|
|
|
|
#define LIVE_RAM 0x00200000u
|
|
|
|
typedef struct { uint32_t phys, val, pc; } Write;
|
|
|
|
static void replay(PsxFrameFingerprint *fp, const Write *w, int n)
|
|
{
|
|
for (int i = 0; i < n; i++)
|
|
psx_fp_record_write(fp, w[i].phys, w[i].val, w[i].pc, LIVE_RAM);
|
|
}
|
|
|
|
int main(void)
|
|
{
|
|
/* CPU stores A, B; DMA words C, D (DMA writes carry whatever the last CPU
|
|
* store PC was when the device was serviced). */
|
|
const Write shard[] = {
|
|
{ 0x00010000u, 1u, 0x80065800u },
|
|
{ 0x000F0000u, 3u, 0x80065800u }, /* DMA, serviced at the barrier */
|
|
{ 0x00010004u, 2u, 0x80065804u },
|
|
{ 0x000F0004u, 4u, 0x80065804u }, /* DMA */
|
|
};
|
|
const Write batched[] = {
|
|
{ 0x00010000u, 1u, 0x80065800u },
|
|
{ 0x00010004u, 2u, 0x80065804u },
|
|
{ 0x000F0000u, 3u, 0x80065804u }, /* DMA, serviced after the block */
|
|
{ 0x000F0004u, 4u, 0x80065804u },
|
|
};
|
|
PsxFrameFingerprint a = PSX_FRAME_FINGERPRINT_INIT;
|
|
PsxFrameFingerprint b = PSX_FRAME_FINGERPRINT_INIT;
|
|
replay(&a, shard, 4);
|
|
replay(&b, batched, 4);
|
|
assert(a.wcount == 4 && b.wcount == 4);
|
|
assert(a.wsum == b.wsum); /* judge: same guest writes */
|
|
assert(a.wr_hash != b.wr_hash); /* locator: service order differs */
|
|
assert(a.pc_hash != b.pc_hash);
|
|
|
|
/* ws still separates different writes: a changed value, and the same two
|
|
* values swapped between two addresses. */
|
|
{
|
|
const Write changed[] = {
|
|
{ 0x00010000u, 1u, 0 }, { 0x00010004u, 5u, 0 },
|
|
{ 0x000F0000u, 3u, 0 }, { 0x000F0004u, 4u, 0 },
|
|
};
|
|
const Write swapped[] = {
|
|
{ 0x00010000u, 2u, 0 }, { 0x00010004u, 1u, 0 },
|
|
{ 0x000F0000u, 3u, 0 }, { 0x000F0004u, 4u, 0 },
|
|
};
|
|
PsxFrameFingerprint c = PSX_FRAME_FINGERPRINT_INIT;
|
|
PsxFrameFingerprint d = PSX_FRAME_FINGERPRINT_INIT;
|
|
replay(&c, changed, 4);
|
|
replay(&d, swapped, 4);
|
|
assert(c.wsum != b.wsum);
|
|
assert(d.wsum != b.wsum);
|
|
}
|
|
|
|
/* A write that straddles a frame snapshot: one backend services it before
|
|
* VBlank, the other after. The cumulative ws/wc differ for that one frame
|
|
* and agree again at the next snapshot. */
|
|
{
|
|
PsxFrameFingerprint early = PSX_FRAME_FINGERPRINT_INIT;
|
|
PsxFrameFingerprint late = PSX_FRAME_FINGERPRINT_INIT;
|
|
replay(&early, batched, 3); /* frame N: DMA word C in */
|
|
replay(&late, batched, 2); /* frame N: DMA word C late */
|
|
PsxFrameFingerprint snap_early = early, snap_late = late;
|
|
assert(snap_early.wsum != snap_late.wsum);
|
|
assert(snap_early.wcount == snap_late.wcount + 1);
|
|
replay(&early, batched + 3, 1); /* frame N+1 */
|
|
replay(&late, batched + 2, 2);
|
|
assert(early.wsum == late.wsum && early.wcount == late.wcount);
|
|
}
|
|
|
|
/* FMV-quiet: a suppressed write changes nothing but qc. If the straddled
|
|
* write above falls into a quiet frame for one run only, ws never agrees
|
|
* again, and qc is where that shows. */
|
|
{
|
|
PsxFrameFingerprint recorded = PSX_FRAME_FINGERPRINT_INIT;
|
|
PsxFrameFingerprint quiet = PSX_FRAME_FINGERPRINT_INIT;
|
|
replay(&recorded, batched, 4);
|
|
replay(&quiet, batched, 2);
|
|
replay(&quiet, batched + 3, 1);
|
|
assert(psx_fp_write_eligible(batched[2].phys, LIVE_RAM));
|
|
psx_fp_note_quiet(&quiet);
|
|
assert(quiet.quiet_count == 1 && recorded.quiet_count == 0);
|
|
assert(quiet.wsum != recorded.wsum);
|
|
assert(quiet.wcount + quiet.quiet_count == recorded.wcount);
|
|
/* Only writes the fingerprint would have recorded are eligible. */
|
|
assert(psx_fp_write_eligible(0x1F800010u, LIVE_RAM));
|
|
assert(!psx_fp_write_eligible(LIVE_RAM, LIVE_RAM));
|
|
assert(!psx_fp_write_eligible(0x1F801810u, LIVE_RAM));
|
|
}
|
|
|
|
/* Scratchpad and MMIO keep their own ordered columns (program order, so the
|
|
* store PC is part of the signature); main RAM beyond the live size is
|
|
* ignored. */
|
|
{
|
|
PsxFrameFingerprint fp = PSX_FRAME_FINGERPRINT_INIT;
|
|
const PsxFrameFingerprint seed = PSX_FRAME_FINGERPRINT_INIT;
|
|
psx_fp_record_write(&fp, LIVE_RAM, 7u, 0x80010000u, LIVE_RAM);
|
|
assert(fp.wcount == 0 && fp.wsum == 0 && fp.wr_hash == seed.wr_hash);
|
|
psx_fp_record_write(&fp, 0x1F800010u, 7u, 0x80010000u, LIVE_RAM);
|
|
assert(fp.sp_count == 1 && fp.wcount == 0 && fp.wsum == 0);
|
|
uint64_t sp = fp.sp_hash;
|
|
PsxFrameFingerprint other_pc = PSX_FRAME_FINGERPRINT_INIT;
|
|
psx_fp_record_write(&other_pc, 0x1F800010u, 7u, 0x80010004u, LIVE_RAM);
|
|
assert(other_pc.sp_hash != sp);
|
|
psx_fp_record_mmio(&fp, 0x1F801070u, 1u, 0x80010008u);
|
|
assert(fp.mmio_count == 1 && fp.mmio_hash != seed.mmio_hash);
|
|
}
|
|
return 0;
|
|
}
|