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>
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) <noreply@anthropic.com>
sv_demoplaying is set before devmap, synced during G_RegisterCvars,
and updated every frame by G_UpdateCvars. No need for defensive
trap_Cvar_VariableIntegerValue calls which add unnecessary syscall
overhead on every invocation.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Three issues fixed:
1. ClientThink called ClientThink_real during demo playback, which
ran server-side PmoveSingle and overwrote the client-owned
ps.origin. Now ClientThink returns early in demo mode after
updating pers.cmd (for button access).
2. Buttons never reached the server while paused: SV_UserMove
discards usercmds with duplicate serverTime (line 1421). During
pause, all usercmds have frozen serverTime. Fix: always process
the last usercmd in the packet during demo playback.
3. Spectator button handling in G_RunFrame demo mode processes
MOUSE1 (attack) to cycle follow targets via Cmd_FollowCycle_f.
Removed debug print.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
cgame runs its own PmoveSingle for the free camera using real time,
independent of server time. Solves pause freezing — camera moves
smoothly while svs.time is frozen.
Changes:
- usercmd_t: add optional origin[3] with 1-bit flag (zero cost for
normal gameplay, 13 bytes during demo viewing)
- msg.c: serialize optional origin in usercmd delta encoding
- CG_SETCLIENTORIGIN trap: cgame sends camera origin to engine
- cl_input.c: CL_FinishMove writes cgame origin into usercmd
- cg_predict.c: CG_PredictPlayerState runs local PmoveSingle for
free camera in svDemo mode, sends origin via trap for PVS
- cg_snapshot.c: detect follow→free camera transition, init camera
from last known position
- cg_main.c: detect svDemo mode from CS_SVDEMO configstring,
lazy-init camera from first snapshot's spectator spawn
- g_main.c: G_InitGame sets CS_SVDEMO configstring, G_RunFrame
copies cmd.origin to ps.origin for PVS instead of ClientThink_real
- bg_public.h: CS_SVDEMO configstring index (26)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Previously all team changes were blocked during demo playback.
Now 'team spectator' (or 'team s') is allowed — it calls
StopFollowing to return to free camera. All other team changes
remain blocked.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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>
- Set sv_demoplaying before devmap so game module knows during
ClientConnect/ClientBegin
- Call ClientUserinfoChanged after forcing spectator team so
CS_PLAYERS configstring has correct team value for cgame
- Record sanitized playerState for spectators (pm_type=PM_SPECTATOR,
PERS_TEAM=TEAM_SPECTATOR) so they show correctly on scoreboard
instead of appearing as regular players from follow-mode corruption
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Prevent spectators from joining a playing team during demo
playback, which would disrupt the playback state. Prints a
message to the client instead.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Call CalculateRanks and ClientEndFrame for the spectator in demo
mode G_RunFrame. SpectatorClientEndFrame copies the followed
player's recorded playerState into the spectator's ps, giving
first-person view with full HUD (health, armor, ammo, weapon).
Standard spectator controls work: mouse1 to cycle, mouse2 to
enter follow from free camera.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Record delta-compressed playerState_t for each active player per
frame using MSG_WriteDeltaPlayerstate. During playback, inject
into game module via SV_GameClientNum and mark players as
CON_CONNECTED with correct team. CalculateRanks and the
scoreboard now show recorded players with scores.
This also lays the groundwork for player-follow spectating.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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>
Add full QL-style PM_StepSlideMove with:
- Master gate: trace from start to final position filters micro-bumps
- Projected position validation: trace down at where player would be
without collision, rejects walls (startsolid) and confirms stairs
- Air-step friction: 3% horizontal penalty on airborne step-ups
- PM_Jump() call when all gates pass on valid stair geometry
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Two changes to PM_StepSlideMove that transform stair traversal:
1. Remove Q3 velocity[2]>0 gate (QL pm_airSteps): allow step-ups
while airborne, enabling bunny-hop stair traversal.
2. Conditional velocity clip: only clip velocity to step-down surface
when moving INTO it (dot product < 0). Skip clip when velocity is
moving away (dot >= 0), preserving upward momentum through steps.
This is THE key mechanic for smooth QL-style stair hopping.
Also adds 100ms jump cooldown (lastJumpTime) to prevent same-frame
double-fires during rapid step-ups.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove the PMF_JUMP_HELD gate from PM_CheckJump so players can
hold jump to bunny hop continuously without releasing between
hops. The existing Pmove() outer loop already forces upmove=20
when PMF_JUMP_HELD is set, making this the only change needed.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Separate the jump execution (velocity, event, animation) from the
gate logic (respawn check, upmove threshold, jump-held check).
This prepares the code for QL features that need to trigger jumps
from contexts other than the normal ground jump path (step jump,
double jump, etc). No behavior change.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Debug TA and Release TA configs now copy their output DLLs to
workdir/missionpack/ instead of workdir/baseq3/, matching the
Q3 Team Arena directory layout. MISSIONPACK was already defined
for TA configs; this completes the baseline TA setup.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>