Technical proposal · Draft for review

Footstep Consistency Audit

Abstract

Footstep sounds in Neotokyo Rebuild were inconsistent: crouch walking made noise, looking around while crouch walking made noise, a stray step played when dropping into a crouch, crouched steps played louder than running, and other players heard different steps than the stepping player. This paper traces each case to its cause across ten instrumented play passes, reviews how the current rule came about, and proposes one footstep formula that every client evaluates the same way, without changing movement. Sections 1 and 2 state the problem and the proposal; sections 3 to 5 add the per-change breakdown, the supporting data and the references.

1Problem

Footsteps sounded inconsistent compared with other games in the genre. What a player heard from their own feet, what the server sent, and what other players heard often disagreed. Loudness was inconsistent too: some quieter ways of moving played louder steps than running.

Expected
  • Crouching is silent unless sprinting.
  • Breaking a speed threshold for any reason still makes noise: a slide from a running jump, being pushed.
  • Standing in place never makes noise.
  • Volume follows how you move: sprinting is the loudest, and a crouch sprint is quieter than a standing sprint but louder than a run.
  • Walking, aiming and crouching follow the same silent rule.
  • Movement feel stays the same.

1.1Why players heard different things

Each footstep is decided separately in three places. Each one runs UpdateStepSound, then IsMakingFootstepSounds decides whether the step is silent. The marks show where each defect sits.

Server

every player, every tick
  1. PlayerMove runs the step timer
  2. Silent movement threshold crouch last step
  3. Volume picked by speed against 80 or 220 u/s volume
  4. PlayStepSound sends to players who can hear the stepper but cannot see them origin

Your client

your own steps, predicted
  1. Same movement code, first prediction only
  2. Silent movement threshold crouch last step
  3. Volume picked by speed against 80 or 220 u/s volume
  4. Plays locally, surface from movement code

Other clients

your steps, as others hear them
  1. C_BasePlayer::Simulate estimates speed from position updates
  2. Silent movement threshold crouch last step
  3. Volume picked by speed against 80 or 220 u/s volume, then plays locally

1.2How loud each step was

Every audible step plays the surface's quiet or loud volume, picked by comparing speed with a fixed run speed: 80 u/s crouched, 220 u/s standing. Crouched steps are then scaled to 65%. Those two numbers come from the Source SDK, which crouches at a third of run speed. NEO's speeds sit on the wrong side of both.

MovementExpectedMaster, concreteWhy
Walk, aim with +walksilentsilentsilent movement threshold
Crouch walksilent0.325above 80, so the loud volume, at 65%
Runquiet0.2under 220, so the quiet volume
Crouch sprintbetween a run and a sprint0.325above 80, so the loud volume, at 65%
Sprint, recon with a 0.85 weaponloudest0.5229.5, above 220
Sprint, recon with a slower weaponloudest0.2216 with a 0.8 weapon, less with the rifles, under 220
Sprint, assaultloudest0.2217.6 at most, with a 0.85 weapon, under 220

So a crouch walk was about 1.6 times as loud as a run, and a sprint was only loud for a recon carrying a 0.85 weapon. Support cannot sprint. Section 3.5 has the fix, and 4.3 the test log counts.

The BALC is the only weapon with no slowdown (1.0), and only the Juggernaut carries it. A Juggernaut sprint reaches 256 u/s and was loud on master. This paper leaves the Juggernaut mode out of the sprint figures; it will be reviewed separately.

1.3How the current rule came about

The first row is engine behavior inherited from the Source SDK. Each later step was a reasonable answer to the problem in front of the team at the time. Together they explain why master's rule depends on the aim and walk keys, and why some questions were deliberately left open.

WhenDecisionGoalLeft open
2019-05
SDK import, 4508d865
Inherit the SDK step volume rule. A step is quiet below a run speed of 80 u/s crouched or 220 u/s standing and loud above it, and crouched steps are scaled to 65% (fvol *= 0.65). Stock engine behavior. The SDK crouches at a third of run speed (about 63 u/s), so its crouched steps stayed under 80 and were the quietest steps. Never retuned. NEO crouches at 0.75 of run speed, so every crouch walk passed 80 and played at 0.325, louder than a standing run at 0.2.
2024-06
#275, #363
Follow the original Neotokyo: footsteps decided by speed alone, above 90 u/s standing and 60 u/s crouched. Parity with the original game, after a report that crouching still made footsteps. Nothing at the time.
2025-02
#877
Implement those limits: standing above 90, crouched above 60. Parity. Every crouch walk speed (102 to 150 u/s) is above 60, so crouch walking made footsteps, as in the original.
2025-07
#1097, from #992
Let the walk key decide: holding +walk is silent below a raised limit, so bunny hopping still makes noise. Walking speed was raised to at least 90. Players could not tell whether they were moving quietly. A held key gives a predictable answer, without a new HUD element. The code comment already named turning and changing direction as ways to gain speed. Raising walk speed changed movement.
2025-10
#1431
Make aiming silent too, set the limit per class (0.7 × run speed, or 0.7 × crouch speed while crouched), and return walk speeds to parity. Aiming moved slower than walking yet made footsteps, and the walk speed change had altered movement. Parity against key-based sneaking was deferred to a later discussion. 90° turns made footsteps, and a higher limit was suggested. Slow analog movement without a key stayed audible, and a speed-only check was suggested.
2026-09
#2151
Move the check into IsMakingFootstepSounds and let water splashes follow footsteps. Sneaking players can enter and leave water silently. No change to the rule itself.
Where this proposal sits
  • The standing rule from #1431 gives way to the crouched rule, so walking, aiming and crouching share one threshold.
  • Step volume follows the movement mode instead of the SDK's speed cutoffs. The SDK's 65% for crouched steps stays.
  • Crouching joins the quiet modes, with a threshold relative to the player's own crouch walk speed. Quiet is still opted into with a key, so the speed-only and analog input suggestions from #1431 remain open for standing movement.
  • The turn headroom answers the 90° turn issue raised in #1431, for crouching and walking.
The open parity question

Under the original game's 60 u/s crouched limit, a crouch walk makes footsteps. Under this proposal it is silent. #1431 deferred the parity discussion; this proposal takes a position for crouching, and that choice is worth discussing on its own.

#1097 also links a Discord thread and a change document, which are not summarised here.

2Proposal

Make every footstep follow one formula, decided the same way by the server, the stepping player and everyone listening. Movement speed and feel do not change; only whether and when a step sound plays.

1Quiet is opt-in. Crouching, walking and aiming are silent at their normal speed, for every class and weapon. Moving faster than that still makes noise: sprinting, sliding, being pushed. Outside those three, every step at 50 u/s or faster is heard, so a runner passing through walk speed stays audible.

2Pressing crouch, walk or aim holds back your next footstep. If you settle into that mode, it is dropped. If you let go too early, it plays right away. Jumping or grabbing a ladder forgets it, and landing from a jump always makes a step.

3Looking around while crouch walking or walking does not count as speeding up. Turning widens the threshold, and one brief switch of movement keys is forgiven.

4Footstep volume follows how you move. Sprinting is the loudest, running keeps its volume, and a crouch sprint is 65% of a sprint: louder than a run, quieter than a standing sprint.

2.1The footstep formula

The same shared code decides a footstep in three places: the server, the stepping player's own client, and every other client that can see them. The terms come in three groups: speeds, turning and timers. Each group gets one picture, and the formulas reuse the same letters.

sound threshold = GetNormSpeed * weapon scale * SILENT_MOVE_THRESHOLD
actual speed    = GetNormSpeed * weapon scale * stance modifier

Both sides carry the weapon, so the gun cancels out: a quiet mode is silent whenever its stance modifier is at or below SILENT_MOVE_THRESHOLD, 0.825. Crouch and walk are 0.75 and aim is 0.6, so all three qualify with every gun; sprinting multiplies by 1.35 or 1.6 and never does. On master the threshold was GetNormSpeed * 0.7 with no weapon term, so the headroom changed with the gun.

2.1.1Speeds

0 0.5 1 = run 1.5 Normalized speed = ground speed ÷ (GetNormSpeed × weapon speed scale) 1.0 is your run speed with your current weapon, for every class volume quiet modesnormal movement Crouch silent Walk silent Aim silent Crouch sprint recon assault 0.325 Run 0.2 Sprint recon assault 0.5 silent up to 0.825 turning: up to 1.1625
The axis is normalized: ground speed divided by class run speed (GetNormSpeed) times the weapon's speed scale, so 1.0 is your run speed with that weapon. The threshold and every stance carry the same two factors, so one picture is exact for every class and weapon. Green band: silent. Dashed band: extra room while turning, which sprinting never gets. Red marks make footsteps, at the volume on the right. Below 50 u/s no posture makes footsteps.
Reference: move speeds per class
ClassRunCrouchWalkAimSilent limitCrouch sprintSprint
Recon200150150120165202.5270
Assault, VIP, Juggernaut16012012096132192256
Support16012012096132cannot sprintcannot sprint

Units per second with no weapon slowdown. Multiply every column by your weapon's speed scale, from 0.675 (PZ) to 0.85 (SRM, knife, pistols); section 4.2 lists every gun. Crouch sprint is faster than a run: by 1.25% for recon and 20% for the others.

v
The player's horizontal speed, because other clients guess vertical speed badly on stairs.
w
Your own run speed with your weapon: GetNormSpeed × weapon speed scale (assault with an SRM: 136). The silent threshold is SILENT_MOVE_THRESHOLD, 0.825, of it.
c
Your own crouch walk speed, which is also your walk speed: 0.75 w.
n
Class run speed with no weapon slowdown (recon 200, others 160), GetNormSpeed. Master's limits are multiples of it; the new rule multiplies it by the weapon first, which gives w.
quiet mode
Crouched, walking or aiming, on the ground. The threshold below applies only inside it; it decides which speed you are measured against, never whether a fast step is silent.
50
No footsteps below 50 u/s in any posture. Outside a quiet mode this is the only threshold.

2.1.2Turning

0901802703600.750.91.051.2 view turn rate r, degrees per second × run speed w silent limit while turning: 0.825 + 0.3375 · min(r / 180, 1) turn headroom added by this PR silent limit without turning, 0.825 dashed: speed from turning (simulated)
Holding a movement key while turning builds speed toward the new direction faster than friction removes the old one, so turning really makes you faster. Switching keys (W+D to W+A) does the same without turning the view. The green area is the turn headroom this PR adds: while the view turns, the silent limit rises from 0.825 to as much as 1.1625 of run speed, so that speed-up does not make footsteps. The dashed line is the speed turning can really reach (simulated with sv_accelerate 10, sv_friction 4). It stays inside the green area at every turn rate.
r
How fast the view is turning, in degrees per second. It keeps its peak and fades over about 0.25 s, about as long as friction takes to remove the extra speed.
t
Turn headroom: 0.3375 × min(r / 180, 1), in units of w (0.45 of c). Zero while sprinting, so crouch sprinting stays loud mid-turn.

2.1.3Timers

0.00.20.40.60.81.01.21.4 seconds after pressing crouch crouch animation, 0.4 s settle 0.1 s step held dropped quiet step quiet step let go here: it plays later: gone one step interval T after the drop
Held step. The game only counts you as crouched once the 0.4 s crouch animation ends, and you keep run speed until then. Now the step that lands in that window waits: once you settle into the crouch it is dropped, and it plays if you stand up again within one step interval, so tapping crouch cannot skip steps.
0.00.51.01.52.02.53.0 seconds crouched on the ground 0.5 s first wiggle: forgiven again: plays 1 s under the threshold forgiven again
One-step forgiveness, in any quiet mode. A single change of strafe direction (W+D to W+A) is normal movement, and the speed it adds is invisible to the turn headroom because the view does not move, so its first step is forgiven. Repeating it is the wiggle, and the rest are heard. A slide from a run or a landing stays audible, because it happens before the 0.5 s.
T
Step interval: 300 ms when fast, 400 ms when slower, plus 100 ms crouched. A footstep is checked each time it runs out.
0.4 s
Crouch animation. Speed and footsteps only count you as crouched after it.
0.1 s
Settle time: still loud this long after crouch, walk or aim takes effect means a slide, push or sprint, so the held step plays.
0.5 s, 1 s
Forgiveness: in a quiet mode on the ground at least 0.5 s before a step can be forgiven; 1 s under the threshold to earn it back.
0.1 s in the air
Landing: after at least this long off the ground, the next step is never silent, whatever the posture. The step timer waits in the air, so after a jump that is the landing tick.

2.1.4Before and after

Before: master
sound threshold = GetNormSpeed * 0.7
actual speed    = GetNormSpeed * weapon scale * stance modifier
quiet modes     = aim, walk
loud if           speed is over a fixed cutoff
  1. quiet modes: crouching was not one, so crouching alone was never silent. A plain crouch walk played at 0.325 volume, louder than a standing run (0.2).
  2. GetNormSpeed * 0.7: no weapon term, so the headroom changed with the gun. While crouched, master used 0.7 × the class crouch speed instead. Crouch with +walk and standing +walk sat only about 10% under the limit with most weapons, up to 38% with the slowest, so looking around while moving pushed them over.
  3. actual speed: measured as full speed, including the vertical speed other clients see when a player steps up stairs.
  4. stance modifier: crouch only counted after the 0.4 s crouch animation, so one running step leaked when dropping into a crouch.
  5. loud if speed is over a fixed cutoff: the SDK's cutoffs, 220 u/s standing and 80 crouched, fit its third speed crouch. Every NEO crouch walk is above 80, so crouched steps were louder than a run, and assault sprints, and recon sprints with most weapons, were under 220, as quiet as a run.
  6. Outside the formula: server footsteps reached nobody (#2230).
After: this PR
sound threshold = GetNormSpeed * weapon scale * SILENT_MOVE_THRESHOLD
actual speed    = GetNormSpeed * weapon scale * stance modifier
quiet modes     = crouch, walk, aim
loud if           sprinting
  1. quiet modes: crouching alone is now a quiet mode, like walking and aiming, and all three share one limit just above their normal speed. Crouch sprinting, slides and pushes stay above it.
  2. sound threshold: it carries the weapon scale like actual speed, so the gun cancels out. It widens while turning, and one brief key switch is forgiven.
  3. actual speed: horizontal speed only.
  4. stance modifier: the step during the crouch animation waits, and is dropped once you settle.
  5. loud if sprinting: the sprint state is networked, so every client picks the same volume. Running is quiet. Crouched steps are scaled to 65%, so a crouch sprint sits between a run and a sprint.
  6. The server footstep origin is fixed in #2230, a separate PR.

Both use the same quiet and loud volumes, and crouched steps are 65% of either. Ladders are always audible: climb speed is a fixed 200 u/s whether crouched or not, and horizontal speed is near zero while climbing. The turning speed curve comes from a movement simulation; every other number is a constant in the code.

2.1.5What this fixes

ScenarioBeforeAfter
Crouch walkingaudible, louder than a runsilent
Crouch walking with +walk or aimsilent until a turnsilent
Crouch sprintingaudible, 0.325audible, 0.325: between a run and a sprint
Sprinting as assault, or as recon with most weaponsas quiet as a runloudest step
Slide from a runaudibleaudible
Pushed while crouchedaudibleaudible
Turning while crouch walkingaudiblesilent
Wiggle walking crouchedaudiblefirst step forgiven, repeats audible
Wiggle walking standingaudiblefirst step forgiven, repeats audible
Walking with the BALCaudiblesilent
Dropping into a crouch from a runone running step leakssilent
Tapping crouch, walk or aimsteps unchangedsteps unchanged, at most 100 ms later
Crouched ladder climbingaudibleaudible
Landing from a jump or a dropaudible, up to 0.5 s lateaudible on the landing tick, in any posture

2.2Changes

2.3What players will notice

3Breakdown of the proposal

Each change in the order a footstep meets it: first the formula's two parts, then the fixes that make other players hear the same answer.

3.1Crouched silent movement threshold

Fixed

Formula part 1, crouched line. Crouching used to be silent only while also aiming or holding +walk, so a plain crouch walk was audible. The threshold is now the player's own crouch walk speed plus 10%, including weapon slowdown. That is the same limit as 0.825 of run speed in the formula.

Crouched speeds per class against the old and new silent movement thresholds The old threshold makes any crouched step above 50 units per second audible unless aiming or walking. The new threshold keeps crouched steps silent up to the class crouch speed plus 10%, so crouch walking is silent and crouch sprinting is audible. 0 50 100 150 200 250 ground speed, units per second Recon 165 Assault 132 Support cannot sprint 132 old: audible above 50 unless aiming or walking
Green band: silent, up to crouch walk speed plus 10%. Hollow dot: crouch with +walk. Filled dot: crouch walk. Diamond: crouch sprint. Drawn with no weapon slowdown. Every crouched speed and the threshold scale by the same weapon factor, so the picture holds for every weapon.
Before
// neo_player_shared.cpp, IsMakingFootstepSounds
if (GetFlags() & FL_DUCKING)
{
    if ((IsInAim() || IsWalking()) &&
        speed <= GetCrouchSpeed() * 0.7f)
        return false;
}
After
if ((GetFlags() & FL_DUCKING) && !isOnLadder)
{
    float crouchSilentGrace = 1.1f;
    // plus turn headroom, see Turning below
    // ground speed: other clients see vertical
    // speed when a player steps up stairs
    if (groundspeed <=
        GetCrouchSpeed_WithActiveWepEncumberment()
        * crouchSilentGrace)
        return false;
}
Weapon slowdown

Every weapon slows its holder, by 0.675× (PZ) to 1.0× (BALC). The threshold first used the class crouch speed alone, so a recon crouch sprinting with a 0.725× rifle (147 u/s) stayed under 165 and made no sound. The threshold now uses the crouch speed with the active weapon applied, which keeps the same 10% margin for every weapon.

3.2Held step when starting a crouch, walk or aim

On branch

Formula part 2. Side-stepping at normal speed and pressing crouch often plays one more footstep. The game only counts a player as crouched once the 0.4 s crouch animation finishes, and a running step fires every 300 ms, so one step almost always lands in between. Walking settles in about 50 ms and is rarely affected, but follows the same rule so both keys behave alike.

Today: the crouch threshold starts when the crouch animation ends An assault player with an SRM runs at 136 units per second and presses crouch at 0 milliseconds. For 400 milliseconds the game still counts them as standing, so a step at 200 milliseconds is audible. After 400 milliseconds speed drops to 102 within about 75 milliseconds and the crouch threshold of 112 applies. counted as standing: audible crouch threshold 112 Today (this branch) 0 100 200 300 400 500 600 700 milliseconds after pressing crouch press crouch crouch animation done 136 last step: audible silent
Today. Assault with an SRM (0.85×): normal run 136, crouch walk 102. Speed only starts dropping once the animation is done, because CalculateSpeed checks the same crouched flag (FL_DUCKING, set at TIME_TO_DUCK, gamemovement.cpp:4767).
Held step in four situations Crouch is pressed at 0 ms and the step timer fires at 200 ms. Holding crouch and settling drops that step at about 450 ms. Letting go at 300 ms plays it at release. Letting go at 600 ms, within one step of settling, plays it at release. Sprinting into a crouch is still loud 0.1 s after the crouch takes effect, so it plays at 500 ms. Held step: the next step is held, then dropped or played 0 100 200 300 400 500 600 700 800 milliseconds after pressing crouch crouch animation done Hold and settle held dropped Let go at 300 ms held plays Let go at 600 ms held dropped plays Sprint into crouch held still loud: plays
Bar: crouch held. Tick at the end of a bar: crouch released. Hollow dot: the step the timer fired, held back. Cross: held step dropped. Filled dot: a footstep that plays. Settling happens at about 450 ms here: the animation ends at 400 ms and friction brings speed under the crouch threshold within about 50 ms.
EventResult
Press crouch, walk or aim while moving on the ground, not already crouchedArmed: the next step the timer fires is held. Each key arms from its own press, so crouch pressed while still aiming arms too
Step timer fires while armedHeld, nothing plays yet
Settled: the mode that armed the step has taken effect, and not loudHeld step dropped
Still loud 0.1 s after crouch, walk or aim takes effect (slide, sprint, push)Held step plays now
Arming key released while a step is heldHeld step plays now
Arming key released within one step interval of the drop, no other quiet mode heldDropped step plays now
Leaving the ground, or on a ladderHeld step forgotten: a landing makes its own step, and ladders are always audible
Key released later than thatNothing extra; normal rules continue
Another loud step while settling into the crouchJoins the held step
A held step playsAt the volume for how you move when it plays, so a step held while standing plays at the crouched 65% once you have crouched. The next step keeps its timing, at least 100 ms later
Why release within one step plays it

Without that rule, a player could repeat crouch, settle, release about every 0.7 s. They would move at full speed for most of each cycle, since the crouch animation keeps run speed, and drop one step per cycle. Giving the step back on an early release means a dropped step always costs a real step's worth of crouched movement. Tapping crouch can only delay a step, never remove it.

Movement is unchanged

The rule only decides whether and when a step sound plays. Speeds, acceleration, the crouch animation and hull sizes stay exactly as they are. Only one step is ever held, and anything still loud after settling plays as before.

Other players get the same answer

Each of the three footstep deciders runs this rule itself, so each needs to know when crouch, walk or aim is held. Walk and aim already are: m_fIsWalking and m_bInAim are sent to every client in NEO (hl2mp_player.cpp:112, neo_player.cpp). For crouch, the engine already defines FL_ANIMDUCKING, "in the process of crouching" (public/const.h:150), inside the m_fFlags every client receives. Nothing in this codebase set it before. The branch sets it while crouch is held (CGameMovement::Duck), which needs no new network data.

Limits

Other clients see press and release about one snapshot late, so a penalty step can sound a few tens of milliseconds later for them than for the player. A second step before settling plays the earlier held one and holds the new one, so a crouch that never settles cannot skip a step either.

3.3Turning and key switching while crouched

On branch

Holding a movement key while turning the view, or switching keys such as W+D to W+A, really raises ground speed: acceleration builds toward the new direction faster than friction removes the old one. The gain reaches about 50% of crouch walk speed, more than the 10% headroom, so looking around while crouch walking made footsteps. The turning chart in section 2.1 shows the threshold against the simulated gain.

Turn headroom
float crouchSilentGrace = 1.1f;
// crouch sprinting stays audible while turning
if (!m_fIsSprinting)
    crouchSilentGrace += 0.45f *
        Min(m_flFootstepTurnRate / 180.f, 1.f);

The turn rate comes from the view yaw, which every client receives, keeps its peak and fades over 0.25 s.

One-step forgiveness

Switching keys does not turn the view, so turn headroom cannot see it. The first crouched step over the threshold is skipped when the player has been crouched on the ground for 0.5 s, is not sprinting and is no faster than 1.55 c. It comes back after 1 s under the threshold.

Test passBeforeAfter
Crouch walking while turning (recon, c = 127.5)13 footsteps at 140.5 to 156.5 u/s, turn rates up to 3,000°/snone from turning; crouch sprinting still audible at up to 483°/s
Wiggle walking crouchedabout 30 footsteps at 142.8 to 165.1 u/s, turn rate under 41°/sfirst step forgiven in each of 7 bursts, the rest audible

3.4One silent threshold for crouch, walk and aim

On branch

Walk speed and crouch walk speed are the same: class speed × weapon × 0.75. Master still measured standing walk and aim against 0.7 × class run speed, which ignores the weapon. The headroom swung from about 10% to 38% by gun, had no allowance for turning, and the BALC was always audible. Walking and aiming now use the crouched rule.

Standing walk, assaultWalk speedMaster, 0.7 nThis branch, 1.1 c
0.85 (SRM, MPN)102112112.2
0.8 (SMAC)96112105.6
0.725 (MX, ZR68-L)8711295.7
0.675 (PZ)8111289.1
1.0 (BALC)120112132
Problem

Two rules for one speed. A heavy weapon gave walking extra headroom that crouching did not get, while a light one left 10% with no allowance for turning, and wiggle walking had no forgiven step.

Fix

Crouched, walking or aiming on the ground: silent up to c × (1.1 + t), with the same one-step forgiveness. With a 0.85 weapon the standing limit is unchanged; slower weapons get the same margin as crouching.

// neo_player_shared.cpp
bool CNEO_Player::IsInSilentMovementMode() const
{
    return (GetFlags() & FL_DUCKING) || IsWalking() || IsInAim();
}

if (IsInSilentMovementMode() && !isOnLadder)
    // silent up to c × (1.1 + t)

Aiming moves at 0.6 × speed, slower than walking, so it keeps about 37% headroom under the shared limit, as on master. Ladders are always audible.

3.5Step volume by movement mode

On branch

The SDK picks a quiet or loud step volume by comparing speed with a fixed run speed, 80 u/s crouched and 220 u/s standing, then scales crouched steps to 65%. Those numbers fit the SDK's own crouch, a third of run speed (about 63 u/s), so its crouched steps stayed under 80 and quiet. NEO crouches at 0.75 of run speed, so every crouch walk passes 80 and gets the loud volume. Standing, an assault sprint stays under 220 with every weapon and a recon sprint only passes it with a 0.85 weapon, so most sprints get the quiet one.

Movement, concreteMasterThis branch
Walk, aim with +walksilentsilent
Crouch walk0.325silent
Run0.20.2
Crouch sprint0.3250.325
Sprint, recon with a 0.85 weapon0.50.5
Sprint, recon with a slower weapon0.20.5
Sprint, assault0.20.5
Problem

A crouch walk played about 1.6 times as loud as a run, and a sprint was only loud for a recon carrying a 0.85 weapon. With master's crouched rule restored, a crouch walk sounded louder than a run.

Fix

Choose the volume from the movement mode. Sprinting is loud and everything else is quiet, for every class and weapon. Crouched steps keep the SDK's 65%, so a crouch sprint is 0.325: louder than a run, quieter than a standing sprint. Crouch walking is silent, so the quiet crouched volume (0.13) only plays on landings and on steps over the threshold.

// neo_player_shared.cpp
bool CNEO_Player::IsQuietFootstep() const
{
    return !m_fIsSprinting;
}

// baseplayer_shared.cpp, UpdateStepSound
fvol = pNeoPlayer->GetSurfaceStepVolume(psurface, bQuietStep); // 0.2 or 0.5 on concrete
if (GetFlags() & FL_DUCKING)
    fvol *= NEO_FS_CROUCH_VOLUME_SCALE; // 0.65

Other surfaces keep their own quiet and loud pair, such as dirt at 0.25 and 0.55. The step interval still follows speed, so step timing does not change. The sprint state and the crouch flag reach every client, so the server, the stepping player and everyone listening pick the same volume.

3.6Landing step

On branch

A flat jump falls too slowly for the engine's landing sound, which needs 350 u/s of fall. The footstep players hear on landing is the regular step whose timer ran out in the air, firing on the first tick back on the ground. Two things had broken that. Since #2151 UpdateStepSound restarted the step timer even in the air, so the first step after landing came at a random phase up to half a second later. And with crouching a quiet mode, a crouched landing at walk speed made no sound at all.

Problem

Crouch-jumps and drops landed silently, and standing landings had a step somewhere in the next half second rather than on impact.

Fix

The step timer waits at zero while off the ground, as in the SDK, so after a jump the step lands on the landing tick. A landing after at least 0.1 s in the air marks the next step decision as never silent, which also covers short drops whose timer has not run out.

A standing jump in place still makes nothing, since speed is under 50 u/s on landing, as before. The engine's hard-landing sound is unchanged.

3.7Server footstep origin

Fixed · PR #2230
Problem

In NEO builds, the only line that set feet sat inside #ifndef NEO, after #2151 removed the unused locals around it. The server then chose recipients from an uninitialized position.

Fix

Move VectorCopy( vecOrigin, feet ); out of the #ifndef NEO block. Upstream PR #2230 makes exactly this change. The branch carries a local copy until it merges.

Behavior change: players who are out of sight but within hearing range get the server's footsteps again, as in the stock SDK.

4Supporting detail

Reference numbers, test data and branch state behind the parts above.

4.1Speed matrix

Top ground speed for each class, posture and gait on flat ground, and whether a step at that speed is silent. Speeds come from CalculateSpeed capped by the movement code's per-gait limit. The right-hand column gives the silent movement threshold.

StandingCrouchedSilent threshold
Class+walknormalsprint+walknormalsprintcrouch, walk, aim

4.2Speed by gun and class

Top ground speed in u/s with each weapon held, from CalculateSpeed: class speed × weapon speed scale, then × 0.75 for crouching or for standing with +walk. Crouch and +walk come out equal for every class and gun. What differs is the silent movement threshold each one is measured against.

GunScaleRecon (200)Assault (160)Support (160)VIP (160)Juggernaut (160)
RunCrouch+walkRunCrouch+walkRunCrouch+walkRunCrouch+walkRunCrouch+walk
BALC1.0 (none)––––––––––––160120120
Knife0.85 (−15%)170127.5127.5136102102136102102––––––
Milso0.85 (−15%)170127.5127.5136102102136102102136102102–––
Tachi0.85 (−15%)170127.5127.5136102102136102102136102102–––
Kyla0.85 (−15%)170127.5127.5136102102136102102136102102–––
MPN450.85 (−15%)170127.5127.5136102102136102102136102102–––
SRM0.85 (−15%)170127.5127.5136102102136102102136102102–––
SRM-S0.85 (−15%)170127.5127.5136102102136102102136102102–––
Frag grenade0.85 (−15%)170127.5127.5136102102136102102136102102–––
Smoke grenade0.85 (−15%)170127.5127.5136102102136102102136102102–––
Detpack0.85 (−15%)170127.5127.5136102102136102102136102102–––
Ghost0.85 (−15%)170127.5127.5136102102136102102136102102–––
Jitte0.8 (−20%)160120120128969612896961289696–––
Jitte scoped0.8 (−20%)160120120128969612896961289696–––
ZR68-C0.8 (−20%)160120120128969612896961289696–––
SMAC0.8 (−20%)160120120128969612896961289696–––
PBK56S *0.8 (−20%)160120120128969612896961289696–––
ZR68-S0.775 (−22.5%)155116.2116.2124939312493931249393–––
Mosok (M41)0.725 (−27.5%)145108.8108.8116878711687871168787–––
Mosok Silenced0.725 (−27.5%)145108.8108.8116878711687871168787–––
Mosok Scoped0.725 (−27.5%)145108.8108.8116878711687871168787–––
MX0.725 (−27.5%)145108.8108.8116878711687871168787–––
MX Silenced0.725 (−27.5%)145108.8108.8116878711687871168787–––
ZR68-L0.725 (−27.5%)145108.8108.8116878711687871168787–––
SRS0.725 (−27.5%)145108.8108.8116878711687871168787–––
AA130.725 (−27.5%)145108.8108.8116878711687871168787–––
Supa-70.7 (−30%)140105105112848411284841128484–––
PZ2520.675 (−32.5%)–––108818110881811088181–––

4.3Test evidence

Four play passes on a listen server. One bot mirrored the host's inputs (bot_mimic 1) so every step could be compared as the stepping player, the server, and another client heard it. neo_footstep_debug logged each footstep decision, and chat messages marked what was heard in game.

Server footstep origin (pass 1)

Pass 1, beforeServer footstepsWith no recipientsExample origin
Bots and host, one round811811(0.0 6.47e36 0.0)

Crouched threshold (before, and pass 4 after)

ScenarioBeforeAfter (pass 4)
Crouch walk54 server steps at 65 to 139 u/s0 steps, yours and the bots'
Crouch sprintaudible30 steps at 172 to 222 u/s
Sprint into crouchaudiblesteps while above the threshold (168 to 186 u/s)
Other clients' estimate of a crouch walkern/ano false steps; all 98 crouched bot steps match the server

Step volume on master

Server steps, master's crouched ruleStepsVolumeSpeed, u/s
Standing run3510.20about 100 to 170
Crouch walk210.32about 96 to 128
Crouched, under 80 u/s110.1351 to 80
Standing sprint, over 220 u/s50.50223 to 236

Shared threshold

Pass, assaultAudible stepsForgiven
Walk straight00
Walk in a circle00
Walk wiggle, 0.85 weapon181
Walk wiggle, MX (0.725)292, one per burst
Aim wiggle, no +walk00
Crouch walk00

Held step and landing, review passes

PassResult
Crouch pressed just before a ladder, climbladder steps audible
Aim held, press crouch, release aim, 10 times0 steps heard; 19 held then dropped
Crouch to stand and aim, 5 times0 steps
Crouch-jump, hold-crouch jump, walk off a box crouchedone step on every landing, at 0.13
Running jumpsstep on the landing tick

Test setup

sv_cheats 1; neo_bot_quota 1
neo_footstep_debug 1; neo_footstep_debug_bots 1
con_logfile footsteps.log
nb_stop 1            // walk up to the bot
nb_stop 0; bot_mimic 1

4.4Footsteps while turning in place

Not reproduced

Reported as footsteps while turning quickly in place. Every movement tick was sampled while turning. Speed stayed at exactly 0 and no footstep played, locally or as other clients hear it.

4.5Branch state

CommitChangeBranch
16dd7ef1Fix footstep sounds playing from an unset origin (copy of PR #2230)base
c2b93eb0Silence crouched footsteps below the player's crouch walk speedfootsteps/sound-consistency
d71c60f3Allow for speed gained by turning or switching keys while crouchedfootsteps/sound-consistency
124ac092Pick footstep volume by movement mode instead of speedfootsteps/sound-consistency
6737d239Share one silent movement threshold across crouch, walk and aimfootsteps/sound-consistency
73291af2Hold the first footstep after pressing crouch, walk or aimfootsteps/sound-consistency
5e81a24cKeep the step on landing audible in every posturefootsteps/sound-consistency
068ab898Start other players' ground surface trace clear of floors and walls-docs only
4b403dc4neo_footstep_debug instrumentation-docs only
596c994dAn experimental crouched threshold setting-docs only

footsteps/sound-consistency-docs sits on top of the PR branch with the ground surface trace fix, the logging, the experiment and these docs.

5References

  1. #275 Crouching still has actors making footsteps sounds
  2. #363 Footstep velocity thresholds at parity
  3. #877 Footstep velocity thresholds at parity
  4. #992 [FEATURE] UI element for walking
  5. #1097 Silent Walking
  6. #1431 Silent footsteps in aim, revert walk speeds to parity
  7. #1831 Walking Indicator (open)
  8. #2151 Fix silently entering/exiting water
  9. #2230 Fix footstep sounds playing from an unset origin (open, separate)