Commit graph

13 commits

Author SHA1 Message Date
d0a4310bad Add svdemo_seekexact for precise seeking with read-forward
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) <noreply@anthropic.com>
2026-03-24 19:38:22 +08:00
628007ec57 Keyframe recording and seeking
Recording:
- svdemo_keyframeInterval cvar (default 5 seconds, 0 = disabled)
- Periodic delta state reset produces full-state frames
- Frame flag bit 1 marks keyframes for reader to reset delta state
- Keyframe index (time + file offset) written at end of file
  with numKf_copy trailer for efficient playback loading
- Dynamic allocation for unlimited keyframe count

Playback:
- Keyframe index loaded from file footer on playback start
- svdemo_seek <seconds>: relative seek from current time
  Finds nearest keyframe at or before target, seeks file position,
  resets delta state + svs.time, sets SNAPFLAG_RESET_ENTITIES
  via demo.seeked flag (one-shot on next frame read)
- Normal keyframe frames only reset delta state (no visual glitch)
- Map restart frames also set SNAPFLAG_RESET_ENTITIES

Demo format: keyframe flag (bit 1) added to frame flags byte.
Backward compatible — old demos have no keyframes (numKf=0).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 18:55:44 +08:00
f871cc004f Fix svdemo_play from in-game and devmap from demo playback
Multiple issues fixed for seamless transitions:

Server lifecycle:
- SV_Shutdown before devmap in SVD_Play_f: drops old clients so
  spectator doesn't land in slot 0 (recorded player collision)
- SVD_IsStarting flag prevents cleanup hooks from destroying demo
  state during our own SV_Shutdown/SV_SpawnServer calls
- SV_SpawnServer stops demo playback on non-demo map changes
  via SVD_CleanupPlayback (no disconnect, just state cleanup)
- SVD_CleanupPlayback made non-static for use from sv_init.c

Cvar handling:
- Use Cvar_Set2 with force=qtrue for CVAR_ROM sv_demoplaying
- Set cvar AFTER SV_Shutdown (old game module gone) but BEFORE
  devmap (so new G_InitGame reads correct value)
- Set CS_SVDEMO configstring after devmap as backup for cgame

Game module:
- ClientBegin: set pm_type=PM_SPECTATOR after ClientSpawn (which
  memsets ps). ClientThink_real normally sets this but is disabled
  in demo mode.
- G_WriteSessionData: skip during demo playback so forced
  TEAM_SPECTATOR doesn't persist to next normal game
- ClientThink: return early in demo mode (no server-side movement)

Removed debug prints and unused SVD_GetPlayMaxClients.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 06:32:08 +08:00
7141d941a3 Demo pause: freeze svs.time, hold demo data consumption
svdemo_pause toggles playback pause. When paused:
- svs.time frozen so entity trajectories freeze correctly
- No demo frames consumed (SVD_PlaybackFrame returns early)
- Game frame still runs at frozen time for spectator movement
- No time jump on unpause — svs.time resumes from where it was

Spectator movement degrades during pause (200ms PmoveSingle cap)
— will be resolved by client-owned camera in a future change.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 03:28:32 +08:00
c6af63ae42 SNAPFLAG_RESET_ENTITIES: no-interpolation reset without gamestate resend
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) <noreply@anthropic.com>
2026-03-24 00:19:36 +08:00
300e7c431a Pause demo playback when no spectators are watching
svdemo_pauseEmpty (default: 1) pauses frame processing when no
client is CS_ACTIVE during demo playback. Prevents the demo from
advancing with nobody watching. Time residual is cleared so the
demo resumes from where it paused when a spectator connects.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 06:25:16 +08:00
b79eb77bb3 Remove spectatorClientNum hack
spectatorClientNum was hardcoded to MAX_CLIENTS-1 but the spectator
actually connected at a different slot. All skip checks using it
were no-ops protecting the wrong slot. Removed entirely:

- Zombie slots handle player slot reservation
- G_RunFrame recreates the spectator entity via ClientThink_real
- PlayerState injection writes all slots (spectator's ps gets
  overwritten by ClientThink_real anyway)
- SVD_SpectatorClientNum accessor removed

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 06:20:52 +08:00
4d89d35a6d Send gamestate resend on map_restart (matches real server behavior)
A real map_restart sends SV_SendClientGameState to all clients,
which triggers CL_ParseGamestate → CL_ClearState on the client,
wiping all snapshot and entity history. Previously we only forced
non-delta snapshots which doesn't clear cgame's entity state.

Now we call SV_SendClientGameState for all active clients on the
restart frame, exactly matching real server behavior.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 06:15:31 +08:00
4f0d46024b Record and replay broadcast server commands (chat, prints)
Capture broadcast server commands (chat, print, cp, etc.) from
SV_SendServerCommand when cl==NULL. Buffer up to 64 commands per
frame. Written after configstrings in the demo file, replayed to
the spectator client during playback.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 05:41:18 +08:00
e58414f564 Auto-record demos with svdemo_autorecord cvar
Set svdemo_autorecord 1 to automatically record a demo on every
map load. Demo files are named <mapname>_YYYYMMDD_HHMMSS.svdm
in the svdemos/ directory.

Refactored SVD_Record_f to use SVD_StartRecording helper so both
manual and auto recording share the same code path. Also fixed
prevPlayers delta state not being cleared on recording start.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 05:11:27 +08:00
a1167ff398 Fix 8 netdemo bugs found in code review
1. File handle leak: SVD_Play_f opened file twice, first handle leaked.
   Fix: memset demo state before opening.

2. svdemo_stop now handles both recording and playback via SVD_Stop_f.
   Playback stop disconnects client to return to menu.

3. Zombie client timeout: skip SV_CheckTimeouts during playback so
   reserved player slots aren't freed.

4. Buffer overflow: increase entity buffer to MAX_GENTITIES*300 and
   playerState buffer to MAX_CLIENTS*600 for worst-case first frame.
   Made static to avoid stack overflow.

5. svs.time jump: don't overwrite svs.time with recorded time.
   Server time advances normally, avoiding timeout/heartbeat issues.

6. map_restart: SVD_ResetDeltaState clears entity/player delta state
   so next frame writes full states, preventing corrupt deltas.

7. Demo end and manual stop both disconnect the client.

8. SV_Shutdown cleans up active recording/playback.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 05:03:33 +08:00
a8044aad8b Server-side demo recording and playback (netdemo)
Records full entity state array each server frame, enabling
free-camera demo playback from any viewpoint.

Recording:
- svdemo_record <name> / svdemo_stop
- Captures entityState_t + PVS fields (svFlags, linked,
  currentOrigin, absmin, absmax) for all active entities
- Records configstring changes per frame
- File format: svdemos/<name>.svdm

Playback:
- svdemo_play <name>
- Loads map with maxclients=64, reserves recorded player slots
  (CS_ZOMBIE with safe rate/timing) so spectator gets a high slot
- Injects recorded entities into sv.gentities each frame with
  SV_LinkEntity for PVS visibility
- Re-applies demo configstrings (CS_PLAYERS etc.) after map load,
  skipping CS_SERVERINFO/CS_SYSTEMINFO to avoid latch restarts
- Game module runs in demo mode (sv_demoplaying cvar): G_RunFrame
  only processes spectator movement, skips all entity logic
- Spectator forced to TEAM_SPECTATOR on connect (ClientBegin)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 04:28:55 +08:00
Sergei Shubin
4c57221941 Initial commit: Quake 3 1.32b GPL source 2026-03-18 13:32:24 +08:00