diff --git a/docs/config_schema.md b/docs/config_schema.md index f7b10eee..f751ed51 100644 --- a/docs/config_schema.md +++ b/docs/config_schema.md @@ -983,6 +983,12 @@ race) agree on `cyc`, `mmio` and `mc` on every frame. They also end with identical ordered write hashes. The only differences are one-frame VBlank straddles of `wc`/`sc`, as documented for batched code. +The scale and selected batching profile are fixed at session startup and +included in the existing netplay compatibility fingerprint. Peers with +different batching choices cannot start together. Both paths use portable C; +this does not authorize compatibility between otherwise different builds or +replace the existing game/core/config identity checks. + Gates (all present gates must be open; with none the scale always applies): - `guest_cycle_scale_gate` — RAM predicates on aligned main-RAM words diff --git a/runtime/include/psx_cycles.h b/runtime/include/psx_cycles.h index 2c4d267e..15c67998 100644 --- a/runtime/include/psx_cycles.h +++ b/runtime/include/psx_cycles.h @@ -124,6 +124,13 @@ static inline uint32_t psx_gcs_recip_for(uint32_t scale) { } /* [timing] guest_cycle_scale_batch: batch scaled CPU charges (psx_cyc.h). */ extern int g_psx_gcs_batch; +/* Fixed session timing profile. Both implementations are portable C and are + * present in every build; peers must agree on the selected one before frame 0. + * Preserve the existing scale-only tag for the unbatched path. */ +static inline uint32_t psx_gcs_session_profile(uint32_t scale, int batch) { + if (scale <= 1u) return 0u; + return 0x6C000000u | (batch ? 0x00800000u : 0u) | scale; +} static inline uint32_t psx_gcs_scale(uint32_t cpu_cycles) { uint64_t t = (uint64_t)cpu_cycles * g_psx_gcs_recip_q16 + g_psx_gcs_frac; g_psx_gcs_frac = (uint32_t)(t & 0xFFFFu); diff --git a/runtime/src/main.cpp b/runtime/src/main.cpp index 9e66770d..4765f44a 100644 --- a/runtime/src/main.cpp +++ b/runtime/src/main.cpp @@ -19631,7 +19631,8 @@ session_reboot: // exactly as before. if (psx_guest_cycle_scale_config() != 1u) { char tag[9]; - std::snprintf(tag, sizeof tag, "%08x", 0x6C000000u | psx_guest_cycle_scale_config()); + std::snprintf(tag, sizeof tag, "%08x", + psx_gcs_session_profile(psx_guest_cycle_scale_config(), g_psx_gcs_batch)); if (std::strlen(net_cfg.content_fingerprint) != 64) std::snprintf(net_cfg.content_fingerprint, sizeof(net_cfg.content_fingerprint), "%s", "0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c00000000"); @@ -19640,8 +19641,8 @@ session_reboot: auto hv = [](char c) { return c <= '9' ? c - '0' : (c | 0x20) - 'a' + 10; }; *d = "0123456789abcdef"[hv(*d) ^ hv(tag[i])]; } - std::printf("psxrecomp: netplay requires guest_cycle_scale %u on every peer\n", - psx_guest_cycle_scale_config()); + std::printf("psxrecomp: netplay requires guest_cycle_scale %u, batch profile %d on every peer\n", + psx_guest_cycle_scale_config(), g_psx_gcs_batch != 0); } const int nrc = psx_netplay_start(&net_cfg); if (nrc != 0) { diff --git a/runtime/tests/test_psx_guest_cycle_scale.c b/runtime/tests/test_psx_guest_cycle_scale.c index 29072e7e..17349008 100644 --- a/runtime/tests/test_psx_guest_cycle_scale.c +++ b/runtime/tests/test_psx_guest_cycle_scale.c @@ -129,6 +129,11 @@ static void check_batch_ignores_host_stack(uint32_t scale, uint32_t charges, uin } int main(void) { + assert(psx_gcs_session_profile(1, 0) == 0u); + assert(psx_gcs_session_profile(1, 1) == 0u); /* no scaled path */ + assert(psx_gcs_session_profile(2, 0) == (0x6C000000u | 2u)); + assert(psx_gcs_session_profile(2, 1) != psx_gcs_session_profile(2, 0)); + assert(psx_gcs_session_profile(3, 1) != psx_gcs_session_profile(2, 1)); assert(psx_gcs_recip_for(1) == 65536u); assert(psx_gcs_recip_for(0) == 65536u); /* never slower than faithful */ assert(psx_gcs_recip_for(2) == 32768u);