fix netplay

This commit is contained in:
Alex Vanderveen
2026-10-11 16:16:33 -04:00
parent 68425a4add
commit c47f7391e8
7 changed files with 121 additions and 4 deletions
+3 -1
View File
@@ -10,7 +10,9 @@ extern "C" {
#define PSX_LOBBY_ID_LEN 40
#define PSX_LOBBY_NAME_LEN 64
#define PSX_LOBBY_VERSION_LEN 32
/* Title compatibility IDs may contain a prefix and a 160-bit source hash.
* Preserve the full 63-byte ID for exact list/join comparisons. */
#define PSX_LOBBY_VERSION_LEN 64
#define PSX_LOBBY_ENDPOINT_LEN 64
#define PSX_LOBBY_MAX_LIST 32
/* Player seats this framework can seat. Was what PSX_LOBBY_MAX_MEMBERS meant
+5
View File
@@ -354,6 +354,11 @@ void psx_netplay_wait_recv(int timeout_ms);
void psx_netplay_admit_wait_info(char *stall_out, size_t stall_cap,
uint32_t *sim_tick_out, int *lead_out);
/* One-line transport snapshot for a session stuck before RUNNING: ICE state,
* selected path, datagrams received, recv-queue drops and time since the last
* peer datagram. Writes "inactive" when no session exists. */
void psx_netplay_link_diag(char *out, size_t cap);
/* Normalize for stabler cross-device blobs: unknown types become digital;
* centred axes (sticks, wheel, twist) snap to 0x80 inside a deadzone; NeGcon
* pressure bytes (ly/rx/ry) are kept exactly; digital centres everything. */
+5 -1
View File
@@ -118,6 +118,9 @@ endif()
# release when no system package is available, so a default build does not
# silently fall back to SDL2. SDL2 remains an explicit, fully supported A/B
# backend.
# Dev-only: the lobby browser lists every version of this title instead of
# pinning to our exact game_version. Joining still requires an exact match.
option(PSX_LOBBY_DEV_LIST "Lobby browser ignores game_version (dev testing only)" OFF)
set(PSX_SDL_BACKEND "SDL3" CACHE STRING "SDL backend (SDL3 or SDL2)")
set_property(CACHE PSX_SDL_BACKEND PROPERTY STRINGS SDL3 SDL2)
string(TOUPPER "${PSX_SDL_BACKEND}" _psx_sdl_backend)
@@ -2002,7 +2005,8 @@ function(psxrecomp_add_runtime_target target)
# target-wide compile line so Ninja does not rebuild every runtime + shard TU.
set_source_files_properties(
"${PSXRECOMP_ROOT}/runtime/src/psx_lobby_client.c"
PROPERTIES COMPILE_DEFINITIONS "PSX_GAME_VERSION=\"${PSXRT_GAME_VERSION}\""
PROPERTIES COMPILE_DEFINITIONS
"PSX_GAME_VERSION=\"${PSXRT_GAME_VERSION}\";$<$<BOOL:${PSX_LOBBY_DEV_LIST}>:PSX_LOBBY_DEV_LIST=1>"
)
set_source_files_properties(
"${PSXRECOMP_ROOT}/runtime/src/crash_trace.c"
+5
View File
@@ -6803,6 +6803,11 @@ static void netplay_barrier_admit(int override) {
(unsigned long long)(now_ms - clock0),
running ? "" : ", linking");
last_stall_log_ms = now_ms;
if (!running) {
char link[320];
psx_netplay_link_diag(link, sizeof(link));
std::fprintf(stderr, "psxrecomp: netplay link not up: %s\n", link);
}
}
psx_lobby_pump();
if (psx_netplay_input_desync(&dt, &lh, &rh)) {
+10 -1
View File
@@ -1084,6 +1084,9 @@ static const char *effective_game_version(const char *override_ver)
* unofficial / mismatched hosts; join still requires an exact version match. */
static int list_filter_version_strict(void)
{
#ifdef PSX_LOBBY_DEV_LIST
return 0;
#endif
const char *gv = effective_game_version(NULL);
return gv && gv[0] && strcmp(gv, "dev") != 0;
}
@@ -1317,7 +1320,7 @@ static void host_ice_step(void)
v.is_host = g_lc.is_host ? 1 : 0;
v.local_slot = local_member_slot();
v.active = psx_lobby_connected() && g_lc.in_lobby && !g_lc.launch_pending &&
!g_lc.join.local_is_spectator && room_relays_via_ice() &&
!g_lc.ice_rtt_suspended && !g_lc.join.local_is_spectator && room_relays_via_ice() &&
v.local_slot >= 0;
if (v.active) {
if (v.is_host) {
@@ -5492,6 +5495,12 @@ int psx_lobby_request_start(const PsxLobbyMatchCaps *match_caps)
return -1;
}
}
/* The encoder selects the host relay's path from the local preference.
* Keep the waiting-room view on that same path while START is in flight:
* callers construct fresh caps and need not copy relay_via_ice. Clearing
* it here destroys the completed agents before LAUNCH can take them. */
caps_local.relay_via_ice = caps_local.relay_host &&
g_relay_via_ice_pref && rnet_host_ice_available();
g_lc.match_caps = caps_local;
send = &caps_local;
}
+30 -1
View File
@@ -548,6 +548,10 @@ int psx_netplay_input_delay(void) { return 2; }
int psx_netplay_catchup_budget(void) { return 0; }
void psx_netplay_catchup_consume_frame(void) {}
void psx_netplay_wait_recv(int timeout_ms) { (void)timeout_ms; }
void psx_netplay_link_diag(char *out, size_t cap)
{
if (out && cap) snprintf(out, cap, "off");
}
void psx_netplay_admit_wait_info(char *stall_out, size_t stall_cap,
uint32_t *sim_tick_out, int *lead_out)
{
@@ -4378,7 +4382,7 @@ int psx_netplay_start(const PsxNetplayConfig *cfg)
}
}
if (!use_ice) {
if (!use_ice && !ice_hub) {
/* Host-as-relay: slot 0 with 3+ seats and no dial peer. */
#if PSX_MAX_PLAYERS >= 3
const int peer_empty =
@@ -5680,6 +5684,31 @@ void psx_netplay_wait_recv(int timeout_ms)
(void)rnet_session_wait_recv(g_np.session, timeout_ms);
}
void psx_netplay_link_diag(char *out, size_t cap)
{
RNetSessionStats st;
static const char *const k_state[] = {
"idle", "gathering", "connecting", "connected", "completed", "failed"
};
unsigned si;
if (!out || !cap) return;
if (!psx_netplay_active() || !g_np.session) {
snprintf(out, cap, "inactive");
return;
}
memset(&st, 0, sizeof(st));
rnet_session_get_stats(g_np.session, &st);
si = (unsigned)st.ice_state;
snprintf(out, cap,
"ice=%s path=%s local=%s remote=%s rx=%u recv_drops=%u rx_age_ms=%llu",
si < sizeof(k_state) / sizeof(k_state[0]) ? k_state[si] : "?",
st.ice_path[0] ? st.ice_path : "-",
st.ice_local[0] ? st.ice_local : "-",
st.ice_remote[0] ? st.ice_remote : "-",
(unsigned)st.packets_rx, (unsigned)st.ice_recv_drops,
(unsigned long long)st.last_peer_rx_age_ms);
}
void psx_netplay_admit_wait_info(char *stall_out, size_t stall_cap,
uint32_t *sim_tick_out, int *lead_out)
{
+63
View File
@@ -218,6 +218,67 @@ static void case_profile_settings_roundtrip(void)
!strcmp(g_lc.match_caps.mods[0].feats, caps.mods[0].feats), "trusted settings survive the online lobby codec");
}
static void case_full_title_profile_version(void)
{
static char version[64];
static PSXModNetplayProfile profile = {0};
char wire[768];
memset(version, 'v', sizeof(version) - 1);
version[sizeof(version) - 1] = '\0';
profile.plugin_id = "lobby.profile.test";
profile.compatibility_id = version;
ck(psx_mod_register_netplay_profile(&profile), "register maximum-length title profile");
psx_lobby_set_game_identity("Profile Fixture", version);
ck(!strcmp(g_lc.filter_game_version, version), "retain all 63 profile bytes in local metadata");
/* Suppress discovery: this fixture checks the native codec/filter only. */
g_list_rtt_active = 1;
snprintf(wire, sizeof(wire), "{\"op\":\"lobby_list\",\"lobbies\":[{"
"\"lobby_id\":\"profile-room\",\"name\":\"Profile Room\","
"\"game_name\":\"Profile Fixture\",\"game_version\":\"%s\"}]}", version);
handle_server_json(wire);
ck(g_lc.list_count == 1, "matching full profile remains visible");
ck(!strcmp(g_lc.list[0].game_version, version), "room metadata retains full profile");
/* A difference beyond the former 31-byte prefix must still reject. */
char *last = strstr(wire, version);
if (last) last[62] = 'w';
ck(last != NULL, "locate profile in fixture");
handle_server_json(wire);
ck(g_lc.list_count == 0, "reject profile differing only in final byte");
g_list_rtt_active = 0;
}
static void case_start_retains_relay_path(void)
{
PsxLobbyMatchCaps caps = {0}, decoded;
char wire[4096];
int prefer, host;
const int old_pref = g_relay_via_ice_pref;
printf(" START keeps the advertised relay path locally\n");
for (prefer = 0; prefer < 2; ++prefer) {
for (host = 0; host < 2; ++host) {
memset(&g_lc, 0, sizeof(g_lc));
/* An unhandshaken fixture queues START without touching a socket. */
g_lc.connected = g_lc.in_lobby = g_lc.is_host = 1;
g_lc.fd = 0;
caps.valid = 1;
caps.relay_host = host;
caps.relay_via_ice = 0; /* fresh launcher caps, as in the regression */
strcpy(caps.session_bios, "openbios");
psx_lobby_set_relay_via_ice_pref(prefer);
ck(psx_lobby_request_start(&caps) == 0, "queue host START");
ck(psx_lobby_encode_match_caps(wire, sizeof(wire), &caps) &&
psx_lobby_decode_match_caps(wire, &decoded), "decode advertised START caps");
ck(g_lc.match_caps.relay_via_ice == decoded.relay_via_ice,
"local ICE path matches the wire before LAUNCH arrives");
ck(room_relays_via_ice() == (host && prefer && rnet_host_ice_available()),
"waiting-room agents remain active for an ICE host START");
}
}
memset(&g_lc, 0, sizeof(g_lc));
g_lc.fd = -1;
psx_lobby_set_relay_via_ice_pref(old_pref);
}
int main(void)
{
case_room_order();
@@ -226,6 +287,8 @@ int main(void)
case_long_line_is_truncated_not_dropped();
case_mod_caps_roundtrip();
case_profile_settings_roundtrip();
case_full_title_profile_version();
case_start_retains_relay_path();
if (g_failures == 0) {
printf("lobby_chat_ring_test: ok\n");
return 0;