On this page
  1. Related
  2. Mechanics
  3. References
  4. Sources

The atlas reads Quake III Arena through the source id Software released under the GPL, which names itself version 1.321,1, the 2002 point release, so every claim here describes 1.32 rather than the 1999 retail build. The player’s movement is one file the server and the client’s prediction share.

On the ground the rules are Quake’s with a stronger friction: an acceleration of 10 against a friction of 6, so a run settles at exactly 320 units/s and stops from it in about 0.36 s (computed)2,3. The move keys are scaled so that a strafe added to a forward run wishes no more than 320, which removes the diagonal gain Quake’s walk had4.

The air rule reverses Quake’s. The push is a tenth of the ground’s, but it stops only when the speed along the wish direction reaches the full 320, not 305. Holding the wish a few degrees off the motion, wider as speed rises, adds about 317 units/s each second at 320 (computed), roughly three times Quake’s rate at 72 frames a second, and nearly the same at any frame rate; this is the strafe jump6. The jump sets the vertical velocity to 270 units/s7,7, and it’s checked before friction, so a bunny hop timed to the first ground step loses nothing to the floor, as in QuakeWorld8.

Mechanics

MechanicKindHow
RunningMomentumacceleration 10 against a friction of 6, up from Quake's 4, so a run still settles at exactly 320 but stops sooner
Strafe-runningAngled air strafeon the ground the diagonal is scaled back to 320; in the air the push is weak but capped only at the full wish speed, so the gain is biggest with the wish a few degrees off the motion
Bunny hoppingFrictionlessthe jump is checked before friction, so a hop on the first ground step keeps all its speed, as in QuakeWorld
JumpOne speedthe vertical velocity is set to 270, not added to, and only after the key has been released since the last jump
Air controlMomentumthe move keys push against the speed carried at a tenth of the ground acceleration, up to the full wish speed along the wish direction
Every claim about this game, by concept (8)
generalid Software's GPL source release of Quake III Arena names its version "Q3 1.32b" (Q3_VERSION in q_shared.h), commented "1.32 released 7-10-2002", so the code read here is the 1.32 point release rather than the 1999 retail build.Code
Bunny hoppingQuake III checks the jump before friction: each step, PmoveSingle traces for ground before the move, PM_WalkMove calls PM_CheckJump before PM_Friction, and a jump clears the walking flag and hands the step to PM_AirMove, whose friction call skips ground friction without that flag. So a hop on the first step that starts on the ground loses no speed to friction, as in QuakeWorld (quake/qw-hop-friction), where WinQuake's takes one frame of it, 5.6% at 72 frames a second (quake/hop-friction). The hop needs the jump key released and pressed again in the air, as in both.Code
JumpQuake III's jump is in the shared movement code: PM_CheckJump, called from PM_WalkMove (so only on walkable ground), needs an up move of at least 10 and the jump-held flag clear, which PmoveSingle clears only on a step whose up move is under 10, so holding the key never jumps twice; it then sets the vertical velocity to JUMP_VELOCITY (270), where Quake's QuakeC adds 270 to it (quake/jump). Gravity is g_gravity, 800, as in Quake. A landing from a fall faster than 200 units/s sets a land timer of 250 ms that a comment says is to "not allow another jump for a little while", but PM_CheckJump never tests it.Code
RunningOn the ground, Quake III's PM_Friction takes the horizontal speed and lowers it by step time × pm_friction × the larger of the speed and pm_stopspeed, scaling all three velocity axes by the same factor, with pm_friction 6 and pm_stopspeed 100 set in the code; it skips ground friction in water deeper than level 1, on a slick surface, and while the knockback timer runs, and it has no edge friction. Quake's ground friction is the same rule with friction 4 (sv_friction) and the same stop speed, doubled over a ledge (quake/ground-friction).Code
Quake III's PM_WalkMove runs friction and then PM_Accelerate with pm_accelerate 10, which is Quake's rule (Quake III's PM_Accelerate comment says "q2 style"): if the speed along the wish direction is under the wish speed, it adds 10 × step time × wish speed along that direction, no more than the shortfall. The full wish speed is the player's speed, set before each move from g_speed (320, × 1.3 with Haste). Computed, not measured: since 10 outpaces the friction of 6, a run settles at exactly 320, as Quake's does at 10 against 4; at 8 ms steps it reaches 320 from rest in 19 steps (0.15 s) and stops from 320 in 45 (0.36 s). These figures leave out the rounding trap_SnapVector applies to the velocity after each step, engine code the atlas hasn't read.Code
Strafe-runningQuake III's keyboard moves are 127 per axis at a run (the speed key or cl_run, one but not both) and 64 at a walk, and PM_CmdScale scales the wish so its length is the player's speed × the largest of the forward, side, and up moves ÷ 127, which a comment says avoids "a sqrt(2) distortion in speed": at a run, forward, a strafe, and both together each wish 320, so a strafe adds no ground speed, where Quake's walk with a strafe wishes 320 against 200 (quake/move-speeds). The up move counts too: computed, holding the jump key (up move 127) in the air with forward and a strafe wishes 320 × √2/√3 ≈ 261. On the ground PM_CheckJump zeroes a held jump's up move, and Pmove lowers it to 20 for the later steps of a command it splits. The figures leave out the rounding trap_SnapVector applies to the velocity after each step, engine code the atlas hasn't read.Code
In the air, Quake III's PM_AirMove runs friction (no ground friction, which needs the walking flag), builds a flat wish velocity, and calls the same PM_Accelerate as the ground with pm_airaccelerate 1: the push is 1 × step time × wish speed (2.56 units/s per 8 ms step at 320), and it stops only when the velocity's component along the wish direction reaches the full wish speed. Quake's air rule is the reverse: a push ten times as strong (10 × 320 × frame time), cut off when that component reaches 30 (quake/air-accelerate). An #else branch left out by #if 1 would push the velocity toward the wish velocity instead, commented "proper way (avoids strafe jump maxspeed bug), but feels bad". The figures leave out the rounding trap_SnapVector applies to the velocity after each step, engine code the atlas hasn't read.Code
Computed from PM_Accelerate with pm_airaccelerate 1, not measured: at a wish speed W of 320 (forward and a strafe at a run, the jump key up, no Haste) the air push is W × step time, a = 2.56 per 8 ms step, and the cap leaves room for all of it while the velocity's component c along the wish direction is at most W − a; the new speed √(v² + 2ca + a²) is then largest at c = W − a, with the wish 7° off a 320 motion, 37° off 400, and 51° off 500. Each 8 ms step adds 2.54 units/s at 320 and 1.63 at 500, about 317 and 204 units/s each second, close to W²/v at any step length (316 at 1/72 s). Quake's gain is fixed per frame instead, about 101 units/s each second at 320 at 72 frames a second (quake/air-strafe-gain). The figures leave out the rounding trap_SnapVector applies to the velocity after each step, engine code the atlas hasn't read.Code

References

  1. ^ abid Software's GPL source release of Quake III Arena names its version "Q3 1.32b" (Q3_VERSION in q_shared.h), commented "1.32 released 7-10-2002", so the code read here is the 1.32 point release rather than the 1999 retail build.Codeid-Software/Quake-III-Arena
    How we know
    29#define	Q3_VERSION		"Q3 1.32b"
    30// 1.32 released 7-10-2002
    
    code/game/q_shared.h lines 29–30 at dbe4ddb · upstream ↗
    1Quake III Arena GPL source release
    
    README.txt line 1 at dbe4ddb · upstream ↗

    Checked against the evidence by its author, an AI model, 2026-10-22.

    Checked by a second AI model, 2026-10-22.

    In the claim ledger

  2. ^On the ground, Quake III's PM_Friction takes the horizontal speed and lowers it by step time × pm_friction × the larger of the speed and pm_stopspeed, scaling all three velocity axes by the same factor, with pm_friction 6 and pm_stopspeed 100 set in the code; it skips ground friction in water deeper than level 1, on a slick surface, and while the knockback timer runs, and it has no edge friction. Quake's ground friction is the same rule with friction 4 (sv_friction) and the same stop speed, doubled over a ledge (quake/ground-friction).Codeid-Software/Quake-III-Arena · id-Software/Quakerevised
    How we know
    34float	pm_stopspeed = 100.0f;
    
    code/game/bg_pmove.c line 34 at dbe4ddb · upstream ↗
    44float	pm_friction = 6.0f;
    
    code/game/bg_pmove.c line 44 at dbe4ddb · upstream ↗
    180	VectorCopy( vel, vec );
    181	if ( pml.walking ) {
    182		vec[2] = 0;	// ignore slope movement
    ⋮
    185	speed = VectorLength(vec);
    ⋮
    196	if ( pm->waterlevel <= 1 ) {
    197		if ( pml.walking && !(pml.groundTrace.surfaceFlags & SURF_SLICK) ) {
    198			// if getting knocked back, no friction
    199			if ( ! (pm->ps->pm_flags & PMF_TIME_KNOCKBACK) ) {
    200				control = speed < pm_stopspeed ? pm_stopspeed : speed;
    201				drop += control*pm_friction*pml.frametime;
    ⋮
    221	newspeed = speed - drop;
    222	if (newspeed < 0) {
    ⋮
    
    code/game/bg_pmove.c lines 180–229 at dbe4ddb · upstream ↗
    42cvar_t	sv_friction = {"sv_friction","4",false,true};
    43cvar_t	sv_stopspeed = {"sv_stopspeed","100"};
    
    WinQuake/sv_phys.c lines 42–43 at bf4ac42 · upstream ↗
    27cvar_t	sv_edgefriction = {"edgefriction", "2"};
    
    WinQuake/sv_user.c line 27 at bf4ac42 · upstream ↗
    144	if (trace.fraction == 1.0)
    145		friction = sv_friction.value*sv_edgefriction.value;
    146	else
    147		friction = sv_friction.value;
    ⋮
    150	control = speed < sv_stopspeed.value ? sv_stopspeed.value : speed;
    151	newspeed = speed - host_frametime*control*friction;
    
    WinQuake/sv_user.c lines 144–151 at bf4ac42 · upstream ↗

    Checked against the evidence by its author, an AI model, 2026-10-22.

    Revised after a check by a second AI model, 2026-10-22.

    In the claim ledger

  3. ^Quake III's PM_WalkMove runs friction and then PM_Accelerate with pm_accelerate 10, which is Quake's rule (Quake III's PM_Accelerate comment says "q2 style"): if the speed along the wish direction is under the wish speed, it adds 10 × step time × wish speed along that direction, no more than the shortfall. The full wish speed is the player's speed, set before each move from g_speed (320, × 1.3 with Haste). Computed, not measured: since 10 outpaces the friction of 6, a run settles at exactly 320, as Quake's does at 10 against 4; at 8 ms steps it reaches 320 from rest in 19 steps (0.15 s) and stops from 320 in 45 (0.36 s). These figures leave out the rounding trap_SnapVector applies to the velocity after each step, engine code the atlas hasn't read.Codeid-Software/Quake-III-Arena · id-Software/Quakerevised
    How we know
    39float	pm_accelerate = 10.0f;
    
    code/game/bg_pmove.c line 39 at dbe4ddb · upstream ↗
    240static void PM_Accelerate( vec3_t wishdir, float wishspeed, float accel ) {
    241#if 1
    242	// q2 style
    ⋮
    246	currentspeed = DotProduct (pm->ps->velocity, wishdir);
    247	addspeed = wishspeed - currentspeed;
    248	if (addspeed <= 0) {
    249		return;
    ⋮
    251	accelspeed = accel*pml.frametime*wishspeed;
    252	if (accelspeed > addspeed) {
    253		accelspeed = addspeed;
    ⋮
    257		pm->ps->velocity[i] += accelspeed*wishdir[i];	
    
    code/game/bg_pmove.c lines 240–258 at dbe4ddb · upstream ↗
    720	PM_Friction ();
    
    code/game/bg_pmove.c line 720 at dbe4ddb · upstream ↗
    772	if ( ( pml.groundTrace.surfaceFlags & SURF_SLICK ) || pm->ps->pm_flags & PMF_TIME_KNOCKBACK ) {
    773		accelerate = pm_airaccelerate;
    774	} else {
    775		accelerate = pm_accelerate;
    ⋮
    778	PM_Accelerate (wishdir, wishspeed, accelerate);
    
    code/game/bg_pmove.c lines 772–778 at dbe4ddb · upstream ↗
    142	{ &g_speed, "g_speed", "320", 0, 0, qtrue  },
    
    code/game/g_main.c line 142 at dbe4ddb · upstream ↗
    2015	trap_SnapVector( pm->ps->velocity );
    
    code/game/bg_pmove.c line 2015 at dbe4ddb · upstream ↗
    843	client->ps.speed = g_speed.value;
    ⋮
    851	if ( client->ps.powerups[PW_HASTE] ) {
    852		client->ps.speed *= 1.3;
    
    code/game/g_active.c lines 843–853 at dbe4ddb · upstream ↗
    195	currentspeed = DotProduct (velocity, wishdir);
    196	addspeed = wishspeed - currentspeed;
    ⋮
    199	accelspeed = sv_accelerate.value*host_frametime*wishspeed;
    200	if (accelspeed > addspeed)
    201		accelspeed = addspeed;
    
    WinQuake/sv_user.c lines 195–201 at bf4ac42 · upstream ↗
    167cvar_t	sv_maxspeed = {"sv_maxspeed", "320", false, true};
    168cvar_t	sv_accelerate = {"sv_accelerate", "10"};
    
    WinQuake/sv_user.c lines 167–168 at bf4ac42 · upstream ↗

    Checked against the evidence by its author, an AI model, 2026-10-22.

    Revised after a check by a second AI model, 2026-10-22.

    In the claim ledger

  4. ^Quake III's keyboard moves are 127 per axis at a run (the speed key or cl_run, one but not both) and 64 at a walk, and PM_CmdScale scales the wish so its length is the player's speed × the largest of the forward, side, and up moves ÷ 127, which a comment says avoids "a sqrt(2) distortion in speed": at a run, forward, a strafe, and both together each wish 320, so a strafe adds no ground speed, where Quake's walk with a strafe wishes 320 against 200 (quake/move-speeds). The up move counts too: computed, holding the jump key (up move 127) in the air with forward and a strafe wishes 320 × √2/√3 ≈ 261. On the ground PM_CheckJump zeroes a held jump's up move, and Pmove lowers it to 20 for the later steps of a command it splits. The figures leave out the rounding trap_SnapVector applies to the velocity after each step, engine code the atlas hasn't read.Codeid-Software/Quake-III-Arena · id-Software/Quakerevised
    How we know
    317	if ( in_speed.active ^ cl_run->integer ) {
    318		movespeed = 127;
    ⋮
    322		movespeed = 64;
    ⋮
    333	side += movespeed * CL_KeyState (&in_moveright);
    334	side -= movespeed * CL_KeyState (&in_moveleft);
    ⋮
    337	up += movespeed * CL_KeyState (&in_up);
    ⋮
    340	forward += movespeed * CL_KeyState (&in_forward);
    ⋮
    343	cmd->forwardmove = ClampChar( forward );
    344	cmd->rightmove = ClampChar( side );
    345	cmd->upmove = ClampChar( up );
    
    code/client/cl_input.c lines 317–345 at dbe4ddb · upstream ↗
    285Returns the scale factor to apply to cmd movements
    286This allows the clients to use axial -127 to 127 values for all directions
    287without getting a sqrt(2) distortion in speed.
    ⋮
    295	max = abs( cmd->forwardmove );
    296	if ( abs( cmd->rightmove ) > max ) {
    297		max = abs( cmd->rightmove );
    ⋮
    299	if ( abs( cmd->upmove ) > max ) {
    300		max = abs( cmd->upmove );
    ⋮
    306	total = sqrt( cmd->forwardmove * cmd->forwardmove
    307		+ cmd->rightmove * cmd->rightmove + cmd->upmove * cmd->upmove );
    308	scale = (float)pm->ps->speed * max / ( 127.0 * total );
    
    code/game/bg_pmove.c lines 285–308 at dbe4ddb · upstream ↗
    627	for ( i = 0 ; i < 2 ; i++ ) {
    628		wishvel[i] = pml.forward[i]*fmove + pml.right[i]*smove;
    ⋮
    630	wishvel[2] = 0;
    ⋮
    632	VectorCopy (wishvel, wishdir);
    633	wishspeed = VectorNormalize(wishdir);
    634	wishspeed *= scale;
    
    code/game/bg_pmove.c lines 627–634 at dbe4ddb · upstream ↗
    369	// must wait for jump to be released
    370	if ( pm->ps->pm_flags & PMF_JUMP_HELD ) {
    371		// clear upmove so cmdscale doesn't lower running speed
    372		pm->cmd.upmove = 0;
    
    code/game/bg_pmove.c lines 369–373 at dbe4ddb · upstream ↗
    2061		if ( pmove->ps->pm_flags & PMF_JUMP_HELD ) {
    2062			pmove->cmd.upmove = 20;
    
    code/game/bg_pmove.c lines 2061–2062 at dbe4ddb · upstream ↗
    2015	trap_SnapVector( pm->ps->velocity );
    
    code/game/bg_pmove.c line 2015 at dbe4ddb · upstream ↗
    213cvar_t	cl_forwardspeed = {"cl_forwardspeed","200", true};
    214cvar_t	cl_backspeed = {"cl_backspeed","200", true};
    215cvar_t	cl_sidespeed = {"cl_sidespeed","350"};
    
    WinQuake/cl_input.c lines 213–215 at bf4ac42 · upstream ↗
    351	if (wishspeed > sv_maxspeed.value)
    ⋮
    353		VectorScale (wishvel, sv_maxspeed.value/wishspeed, wishvel);
    354		wishspeed = sv_maxspeed.value;
    
    WinQuake/sv_user.c lines 351–354 at bf4ac42 · upstream ↗

    Checked against the evidence by its author, an AI model, 2026-10-22.

    Revised after a check by a second AI model, 2026-10-22.

    In the claim ledger

  5. ^In the air, Quake III's PM_AirMove runs friction (no ground friction, which needs the walking flag), builds a flat wish velocity, and calls the same PM_Accelerate as the ground with pm_airaccelerate 1: the push is 1 × step time × wish speed (2.56 units/s per 8 ms step at 320), and it stops only when the velocity's component along the wish direction reaches the full wish speed. Quake's air rule is the reverse: a push ten times as strong (10 × 320 × frame time), cut off when that component reaches 30 (quake/air-accelerate). An #else branch left out by #if 1 would push the velocity toward the wish velocity instead, commented "proper way (avoids strafe jump maxspeed bug), but feels bad". The figures leave out the rounding trap_SnapVector applies to the velocity after each step, engine code the atlas hasn't read.Codeid-Software/Quake-III-Arena · id-Software/Quakerevised
    How we know
    40float	pm_airaccelerate = 1.0f;
    
    code/game/bg_pmove.c line 40 at dbe4ddb · upstream ↗
    610	PM_Friction();
    ⋮
    628		wishvel[i] = pml.forward[i]*fmove + pml.right[i]*smove;
    ⋮
    630	wishvel[2] = 0;
    ⋮
    633	wishspeed = VectorNormalize(wishdir);
    634	wishspeed *= scale;
    ⋮
    636	// not on ground, so little effect on velocity
    637	PM_Accelerate (wishdir, wishspeed, pm_airaccelerate);
    
    code/game/bg_pmove.c lines 610–637 at dbe4ddb · upstream ↗
    197		if ( pml.walking && !(pml.groundTrace.surfaceFlags & SURF_SLICK) ) {
    
    code/game/bg_pmove.c line 197 at dbe4ddb · upstream ↗
    2015	trap_SnapVector( pm->ps->velocity );
    
    code/game/bg_pmove.c line 2015 at dbe4ddb · upstream ↗
    240static void PM_Accelerate( vec3_t wishdir, float wishspeed, float accel ) {
    241#if 1
    ⋮
    246	currentspeed = DotProduct (pm->ps->velocity, wishdir);
    247	addspeed = wishspeed - currentspeed;
    248	if (addspeed <= 0) {
    249		return;
    ⋮
    251	accelspeed = accel*pml.frametime*wishspeed;
    252	if (accelspeed > addspeed) {
    253		accelspeed = addspeed;
    ⋮
    259#else
    260	// proper way (avoids strafe jump maxspeed bug), but feels bad
    ⋮
    267	VectorSubtract( wishVelocity, pm->ps->velocity, pushDir );
    ⋮
    
    code/game/bg_pmove.c lines 240–275 at dbe4ddb · upstream ↗
    212	wishspd = VectorNormalize (wishveloc);
    213	if (wishspd > 30)
    214		wishspd = 30;
    215	currentspeed = DotProduct (velocity, wishveloc);
    216	addspeed = wishspd - currentspeed;
    ⋮
    220	accelspeed = sv_accelerate.value*wishspeed * host_frametime;
    221	if (accelspeed > addspeed)
    222		accelspeed = addspeed;
    
    WinQuake/sv_user.c lines 212–224 at bf4ac42 · upstream ↗

    Checked against the evidence by its author, an AI model, 2026-10-22.

    Revised after a check by a second AI model, 2026-10-22.

    In the claim ledger

  6. ^Computed from PM_Accelerate with pm_airaccelerate 1, not measured: at a wish speed W of 320 (forward and a strafe at a run, the jump key up, no Haste) the air push is W × step time, a = 2.56 per 8 ms step, and the cap leaves room for all of it while the velocity's component c along the wish direction is at most W − a; the new speed √(v² + 2ca + a²) is then largest at c = W − a, with the wish 7° off a 320 motion, 37° off 400, and 51° off 500. Each 8 ms step adds 2.54 units/s at 320 and 1.63 at 500, about 317 and 204 units/s each second, close to W²/v at any step length (316 at 1/72 s). Quake's gain is fixed per frame instead, about 101 units/s each second at 320 at 72 frames a second (quake/air-strafe-gain). The figures leave out the rounding trap_SnapVector applies to the velocity after each step, engine code the atlas hasn't read.Codeid-Software/Quake-III-Arena · id-Software/Quakerevised
    How we know
    246	currentspeed = DotProduct (pm->ps->velocity, wishdir);
    247	addspeed = wishspeed - currentspeed;
    248	if (addspeed <= 0) {
    249		return;
    ⋮
    251	accelspeed = accel*pml.frametime*wishspeed;
    252	if (accelspeed > addspeed) {
    253		accelspeed = addspeed;
    
    code/game/bg_pmove.c lines 246–254 at dbe4ddb · upstream ↗
    40float	pm_airaccelerate = 1.0f;
    
    code/game/bg_pmove.c line 40 at dbe4ddb · upstream ↗
    637	PM_Accelerate (wishdir, wishspeed, pm_airaccelerate);
    
    code/game/bg_pmove.c line 637 at dbe4ddb · upstream ↗
    1909	pml.frametime = pml.msec * 0.001;
    
    code/game/bg_pmove.c line 1909 at dbe4ddb · upstream ↗
    2015	trap_SnapVector( pm->ps->velocity );
    
    code/game/bg_pmove.c line 2015 at dbe4ddb · upstream ↗
    179	{ &pmove_msec, "pmove_msec", "8", CVAR_SYSTEMINFO, 0, qfalse},
    
    code/game/g_main.c line 179 at dbe4ddb · upstream ↗
    212	wishspd = VectorNormalize (wishveloc);
    213	if (wishspd > 30)
    214		wishspd = 30;
    215	currentspeed = DotProduct (velocity, wishveloc);
    216	addspeed = wishspd - currentspeed;
    ⋮
    220	accelspeed = sv_accelerate.value*wishspeed * host_frametime;
    
    WinQuake/sv_user.c lines 212–222 at bf4ac42 · upstream ↗

    Checked against the evidence by its author, an AI model, 2026-10-22.

    Revised after a check by a second AI model, 2026-10-22.

    In the claim ledger

  7. ^ abQuake III's jump is in the shared movement code: PM_CheckJump, called from PM_WalkMove (so only on walkable ground), needs an up move of at least 10 and the jump-held flag clear, which PmoveSingle clears only on a step whose up move is under 10, so holding the key never jumps twice; it then sets the vertical velocity to JUMP_VELOCITY (270), where Quake's QuakeC adds 270 to it (quake/jump). Gravity is g_gravity, 800, as in Quake. A landing from a fall faster than 200 units/s sets a land timer of 250 ms that a comment says is to "not allow another jump for a little while", but PM_CheckJump never tests it.Codeid-Software/Quake-III-Arena · id-Software/Quake-Tools
    How we know
    29#define	JUMP_VELOCITY	270
    
    code/game/bg_local.h line 29 at dbe4ddb · upstream ↗
    359static qboolean PM_CheckJump( void ) {
    360	if ( pm->ps->pm_flags & PMF_RESPAWNED ) {
    361		return qfalse;		// don't allow jump until all buttons are up
    ⋮
    364	if ( pm->cmd.upmove < 10 ) {
    365		// not holding jump
    366		return qfalse;
    ⋮
    370	if ( pm->ps->pm_flags & PMF_JUMP_HELD ) {
    ⋮
    373		return qfalse;
    ⋮
    376	pml.groundPlane = qfalse;		// jumping away
    377	pml.walking = qfalse;
    378	pm->ps->pm_flags |= PMF_JUMP_HELD;
    ⋮
    380	pm->ps->groundEntityNum = ENTITYNUM_NONE;
    ⋮
    
    code/game/bg_pmove.c lines 359–393 at dbe4ddb · upstream ↗
    1916	if ( pm->cmd.upmove < 10 ) {
    1917		// not holding jump
    1918		pm->ps->pm_flags &= ~PMF_JUMP_HELD;
    
    code/game/bg_pmove.c lines 1916–1918 at dbe4ddb · upstream ↗
    1988	} else if ( pml.walking ) {
    1989		// walking on ground
    1990		PM_WalkMove();
    
    code/game/bg_pmove.c lines 1988–1990 at dbe4ddb · upstream ↗
    710	if ( PM_CheckJump () ) {
    
    code/game/bg_pmove.c line 710 at dbe4ddb · upstream ↗
    1183		// don't do landing time if we were just going down a slope
    1184		if ( pml.previous_velocity[2] < -200 ) {
    1185			// don't allow another jump for a little while
    1186			pm->ps->pm_flags |= PMF_TIME_LAND;
    1187			pm->ps->pm_time = 250;
    
    code/game/bg_pmove.c lines 1183–1187 at dbe4ddb · upstream ↗
    143	{ &g_gravity, "g_gravity", "800", 0, 0, qtrue  },
    
    code/game/g_main.c line 143 at dbe4ddb · upstream ↗
    840	client->ps.gravity = g_gravity.value;
    
    code/game/g_active.c line 840 at dbe4ddb · upstream ↗
    811	self.velocity_z = self.velocity_z + 270;
    
    qcc/v101qc/client.qc line 811 at c0d1b91 · upstream ↗

    Checked against the evidence by its author, an AI model, 2026-10-22.

    Checked by a second AI model, 2026-10-22.

    In the claim ledger

  8. ^Quake III checks the jump before friction: each step, PmoveSingle traces for ground before the move, PM_WalkMove calls PM_CheckJump before PM_Friction, and a jump clears the walking flag and hands the step to PM_AirMove, whose friction call skips ground friction without that flag. So a hop on the first step that starts on the ground loses no speed to friction, as in QuakeWorld (quake/qw-hop-friction), where WinQuake's takes one frame of it, 5.6% at 72 frames a second (quake/hop-friction). The hop needs the jump key released and pressed again in the air, as in both.Codeid-Software/Quake-III-Arena · id-Software/Quake · id-Software/Quake-Toolsrevised
    How we know
    1962	// set groundentity
    1963	PM_GroundTrace();
    
    code/game/bg_pmove.c lines 1962–1963 at dbe4ddb · upstream ↗
    1165	pml.groundPlane = qtrue;
    1166	pml.walking = qtrue;
    
    code/game/bg_pmove.c lines 1165–1166 at dbe4ddb · upstream ↗
    710	if ( PM_CheckJump () ) {
    711		// jumped away
    712		if ( pm->waterlevel > 1 ) {
    713			PM_WaterMove();
    714		} else {
    715			PM_AirMove();
    ⋮
    717		return;
    ⋮
    720	PM_Friction ();
    
    code/game/bg_pmove.c lines 710–720 at dbe4ddb · upstream ↗
    377	pml.walking = qfalse;
    
    code/game/bg_pmove.c line 377 at dbe4ddb · upstream ↗
    610	PM_Friction();
    
    code/game/bg_pmove.c line 610 at dbe4ddb · upstream ↗
    196	if ( pm->waterlevel <= 1 ) {
    197		if ( pml.walking && !(pml.groundTrace.surfaceFlags & SURF_SLICK) ) {
    ⋮
    200				control = speed < pm_stopspeed ? pm_stopspeed : speed;
    201				drop += control*pm_friction*pml.frametime;
    
    code/game/bg_pmove.c lines 196–201 at dbe4ddb · upstream ↗
    1916	if ( pm->cmd.upmove < 10 ) {
    1917		// not holding jump
    1918		pm->ps->pm_flags &= ~PMF_JUMP_HELD;
    
    code/game/bg_pmove.c lines 1916–1918 at dbe4ddb · upstream ↗
    889	if (pmove.cmd.buttons & BUTTON_JUMP)
    890		JumpButton ();
    891	else
    892		pmove.oldbuttons &= ~BUTTON_JUMP;
    ⋮
    894	PM_Friction ();
    
    QW/client/pmove.c lines 889–894 at bf4ac42 · upstream ↗
    674	if (onground == -1)
    675		return;		// in air, so no effect
    ⋮
    677	if ( pmove.oldbuttons & BUTTON_JUMP )
    678		return;		// don't pogo stick
    ⋮
    680	onground = -1;
    ⋮
    683	pmove.oldbuttons |= BUTTON_JUMP;	// don't jump again until released
    
    QW/client/pmove.c lines 674–683 at bf4ac42 · upstream ↗
    366	else if (onground != -1) // apply ground friction
    ⋮
    368		control = speed < movevars.stopspeed ? movevars.stopspeed : speed;
    369		drop += control*friction*frametime;
    
    QW/client/pmove.c lines 366–369 at bf4ac42 · upstream ↗
    972	if (self.button2)
    ⋮
    974		PlayerJump ();
    ⋮
    976	else
    977		self.flags = self.flags | FL_JUMPRELEASED;
    
    qcc/v101qc/client.qc lines 972–977 at c0d1b91 · upstream ↗
    799		return;
    ⋮
    801	if ( !(self.flags & FL_JUMPRELEASED) )
    802		return;		// don't pogo stick
    
    qcc/v101qc/client.qc lines 798–811 at c0d1b91 · upstream ↗
    361	else if ( onground )
    ⋮
    363		SV_UserFriction ();
    364		SV_Accelerate ();
    
    WinQuake/sv_user.c lines 361–364 at bf4ac42 · upstream ↗
    1069	PR_ExecuteProgram (pr_global_struct->PlayerPreThink);
    
    WinQuake/sv_phys.c line 1069 at bf4ac42 · upstream ↗

    Checked against the evidence by its author, an AI model, 2026-10-22.

    Revised after a check by a second AI model, 2026-10-22.

    In the claim ledger

Sources

Code