From fe628a2cc4e8ba5b31fd5797ed452f5c35bbea87 Mon Sep 17 00:00:00 2001 From: serge_shubin Date: Tue, 24 Mar 2026 05:00:21 +0800 Subject: [PATCH] Remove PVS data from demo format, derive during playback Stop recording currentOrigin, absmin, absmax, linked per entity (44 bytes per moving entity per frame). During playback, G_RunFrame computes currentOrigin via BG_EvaluateTrajectory and calls trap_LinkEntity to register in BSP for PVS. svFlags still recorded (1-bit change flag + 4 bytes when changed). Entity linking moved from SVD_ReadFrame (server, no trajectory eval) to G_RunFrame demo mode (game module, has BG_EvaluateTrajectory). Scan all MAX_GENTITIES during playback since recorded entities may have indices above level.num_entities (game-module-spawned count). Demo format bumped to v3. Significant file size reduction. Co-Authored-By: Claude Opus 4.6 (1M context) --- code/game/g_main.c | 21 ++++++++++++ code/server/sv_netdemo.c | 71 ++++++++-------------------------------- 2 files changed, 34 insertions(+), 58 deletions(-) diff --git a/code/game/g_main.c b/code/game/g_main.c index 43f8d9d..117f6d7 100644 --- a/code/game/g_main.c +++ b/code/game/g_main.c @@ -1792,6 +1792,27 @@ int start, end; } } + // evaluate trajectories and link all recorded entities for PVS. + // SVD_ReadFrame injected entityState_t but didn't link because + // the server doesn't have BG_EvaluateTrajectory. + // scan up to MAX_GENTITIES because recorded entities may exceed + // level.num_entities (which only counts game-module-spawned entities). + for ( i = 0; i < MAX_GENTITIES; i++ ) { + ent = &g_entities[i]; + if ( ent->s.eType == 0 && !ent->inuse ) { + continue; + } + // skip spectator slot + if ( ent->client && ent->client->pers.connected == CON_CONNECTED + && ent->client->sess.sessionTeam == TEAM_SPECTATOR ) { + continue; + } + // compute currentOrigin from trajectory + BG_EvaluateTrajectory( &ent->s.pos, level.time, ent->r.currentOrigin ); + ent->r.linked = qtrue; + trap_LinkEntity( ent ); + } + // update rankings so follow mode can cycle through players CalculateRanks(); diff --git a/code/server/sv_netdemo.c b/code/server/sv_netdemo.c index a65752f..0f2c441 100644 --- a/code/server/sv_netdemo.c +++ b/code/server/sv_netdemo.c @@ -51,7 +51,7 @@ snapshot pipeline delivers them to a spectator client. // #define SVDEMO_MAGIC (('S') | ('V' << 8) | ('D' << 16) | ('M' << 24)) -#define SVDEMO_VERSION 2 // v2: optional LZ4 compression +#define SVDEMO_VERSION 3 // v3: removed PVS data, svFlags only #define SVDEMO_MAX_MAPNAME 64 // header flags @@ -61,14 +61,10 @@ snapshot pipeline delivers them to a spectator client. // State // --------------------------------------------------------------- -// per-entity data stored for delta compression (entityState + PVS fields) +// per-entity data stored for delta compression typedef struct { entityState_t es; int svFlags; - qboolean linked; - vec3_t currentOrigin; - vec3_t absmin; - vec3_t absmax; qboolean active; // was this entity present last frame? } svdEntityState_t; @@ -275,38 +271,17 @@ static void SVD_WriteFrame( fileHandle_t f ) { } MSG_WriteDeltaEntity( &msg, from, &ent->s, qtrue ); - // write PVS fields only if changed from previous frame - { - qboolean pvsChanged = !demo.prevEntities[i].active - || ent->r.svFlags != demo.prevEntities[i].svFlags - || ent->r.linked != demo.prevEntities[i].linked - || !VectorCompare( ent->r.currentOrigin, demo.prevEntities[i].currentOrigin ) - || !VectorCompare( ent->r.absmin, demo.prevEntities[i].absmin ) - || !VectorCompare( ent->r.absmax, demo.prevEntities[i].absmax ); - - MSG_WriteBits( &msg, pvsChanged, 1 ); - if ( pvsChanged ) { - MSG_WriteLong( &msg, ent->r.svFlags ); - MSG_WriteLong( &msg, ent->r.linked ); - MSG_WriteFloat( &msg, ent->r.currentOrigin[0] ); - MSG_WriteFloat( &msg, ent->r.currentOrigin[1] ); - MSG_WriteFloat( &msg, ent->r.currentOrigin[2] ); - MSG_WriteFloat( &msg, ent->r.absmin[0] ); - MSG_WriteFloat( &msg, ent->r.absmin[1] ); - MSG_WriteFloat( &msg, ent->r.absmin[2] ); - MSG_WriteFloat( &msg, ent->r.absmax[0] ); - MSG_WriteFloat( &msg, ent->r.absmax[1] ); - MSG_WriteFloat( &msg, ent->r.absmax[2] ); - } + // write svFlags only if changed (rarely changes) + if ( ent->r.svFlags != demo.prevEntities[i].svFlags ) { + MSG_WriteBits( &msg, 1, 1 ); + MSG_WriteLong( &msg, ent->r.svFlags ); + } else { + MSG_WriteBits( &msg, 0, 1 ); } // update prev state demo.prevEntities[i].es = ent->s; demo.prevEntities[i].svFlags = ent->r.svFlags; - demo.prevEntities[i].linked = ent->r.linked; - VectorCopy( ent->r.currentOrigin, demo.prevEntities[i].currentOrigin ); - VectorCopy( ent->r.absmin, demo.prevEntities[i].absmin ); - VectorCopy( ent->r.absmax, demo.prevEntities[i].absmax ); demo.prevEntities[i].active = qtrue; } else if ( demo.prevEntities[i].active ) { // entity was removed — write a remove marker @@ -734,40 +709,20 @@ static qboolean SVD_ReadFrame( fileHandle_t f ) { continue; } - // read PVS fields - { - qboolean pvsChanged = MSG_ReadBits( &msg, 1 ); - if ( pvsChanged ) { - demo.playPrevEntities[entNum].svFlags = MSG_ReadLong( &msg ); - demo.playPrevEntities[entNum].linked = MSG_ReadLong( &msg ); - demo.playPrevEntities[entNum].currentOrigin[0] = MSG_ReadFloat( &msg ); - demo.playPrevEntities[entNum].currentOrigin[1] = MSG_ReadFloat( &msg ); - demo.playPrevEntities[entNum].currentOrigin[2] = MSG_ReadFloat( &msg ); - demo.playPrevEntities[entNum].absmin[0] = MSG_ReadFloat( &msg ); - demo.playPrevEntities[entNum].absmin[1] = MSG_ReadFloat( &msg ); - demo.playPrevEntities[entNum].absmin[2] = MSG_ReadFloat( &msg ); - demo.playPrevEntities[entNum].absmax[0] = MSG_ReadFloat( &msg ); - demo.playPrevEntities[entNum].absmax[1] = MSG_ReadFloat( &msg ); - demo.playPrevEntities[entNum].absmax[2] = MSG_ReadFloat( &msg ); - } + // read svFlags + if ( MSG_ReadBits( &msg, 1 ) ) { + demo.playPrevEntities[entNum].svFlags = MSG_ReadLong( &msg ); } demo.playPrevEntities[entNum].es = newEs; demo.playPrevEntities[entNum].active = qtrue; - // apply to server entity + // apply to server entity (entity linking done in G_RunFrame + // which has BG_EvaluateTrajectory for computing currentOrigin) ent = SV_GentityNum( entNum ); ent->s = newEs; ent->s.number = entNum; ent->r.svFlags = demo.playPrevEntities[entNum].svFlags; - ent->r.linked = demo.playPrevEntities[entNum].linked; - VectorCopy( demo.playPrevEntities[entNum].currentOrigin, ent->r.currentOrigin ); - VectorCopy( demo.playPrevEntities[entNum].absmin, ent->r.absmin ); - VectorCopy( demo.playPrevEntities[entNum].absmax, ent->r.absmax ); - - if ( ent->r.linked ) { - SV_LinkEntity( ent ); - } if ( entNum + 1 > sv.num_entities ) { sv.num_entities = entNum + 1;