Feature/sv demo cinema#13
Open
sedawkgrep wants to merge 9 commits into
Open
Conversation
Allows a server to replay a demo file and stream it to all connected
clients simultaneously, locked to the recorded player's camera.
New command: sv_playdemo <name>
- Server reads the demo's gamestate (configstrings, baselines) and
serves them to spectators via the normal handshake path.
- Each frame the demo reader decodes one snapshot into sv.demoPS and
sv.demoEnts[], which SV_BuildClientSnapshot re-encodes per client
using the existing delta-compression pipeline.
- Every spectator receives sv.demoClientNum as their clientNum so
cgame initializes identically to local demo playback.
- At EOF the server holds the final frame indefinitely (active
keep-alive) with a "Demo playback ended" centerprint.
- A normal "map" command returns to live game mode.
- No game VM is spawned; GAME_CLIENT_CONNECT and GAME_CLIENT_BEGIN
are bypassed for spectators in demo mode.
- sv_autoRecordDemos is suppressed during playback.
New file: code/server/sv_demo_play.cpp
Modified: server.h (replay state in server_t, new declarations)
sv_init.cpp (SV_Startup/ChangeMaxClients/ClearServer un-staticked)
sv_main.cpp (SV_Frame: demo advance branch)
sv_snapshot.cpp (SV_BuildClientSnapshot: demo path)
sv_client.cpp (clientNum override, VM guards, auto-record guard)
sv_ccmds.cpp (sv_playdemo command registration)
meson.build / Makefile (new source file)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Crash: SV_SpawnDemoServer previously called SV_ShutdownGameProgs() before confirming the demo file could be opened. If the file was missing, the function returned early leaving gvm null while sv_running was 1, causing a null-dereference on the next VM_Call in SV_Frame. Fix: open and validate the demo file first. Only tear down the running game after the handle is confirmed. Late-failure paths (corrupt/truncated demo) now call SV_Shutdown to drop clients cleanly instead of returning into a gvm-null server state. The file handle is re-stashed in sv.demoFile after SV_ClearServer (which zeros sv) to avoid a double-close. Path search: also search demos/server/ (where sv_autoRecordDemos writes) in addition to demos/, and print each path attempted on failure so the user knows exactly where to place the file. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
MSG_Init zeroes the entire msg_t including cursize, so the call immediately after SV_DemoReadRawMessage filled the buffer was causing every subsequent read to return -1 and the configstring loop to hit "bad command byte -1". Fix: remove the re-init entirely. After SV_DemoReadRawMessage the cursor is at byte 0; skip the ack long then loop draining any svc_serverCommand entries (as SV_SendClientGameState writes before svc_gamestate), then break on svc_gamestate. The cursor is then positioned at reliableSequence, which is exactly where SV_DemoParseGamestate expects to start reading. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
SV_DemoParseSnapshot was reading command bytes before consuming the 4-byte lastClientCommand ack that opens every demo message, causing the ack's bytes to be misread as command bytes (appearing as 0, 0, 0, 0... 1, 2... and ultimately SIGSEGV when entities failed to decode). Fix: read and discard the ack long before the server-command drain loop, mirroring what SV_SpawnDemoServer already does for the gamestate message. SV_BuildDemoSnapshot was not updating svs.currentStoragePosition after staging entities into snapshotEntities[], so every frame would stage into the same circular buffer slot, corrupting delta references for subsequent frames. Fix: advance currentStoragePosition to the index after the last entity staged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
In demo cinema mode gvm is null (SV_ShutdownGameProgs was called and SV_InitGameProgs is intentionally skipped). Any spectator activity that triggers a game-VM call crashes with a null dereference. Add !sv.demoPlayback guards to every site that was not already protected: - SV_DirectConnect reconnect path: GAME_CLIENT_DISCONNECT on the old slot - SV_DropClient: GAME_CLIENT_DISCONNECT - SV_ClientUserinfoChanged: GAME_CLIENT_USERINFO_CHANGED - SV_ExecuteClientCommand: gvm->forceDataMask read + GAME_CLIENT_COMMAND - SV_ClientThink: GAME_CLIENT_THINK (spectator usercmds are silently dropped) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… mode When the demo stream replays a CS_SERVERINFO configstring update and a spectator is CS_ACTIVE, SV_SetConfigstring iterates connected clients and checks client entity flags via SV_GentityNum(). In demo cinema mode sv.gentities is null (SV_InitGameProgs is intentionally skipped), so SV_GentityNum() computes (null + offset) and dereferences it, causing SIGSEGV. Fix: skip the SVF_NOSERVERINFO check when sv.gentities is null. In demo cinema mode there is no game logic setting that flag, so all CS_ACTIVE spectators should receive every configstring update. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
SV_UpdateConfigstrings had the same SV_GentityNum(clientIndex)->r.svFlags pattern as SV_SetConfigstring. It is called from SV_ClientEnterWorld when a spectator transitions CS_PRIMED→CS_ACTIVE, making it the actual crash site for the signal-11 when a client connected to a demo cinema session. SV_StatusResponse also accessed SV_GameClientNum for every connected client to get PERS_SCORE, with no guard for demo mode where sv.gameClients is null. Guard with sv.gameClients != nullptr and report score 0 for spectators. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Three fixes for sv_playdemo spectator connections: - sv_client.cpp: Guard gvm->forceDataMask in SV_UserinfoChanged with a null check. gvm is null in demo cinema mode (no game VM), so any client connecting caused a null deref before the existing !sv.demoPlayback guard at the VM_Call site was reached. - sv_demo_play.cpp: Cap entsToSend at MAX_SNAPSHOT_ENTITIES (256) in SV_BuildDemoSnapshot before writing into clientSnapshot_t::ents[], which only has 256 slots despite demoNumEnts ranging up to MAX_GENTITIES (1024). - sv_snapshot.cpp: Replace delta_angles view-lock hack with PMF_FOLLOW. The previous approach set delta_angles per snapshot to steer the spectator's predicted viewangles toward the demo's; between snapshots prediction drifted and each arrival snapped back, causing jitter. PMF_FOLLOW makes cgame call CG_InterpolatePlayerState(grabAngles=false) instead, which lerps position and viewangles directly between demo snapshots with no mouse input applied — smooth and jitter-free. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
SV_MapRestart_f calls SV_RestartGameProgs and VM_Call(gvm, ...) directly with no demo guard; since gvm is null during demo playback these crash. map_restart is semantically wrong in demo mode anyway — use 'map <name>' to exit cinema and start a normal game. Print a helpful error instead. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This is a new mode which turns the server into a video player for watching demo files.
sv_playdemo streams the demo to all the clients and will sit in the post-match idle state indefinitely once completed. (so clients don't automatically disconnect)
You can get back to regular gameplay with either map or devmap.