From c6af63ae428a8ef52159c34d49398ecd3a542f90 Mon Sep 17 00:00:00 2001 From: serge_shubin Date: Tue, 24 Mar 2026 00:19:36 +0800 Subject: [PATCH] SNAPFLAG_RESET_ENTITIES: no-interpolation reset without gamestate resend MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the gamestate resend approach for map_restart with a custom snapshot flag (SNAPFLAG_RESET_ENTITIES, bit 4 in snapFlags byte). Server side (sv_netdemo.c): - On restart frame, OR the flag into svs.snapFlagServerBit (one-shot) - Cleared at the start of the next playback frame Client side (cg_snapshot.c): - CG_SetNextSnap: clear currentValid for all entities when flag is set, making them all "new" — existing interpolation check at line 228 sets interpolate=qfalse, CG_TransitionEntity calls CG_ResetEntity - Also set cg.nextFrameTeleport=qtrue to prevent playerstate interpolation during follow mode No loading screen, no lost frames, no gamestate resend. Entities and playerstate both snap to correct positions on map_restart. Co-Authored-By: Claude Opus 4.6 (1M context) --- code/cgame/cg_snapshot.c | 14 ++++++++++++++ code/game/q_shared.h | 1 + code/server/server.h | 1 - code/server/sv_netdemo.c | 19 +++++++------------ 4 files changed, 22 insertions(+), 13 deletions(-) diff --git a/code/cgame/cg_snapshot.c b/code/cgame/cg_snapshot.c index add49f0..6bc3a0c 100644 --- a/code/cgame/cg_snapshot.c +++ b/code/cgame/cg_snapshot.c @@ -203,6 +203,15 @@ static void CG_SetNextSnap( snapshot_t *snap ) { cg.nextSnap = snap; + // SNAPFLAG_RESET_ENTITIES: invalidate all entities so they are + // treated as new (no interpolation from old positions). + // Must happen before the entity loop below. + if ( snap->snapFlags & SNAPFLAG_RESET_ENTITIES ) { + for ( num = 0 ; num < MAX_GENTITIES ; num++ ) { + cg_entities[ num ].currentValid = qfalse; + } + } + BG_PlayerStateToEntityState( &snap->ps, &cg_entities[ snap->ps.clientNum ].nextState, qfalse ); cg_entities[ cg.snap->ps.clientNum ].interpolate = qtrue; @@ -241,6 +250,11 @@ static void CG_SetNextSnap( snapshot_t *snap ) { cg.nextFrameTeleport = qtrue; } + // entity reset also prevents playerstate interpolation + if ( cg.nextSnap->snapFlags & SNAPFLAG_RESET_ENTITIES ) { + cg.nextFrameTeleport = qtrue; + } + // sort out solid entities CG_BuildSolidList(); } diff --git a/code/game/q_shared.h b/code/game/q_shared.h index 99e5a67..4834c94 100644 --- a/code/game/q_shared.h +++ b/code/game/q_shared.h @@ -1085,6 +1085,7 @@ typedef enum { #define SNAPFLAG_RATE_DELAYED 1 #define SNAPFLAG_NOT_ACTIVE 2 // snapshot used during connection and for zombies #define SNAPFLAG_SERVERCOUNT 4 // toggled every map_restart so transitions can be detected +#define SNAPFLAG_RESET_ENTITIES 16 // snap all entities to current position, no interpolation // // per-level limits diff --git a/code/server/server.h b/code/server/server.h index 328289a..02f8ae8 100644 --- a/code/server/server.h +++ b/code/server/server.h @@ -289,7 +289,6 @@ void SV_AuthorizeIpPacket( netadr_t from ); void SV_ExecuteClientMessage( client_t *cl, msg_t *msg ); void SV_UserinfoChanged( client_t *cl ); -void SV_SendClientGameState( client_t *client ); void SV_ClientEnterWorld( client_t *client, usercmd_t *cmd ); void SV_DropClient( client_t *drop, const char *reason ); diff --git a/code/server/sv_netdemo.c b/code/server/sv_netdemo.c index 090a74b..8a5414a 100644 --- a/code/server/sv_netdemo.c +++ b/code/server/sv_netdemo.c @@ -677,22 +677,14 @@ static qboolean SVD_ReadFrame( fileHandle_t f ) { byte frameFlags; FS_Read( &frameFlags, 1, f ); if ( frameFlags & 1 ) { - int j; // map was restarted — reset playback delta state to match // the recording's reset (both now decode from zero baseline). - // Also toggle server bit and force non-delta snapshot so the - // client clears old entity state and doesn't interpolate. Com_Memset( demo.playPrevEntities, 0, sizeof(demo.playPrevEntities) ); Com_Memset( demo.playPrevPlayers, 0, sizeof(demo.playPrevPlayers) ); - svs.snapFlagServerBit ^= SNAPFLAG_SERVERCOUNT; - // send a full gamestate to all active clients — this makes - // CL_ParseGamestate → CL_ClearState wipe all snapshot/entity - // history, exactly like a real map_restart does. - for ( j = 0; j < sv_maxclients->integer; j++ ) { - if ( svs.clients[j].state >= CS_PRIMED ) { - SV_SendClientGameState( &svs.clients[j] ); - } - } + // set one-shot SNAPFLAG_RESET_ENTITIES so cgame snaps all + // entities to current position without interpolation. + // OR'd into snapFlagServerBit, cleared after one frame. + svs.snapFlagServerBit |= SNAPFLAG_RESET_ENTITIES; } } @@ -1020,6 +1012,9 @@ qboolean SVD_PlaybackFrame( void ) { demo.needConfigstrings = qfalse; } + // clear one-shot reset flag from previous frame before reading new one + svs.snapFlagServerBit &= ~SNAPFLAG_RESET_ENTITIES; + if ( !SVD_ReadFrame( demo.playFile ) ) { Com_Printf( "Server demo playback finished.\n" ); SVD_CleanupPlayback();