Skip to content

Commit ef67a86

Browse files
committed
Make pusher/rider contact robust against float rounding (#963, #700)
Gated behind sv_gameplayfix_elevators 3 (new default; 0-2 = legacy): 1. Pusher positions are evaluated in double from the leg end implied by current state (dest = origin + velocity*(nextthink - ltime)) instead of accumulating velocity*movetime rounding per tick, so legs arrive exactly where QC aimed them. No stored state; QC changes re-derive. 2. Riders are carried to pusher_new + (rider - pusher_old), keeping the relative offset bounded instead of drifting via parallel integration. 3. Rounding-level interpenetration is no longer fatal: a blocked rider is settled onto the pusher with a single sweep from PUSH_CONTACT_EPSILON above (provably outside), running before the corpse path so items don't get their bbox zeroed - which used to leave them falling through elevators. Deeper overlaps are genuine squeezes and still crush. Also un-archive the physics debug cvars (sv_fastpushmove, sv_fte_recursivehullckeck, sv_fte_createareanode) so stale defaults stop latching in user configs.
1 parent 4a40a8a commit ef67a86

2 files changed

Lines changed: 96 additions & 34 deletions

File tree

Quake/sv_phys.c

Lines changed: 94 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -49,14 +49,17 @@ cvar_t sv_nostep = {"sv_nostep", "0", CVAR_NONE};
4949
cvar_t sv_freezenonclients = {"sv_freezenonclients", "0", CVAR_NONE};
5050
cvar_t sv_gameplayfix_spawnbeforethinks = {"sv_gameplayfix_spawnbeforethinks", "0", CVAR_NONE};
5151
cvar_t sv_gameplayfix_bouncedownslopes = {"sv_gameplayfix_bouncedownslopes", "1", CVAR_NONE}; // fixes grenades making horrible noises on slopes.
52-
cvar_t sv_fastpushmove = {"sv_fastpushmove", "1", CVAR_ARCHIVE}; // 0=old SV_PushMove processing; 1= faster SV_PushMove, (default)
52+
cvar_t sv_fastpushmove = {"sv_fastpushmove", "1", CVAR_NONE}; // 0=old SV_PushMove processing; 1= faster SV_PushMove, (default)
5353
cvar_t sv_pushgrid = {"sv_pushgrid", "1", CVAR_NONE}; // cull SV_PushMove candidates with a spatial hash, needs sv_fastpushmove
5454
cvar_t sv_analyticphysics = {"sv_analyticphysics", "1", CVAR_NONE}; // gravity/friction integration matches 72Hz physics at any tick rate
5555

5656
qboolean sv_analyticphysics_frame = true; // sv_analyticphysics latched per SV_Physics, QC can flip the cvar mid-tick
5757

5858
#define MOVE_EPSILON 0.01
5959

60+
// max depth float rounding can embed an entity into the surface it rests on, anything deeper is a real overlap
61+
#define PUSH_CONTACT_EPSILON (2 * DIST_EPSILON)
62+
6063
static void SV_Physics_Toss (edict_t *ent);
6164

6265
// For usage by SV_PushMove, allocate at max possible size,
@@ -659,27 +662,32 @@ PUSHMOVE
659662
===============================================================================
660663
*/
661664

665+
// 0=off; 1=legacy DIST_EPSILON nudge, clients only; 2=legacy nudge, all entities; 3=robust pusher contact (default)
666+
cvar_t sv_gameplayfix_elevators = {"sv_gameplayfix_elevators", "3", CVAR_NONE};
667+
668+
static trace_t SV_PushEntityMove (edict_t *ent, vec3_t start, vec3_t end)
669+
{
670+
if (ent->v.movetype == MOVETYPE_FLYMISSILE)
671+
return SV_Move (start, ent->v.mins, ent->v.maxs, end, MOVE_MISSILE, ent);
672+
else if (ent->v.solid == SOLID_TRIGGER || ent->v.solid == SOLID_NOT)
673+
// only clip against bmodels
674+
return SV_Move (start, ent->v.mins, ent->v.maxs, end, MOVE_NOMONSTERS, ent);
675+
else
676+
return SV_Move (start, ent->v.mins, ent->v.maxs, end, MOVE_NORMAL, ent);
677+
}
678+
662679
/*
663680
============
664-
SV_PushEntity
681+
SV_PushEntityTo
665682
666683
Does not change the entities velocity at all
667684
============
668685
*/
669-
static trace_t SV_PushEntity (edict_t *ent, vec3_t push)
686+
static trace_t SV_PushEntityTo (edict_t *ent, vec3_t end)
670687
{
671688
trace_t trace;
672-
vec3_t end;
673689

674-
VectorAdd (ent->v.origin, push, end);
675-
676-
if (ent->v.movetype == MOVETYPE_FLYMISSILE)
677-
trace = SV_Move (ent->v.origin, ent->v.mins, ent->v.maxs, end, MOVE_MISSILE, ent);
678-
else if (ent->v.solid == SOLID_TRIGGER || ent->v.solid == SOLID_NOT)
679-
// only clip against bmodels
680-
trace = SV_Move (ent->v.origin, ent->v.mins, ent->v.maxs, end, MOVE_NOMONSTERS, ent);
681-
else
682-
trace = SV_Move (ent->v.origin, ent->v.mins, ent->v.maxs, end, MOVE_NORMAL, ent);
690+
trace = SV_PushEntityMove (ent, ent->v.origin, end);
683691

684692
if (trace.ent)
685693
assert_always (!trace.ent->free);
@@ -701,7 +709,6 @@ static trace_t SV_PushEntity (edict_t *ent, vec3_t push)
701709
SV_PushMove
702710
============
703711
*/
704-
cvar_t sv_gameplayfix_elevators = {"sv_gameplayfix_elevators", "2", CVAR_ARCHIVE}; // 0=off; 1=clients only; 2=all entities
705712

706713
static void SV_PushMove (edict_t *pusher, float movetime)
707714
{
@@ -724,9 +731,27 @@ static void SV_PushMove (edict_t *pusher, float movetime)
724731
return;
725732
}
726733

734+
const qboolean robust_push = (sv_gameplayfix_elevators.value >= 3.f);
735+
const float newltime = pusher->v.ltime + movetime;
736+
vec3_t neworigin;
737+
727738
for (i = 0; i < 3; i++)
728739
{
729-
move[i] = pusher->v.velocity[i] * movetime;
740+
if (robust_push)
741+
{
742+
// the mover runs at constant velocity until its think at nextthink, so
743+
// evaluate the position on that trajectory in double instead of
744+
// accumulating velocity*movetime rounding every tick: the final partial
745+
// step lands exactly on the destination QC aimed for
746+
const double dest = (double)pusher->v.origin[i] + (double)pusher->v.velocity[i] * ((double)pusher->v.nextthink - (double)pusher->v.ltime);
747+
neworigin[i] = (float)(dest - (double)pusher->v.velocity[i] * ((double)pusher->v.nextthink - (double)newltime));
748+
move[i] = neworigin[i] - pusher->v.origin[i];
749+
}
750+
else
751+
{
752+
move[i] = pusher->v.velocity[i] * movetime;
753+
neworigin[i] = pusher->v.origin[i] + move[i];
754+
}
730755
mins[i] = pusher->v.absmin[i] + move[i];
731756
maxs[i] = pusher->v.absmax[i] + move[i];
732757
// the grid query must span the whole sweep: riders rest on the pre-move
@@ -739,8 +764,8 @@ static void SV_PushMove (edict_t *pusher, float movetime)
739764

740765
// move the pusher to it's final position
741766

742-
VectorAdd (pusher->v.origin, move, pusher->v.origin);
743-
pusher->v.ltime += movetime;
767+
VectorCopy (neworigin, pusher->v.origin);
768+
pusher->v.ltime = newltime;
744769
SV_LinkEdict (pusher, false);
745770

746771
// see if any solid entities are inside the final position
@@ -835,9 +860,20 @@ static void SV_PushMove (edict_t *pusher, float movetime)
835860
|| solid_backup == SOLID_BBOX // normally boxes
836861
|| solid_backup == SOLID_SLIDEBOX) // normally monsters
837862
{
863+
vec3_t dest;
864+
if (robust_push)
865+
{
866+
// carry to a target derived from the pusher's new origin so the
867+
// relative offset can't drift from parallel float integration
868+
for (i = 0; i < 3; i++)
869+
dest[i] = pusher->v.origin[i] + (entorig[i] - pushorig[i]);
870+
}
871+
else
872+
VectorAdd (entorig, move, dest);
873+
838874
// try moving the contacted entity
839875
pusher->v.solid = SOLID_NOT;
840-
SV_PushEntity (check, move);
876+
SV_PushEntityTo (check, dest);
841877

842878
// if it is still inside the pusher, block
843879
if (pusher->v.skin < 0)
@@ -858,6 +894,31 @@ static void SV_PushMove (edict_t *pusher, float movetime)
858894
if (check->v.mins[0] == check->v.maxs[0])
859895
continue;
860896

897+
// riders only embed through their ground contact and never deeper than
898+
// PUSH_CONTACT_EPSILON, so a single sweep from above recovers the exact
899+
// contact position. must run before the corpse path so items don't get
900+
// their bbox zeroed over a rounding error; real squeezes still crush.
901+
if (robust_push && riding && block == pusher)
902+
{
903+
vec3_t pushedorg, above;
904+
trace_t settle;
905+
906+
VectorCopy (check->v.origin, pushedorg);
907+
VectorCopy (check->v.origin, above);
908+
above[2] += PUSH_CONTACT_EPSILON;
909+
settle = SV_PushEntityMove (check, above, pushedorg);
910+
if (!settle.startsolid)
911+
{
912+
VectorCopy (settle.endpos, check->v.origin);
913+
if (!SV_TestEntityPosition (check))
914+
{
915+
SV_LinkEdict (check, false);
916+
continue;
917+
}
918+
VectorCopy (pushedorg, check->v.origin);
919+
}
920+
}
921+
861922
if (check->v.solid == SOLID_NOT || check->v.solid == SOLID_TRIGGER)
862923
{ // corpse
863924
check->v.mins[0] = check->v.mins[1] = 0;
@@ -866,7 +927,7 @@ static void SV_PushMove (edict_t *pusher, float movetime)
866927
}
867928

868929
// try moving the entity up a bit if it's blocked by the pusher while also standing on it
869-
if (riding && block == pusher &&
930+
if (!robust_push && riding && block == pusher &&
870931
(sv_gameplayfix_elevators.value >= 2.f || (sv_gameplayfix_elevators.value && NUM_FOR_EDICT (check) <= svs.maxclients)))
871932
{
872933
check->v.origin[2] += DIST_EPSILON;
@@ -1088,7 +1149,7 @@ static int SV_TryUnstick (edict_t *ent, vec3_t oldvel)
10881149
{
10891150
int i;
10901151
vec3_t oldorg;
1091-
vec3_t dir;
1152+
vec3_t dir, dest;
10921153
int clip;
10931154
trace_t steptrace;
10941155

@@ -1134,7 +1195,8 @@ static int SV_TryUnstick (edict_t *ent, vec3_t oldvel)
11341195
break;
11351196
}
11361197

1137-
SV_PushEntity (ent, dir);
1198+
VectorAdd (ent->v.origin, dir, dest);
1199+
SV_PushEntityTo (ent, dest);
11381200

11391201
// retry the original move
11401202
ent->v.velocity[0] = oldvel[0];
@@ -1207,13 +1269,11 @@ static void SV_WalkMove (edict_t *ent)
12071269
//
12081270
VectorCopy (oldorg, ent->v.origin); // back to start pos
12091271

1210-
VectorCopy (vec3_origin, upmove);
1211-
VectorCopy (vec3_origin, downmove);
1212-
upmove[2] = STEPSIZE;
1213-
downmove[2] = -STEPSIZE + oldvel[2] * host_frametime;
1272+
VectorCopy (ent->v.origin, upmove);
1273+
upmove[2] += STEPSIZE;
12141274

12151275
// move up
1216-
SV_PushEntity (ent, upmove); // FIXME: don't link?
1276+
SV_PushEntityTo (ent, upmove); // FIXME: don't link?
12171277

12181278
// move forward
12191279
ent->v.velocity[0] = oldvel[0];
@@ -1237,15 +1297,17 @@ static void SV_WalkMove (edict_t *ent)
12371297
SV_WallFriction (ent, &steptrace);
12381298

12391299
// move down
1240-
downtrace = SV_PushEntity (ent, downmove); // FIXME: don't link?
1300+
VectorCopy (ent->v.origin, downmove);
1301+
downmove[2] += -STEPSIZE + oldvel[2] * host_frametime;
1302+
downtrace = SV_PushEntityTo (ent, downmove); // FIXME: don't link?
12411303

12421304
if (downtrace.plane.normal[2] > 0.7)
12431305
{
12441306
if (ent->v.solid == SOLID_BSP)
12451307
{
12461308
ent->v.flags = (int)ent->v.flags | FL_ONGROUND;
12471309

1248-
// SV_PushEntity() call SV_LinkEdict (true) that could free downtrace.ent
1310+
// SV_PushEntityTo() calls SV_LinkEdict (true) that could free downtrace.ent
12491311
if (downtrace.ent && !downtrace.ent->free)
12501312
ent->v.groundentity = EDICT_TO_PROG (downtrace.ent);
12511313
}
@@ -1447,7 +1509,7 @@ Toss, bounce, and fly movement. When onground, do nothing.
14471509
static void SV_Physics_Toss (edict_t *ent)
14481510
{
14491511
trace_t trace;
1450-
vec3_t move;
1512+
vec3_t end;
14511513
float backoff;
14521514

14531515
// regular thinking
@@ -1468,8 +1530,8 @@ static void SV_Physics_Toss (edict_t *ent)
14681530
VectorMA (ent->v.angles, host_frametime, ent->v.avelocity, ent->v.angles);
14691531

14701532
// move origin
1471-
VectorScale (ent->v.velocity, host_frametime, move);
1472-
trace = SV_PushEntity (ent, move);
1533+
VectorMA (ent->v.origin, host_frametime, ent->v.velocity, end);
1534+
trace = SV_PushEntityTo (ent, end);
14731535

14741536
if (ent->free)
14751537
return;
@@ -1495,7 +1557,7 @@ static void SV_Physics_Toss (edict_t *ent)
14951557
{
14961558
ent->v.flags = (int)ent->v.flags | FL_ONGROUND;
14971559

1498-
// SV_PushEntity() call SV_LinkEdict (true) that could free trace.ent
1560+
// SV_PushEntityTo() calls SV_LinkEdict (true) that could free trace.ent
14991561
if (trace.ent && !trace.ent->free)
15001562
ent->v.groundentity = EDICT_TO_PROG (trace.ent);
15011563

Quake/world.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
3030
// but it differs from other engines and sometimes entities are falling through
3131
// the world at level start because being misplaced.
3232
// In such case, disable it (0) to get the same behaviour as QuakeSpasm.
33-
cvar_t sv_fte_recursivehullckeck = {"sv_fte_recursivehullckeck", "1", CVAR_ARCHIVE};
33+
cvar_t sv_fte_recursivehullckeck = {"sv_fte_recursivehullckeck", "1", CVAR_NONE};
3434

35-
cvar_t sv_fte_createareanode = {"sv_fte_createareanode", "1", CVAR_ARCHIVE};
35+
cvar_t sv_fte_createareanode = {"sv_fte_createareanode", "1", CVAR_NONE};
3636

3737
/*
3838

0 commit comments

Comments
 (0)