Repository navigation
Bots follow a partial chase path as far as its areas reach - #2165
Merged
sunzenshen merged 5 commits intoOct 17, 2026
Merged
Conversation
sunzenshen
force-pushed
the
bot-chase-partial-path-pr
branch
2 times, most recently
from
September 26, 2026 12:06
4991b14 to
8f71472
Compare
sunzenshen
added a commit
to sunzenshen/neo
that referenced
this pull request
Oct 1, 2026
…nto the research branch
sunzenshen
force-pushed
the
bot-chase-partial-path-pr
branch
from
October 3, 2026 17:50
dcb3c61 to
70b3fda
Compare
sunzenshen
force-pushed
the
bot-chase-partial-path-pr
branch
from
October 12, 2026 03:56
70b3fda to
8fb5d74
Compare
sunzenshen
marked this pull request as ready for review
October 15, 2026 04:01
ChasePath::RefreshPath treats every failed Path::Compute as FAIL_NO_PATH_EXISTS: it fires OnMoveToFailure, invalidates the path and blocks repathing for 0.005 s per unit of range. But a failed Compute still leaves a valid PARTIAL_PATH ending in the reachable area closest to the target, and the bot's own path cost asks for exactly that: neo_bot_path_cost refuses hazard areas under DEFAULT_ROUTE "to partially path up to boundary of hazard area". Over 584 measured chase failures every one carried such a path, the median ended beside the target (141 u short) and the blackout ran a median 6.3 s with the bot standing still. Count a partial path that already covers 90 % of the straight-line range to the target as a path. One that ends well short still fails, so the range-scaled throttle keeps guarding the genuinely unreachable case. Measured on six maps (oilstain, transit, bullet, yard, apparatus, saitama), 49 pinned matches per arm, paired per pin: FAIL_NO_PATH_EXISTS 11.63 -> 0.08 a match x0.12 [0.10, 0.14], 47 down / 0 up other move failures 2.92 -> 1.71 OnStuck 69.7 -> 68.1 x1.12 [0.92, 1.37] (no change) kills 13.1 -> 13.6 x1.05 [0.95, 1.15] rounds won by capture 0.39 -> 0.47 It lowered FAIL_NO_PATH_EXISTS on every map and moved nothing else.
A partial chase path runs through the areas the search reached and then straight to the subject, through whatever stopped the search: a hazard area refused under DEFAULT_ROUTE, a blocked area, a climb the class cannot make. The previous commit's 90 % gate measured the distance to that appended subject position, so it passed practically every partial path, and the bot walked the unreachable last leg into walls and back into the smoke the path had avoided. Follow a partial chase path regardless of distance, with two bounds: - refuse one that crosses no area before its last leg (the bot already stands in the last area it can reach), as a failed search does; - walk the last leg only while the ground a hull width ahead is still the last reachable area, then wait there on the range-scaled retry. If the follower drops the path on the leg, wait the same way instead of rebuilding it on the next tick. sv_neo_bot_path_partial_chase_enable 0 restores the stock behaviour. Measured with bots on six maps, 24 matches an arm, against following the whole partial path: hazard re-entries 2.17 -> 0.42 a match, stuck on the last leg 0.62 -> 0, stuck on a partial path 0.42 a match against ~8 on full chase paths. Against stock on all 29 ntre maps: OnStuck, falls and kills unchanged, FAIL_NO_PATH_EXISTS down on 25 maps.
The partial chase path is the behavior the change proposes, so it is implemented directly instead of behind sv_neo_bot_path_partial_chase_enable. The stock behavior stays in history for comparison.
BackOff repeated RefreshPath's 0.005 s per unit of range as a bare literal. Name it CHASE_RETRY_SECONDS_PER_UNIT and say where the rate comes from, so a reader can keep the two in step without touching the stock failure branch.
The look-ahead is 24 u and a hull is 32 u wide (HalfHumanWidth 16), so it is three quarters of a hull width, not about one.
sunzenshen
force-pushed
the
bot-chase-partial-path-pr
branch
from
October 15, 2026 04:08
cdd3aaf to
1b24371
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Background
In bot matches with smoke grenades, bots chasing an enemy they could not fully reach stood still for seconds at a time. NEO's path cost refuses smoke and other hazard areas on purpose, so that paths stop at their edge; when that left no complete route to the chased enemy, the chase threw the partial path away and the bot waited out the retry timer (10 s for a target 2000 u away). This PR has the bot walk the partial path to the edge of what it can reach instead, and roughly halves
FAIL_NO_PATH_EXISTSfailures on every map measured.Navmesh PRs that rely on this
None of the open navmesh PRs needs this. It is a general chase fix that acts wherever a hazard or a gap in the mesh cuts a chase short, on every map and navmesh.
What changed and why
When
Path::Computecannot reach a chase target,ChasePath::RefreshPathnow keeps the partialpath it built instead of treating it as a failure, and bounds how far the bot follows it:
stands in the last area it can reach);
still the last reachable area, then waits there on the usual range-scaled retry;
rebuilding the same path on the next tick.
Nothing changes when the path compute succeeds, and nothing outside
ChasePathis touched. Thethird commit removes the
sv_neo_bot_path_partial_chase_enablecvar the earlier commits added: thePR implements the behavior directly. The last two commits name the retry rate
BackOffshareswith
RefreshPathand correct a comment.The defect
When
Path::Computecannot reach the goal area it returns false, but it has still built a validpartial path.
RefreshPaththrows that away: it firesOnMoveToFailure(FAIL_NO_PATH_EXISTS),invalidates the path and blocks re-pathing for 0.005 s per unit of range (10 s for a target
2000 u away), while the bot stands still.
NEO's own path cost relies on partial paths:
neo_bot_path_cost.cpprefuses hazard areas underDEFAULT_ROUTE"to partially path up to boundary of hazard area".Why the bounds
A partial path is the chain of areas the search reached plus one straight leg from the last of
them to the target itself (
Computeappends the goal position whenincludeGoalIfPathFails).That leg runs through whatever stopped the search. The first commit on this PR accepted a partial
path covering 90 % of the range, but it measured the distance to that appended target position, so
it passed 1,775 of 1,781 partial paths. Following the whole path had bots walk the leg into walls
and back into the smoke the path had avoided. The two bounds above stop the bot at the edge of the
reachable mesh instead. A time-limited attempt at the leg was also measured; it performed the same
and needed more state, so the edge check was kept.
With #2174
#2174 moves the chase of the enemy ghost carrier into
CNEOBotCtgEnemyChase, which still drives aChasePaththroughCNEOBotPathUpdateChase, so this change applies to that chase as it does toCNEOBotAttackand the escorts. The two PRs touch different files and merge in either order.CNEOBotCtgEnemyChase::OnResumeinvalidates the chase path;ChasePath::Invalidateleaves theretry timer alone, so a bot waiting at the edge of its reachable mesh keeps waiting after a
suspension, as it does after a stock failure.
Measurement
Headless bot matches on master
f426c222bagainst master + this PR (Release, both arms the samebuild type), 30 bots, smoke grenades thrown freely (
sv_neo_bot_grenade_polite_smoke 0) to raisehazard exposure, one round a match, every XP rank tier rotated across the matches. CTG and JGR
maps ran once per pinned ghost or juggernaut spawn: oilstain (5 spawns), transit (5), saitama (17),
yard (3); bullet (TDM) and apparatus (DM) ran 14 matches each. 58 matches an arm, paired by spawn,
0 asserts, every match ended on its own.
FAIL_NO_PATH_EXISTSFAIL_FELL_OFFFAIL_NO_PATH_EXISTSis lower on all six maps (x0.33 to x0.74). What remains is the two failuresthis PR keeps: a bot already at the edge of its reachable mesh, and a bot that has walked to that
edge (about one edge stop in ten partial paths followed).
By XP tier, stuck episodes x1.06 to x1.12 and kills -3.8 (Sergeant) to +2.6 (Private) a match;
no tier stands out.
Per map, three tails stand out, all on few pairs: saitama's stuck episodes x1.43 [1.00, 2.07]
(17 pairs), transit's stucks while chasing 1.6 -> 8.0 a match (x5.00 [1.07, 43], 5 pairs) and
yard's kills -11.0 [-13.0, -7.0] (3 pairs). Few of those stucks follow a partial path: of the
stucks while chasing with this PR, 18 of 86 on saitama and 2 of 40 on transit came within 10 s
after the same bot accepted a partial path or stopped at its edge (121 of 1019 over all maps);
the rest were on complete paths.
OnStuck leans up without leaving its interval. The repeating spot is bullet's smoke corridor
(x ≈ -1024): bots stick there in both arms, mostly while retreating from the hazard (104 of 154
events on master, 130 of 206 with this PR), so a bot that walks to the smoke's edge instead of
standing still meets that existing retreat problem more often. A bot that stops at the edge is
stuck within 5 s afterwards in 2 of 489 edge stops.
Risk
A bot may now walk toward a target it cannot fully reach instead of standing still; it stops at the
edge of the last area it can reach and retries on the stock timer. Most chasing stucks happen on
complete paths and come from map geometry, the same with or without this change.
Toolchain
Linked Issues