From d0a4310bade4d1f65f9b8ac59bc0d1178d6f17fe Mon Sep 17 00:00:00 2001 From: serge_shubin Date: Tue, 24 Mar 2026 19:38:22 +0800 Subject: [PATCH] Add svdemo_seekexact for precise seeking with read-forward MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit svdemo_seek snaps to the nearest keyframe (fast, ±interval accuracy). svdemo_seekexact reads forward from the keyframe to the exact target time, giving frame-accurate positioning at the cost of a brief processing delay. Co-Authored-By: Claude Opus 4.6 (1M context) --- code/server/server.h | 1 + code/server/sv_ccmds.c | 1 + code/server/sv_netdemo.c | 70 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 72 insertions(+) diff --git a/code/server/server.h b/code/server/server.h index ec44870..6d59686 100644 --- a/code/server/server.h +++ b/code/server/server.h @@ -356,6 +356,7 @@ void SVD_CleanupPlayback( void ); void SVD_Stop_f( void ); void SVD_Pause_f( void ); void SVD_Seek_f( void ); +void SVD_SeekExact_f( void ); qboolean SVD_PlaybackFrame( void ); qboolean SVD_IsRecording( void ); qboolean SVD_IsPlaying( void ); diff --git a/code/server/sv_ccmds.c b/code/server/sv_ccmds.c index 3e4df9b..7f1370f 100644 --- a/code/server/sv_ccmds.c +++ b/code/server/sv_ccmds.c @@ -746,6 +746,7 @@ void SV_AddOperatorCommands( void ) { Cmd_AddCommand ("svdemo_play", SVD_Play_f); Cmd_AddCommand ("svdemo_pause", SVD_Pause_f); Cmd_AddCommand ("svdemo_seek", SVD_Seek_f); + Cmd_AddCommand ("svdemo_seekexact", SVD_SeekExact_f); } /* diff --git a/code/server/sv_netdemo.c b/code/server/sv_netdemo.c index 3409341..f443f05 100644 --- a/code/server/sv_netdemo.c +++ b/code/server/sv_netdemo.c @@ -1104,6 +1104,76 @@ void SVD_Seek_f( void ) { Com_Printf( "Seeked to time %d.\n", svs.time ); } +void SVD_SeekExact_f( void ) { + int targetTime, i, bestKf; + float seconds; + + if ( !demo.playing ) { + Com_Printf( "Not playing a server demo.\n" ); + return; + } + + if ( Cmd_Argc() < 2 ) { + Com_Printf( "Usage: svdemo_seekexact \n" ); + return; + } + + if ( demo.numKeyframes <= 0 ) { + Com_Printf( "No keyframes in this demo.\n" ); + return; + } + + seconds = atof( Cmd_Argv(1) ); + targetTime = svs.time + (int)(seconds * 1000); + + // find nearest keyframe at or before target time + bestKf = -1; + for ( i = 0; i < demo.numKeyframes; i++ ) { + if ( demo.keyframeTimes[i] <= targetTime ) { + bestKf = i; + } else { + break; + } + } + + if ( bestKf < 0 ) { + bestKf = 0; + targetTime = demo.keyframeTimes[0]; + } + + // seek to keyframe + FS_Seek( demo.playFile, demo.keyframeOffsets[bestKf], FS_SEEK_SET ); + Com_Memset( demo.playPrevEntities, 0, sizeof(demo.playPrevEntities) ); + Com_Memset( demo.playPrevPlayers, 0, sizeof(demo.playPrevPlayers) ); + svs.time = demo.keyframeTimes[bestKf]; + svs.snapFlagServerBit ^= SNAPFLAG_SERVERCOUNT; + demo.seeked = qtrue; + demo.endOfDemo = qfalse; + + // read forward from keyframe to target time + while ( svs.time < targetTime ) { + if ( !SVD_ReadFrame( demo.playFile ) ) { + demo.endOfDemo = qtrue; + break; + } + } + + // reset client snapshot timing + { + int j; + for ( j = 0; j < sv_maxclients->integer; j++ ) { + if ( svs.clients[j].state >= CS_ACTIVE ) { + svs.clients[j].nextSnapshotTime = svs.time; + } + } + } + + sv.timeResidual = 1000 / sv_fps->integer; + + Com_Printf( "Seeked to time %d (read forward %d ms from keyframe).\n", + svs.time, svs.time - demo.keyframeTimes[bestKf] ); +} + /* Called from SV_Frame() to advance playback by one frame. Returns qtrue if a frame was read, qfalse if demo ended.