Repository navigation
Refuse a path whose last leg climbs past a jump - #2212
Closed
sunzenshen wants to merge 1 commit into
Closed
sunzenshen wants to merge 1 commit into
sunzenshen wants to merge 1 commit into
Conversation
This was referenced Oct 10, 2026
sunzenshen
force-pushed
the
bot-trivial-path-unusable
branch
from
October 10, 2026 01:05
ddfa743 to
f84ef29
Compare
Contributor
Author
|
Also don't love how this is a custom solution for one specific map. Maybe let's test the new navmeshes without this PR and see if the results are good enough? |
A bot told to reach a goal high above it, such as cover on a ramp or
walkway overhead, can be handed a path whose last leg is a straight
line up to the goal. The path follower raises FAIL_FELL_OFF ("goal is
high above us") on every tick the bot stands below it, the behavior
computes the same line again, and the bot stands there until it is
shot or the behavior changes.
Path::Compute builds such a leg in two ways. When no area the search
reaches is nearer the goal than the bot's own, it falls back to
BuildTrivialPath, a two-segment partial path, and CNEOBotPathCompute
accepts any partial path. When the goal has no nav area of its own
(GetNearestNavArea finds none with its line-of-sight check), the search
stops at the first area that "contains" the goal, and CNavArea::Contains
only compares against areas whose extent overlaps its own height range.
A floor area directly below the goal therefore contains it: the search
ends there, reports the goal reached, and builds a straight line up to
the goal from that area.
So every path's last leg is judged, complete or not: when the goal
sits more than the bot's jump height (GetMaxJumpHeight) above the floor
of the area the leg starts from, and no planned jump-up, gap jump, drop
or ladder leads into it, the path is unusable. The compute then falls
through to the reservation-free pass and reports no path. Attack
already drops its cover area and chases on a failed compute; retreat to
cover now picks another cover area, or ends when the pick repeats,
instead of ignoring the result.
On ntre_apparatus_dm, area 2067 below the ramp area 7687, every one of
547 FAIL_FELL_OFF events in 48 matches had this two-segment path.
sunzenshen
force-pushed
the
bot-trivial-path-unusable
branch
from
October 12, 2026 03:53
f84ef29 to
86828c3
Compare
Contributor
Author
|
After everything recently merged, this didn't gain much in recent tests, so closing this time. |
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
CNEOBotPathComputeaccepts a path whose last leg ends far above the floor it starts from, and bots then stand under the goal.Path::Computebuilds such a path whenGetNearestNavAreafinds no area for the goal position: the search then stops at the first area for whichCNavArea::Containsreports the goal inside it.Containschecks the point only against areas whose extent overlaps its own, height included, so a flat floor area directly under the goal "contains" a point 170 u above it. The search reports the goal reached and ends the path with a straight leg from that floor up to the goal (when the bot already stands in that area, the same line comes fromBuildTrivialPath). The path follower raisesFAIL_FELL_OFF("goal is high above us") on every update while the bot stands under the goal, the behavior computes the same path again, and the bot stands there until it is shot or its behavior changes.ntre_apparatus_dmhits this under the top of a ramp at (720, 137, 584): bots retreating to cover on the ramp, or fighting from it, walk to the floor below and stand. With the navmesh from NeotokyoRebuild/neoAssets#157 it is the map's top stuck spot, about 14FAIL_FELL_OFFevents a 30-bot match toward that cover; with this PR, 0.3.Dependencies and status
Needs:
Helped by:
Helps:
ntre_apparatus_dm's first navmesh): this is its top stuck spot, and no navmesh edit there removes it.Pending before review:
What this PR does
neo_bot_path_compute.cpp).IsPathUsablerefuses a path, complete or partial, when its goal ends more thanGetMaxJumpHeight()above the floor of the area its last leg starts from, measured on that area's own plane. Legs with a planned jump-up, gap jump, drop or ladder at either end are left alone.CNEOBotPathComputethen runs its existing second pass (fastest route, reservations ignored) and returns false if that path is refused too. A path that reaches its goal area never trips the check: its last leg lies in that area and ends on its surface.neo_bot_retreat_to_cover.cpp). It ignored the result of its path compute. On a failure it now picks another cover area, and ends ("No path to my cover") when there is none or the pick repeats.How to test
ntre_apparatus_dmhas no navmesh on master: copyneo/maps/nav/ntre_apparatus_dm.navfrom ntre_apparatus_dm: first navmesh neoAssets#157 into the game'smaps/nav/folder.map ntre_apparatus_dm,sv_cheats 1,neo_bot_quota 30,nb_debug behavior, and join spectator.developer 1withnb_debug pathprintsOnMoveToFailure( FAIL_FELL_OFF )on every update). With this PR, it takes the ramp or goes to other cover; the behavior output shows "No path to my cover" when a re-pick repeats.Risk
The check runs on every path a NEO bot computes (about 30 call sites); it refuses only paths whose last leg climbs past a jump, which the follower failed on arrival before this PR. What a refused caller does:
sv_neo_bot_attack_cover_search_interval(1 s).COMPLETE_PATH(seek and destroy, CTG and JGR seek and capture, juggernaut, get health, move to vantage point) take their existing "no path" branch.FAIL_FELL_OFF. On CTG maps, escorting or intercepting a ghost carrier on a ledge with no nav area found under it is the case to watch.CNavArea::ContainsorPath::Compute("Considered, not included").Cost
Path computation only (not per tick): two segment-type checks, one
CNavArea::GetZand one comparison per pass ofCNEOBotPathCompute; no trace. RetreatToCover runs one more cover search per failed path compute, the same search it runs when it starts.Results
Every match below: Release builds on Linux of upstream master at #2175 with #2198 and #2176, without and with this PR. The build with this PR also carried a change #2198 leaves out (a gap jump's goal handed on to its landing once the bot is below the take-off; #2198's "Considered, not included"), so every "with this PR" number below is this PR plus that hand-off. Measured on its own against #2198 as published (9 maps, 72 pairs, 30 bots, same builds otherwise), the hand-off moved fall deaths x0.87 [0.55, 1.44],
FAIL_FELL_OFFx1.02 [0.77, 1.32], stuck episodes x1.19 [1.00, 1.41] and kills -0.6 a match [-1.7, +0.5]. Both builds have the same test-only logging (kills, stuck events, move failures, match end). #157's navmesh with edits in progress at other spots; 30 bots, default weapon damage (1.485), 15 s pre-round freeze; both arms of each pair in the same batch of 8 parallel servers. Every match reached match end with 0 asserts and a clean exit. "Stuck" isOnStuckplusFAIL_STUCKmove failures, without the round-start spawn crowd,SeekWeaponand dead bots; an episode is one bot's events less than 2 s apart. Fall deaths are deaths totrigger_hurtor the world. Intervals are 95 % bootstrap intervals (4000 draws) of the paired ratio.ntre_apparatus_dm, 24 pairs of one-round, 5-minute DM matches (48 matches):FAIL_FELL_OFFtoward the ramp coverFAIL_FELL_OFFFAIL_FELL_OFFmove failures, whole mapFAIL_NO_PATHmove failuresNo new stuck spot above one episode a match. In 10 more matches with this PR and
nb_debug behavior, no bot failed toward the ramp cover, and RetreatToCover ended with "No path to my cover" once.Fall deaths: the lean in the table did not replicate. The 24 pairs above had 24 more fall deaths with this PR (13 at the edges of three gaps, areas 3404, 8352 and 8340, already marked PRECISE and CLIFF; 4 at area 74). 16 more pairs on the same mesh and builds, run later, gave 83 -> 67 (x0.81 [0.55, 1.18]) with no rise at any of those four areas (3404 3 -> 2, 8352 4 -> 2, 8340 4 -> 4, area 74 1 -> 2). Over all 40 pairs the ratio of sums is x1.04. Read as noise; no mechanism of this PR that reaches a gap edge has been found.
No run at 12 bots exists for this PR.
Every other map. The 28 other
ntre_maps in scope, each on the navmesh at the head of its neoAssets PR, without and with this PR (the same two builds as above, 30 bots, damage 1.485, 15 s freeze, Release, Linux, 8 parallel servers): 1 round a match, CTG 3 minutes, JGR and TDM 5 minutes, the ghost or juggernaut pinned to one spawn a match (up to 4 spawns a map, each once; bullet_tdm and isolation 4, yard 3 spawns x 3 repeats). 117 pairs, 234 matches, every one at match end with 0 asserts and a clean exit. Cells are totals over the map's matches, without -> with this PR; kills a match; captures and eliminations are rounds of the map's pairs. Fall deaths are near zero on these maps in either build (6 -> 9 in all, skyline 6 -> 7, sentinel_ctg 0 -> 1, yard 0 -> 1).FAIL_FELL_OFFFAIL_NO_PATHntre_ballistrade_ctgntre_bullet_tdmntre_dawn_ctgntre_decom_ctgntre_disengage_ctgntre_dusk_ctgntre_engage_ctgntre_ghost_ctgntre_isolation_ctgntre_marketa_ctgntre_oilstain_ctgntre_pissalley_ctgntre_redlight_ctgntre_ridgeline_ctgntre_rise_ctgntre_rogue_ctgntre_saitama_ctgntre_sentinel_ctgntre_sentinel_jgrntre_shrine_ctgntre_skyline_ctgntre_subsurface_ctgntre_tarmac_ctgntre_terminal_jgrntre_threadplate_ctgntre_transit_ctgntre_vtol_ctgntre_yard_jgrPooled over the 117 pairs (ratio of sums, with this PR over without):
FAIL_FELL_OFF509 -> 343, x0.67 [0.51, 0.88];FAIL_NO_PATH553 -> 547, x0.99 [0.75, 1.30]; stuck episodes 317 -> 264, x0.83 [0.68, 1.02]; stuck events 1230 -> 936, x0.76 [0.58, 1.00]; fall deaths 6 -> 9; kills -1.7 a match [-3.1, -0.4] (CTG, 96 pairs, -0.5 [-1.4, +0.3]; JGR, 17 pairs, -8.5 [-15.7, -1.6]; TDM, 4 pairs, -0.5); captures 13 -> 8 and eliminations 80 -> 87 of 117 rounds.FAIL_NO_PATHis this PR's own risk metric (a refused path is reported as no path) and is flat; the maps where it rose from near zero (vtol 0 -> 13, shrine 1 -> 10, ghost 0 -> 5) are scattered Attack events with no goal area, not one spot, and the pooled count does not move. The overhang-heavy maps (bullet_tdm, sentinel_jgr, tarmac, threadplate, transit) do not stand apart from the rest: sentinel_jgr and transit'sFAIL_FELL_OFFfell, bullet_tdm's and threadplate's rose, all within the per-map scatter.Three follow-ups on what moved in the first wave, same setup: (1) captures 13 -> 8 across 8 maps, 1 or 2 each (9 pairs where only the build without this PR captured, 4 the other way): 36 more CTG pairs on those 8 maps gave captures 2 -> 4; not reproduced. (2) rogue_ctg
FAIL_NO_PATH10 -> 23 and ghost_ctg stuck episodes 8 -> 14 (the only per-map cells with an interval clear of 1): 8 more rogue pairs gaveFAIL_NO_PATH60 -> 58, 4 more ghost pairs gave episodes 14 -> 13 andFAIL_FELL_OFF14 -> 24 this time; the cells alternate, not reproduced. (3) JGR kills -8.5 a match over 17 pairs: 22 more JGR pairs gave -3.6 [-15.0, +5.9], terminal_jgr 7 pairs down and 5 up out of 12 with a spread of 22 to 95 kills a match; still leaning, listed under Risk. New stuck spots with this PR (2 or more events a match where the build without had under 0.5 within 128 u) were one-bot long stucks at static geometry or a prop in 1 of 4 matches, a different set in each wave, except one physics-prop spot on subsurface_ctg at (-642, -3898, -1760) that appeared in both; none is a fall or a no-path spot. Watch list: skyline_ctg fall deaths 8 -> 14 over its 8 pairs (it also moved under #2198 alone, 6 -> 10 in that PR's screen; no link to this PR's mechanism read yet) and that subsurface prop spot.Approaches tried and dropped
BuildTrivialPathreturns for a partial path. The path bots loop on at this spot is reported complete (theCNavArea::Containscase), so the check never ran there: 106FAIL_FELL_OFFtoward the ramp cover in 10 matches. This led to judging the last leg of every path.Considered, not included
CNavArea::Contains, orPath::Computerefusing its own result). It would also save the walk to the floor under the cover, but it changes stock NextBot code that every path search and other nav queries use; refusing the result inCNEOBotPathComputekeeps the change to NEO bots.Toolchain
Linked Issues