Files
WoWee/tests/test_movement_limits.cpp
Kelsi 7e62d150f5 fix(movement): stop sinking through hills when a frame runs long
Floor selection rejects any surface more than kMaxStepUp above the feet as
unreachable, and kMaxStepUp is 0.60 yards. At the steepest walkable slope
that budget covers a mounted player for about 1/40th of a second: a smooth
frame rises 0.28 yards, a 20 fps frame rises 0.83 and the terrain the player
is climbing stops counting as ground.

From there it compounds. With no floor the player falls, which puts the feet
further below the surface, so the next sample is rejected by a wider margin
than the last. Nothing recovers: the narrow fallback allows 0.5 yards of
penetration and only for 0.10s outdoors, and void recovery does not fire
until 60 yards down — by which point the player has fallen through the hill.

Outdoors the heightfield has one surface per column, so feet below it is
never a valid position. Push back out to the surface when that happens,
which also makes the climb itself smooth: each frame moves, penetrates
slightly, and is lifted clear. Restricted to open ground — not inside a WMO,
no WMO or M2 floor sampled or hinted nearby — because a cave, a tunnel or
Ironforge is legitimately beneath the terrain and must never be yanked up
onto the mountain above it. Bounded below by 0.10 yards so ordinary contact
and one frame of gravity do not trigger it, and above by 12 so anything
deeper is left to the existing void recovery.
2026-07-31 15:17:59 -07:00

39 lines
1.8 KiB
C++

#include <catch_amalgamated.hpp>
#include "rendering/movement_limits.hpp"
TEST_CASE("stock hill climbing limits are shared by all surfaces") {
using namespace wowee::rendering::movement;
REQUIRE(kMaxWalkableSlopeDegrees == 50.0f);
REQUIRE(isWalkableNormal(kMinWalkableNormalZ));
REQUIRE_FALSE(isWalkableNormal(kMinWalkableNormalZ - 0.001f));
REQUIRE(isReachableStep(kMaxStepUp));
REQUIRE_FALSE(isReachableStep(kMaxStepUp + 0.001f));
}
// A walkable slope can rise faster than the step-up budget allows for, which is
// why grounding cannot rely on the budget alone. At the steepest walkable angle
// a mounted player crosses more ground per frame than kMaxStepUp covers as soon
// as the frame runs long — and the floor selection rejects any surface above
// feet + budget as unreachable, so the terrain under a climbing player stops
// counting as ground and they sink into the hill.
TEST_CASE("a walkable slope out-climbs the step-up budget in a long frame") {
using namespace wowee::rendering::movement;
// tan(50 degrees), the rise per unit travelled along the steepest slope a
// player may walk up.
constexpr float kSteepestRisePerYard = 1.19175f;
auto riseOverFrame = [](float speedYardsPerSec, float frameSeconds) {
return speedYardsPerSec * frameSeconds * kSteepestRisePerYard;
};
// A smooth frame stays well inside the budget at every travel speed.
CHECK(riseOverFrame(7.0f, 1.0f / 60.0f) < kMaxStepUp); // running
CHECK(riseOverFrame(14.0f, 1.0f / 60.0f) < kMaxStepUp); // epic mount
// A slow frame does not. This is the case that put the player inside the
// hill, so grounding has to recover from penetration rather than assume it
// cannot happen.
CHECK(riseOverFrame(14.0f, 1.0f / 20.0f) > kMaxStepUp);
}