Movement (src/game/pmove.cpp)
- A grab inside the release distance starts the grapple cooldown. The owner reported: "if you are against a wall and press grapple it plays the sound like 100 times per second". The hook lets go when you're within 1.2 m of its anchor (kHookRelease). Up against a wall, the anchor is already that close, so the grab let go in the same 1 ms step it was made. The 1 s cooldown only started when a hook that was already held at the start of a step let go, so it never started here. With the button held, the hook grabbed again on the next step, 1000 times a second, and each grab raised ME_HOOK, so the client played the fire and hit sounds every frame. Now a grab released in its own step starts the cooldown too: holding grapple against a wall grabs once a second (at 0 ms and 1001 ms in the test). Normal grapples are unchanged, since their release already started the cooldown.
Physics editor (tools/physics_editor/web/js/mockpm.js)
- The page's mock pmove has the same fix, so its grapple scenarios match the game.
Tests (tests/tests.cpp)
- testGrappleAgainstWall (movement group): grapple held for 1.5 s facing a wall 1 m away gives exactly 2 grabs, 1 s apart. Without the fix it gave 1500.
Compatibility
- No protocol change (still 44). A client from 1.18.22-1.18.31 on a 1.18.32 server (or the other way round) predicts the old regrabs against a wall until it gets corrected. Grapples anywhere else behave the same on both versions.