Reconcile current master with validated X4 co-op support for PR23 playtest

This commit is contained in:
Matthew Stanley
2026-10-10 20:49:35 -07:00
115 changed files with 6508 additions and 312 deletions
+124 -10
View File
@@ -357,6 +357,7 @@ static inline PGXPValue *pgxp_ptr(uint32_t addr) {
* pass, fails closed: rollback invalidates everything. */
struct PGXPJournalEntry {
PGXPValue *slot;
size_t index;
PGXPValue old;
};
@@ -378,10 +379,10 @@ static inline size_t ck_index(const PGXPValue *pv) {
}
/* Journal a RAM / scratchpad slot before its first mutation in a pass. */
static inline void ck_note(PGXPValue *pv) {
static inline void ck_note(PGXPValue *pv, uint32_t addr) {
if (s_ck_depth == 0 || !pv) return;
if (s_ram != s_ck_ram && !(pv >= s_scratch &&
pv < s_scratch + PGXP_SCRATCH_WORDS)) {
if (s_ck_lossy || !s_ck_bits) { s_ck_lossy = 1; return; }
if (s_ram != s_ck_ram) {
s_ck_lossy = 1; /* shadow RAM appeared mid-pass */
return;
}
@@ -398,6 +399,7 @@ static inline void ck_note(PGXPValue *pv) {
}
s_ck_bits[i >> 3] |= bit;
s_ck_log[s_ck_n].slot = pv;
s_ck_log[s_ck_n].index = i;
s_ck_log[s_ck_n].old = *pv;
s_ck_n++;
}
@@ -409,11 +411,89 @@ static void ck_wrapped(void) {
static int ck_in_pass(void) { return s_ck_depth != 0; }
static inline PGXPValue *pgxp_ptr_w(uint32_t addr) {
PGXPValue *pv = pgxp_ptr(addr);
ck_note(pv);
PGXPValue *pv = pgxp_ptr(addr, true);
ck_note(pv, addr);
return pv;
}
static int packet_shadow_address(uint32_t addr, uint32_t *canonical) {
if ((addr & 3u) || addr >= 0xC0000000u) return 0;
const uint32_t physical = addr & 0x1FFFFFFFu;
if (physical < PSX_MAIN_RAM_WINDOW_BYTES) {
*canonical = psx_ram_canonical_offset(physical);
return 1;
}
if ((physical & 0xFFFFFC00u) == 0x1F800000u) {
*canonical = physical;
return 1;
}
if (psx_mod_gpu_dma_memory_contains(addr, 4u)) {
*canonical = physical;
return 1;
}
return 0;
}
extern "C" int pgxp_capture_word_shadow(uint32_t addr, uint32_t expected,
PGXPWordShadow *out) {
uint32_t canonical;
if (!out || !packet_shadow_address(addr, &canonical)) return 0;
PGXPWordShadow receipt{};
receipt.address = canonical;
receipt.value = expected;
receipt.source_generation = s_gen;
const PGXPValue *source = pgxp_ptr(addr);
if (s_enabled && source && source->gen == s_gen && source->value == expected) {
receipt.valid = 1;
receipt.x16 = source->x16;
receipt.y16 = source->y16;
receipt.z = source->z;
receipt.flags = source->flags;
receipt.projection = source->projection;
}
*out = receipt;
return 1;
}
static int restore_word_shadow(uint32_t addr, uint32_t expected,
const PGXPWordShadow *in, bool relocated) {
uint32_t canonical, source;
if (!in || !s_ck_depth || s_ck_lossy || !packet_shadow_address(addr, &canonical) ||
!packet_shadow_address(in->address, &source) || source != in->address ||
(!relocated && in->address != canonical) ||
in->value != expected || in->valid > 1u ||
in->source_generation != s_ck_gen) return 0;
if (in->valid && !s_enabled) return 0;
PGXPValue *destination = pgxp_ptr(addr);
// With PGXP disabled/unallocated, there is already no RAM shadow to clear.
if (!destination) return in->valid == 0u;
ck_note(destination, addr);
if (s_ck_lossy) return 0;
PGXPValue restored{};
if (in->valid) {
restored.value = in->value;
restored.gen = s_gen;
restored.x16 = in->x16;
restored.y16 = in->y16;
restored.z = in->z;
restored.flags = in->flags;
restored.projection = in->projection;
}
*destination = restored;
return 1;
}
extern "C" int pgxp_restore_word_shadow(uint32_t addr, uint32_t expected,
const PGXPWordShadow *in) {
return restore_word_shadow(addr, expected, in, false);
}
extern "C" int pgxp_restore_relocated_word_shadow(uint32_t destination,
uint32_t expected,
const PGXPWordShadow *in) {
return restore_word_shadow(destination, expected, in, true);
}
extern "C" void pgxp_checkpoint_begin(void) {
if (s_ck_depth++ != 0) return; /* the outermost pass journals */
if (!s_ck_bits) {
@@ -440,7 +520,7 @@ extern "C" void pgxp_checkpoint_rollback(void) {
for (size_t i = s_ck_n; i-- > 0;) {
PGXPJournalEntry *e = &s_ck_log[i];
*e->slot = e->old;
size_t k = ck_index(e->slot);
size_t k = e->index;
s_ck_bits[k >> 3] &= (uint8_t)~(1u << (k & 7u));
}
s_ck_n = 0;
@@ -921,6 +1001,17 @@ static inline int half_exact(const PGXPValue *pv, uint32_t value, int hi_half) {
}
/* A saturated projection shadow sits beyond the clamped word; it is not
* the value of that half and must not be carried as one. */
if (s_preserve_projection) {
const int32_t field = (int32_t)((uint32_t)half << 21) >> 21;
const int64_t d = (int64_t)v16 - (int64_t)half * 65536;
// Clip metadata uses bits 13..15. Bits 11/12 must still be the
// coordinate's sign fill; a real wrapped coordinate stays strict.
const uint16_t sign_fill = field < 0 ? 0x1800u : 0;
if (field > -0x400 && field < 0x3FF &&
(uint16_t(half) & 0x1800u) == sign_fill)
return d > -(int64_t)PGXP_PPP_AGREE_BELOW * 65536 &&
d < (int64_t)PGXP_PPP_AGREE_ABOVE * 65536;
}
return (v16 >> 16) == half;
}
@@ -1568,9 +1659,9 @@ extern "C" void pgxp_gte_reg_written(int reg, uint32_t value) {
* packet half, `half` the raw 16-bit half in the word. Exact (integer part
* equals the native parse) unless preserve-projection is on; then the precise
* position may sit a bounded distance from it (PGXP_PPP_AGREE_*), except
* when the half is at or beyond the GTE saturation limits: there the guest
* integer is a clamp that says nothing about the vertex, or (beyond them) a
* CPU-modified word the GPU's 11-bit parse wraps. */
* when the decoded coordinate is saturated or the word is a genuinely
* wrapped coordinate. Known outcode bits 13..15 alone are not saturation;
* bits 11/12 must still match the decoded coordinate's sign fill. */
static inline int pgxp_agrees(int32_t p16, int32_t native, int16_t half,
int derived) {
const int64_t d = (int64_t)p16 - (int64_t)native * 65536;
@@ -1578,7 +1669,9 @@ static inline int pgxp_agrees(int32_t p16, int32_t native, int16_t half,
* own arithmetic chose, so the value may sit one pixel either side of
* the truncation window - still bounded, still the same vertex. */
if (derived) return d > -65536 && d < 2 * 65536;
if (!s_preserve_projection || half <= -0x400 || half >= 0x3FF)
const uint16_t sign_fill = native < 0 ? 0x1800u : 0;
if (!s_preserve_projection || native <= -0x400 || native >= 0x3FF ||
(uint16_t(half) & 0x1800u) != sign_fill)
return (p16 >> 16) == native;
return d > -(int64_t)PGXP_PPP_AGREE_BELOW * 65536 &&
d < (int64_t)PGXP_PPP_AGREE_ABOVE * 65536;
@@ -1749,6 +1842,27 @@ extern "C" void pgxp_store_gte_reg(uint32_t addr, uint8_t reg) {
*dst = *src;
}
extern "C" int pgxp_store_flagged_gte_sxy(uint32_t addr, uint8_t reg,
uint32_t source_word, uint32_t stored_word) {
if (!g_pgxp_active || (addr & 3u) || reg < 12 || reg > 15 ||
((source_word ^ stored_word) & 0x07FF07FFu)) return 0;
const PGXPValue* src = &s_gte[reg];
if (src->gen != s_gen || src->value != source_word || !src->z ||
(src->flags & (PGXP_F_VXY | PGXP_F_VZ)) != (PGXP_F_VXY | PGXP_F_VZ)) return 0;
const int64_t x = (int64_t)src->x16 +
((int64_t)(int16_t)stored_word - (int16_t)source_word) * 65536;
const int64_t y = (int64_t)src->y16 +
((int64_t)(int16_t)(stored_word >> 16) - (int16_t)(source_word >> 16)) * 65536;
if (x < INT32_MIN || x > INT32_MAX || y < INT32_MIN || y > INT32_MAX) return 0;
PGXPValue* dst = pgxp_ptr_w(addr);
if (!dst) return 0;
*dst = *src;
dst->value = stored_word;
dst->x16 = (int32_t)x;
dst->y16 = (int32_t)y;
return 1;
}
extern "C" int pgxp_debug_shadow(int space, uint32_t key, int *live,
uint32_t *value, uint32_t *flags,
int32_t *x16, int32_t *y16, uint16_t *z) {