On this page
  1. Movement
  2. Weapons and damage
  3. Monsters and skill
  4. Related
  5. Mechanics
  6. References
  7. Sources

The atlas reads Quake II through the source id Software released under the GPL in 2001, which names itself version 3.191,1, so every claim here describes 3.19 rather than the 1997 retail build. Player movement is engine code that the server and the client’s prediction both run; weapons, damage, and monsters live in the game library.

#Movement

On the ground an acceleration of 10 works against a friction of 6, the numbers Quake III Arena would keep, where Quake’s friction is 4, so a run settles at exactly the 300 units/s cap (computed)2. That cap is on the wish velocity’s length, and with the view level a run already wishes more than 300, so a strafe added to a run gains nothing on the ground; only a walk does3.

In the air the push is a tenth of the ground’s, but it stops only when the speed along the wish direction reaches the full wish speed, not 30 as in Quake. Quake III kept this rule and called its acceleration code “q2 style”. A deathmatch server can switch Quake’s air rule back on, though only as an on-off switch4. The jump adds 270 units/s5,5 to the vertical velocity, as Quake’s does and Quake III’s doesn’t6.

The jump is checked before friction, so a bunny hop on the first ground step loses nothing to the floor, as in QuakeWorld. A landing faster than 200 units/s downward can set a timer that blocks the next jump for 18 units, 144 ms at 8 ms commands. By the atlas’s reading of the code, a landing step that reaches the floor would not set it; how often a hop’s landing does has not been measured7.

#Weapons and damage

The machine gun, chaingun, shotguns, and railgun are hitscan: each bullet or pellet traces its own randomly spread line, and the rail goes on through every monster or player in its path. The blaster, grenades, and rockets are projectiles8. Damage is mostly fixed. The rocket’s direct hit rolls 100 to 119, and its splash falls off by half a unit of damage per unit of distance9. A player who hits a monster that hasn’t noticed anyone does double damage, splash excepted10.

A player’s own splash does half damage, but the push from it is more than three times as strong, a “rocket jump hack” in the code’s comment. A rocket under a standing player’s feet adds about 424 units/s upward (computed)11. Weapon pickups add to an inventory that keeps everything taken12.

#Monsters and skill

Hurt monsters turn on each other under narrower rules than Quake’s: only a different class that walks, flies, or swims as they do, and never the tanks or the bosses; failing that, a monster joins in on whoever its attacker was after13. In the four monsters read (soldier, infantry, gunner, tank), a flinch is mostly a cooldown, at most one pain animation every 3 seconds, rather than a roll like Doom’s pain chance14. Only the tank rolls, for light hits, and a soldier knocked into the air can switch flinches within the cooldown. Nightmare drops the animations and stretches the cooldown to 5 seconds15. Outside deathmatch, Easy halves the damage a player takes, and each skill level removes the entities a map flags against it16.

Mechanics

MechanicKindHow
RunningMomentumacceleration 10 against a friction of 6 settles a run at exactly the 300 cap, the numbers Quake III kept
Strafe-runningAngled air strafeon the ground the 300 cap takes away a strafe's gain at a run; in the air the push is weak but capped only at the full wish speed, unless a deathmatch server switches on Quake's rule
Bunny hoppingFrictionlessthe jump is checked before friction, so a hop on the first ground step keeps its speed, but a hard landing can lock the jump for 18 or 25 timer units
JumpOne speedadds 270 units/s upward, at least 270 in all, 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
HitscanHitscan beside projectilesbullets, pellets, and the railgun trace a line when fired, the railgun through every body in its path; the blaster, grenades, and rockets fly
Damage formulaBase plus a rollfixed amounts, except the rocket's 100 plus a roll of 0 to 19; a player's non-splash hit on a monster that hasn't noticed anyone does double
Rocket jumpingBoosted self-pusha player's own splash does half damage but pushes at 1600 over mass, not 500, commented as the rocket jump hack
Difficulty levelDamage halvedoutside deathmatch, Easy halves the damage a player takes and each skill removes the entities flagged against it
Pain chanceCooldownin the four monsters read, a 3-second cooldown rather than a roll, 5 seconds with no pain animations on Nightmare; the tank adds a roll for light hits
Monster infightingSame kinda hurt monster turns on another of a different class that moves the same way, except the tanks and bosses, and otherwise joins its attacker's target
Weapon pickupInventoryevery weapon taken stays in the inventory with its ammo; the player switches only to a new one, and in deathmatch only from the blaster
Every claim about this game, by concept (14)
generalid Software's GPL source release of Quake II calls itself "the complete source code for Quake 2, version 3.19", and qcommon.h sets VERSION to 3.19, so the code read here is the 3.19 point release rather than the 1997 retail build.Code
Bunny hoppingQuake II checks the jump before friction: Pmove calls PM_CheckJump, which clears the ground entity, and then PM_Friction, whose ground friction needs a ground entity, so a hop on the first command that starts on the ground loses no speed to friction. QuakeWorld's PlayerMove has the same order: JumpButton clears onground before PM_Friction, whose ground friction needs it. Two conditions apply. The jump key must be released and pressed again (the jump claim). And when the position check first finds ground with the vertical speed below −200, it sets a landing timer of 18 units, or 25 below −400, that blocks the jump until it runs out; each command lowers it by its length in ms divided by 8, rounded down and at least 1, so 18 units last 144 ms at 8 or 16 ms commands and 180 ms at 10 ms (computed). By the atlas's reading the slide move clips the velocity along a floor it runs into, and the stepping move keeps that clipped vertical speed, so a landing step that reaches the floor would not set the timer; how often a hop's landing sets it has not been measured.Code
Damage formulaA Quake II rocket's direct hit does 100 + (int)(random() * 20.0) damage, 100 to 119, the roll made when it is fired; the explosion then does radius damage to everything else within 120 units, the rocket's 120 less half the distance from the explosion to the center of the target's box, and the target hit directly takes no splash. A quad damage multiplies both by 4. The railgun's damage is a constant, 150 in single player and 100 in deathmatch.Code
Quake II's T_Damage doubles the damage a player does to a living monster that has no enemy yet, commented "bonus damage for suprising a monster", unless the damage is radius damage; the doubling comes after Easy's halving, which applies only to players.Code
Difficulty levelOn Easy (skill 0) outside deathmatch, Quake II's T_Damage halves the damage a player takes, to no less than 1; the knockback is worked out separately and isn't halved. Levels also pick their entities by skill: an entity flagged not-easy, not-medium, or not-hard is removed at load on that skill, with Nightmare (3) counted as hard.Code
HitscanQuake II's bullets, pellets, and railgun are hitscan: fire_lead, which fire_bullet (the machine gun and chaingun) and fire_shotgun (once per pellet, for both shotguns) call, traces a line 8192 units long in the frame the gun fires, bent by a random spread (crandom × the spread, each way) for each bullet or pellet, and fire_rail traces 8192 units and keeps tracing past every monster or player it hits, damaging each. The blaster, grenades, and rockets launch entities that travel: the player's blaster bolt at 1000 units/s, the launcher's grenade at 600, and the rocket at 650.Code
JumpQuake II's PM_CheckJump, which Pmove runs before friction, does nothing while the landing timer flag is set; otherwise it needs an up move of at least 10 and the jump-held flag clear, which only a command with the key released clears, so holding the key never jumps twice. On ground it adds 270 to the vertical velocity, raising it to at least 270, and clears the ground entity; in water at level 2 or more it sets an upward swim speed instead (100 in water, 80 in slime, 50 in lava). Quake's QuakeC jump and QuakeWorld's movement code also add 270. Gravity is sv_gravity, 800.Code
Monster infightingWhen a monster survives damage from another monster, Quake II's M_ReactToDamage makes the attacker its enemy if both move the same way (walking, flying, or swimming), their class names differ, and the attacker isn't a tank, super tank, Makron, or Jorg; failing that, it fights back only if the attacker was targeting it, and otherwise takes on the attacker's enemy, unless that enemy is itself. Nothing happens when it hurt itself or the attacker is already its enemy. A player it was chasing is kept as the old enemy. Quake's rule, in the version 1.01 QuakeC of id's Quake-Tools release, is simpler: a monster turns on any attacker of a different class, and soldiers turn on each other too.Code
Pain chanceIn the four Quake II monsters read, a flinch is a cooldown rather than a roll: the soldier's, infantry's, and gunner's pain functions play a pain animation unless one started within the last 3 seconds (pain_debounce_time), choosing which at random (soldier, infantry) or by the damage (gunner), and on Nightmare (skill 3) they return before the animation. The soldier has an exception both ways: knocked upward faster than 100, it switches to a fourth pain animation even within the cooldown (if already in one of the other three) and even on Nightmare. On Nightmare the cooldown is 5 seconds, not 3: T_Damage resets it to 5 after each hit that calls the pain function. The tank adds a roll: it ignores hits of 10 or less, and flinches from 11 to 30 only when random() is at most 0.2; on hard and Nightmare it doesn't flinch during two of its attacks.Code
Rocket jumpingQuake II's radius damage halves the points for the entity that caused it, so a player takes half splash from their own rocket or grenade, and T_Damage pushes a player who hurt themselves with 1600 × knockback / mass instead of 500 ×, commented "the rocket jump hack...". The player's mass is 200, so a self-inflicted push is 8 units/s per point against 2.5 from anyone else, and the radius damage passes its points as the knockback. Computed: a rocket exploding on the floor directly under a standing player, 28 units below the center of the player's box, does 120 − 14 = 106, halved to 53, which adds 424 units/s upward, against a jump's 270.Code
RunningOn the ground, Quake II's Pmove runs PM_Friction, which lowers the speed by step time × 6 (pm_friction) × the larger of the speed and 100 (pm_stopspeed) on any ground that isn't slick, and then PM_Accelerate with pm_accelerate 10: if the speed along the wish direction is under the wish speed, it adds 10 × step time × wish speed, no more than the shortfall. The wish velocity's length is capped at 300 (pm_maxspeed), or 100 ducked. These are the numbers Quake III keeps (friction 6, acceleration 10), where Quake's friction is 4. Computed, not measured: since 10 outpaces 6, a run settles at exactly 300 units/s. The movement constants are C globals, not cvars.Code
Strafe-runningQuake II's keyboard moves are cl_forwardspeed, cl_sidespeed, and cl_upspeed 200, all doubled while running, which the speed key toggles: with cl_run 0 holding the key runs, and with cl_run 1 holding it walks. The movement code builds the wish velocity from the view's forward and right vectors, with the pitch first divided by 3, then zeroes its vertical part and caps its length at 300. Computed from these, with the view level (pitch 0): walking forward wishes 200 and walking forward with a strafe about 283, but a run wishes 400 forward and about 566 on the diagonal, both cut to 300, so a strafe adds nothing to a run on the ground. Looking up or down shortens the forward part before the cap.Code
In the air, Quake II's PM_AirMove calls the ground's PM_Accelerate with an acceleration of 1 while pm_airaccelerate is 0: the push is 1 × step time × wish speed (up to 300), and it stops only when the velocity's component along the wish direction reaches the full wish speed. Quake III keeps this rule, with pm_airaccelerate 1 and a PM_Accelerate commented "q2 style". In deathmatch the server copies its latched cvar sv_airaccelerate (default 0) into pm_airaccelerate, and any nonzero value switches to PM_AirAccelerate, which is Quake's air rule: the wish speed capped at 30 for the shortfall, the push from the uncapped wish speed × pm_accelerate (10) × step time. The server's movement code uses the value only as a nonzero test, so it acts as an on-off switch; outside deathmatch it is forced to 0. The server also sends the value to clients as the CS_AIRACCEL config string, for their prediction, in code not read here.Code
Weapon pickupA Quake II weapon pickup adds the weapon to the player's inventory, which keeps every weapon taken, and gives that weapon's ammo with it unless the item carries the DROPPED_ITEM flag. The player switches to it only when it is the first of its kind carried, and in deathmatch only while the blaster is in hand. In coop, or in deathmatch with weapons-stay set, a player who already has the weapon can't take a placed one. A placed weapon picked up in deathmatch stays where it is with weapons-stay set and otherwise respawns after 30 seconds, and in coop it stays; a weapon flagged DROPPED_PLAYER_ITEM, which a dying player's weapon is, gives its ammo but neither stays nor respawns.Code

References

  1. ^ abid Software's GPL source release of Quake II calls itself "the complete source code for Quake 2, version 3.19", and qcommon.h sets VERSION to 3.19, so the code read here is the 3.19 point release rather than the 1997 retail build.Codeid-Software/Quake-2
    How we know
    2This is the complete source code for Quake 2, version 3.19, buildable with
    
    readme.txt line 2 at 372afde · upstream ↗
    26#define	VERSION		3.19
    
    qcommon/qcommon.h line 26 at 372afde · 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 II's Pmove runs PM_Friction, which lowers the speed by step time × 6 (pm_friction) × the larger of the speed and 100 (pm_stopspeed) on any ground that isn't slick, and then PM_Accelerate with pm_accelerate 10: if the speed along the wish direction is under the wish speed, it adds 10 × step time × wish speed, no more than the shortfall. The wish velocity's length is capped at 300 (pm_maxspeed), or 100 ducked. These are the numbers Quake III keeps (friction 6, acceleration 10), where Quake's friction is 4. Computed, not measured: since 10 outpaces 6, a run settles at exactly 300 units/s. The movement constants are C globals, not cvars.Codeid-Software/Quake-2 · id-Software/Quake-III-Arena · id-Software/Quake
    How we know
    53float	pm_stopspeed = 100;
    54float	pm_maxspeed = 300;
    55float	pm_duckspeed = 100;
    56float	pm_accelerate = 10;
    ⋮
    59float	pm_friction = 6;
    
    qcommon/pmove.c lines 53–59 at 372afde · upstream ↗
    1335		PM_CheckJump ();
    ⋮
    1337		PM_Friction ();
    
    qcommon/pmove.c lines 1335–1337 at 372afde · upstream ↗
    364// apply ground friction
    365	if ((pm->groundentity && pml.groundsurface && !(pml.groundsurface->flags & SURF_SLICK) ) || (pml.ladder) )
    ⋮
    367		friction = pm_friction;
    368		control = speed < pm_stopspeed ? pm_stopspeed : speed;
    369		drop += control*friction*pml.frametime;
    
    qcommon/pmove.c lines 364–370 at 372afde · upstream ↗
    397void PM_Accelerate (vec3_t wishdir, float wishspeed, float accel)
    ⋮
    402	currentspeed = DotProduct (pml.velocity, wishdir);
    403	addspeed = wishspeed - currentspeed;
    404	if (addspeed <= 0)
    405		return;
    406	accelspeed = accel*pml.frametime*wishspeed;
    407	if (accelspeed > addspeed)
    408		accelspeed = addspeed;
    
    qcommon/pmove.c lines 397–411 at 372afde · upstream ↗
    607	maxspeed = (pm->s.pm_flags & PMF_DUCKED) ? pm_duckspeed : pm_maxspeed;
    ⋮
    609	if (wishspeed > maxspeed)
    ⋮
    611		VectorScale (wishvel, maxspeed/wishspeed, wishvel);
    612		wishspeed = maxspeed;
    
    qcommon/pmove.c lines 607–613 at 372afde · upstream ↗
    635	else if ( pm->groundentity )
    636	{	// walking on ground
    637		pml.velocity[2] = 0; //!!! this is before the accel
    638		PM_Accelerate (wishdir, wishspeed, pm_accelerate);
    
    qcommon/pmove.c lines 635–638 at 372afde · upstream ↗
    39float	pm_accelerate = 10.0f;
    
    code/game/bg_pmove.c line 39 at dbe4ddb · upstream ↗
    44float	pm_friction = 6.0f;
    
    code/game/bg_pmove.c line 44 at dbe4ddb · upstream ↗
    42cvar_t	sv_friction = {"sv_friction","4",false,true};
    
    WinQuake/sv_phys.c line 42 at bf4ac42 · 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

  3. ^Quake II's keyboard moves are cl_forwardspeed, cl_sidespeed, and cl_upspeed 200, all doubled while running, which the speed key toggles: with cl_run 0 holding the key runs, and with cl_run 1 holding it walks. The movement code builds the wish velocity from the view's forward and right vectors, with the pitch first divided by 3, then zeroes its vertical part and caps its length at 300. Computed from these, with the view level (pitch 0): walking forward wishes 200 and walking forward with a strafe about 283, but a run wishes 400 forward and about 566 on the diagonal, both cut to 300, so a strafe adds nothing to a run on the ground. Looking up or down shortens the forward part before the cap.Codeid-Software/Quake-2revised
    How we know
    1441	cl_upspeed = Cvar_Get ("cl_upspeed", "200", 0);
    1442	cl_forwardspeed = Cvar_Get ("cl_forwardspeed", "200", 0);
    1443	cl_sidespeed = Cvar_Get ("cl_sidespeed", "200", 0);
    ⋮
    1448	cl_run = Cvar_Get ("cl_run", "0", CVAR_ARCHIVE);
    
    client/cl_main.c lines 1441–1448 at 372afde · upstream ↗
    289	cmd->sidemove += cl_sidespeed->value * CL_KeyState (&in_moveright);
    ⋮
    297		cmd->forwardmove += cl_forwardspeed->value * CL_KeyState (&in_forward);
    ⋮
    304	if ( (in_speed.state & 1) ^ (int)(cl_run->value) )
    ⋮
    306		cmd->forwardmove *= 2;
    307		cmd->sidemove *= 2;
    308		cmd->upmove *= 2;
    
    client/cl_input.c lines 289–309 at 372afde · upstream ↗
    1344			VectorCopy(pm->viewangles, angles);
    1345			if (angles[PITCH] > 180)
    1346				angles[PITCH] = angles[PITCH] - 360;
    1347			angles[PITCH] /= 3;
    ⋮
    1349			AngleVectors (angles, pml.forward, pml.right, pml.up);
    
    qcommon/pmove.c lines 1344–1349 at 372afde · upstream ↗
    596		wishvel[i] = pml.forward[i]*fmove + pml.right[i]*smove;
    597	wishvel[2] = 0;
    ⋮
    602	wishspeed = VectorNormalize(wishdir);
    ⋮
    607	maxspeed = (pm->s.pm_flags & PMF_DUCKED) ? pm_duckspeed : pm_maxspeed;
    ⋮
    609	if (wishspeed > maxspeed)
    
    qcommon/pmove.c lines 595–613 at 372afde · 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. ^In the air, Quake II's PM_AirMove calls the ground's PM_Accelerate with an acceleration of 1 while pm_airaccelerate is 0: the push is 1 × step time × wish speed (up to 300), and it stops only when the velocity's component along the wish direction reaches the full wish speed. Quake III keeps this rule, with pm_airaccelerate 1 and a PM_Accelerate commented "q2 style". In deathmatch the server copies its latched cvar sv_airaccelerate (default 0) into pm_airaccelerate, and any nonzero value switches to PM_AirAccelerate, which is Quake's air rule: the wish speed capped at 30 for the shortfall, the push from the uncapped wish speed × pm_accelerate (10) × step time. The server's movement code uses the value only as a nonzero test, so it acts as an on-off switch; outside deathmatch it is forced to 0. The server also sends the value to clients as the CS_AIRACCEL config string, for their prediction, in code not read here.Codeid-Software/Quake-2 · id-Software/Quake-III-Arena · id-Software/Quakerevised
    How we know
    57float	pm_airaccelerate = 0;
    
    qcommon/pmove.c line 57 at 372afde · upstream ↗
    652	else
    653	{	// not on ground, so little effect on velocity
    654		if (pm_airaccelerate)
    655			PM_AirAccelerate (wishdir, wishspeed, pm_accelerate);
    656		else
    657			PM_Accelerate (wishdir, wishspeed, 1);
    
    qcommon/pmove.c lines 652–657 at 372afde · upstream ↗
    414void PM_AirAccelerate (vec3_t wishdir, float wishspeed, float accel)
    ⋮
    419	if (wishspd > 30)
    420		wishspd = 30;
    421	currentspeed = DotProduct (pml.velocity, wishdir);
    422	addspeed = wishspd - currentspeed;
    423	if (addspeed <= 0)
    424		return;
    425	accelspeed = accel * wishspeed * pml.frametime;
    
    qcommon/pmove.c lines 414–427 at 372afde · upstream ↗
    974	sv_airaccelerate = Cvar_Get("sv_airaccelerate", "0", CVAR_LATCH);
    
    server/sv_main.c line 974 at 372afde · upstream ↗
    196	if (Cvar_VariableValue ("deathmatch"))
    ⋮
    198		sprintf(sv.configstrings[CS_AIRACCEL], "%g", sv_airaccelerate->value);
    199		pm_airaccelerate = sv_airaccelerate->value;
    ⋮
    201	else
    ⋮
    203		strcpy(sv.configstrings[CS_AIRACCEL], "0");
    204		pm_airaccelerate = 0;
    
    server/sv_init.c lines 196–205 at 372afde · upstream ↗
    40float	pm_airaccelerate = 1.0f;
    
    code/game/bg_pmove.c line 40 at dbe4ddb · upstream ↗
    240static void PM_Accelerate( vec3_t wishdir, float wishspeed, float accel ) {
    241#if 1
    242	// q2 style
    
    code/game/bg_pmove.c lines 240–242 at dbe4ddb · upstream ↗
    637	PM_Accelerate (wishdir, wishspeed, pm_airaccelerate);
    
    code/game/bg_pmove.c line 637 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–220 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. ^ abQuake II's PM_CheckJump, which Pmove runs before friction, does nothing while the landing timer flag is set; otherwise it needs an up move of at least 10 and the jump-held flag clear, which only a command with the key released clears, so holding the key never jumps twice. On ground it adds 270 to the vertical velocity, raising it to at least 270, and clears the ground entity; in water at level 2 or more it sets an upward swim speed instead (100 in water, 80 in slime, 50 in lava). Quake's QuakeC jump and QuakeWorld's movement code also add 270. Gravity is sv_gravity, 800.Codeid-Software/Quake-2 · id-Software/Quake-Tools · id-Software/Quake
    How we know
    778void PM_CheckJump (void)
    ⋮
    780	if (pm->s.pm_flags & PMF_TIME_LAND)
    781	{	// hasn't been long enough since landing to jump again
    782		return;
    ⋮
    785	if (pm->cmd.upmove < 10)
    786	{	// not holding jump
    787		pm->s.pm_flags &= ~PMF_JUMP_HELD;
    788		return;
    ⋮
    791	// must wait for jump to be released
    792	if (pm->s.pm_flags & PMF_JUMP_HELD)
    793		return;
    ⋮
    796		return;
    ⋮
    
    qcommon/pmove.c lines 778–822 at 372afde · upstream ↗
    164	sv_gravity = gi.cvar ("sv_gravity", "800", 0);
    
    game/g_save.c line 164 at 372afde · upstream ↗
    811	self.velocity_z = self.velocity_z + 270;
    
    qcc/v101qc/client.qc line 811 at c0d1b91 · upstream ↗
    681	pmove.velocity[2] += 270;
    
    QW/client/pmove.c line 681 at bf4ac42 · 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

  6. ^Quake 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

  7. ^Quake II checks the jump before friction: Pmove calls PM_CheckJump, which clears the ground entity, and then PM_Friction, whose ground friction needs a ground entity, so a hop on the first command that starts on the ground loses no speed to friction. QuakeWorld's PlayerMove has the same order: JumpButton clears onground before PM_Friction, whose ground friction needs it. Two conditions apply. The jump key must be released and pressed again (the jump claim). And when the position check first finds ground with the vertical speed below −200, it sets a landing timer of 18 units, or 25 below −400, that blocks the jump until it runs out; each command lowers it by its length in ms divided by 8, rounded down and at least 1, so 18 units last 144 ms at 8 or 16 ms commands and 180 ms at 10 ms (computed). By the atlas's reading the slide move clips the velocity along a floor it runs into, and the stepping move keeps that clipped vertical speed, so a landing step that reaches the floor would not set the timer; how often a hop's landing sets it has not been measured.Codeid-Software/Quake-2 · id-Software/Quakerevised
    How we know
    1333	else
    ⋮
    1335		PM_CheckJump ();
    ⋮
    1337		PM_Friction ();
    
    qcommon/pmove.c lines 1333–1337 at 372afde · upstream ↗
    819	pm->groundentity = NULL;
    
    qcommon/pmove.c line 819 at 372afde · upstream ↗
    365	if ((pm->groundentity && pml.groundsurface && !(pml.groundsurface->flags & SURF_SLICK) ) || (pml.ladder) )
    
    qcommon/pmove.c line 365 at 372afde · upstream ↗
    714			if (! (pm->s.pm_flags & PMF_ON_GROUND) )
    715			{	// just hit the ground
    716				pm->s.pm_flags |= PMF_ON_GROUND;
    717				// don't do landing time if we were just going down a slope
    718				if (pml.velocity[2] < -200)
    ⋮
    720					pm->s.pm_flags |= PMF_TIME_LAND;
    721					// don't allow another jump for a little while
    722					if (pml.velocity[2] < -400)
    723						pm->s.pm_time = 25;	
    724					else
    725						pm->s.pm_time = 18;
    
    qcommon/pmove.c lines 714–726 at 372afde · upstream ↗
    1302	// drop timing counter
    1303	if (pm->s.pm_time)
    ⋮
    1305		int		msec;
    ⋮
    1307		msec = pm->cmd.msec >> 3;
    1308		if (!msec)
    1309			msec = 1;
    1310		if ( msec >= pm->s.pm_time) 
    ⋮
    1312			pm->s.pm_flags &= ~(PMF_TIME_WATERJUMP | PMF_TIME_LAND | PMF_TIME_TELEPORT);
    1313			pm->s.pm_time = 0;
    ⋮
    1315		else
    1316			pm->s.pm_time -= msec;
    
    qcommon/pmove.c lines 1302–1316 at 372afde · upstream ↗
    685	point[2] = pml.origin[2] - 0.25;
    ⋮
    693		trace = pm->trace (pml.origin, pm->mins, pm->maxs, point);
    
    qcommon/pmove.c lines 683–693 at 372afde · upstream ↗
    219		for (i=0 ; i<numplanes ; i++)
    ⋮
    221			PM_ClipVelocity (pml.velocity, planes[i], pml.velocity, 1.01);
    
    qcommon/pmove.c lines 219–221 at 372afde · upstream ↗
    332	//!! Special case
    333	// if we were walking along a plane, then we need to copy the Z over
    334	pml.velocity[2] = down_v[2];
    
    qcommon/pmove.c lines 332–334 at 372afde · 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 ↗
    680	onground = -1;
    
    QW/client/pmove.c line 680 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 ↗

    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

  8. ^Quake II's bullets, pellets, and railgun are hitscan: fire_lead, which fire_bullet (the machine gun and chaingun) and fire_shotgun (once per pellet, for both shotguns) call, traces a line 8192 units long in the frame the gun fires, bent by a random spread (crandom × the spread, each way) for each bullet or pellet, and fire_rail traces 8192 units and keeps tracing past every monster or player it hits, damaging each. The blaster, grenades, and rockets launch entities that travel: the player's blaster bolt at 1000 units/s, the launcher's grenade at 600, and the rocket at 650.Codeid-Software/Quake-2
    How we know
    152		r = crandom()*hspread;
    153		u = crandom()*vspread;
    154		VectorMA (start, 8192, forward, end);
    155		VectorMA (end, r, right, end);
    156		VectorMA (end, u, up, end);
    ⋮
    165		tr = gi.trace (start, NULL, NULL, end, self, content_mask);
    
    game/g_weapon.c lines 152–165 at 372afde · upstream ↗
    667	VectorMA (start, 8192, aimdir, end);
    ⋮
    672	while (ignore)
    ⋮
    674		tr = gi.trace (from, NULL, NULL, end, ignore, mask);
    ⋮
    681		else
    ⋮
    683			if ((tr.ent->svflags & SVF_MONSTER) || (tr.ent->client))
    684				ignore = tr.ent;
    685			else
    686				ignore = NULL;
    ⋮
    688			if ((tr.ent != self) && (tr.ent->takedamage))
    689				T_Damage (tr.ent, self, self, aimdir, tr.endpos, tr.plane.normal, damage, kick, 0, MOD_RAILGUN);
    
    game/g_weapon.c lines 667–689 at 372afde · upstream ↗
    277void fire_bullet (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int hspread, int vspread, int mod)
    ⋮
    279	fire_lead (self, start, aimdir, damage, kick, TE_GUNSHOT, hspread, vspread, mod);
    ⋮
    287Shoots shotgun pellets.  Used by shotgun and super shotgun.
    ⋮
    294	for (i = 0; i < count; i++)
    295		fire_lead (self, start, aimdir, damage, kick, TE_SHOTGUN, hspread, vspread, mod);
    
    game/g_weapon.c lines 277–295 at 372afde · upstream ↗
    1014	fire_bullet (ent, start, forward, damage, kick, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, MOD_MACHINEGUN);
    
    game/p_weapon.c line 1014 at 372afde · upstream ↗
    1151		fire_bullet (ent, start, forward, damage, kick, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, MOD_CHAINGUN);
    
    game/p_weapon.c line 1151 at 372afde · upstream ↗
    1213		fire_shotgun (ent, start, forward, damage, kick, 500, 500, DEFAULT_DEATHMATCH_SHOTGUN_COUNT, MOD_SHOTGUN);
    
    game/p_weapon.c line 1213 at 372afde · upstream ↗
    1266	fire_shotgun (ent, start, forward, damage, kick, DEFAULT_SHOTGUN_HSPREAD, DEFAULT_SHOTGUN_VSPREAD, DEFAULT_SSHOTGUN_COUNT/2, MOD_SSHOTGUN);
    
    game/p_weapon.c line 1266 at 372afde · upstream ↗
    728	fire_grenade (ent, start, forward, damage, 600, 2.5, radius);
    
    game/p_weapon.c line 728 at 372afde · upstream ↗
    620void fire_rocket (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, float damage_radius, int radius_damage)
    ⋮
    628	VectorScale (dir, speed, rocket->velocity);
    629	rocket->movetype = MOVETYPE_FLYMISSILE;
    
    game/g_weapon.c lines 620–629 at 372afde · upstream ↗
    783	fire_rocket (ent, start, forward, damage, 650, damage_radius, radius_damage);
    
    game/p_weapon.c line 783 at 372afde · upstream ↗
    832	fire_blaster (ent, start, forward, damage, 1000, effect, hyper);
    
    game/p_weapon.c line 832 at 372afde · 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

  9. ^A Quake II rocket's direct hit does 100 + (int)(random() * 20.0) damage, 100 to 119, the roll made when it is fired; the explosion then does radius damage to everything else within 120 units, the rocket's 120 less half the distance from the explosion to the center of the target's box, and the target hit directly takes no splash. A quad damage multiplies both by 4. The railgun's damage is a constant, 150 in single player and 100 in deathmatch.Codeid-Software/Quake-2
    How we know
    767	damage = 100 + (int)(random() * 20.0);
    768	radius_damage = 120;
    769	damage_radius = 120;
    770	if (is_quad)
    ⋮
    772		damage *= 4;
    773		radius_damage *= 4;
    
    game/p_weapon.c lines 767–774 at 372afde · upstream ↗
    589	if (other->takedamage)
    ⋮
    591		T_Damage (other, ent, ent->owner, ent->velocity, ent->s.origin, plane->normal, ent->dmg, 0, 0, MOD_ROCKET);
    ⋮
    607	T_RadiusDamage(ent, ent->owner, ent->radius_dmg, other, ent->dmg_radius, MOD_R_SPLASH);
    
    game/g_weapon.c lines 589–607 at 372afde · upstream ↗
    554	while ((ent = findradius(ent, inflictor->s.origin, radius)) != NULL)
    ⋮
    556		if (ent == ignore)
    557			continue;
    ⋮
    559			continue;
    ⋮
    561		VectorAdd (ent->mins, ent->maxs, v);
    562		VectorMA (ent->s.origin, 0.5, v, v);
    563		VectorSubtract (inflictor->s.origin, v, v);
    564		points = damage - 0.5 * VectorLength (v);
    ⋮
    572				T_Damage (ent, inflictor, attacker, dir, inflictor->s.origin, vec3_origin, (int)points, (int)points, DAMAGE_RADIUS, mod);
    
    game/g_combat.c lines 554–572 at 372afde · upstream ↗
    94			eorg[j] = org[j] - (from->s.origin[j] + (from->mins[j] + from->maxs[j])*0.5);
    95		if (VectorLength(eorg) > rad)
    
    game/g_utils.c lines 94–95 at 372afde · upstream ↗
    1310	if (deathmatch->value)
    1311	{	// normal damage is too extreme in dm
    1312		damage = 100;
    1313		kick = 200;
    ⋮
    1315	else
    ⋮
    1317		damage = 150;
    
    game/p_weapon.c lines 1310–1318 at 372afde · 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

  10. ^Quake II's T_Damage doubles the damage a player does to a living monster that has no enemy yet, commented "bonus damage for suprising a monster", unless the damage is radius damage; the doubling comes after Easy's halving, which applies only to players.Codeid-Software/Quake-2
    How we know
    421// bonus damage for suprising a monster
    422	if (!(dflags & DAMAGE_RADIUS) && (targ->svflags & SVF_MONSTER) && (attacker->client) && (!targ->enemy) && (targ->health > 0))
    423		damage *= 2;
    
    game/g_combat.c lines 421–423 at 372afde · 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

  11. ^Quake II's radius damage halves the points for the entity that caused it, so a player takes half splash from their own rocket or grenade, and T_Damage pushes a player who hurt themselves with 1600 × knockback / mass instead of 500 ×, commented "the rocket jump hack...". The player's mass is 200, so a self-inflicted push is 8 units/s per point against 2.5 from anyone else, and the radius damage passes its points as the knockback. Computed: a rocket exploding on the floor directly under a standing player, 28 units below the center of the player's box, does 120 − 14 = 106, halved to 53, which adds 424 units/s upward, against a jump's 270.Codeid-Software/Quake-2
    How we know
    564		points = damage - 0.5 * VectorLength (v);
    565		if (ent == attacker)
    566			points = points * 0.5;
    ⋮
    572				T_Damage (ent, inflictor, attacker, dir, inflictor->s.origin, vec3_origin, (int)points, (int)points, DAMAGE_RADIUS, mod);
    
    game/g_combat.c lines 564–572 at 372afde · upstream ↗
    436			if (targ->mass < 50)
    437				mass = 50;
    438			else
    439				mass = targ->mass;
    ⋮
    441			if (targ->client  && attacker == targ)
    442				VectorScale (dir, 1600.0 * (float)knockback / mass, kvel);	// the rocket jump hack...
    443			else
    444				VectorScale (dir, 500.0 * (float)knockback / mass, kvel);
    ⋮
    446			VectorAdd (targ->velocity, kvel, targ->velocity);
    
    game/g_combat.c lines 436–446 at 372afde · upstream ↗
    1099	vec3_t	mins = {-16, -16, -24};
    1100	vec3_t	maxs = {16, 16, 32};
    
    game/p_client.c lines 1099–1100 at 372afde · upstream ↗
    1170	ent->mass = 200;
    
    game/p_client.c line 1170 at 372afde · 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

  12. ^A Quake II weapon pickup adds the weapon to the player's inventory, which keeps every weapon taken, and gives that weapon's ammo with it unless the item carries the DROPPED_ITEM flag. The player switches to it only when it is the first of its kind carried, and in deathmatch only while the blaster is in hand. In coop, or in deathmatch with weapons-stay set, a player who already has the weapon can't take a placed one. A placed weapon picked up in deathmatch stays where it is with weapons-stay set and otherwise respawns after 30 seconds, and in coop it stays; a weapon flagged DROPPED_PLAYER_ITEM, which a dying player's weapon is, gives its ammo but neither stays nor respawns.Codeid-Software/Quake-2revised
    How we know
    125	if ( ( ((int)(dmflags->value) & DF_WEAPONS_STAY) || coop->value) 
    126		&& other->client->pers.inventory[index])
    ⋮
    128		if (!(ent->spawnflags & (DROPPED_ITEM | DROPPED_PLAYER_ITEM) ) )
    129			return false;	// leave the weapon for others to pickup
    ⋮
    132	other->client->pers.inventory[index]++;
    ⋮
    134	if (!(ent->spawnflags & DROPPED_ITEM) )
    ⋮
    136		// give them some ammo with it
    137		ammo = FindItem (ent->item->ammo);
    ⋮
    140		else
    141			Add_Ammo (other, ammo, ammo->quantity);
    ⋮
    143		if (! (ent->spawnflags & DROPPED_PLAYER_ITEM) )
    ⋮
    145			if (deathmatch->value)
    ⋮
    
    game/p_weapon.c lines 125–160 at 372afde · upstream ↗
    814	if (!((coop->value) &&  (ent->item->flags & IT_STAY_COOP)) || (ent->spawnflags & (DROPPED_ITEM | DROPPED_PLAYER_ITEM)))
    ⋮
    816		if (ent->flags & FL_RESPAWN)
    817			ent->flags &= ~FL_RESPAWN;
    818		else
    819			G_FreeEdict (ent);
    
    game/g_items.c lines 814–819 at 372afde · upstream ↗
    439		drop = Drop_Item (self, item);
    ⋮
    441		drop->spawnflags = DROPPED_PLAYER_ITEM;
    
    game/p_client.c lines 439–441 at 372afde · 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

  13. ^When a monster survives damage from another monster, Quake II's M_ReactToDamage makes the attacker its enemy if both move the same way (walking, flying, or swimming), their class names differ, and the attacker isn't a tank, super tank, Makron, or Jorg; failing that, it fights back only if the attacker was targeting it, and otherwise takes on the attacker's enemy, unless that enemy is itself. Nothing happens when it hurt itself or the attacker is already its enemy. A player it was chasing is kept as the old enemy. Quake's rule, in the version 1.01 QuakeC of id's Quake-Tools release, is simpler: a monster turns on any attacker of a different class, and soldiers turn on each other too.Codeid-Software/Quake-2 · id-Software/Quake-Toolsrevised
    How we know
    295void M_ReactToDamage (edict_t *targ, edict_t *attacker)
    ⋮
    297	if (!(attacker->client) && !(attacker->svflags & SVF_MONSTER))
    298		return;
    ⋮
    300	if (attacker == targ || attacker == targ->enemy)
    301		return;
    
    game/g_combat.c lines 295–301 at 372afde · upstream ↗
    335	// it's the same base (walk/swim/fly) type and a different classname and it's not a tank
    336	// (they spray too much), get mad at them
    337	if (((targ->flags & (FL_FLY|FL_SWIM)) == (attacker->flags & (FL_FLY|FL_SWIM))) &&
    338		 (strcmp (targ->classname, attacker->classname) != 0) &&
    339		 (strcmp(attacker->classname, "monster_tank") != 0) &&
    340		 (strcmp(attacker->classname, "monster_supertank") != 0) &&
    341		 (strcmp(attacker->classname, "monster_makron") != 0) &&
    342		 (strcmp(attacker->classname, "monster_jorg") != 0) )
    ⋮
    344		if (targ->enemy && targ->enemy->client)
    345			targ->oldenemy = targ->enemy;
    346		targ->enemy = attacker;
    ⋮
    350	// if they *meant* to shoot us, then shoot back
    ⋮
    
    game/g_combat.c lines 335–367 at 372afde · upstream ↗
    506	if (targ->svflags & SVF_MONSTER)
    ⋮
    508		M_ReactToDamage (targ, attacker);
    
    game/g_combat.c lines 506–508 at 372afde · upstream ↗
    198	if ( (self.flags & FL_MONSTER) && attacker != world)
    ⋮
    200	// get mad unless of the same class (except for soldiers)
    201		if (self != attacker && attacker != self.enemy)
    ⋮
    203			if ( (self.classname != attacker.classname) 
    204			|| (self.classname == "monster_army" ) )
    ⋮
    206				if (self.enemy.classname == "player")
    207					self.oldenemy = self.enemy;
    208				self.enemy = attacker;
    
    qcc/v101qc/combat.qc lines 198–210 at c0d1b91 · 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

  14. ^A Doom monster that survives a hit enters its pain state when P_Random() is below its pain chance, out of 256, unless it is a lost soul in mid-charge; the table gives the zombieman and imp 200, the demon 180, the shotgun guy 170, the cacodemon 128, the Baron of Hell 50, the Spider Mastermind 40, and the Cyberdemon 20, and the lost soul's 256 always succeeds. The pain sets MF_JUSTHIT, so the monster's next missile-range check passes at once if it can see its target.Codeid-Software/DOOMrevised
    How we know
    887    target->health -= damage;	
    888    if (target->health <= 0)
    ⋮
    894    if ( (P_Random () < target->info->painchance)
    895	 && !(target->flags&MF_SKULLFLY) )
    ⋮
    897	target->flags |= MF_JUSTHIT;	// fight back!
    ⋮
    899	P_SetMobjState (target, target->info->painstate);
    
    linuxdoom-1.10/p_inter.c lines 887–900 at a77dfb9 · upstream ↗
    197boolean P_CheckMissileRange (mobj_t* actor)
    ⋮
    201    if (! P_CheckSight (actor, actor->target) )
    202	return false;
    ⋮
    204    if ( actor->flags & MF_JUSTHIT )
    ⋮
    206	// the target just hit the enemy,
    207	// so fight back!
    208	actor->flags &= ~MF_JUSTHIT;
    209	return true;
    
    linuxdoom-1.10/p_enemy.c lines 197–209 at a77dfb9 · upstream ↗
    1134    {		// MT_POSSESSED
    ⋮
    1143	200,		// painchance
    
    linuxdoom-1.10/info.c lines 1134–1143 at a77dfb9 · upstream ↗
    1160    {		// MT_SHOTGUY
    ⋮
    1169	170,		// painchance
    
    linuxdoom-1.10/info.c lines 1160–1169 at a77dfb9 · upstream ↗
    1394    {		// MT_TROOP
    ⋮
    1403	200,		// painchance
    
    linuxdoom-1.10/info.c lines 1394–1403 at a77dfb9 · upstream ↗
    1420    {		// MT_SERGEANT
    ⋮
    1429	180,		// painchance
    
    linuxdoom-1.10/info.c lines 1420–1429 at a77dfb9 · upstream ↗
    1472    {		// MT_HEAD
    ⋮
    1481	128,		// painchance
    
    linuxdoom-1.10/info.c lines 1472–1481 at a77dfb9 · upstream ↗
    1498    {		// MT_BRUISER
    ⋮
    1507	50,		// painchance
    
    linuxdoom-1.10/info.c lines 1498–1507 at a77dfb9 · upstream ↗
    1576    {		// MT_SKULL
    ⋮
    1585	256,		// painchance
    
    linuxdoom-1.10/info.c lines 1576–1585 at a77dfb9 · upstream ↗
    1602    {		// MT_SPIDER
    ⋮
    1611	40,		// painchance
    
    linuxdoom-1.10/info.c lines 1602–1611 at a77dfb9 · upstream ↗
    1654    {		// MT_CYBORG
    ⋮
    1663	20,		// painchance
    
    linuxdoom-1.10/info.c lines 1654–1663 at a77dfb9 · upstream ↗
    341    {CC_ZOMBIE, MT_POSSESSED},
    342    {CC_SHOTGUN, MT_SHOTGUY},
    ⋮
    344    {CC_IMP, MT_TROOP},
    345    {CC_DEMON, MT_SERGEANT},
    346    {CC_LOST, MT_SKULL},
    347    {CC_CACO, MT_HEAD},
    ⋮
    349    {CC_BARON, MT_BRUISER},
    ⋮
    355    {CC_SPIDER, MT_SPIDER},
    356    {CC_CYBER, MT_CYBORG},
    
    linuxdoom-1.10/f_finale.c lines 341–356 at a77dfb9 · upstream ↗
    677#define CC_ZOMBIE	"ZOMBIEMAN"
    678#define CC_SHOTGUN	"SHOTGUN GUY"
    ⋮
    680#define CC_IMP	"IMP"
    681#define CC_DEMON	"DEMON"
    682#define CC_LOST	"LOST SOUL"
    683#define CC_CACO	"CACODEMON"
    ⋮
    685#define CC_BARON	"BARON OF HELL"
    ⋮
    691#define CC_SPIDER	"THE SPIDER MASTERMIND"
    692#define CC_CYBER	"THE CYBERDEMON"
    
    linuxdoom-1.10/d_englsh.h lines 677–692 at a77dfb9 · upstream ↗

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

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

    In the claim ledger

  15. ^In the four Quake II monsters read, a flinch is a cooldown rather than a roll: the soldier's, infantry's, and gunner's pain functions play a pain animation unless one started within the last 3 seconds (pain_debounce_time), choosing which at random (soldier, infantry) or by the damage (gunner), and on Nightmare (skill 3) they return before the animation. The soldier has an exception both ways: knocked upward faster than 100, it switches to a fourth pain animation even within the cooldown (if already in one of the other three) and even on Nightmare. On Nightmare the cooldown is 5 seconds, not 3: T_Damage resets it to 5 after each hit that calls the pain function. The tank adds a roll: it ignores hits of 10 or less, and flinches from 11 to 30 only when random() is at most 0.2; on hard and Nightmare it doesn't flinch during two of its attacks.Codeid-Software/Quake-2revised
    How we know
    417	if (level.time < self->pain_debounce_time)
    ⋮
    419		if ((self->velocity[2] > 100) && ( (self->monsterinfo.currentmove == &soldier_move_pain1) || (self->monsterinfo.currentmove == &soldier_move_pain2) || (self->monsterinfo.currentmove == &soldier_move_pain3)))
    420			self->monsterinfo.currentmove = &soldier_move_pain4;
    421		return;
    ⋮
    424	self->pain_debounce_time = level.time + 3;
    ⋮
    434	if (self->velocity[2] > 100)
    ⋮
    436		self->monsterinfo.currentmove = &soldier_move_pain4;
    437		return;
    ⋮
    440	if (skill->value == 3)
    441		return;		// no pain anims in nightmare
    ⋮
    443	r = random();
    ⋮
    445	if (r < 0.33)
    ⋮
    
    game/m_soldier.c lines 417–450 at 372afde · upstream ↗
    222	if (level.time < self->pain_debounce_time)
    223		return;
    ⋮
    225	self->pain_debounce_time = level.time + 3;
    ⋮
    227	if (skill->value == 3)
    228		return;		// no pain anims in nightmare
    ⋮
    230	n = rand() % 2;
    
    game/m_infantry.c lines 222–230 at 372afde · upstream ↗
    288	if (level.time < self->pain_debounce_time)
    289		return;
    ⋮
    291	self->pain_debounce_time = level.time + 3;
    ⋮
    298	if (skill->value == 3)
    299		return;		// no pain anims in nightmare
    ⋮
    301	if (damage <= 10)
    302		self->monsterinfo.currentmove = &gunner_move_pain3;
    
    game/m_gunner.c lines 288–302 at 372afde · upstream ↗
    293	if (damage <= 10)
    294		return;
    ⋮
    296	if (level.time < self->pain_debounce_time)
    297			return;
    ⋮
    299	if (damage <= 30)
    300		if (random() > 0.2)
    301			return;
    ⋮
    303	// If hard or nightmare, don't go into pain while attacking
    304	if ( skill->value >= 2)
    ⋮
    307			return;
    ⋮
    309			return;
    ⋮
    312	self->pain_debounce_time = level.time + 3;
    
    game/m_tank.c lines 293–312 at 372afde · upstream ↗
    506	if (targ->svflags & SVF_MONSTER)
    ⋮
    508		M_ReactToDamage (targ, attacker);
    509		if (!(targ->monsterinfo.aiflags & AI_DUCKED) && (take))
    ⋮
    511			targ->pain (targ, attacker, knockback, take);
    512			// nightmare mode monsters don't go into pain frames often
    513			if (skill->value == 3)
    514				targ->pain_debounce_time = level.time + 5;
    
    game/g_combat.c lines 506–514 at 372afde · 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

  16. ^On Easy (skill 0) outside deathmatch, Quake II's T_Damage halves the damage a player takes, to no less than 1; the knockback is worked out separately and isn't halved. Levels also pick their entities by skill: an entity flagged not-easy, not-medium, or not-hard is removed at load on that skill, with Nightmare (3) counted as hard.Codeid-Software/Quake-2
    How we know
    404	// easy mode takes half damage
    405	if (skill->value == 0 && deathmatch->value == 0 && targ->client)
    ⋮
    407		damage *= 0.5;
    408		if (!damage)
    409			damage = 1;
    
    game/g_combat.c lines 404–410 at 372afde · upstream ↗
    428// figure momentum add
    ⋮
    444				VectorScale (dir, 500.0 * (float)knockback / mass, kvel);
    
    game/g_combat.c lines 428–446 at 372afde · upstream ↗
    585			else
    ⋮
    587				if ( /* ((coop->value) && (ent->spawnflags & SPAWNFLAG_NOT_COOP)) || */
    588					((skill->value == 0) && (ent->spawnflags & SPAWNFLAG_NOT_EASY)) ||
    589					((skill->value == 1) && (ent->spawnflags & SPAWNFLAG_NOT_MEDIUM)) ||
    590					(((skill->value == 2) || (skill->value == 3)) && (ent->spawnflags & SPAWNFLAG_NOT_HARD))
    ⋮
    593						G_FreeEdict (ent);	
    
    game/g_spawn.c lines 585–593 at 372afde · 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

Sources

Code