Repository navigation
Bots jump a gap link down instead of dropping into it - #2198
Merged
Merged
Conversation
This was referenced Oct 4, 2026
Path::ComputePathDetails marks every walk link onto an area more than a step lower as a drop, and pushes the drop point forward from the ledge until a hull column below it is clear. When the two areas do not touch, that column is over the gap, so the bot falls into it. The gap-jump pass that follows only looks at ground segments, so it never turns such a link into a jump. Leave a link down to an area more than 38 u away in 2D (1.9 of NEO's 20 u generation steps: the gap-jump pass's own separation test, now a named constant) on the ground. The gap-jump pass then makes it a run-up and a jump for the landing; where the drop is more than twice the gap it adds no jump and the bot walks off the ledge toward the landing. A path recomputed in mid-air starts from the take-off area, so its first goal is the take-off point behind the bot, which it cannot reach once it lands lower, and it walks back into the gap. Move a gap-jump goal on to its landing once the bot is jumping the gap or has reached the landing's area.
The comment above the drop test named the jump the gap-jump pass adds, but not the other outcome: on a link whose drop is more than twice the gap, the pass adds no jump and the bot walks off the ledge toward the landing. Name both, so a reader of the drop test does not have to work out the pass's level test.
CheckProgress runs only from Update, after Update has returned on a NULL goal, and the stock code below the gap-jump hand-off reads the goal unguarded through IsAtGoal, so the extra m_goal test on the hand-off guards nothing the rest of the function does not already assume.
Name the constant's value in units (38 u: 1.9 of NEO's 20 u generation steps) and that it is the gap-jump pass's stock separation test, so a reader can tell it is inherited, not tuned. Say in the helper's comment that the pass repeats the measure from a path position that earlier insertions may have moved, so the two tests share a constant, not a point: a link one test calls a gap and the other does not ends in a walk-off toward the landing, not a drop into the gap.
The comment gave one cause, a path recomputed in mid-air, for two conditions. Say what each covers: a bot still in the jump, and a bot already standing on the landing's area when the re-path lands.
sunzenshen
force-pushed
the
bot-gap-drop-jump
branch
from
October 9, 2026 10:07
55b17f3 to
e7987b3
Compare
This was referenced Oct 9, 2026
Contributor
Author
|
To be honest, the fact that this PR improves numbers on map ntre_apparatus_ctg seems to say more about how cursed its navmeshes are than anything else. For testing, might want to watch out for bots suddenly jumping between navarea transitions where they didn't before. One option is to test apparatus without this PR and see if the behavior looks okay enough without it. |
sunzenshen
marked this pull request as ready for review
October 10, 2026 01:52
sunmachine
approved these changes
Oct 11, 2026
sunmachine
left a comment
Contributor
There was a problem hiding this comment.
No issues with the code as far as I could find.
sunzenshen
added a commit
to sunzenshen/neo
that referenced
this pull request
Oct 13, 2026
Resolving 0d5d1a4 beside NeotokyoRebuild#2198's IsLinkAcrossGap left two #ifdef NEO openings for one #endif. Found by the Debug build of the rebased tip.
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
Path::ComputePathDetailsturns every walk link onto an area more than a step lower into aDROP_DOWN. It pushes the crossing point forward from the ledge, 10 u at a time up to two hull widths, until a hull traced straight down is clear, and the bot falls from there. When the two areas do not touch, the first clear column is over the gap between them, so the bot drops into the gap; only bots carried across by their run speed reach the landing. The gap-jump pass that follows, which turns a link between areas more than 38 u apart into a run-up and a jump, only looks at ground segments, so a link down across a gap never becomes a jump.ntre_apparatus_dmhits this in every match. Its spawn halls' exits are links down across a slot that falls to a kill volume (about 50 u across and 48 u down). On master with the navmesh from NeotokyoRebuild/neoAssets#157, a 30-bot, 5-minute DM match has 29.6 fall deaths, most of them in that slot; with this PR, 4.7.Dependencies and status
Needs:
Needed by:
ntre_apparatus_dm's first navmesh) depends on this PR and should merge after it: its four spawn halls' exits are links down across that slot.Pending before review:
What this PR does
Two changes, both in the path code:
NextBotPath.cpp). A walk link down to an area more than 38 u away in 2D is no longer marked as a drop. 38 u (1.9 of NEO's 20 u generation steps) is the gap-jump pass's own separation test, unchanged from stock and now one named constant,GapSeparationTolerance, shared by both places. The gap-jump pass then makes the link a run-up to the ledge and a jump for the landing, as it does for level gaps. Where the drop is more than twice the gap, that pass adds no jump, and the bot walks off the ledge heading for the landing instead of falling from a point over the gap.NextBotPathFollow.cpp). A path recomputed while the bot is in the air starts from its last known area, the one it jumped from, so the new path's goal is the take-off point behind it. The bot lands below that point,IsAtGoaldoes not pass a goal more than a step up, and the bot walks back into the gap. InCheckProgress(), a gap-jump goal now moves on to its landing once the bot is jumping a gap or has reached the landing's area.Paths with no link down across a gap are unchanged.
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, thenneo_bot_quota 30, and join spectator.Risk
Low. The change acts only on paths that cross a link down between areas that do not touch.
ntre_meshes, almost all on links bots do not use in matches; weighted by how often bots cross each link, they carry 0.2 crossings a match against 25 for the links this PR fixes.ntre_apparatus_dmhas its path recomputed in mid-air (5.3 of 42.7 a match, in the collapsed block). Change 2 then heads for the landing while the bot is still jumping or once it stands on the landing's area. A bot that lands elsewhere, short on mesh in the gap or long past a narrow landing, keeps the take-off above it as its goal and presses toward that ledge until the stuck monitor re-paths. Handing the goal on in that case too measured no difference ("Considered, not included").Cost
Path computation (not per tick): two closest-point queries for each link down on a path; on a link down across a gap, these replace the stock drop search (up to seven hull traces and a ground-height query). Path following, on every map: one segment-type check per update, plus a segment lookup and two cheap calls while the current goal is a gap jump.
Results
Every match below: Release builds on Linux of upstream master at #2175, without and with this PR, both with the same test-only logging (kills, stuck events, match end); 30 bots, default weapon damage (1.485), 15 s pre-round freeze; both arms of each pair run back to back in the same batch of 8 parallel servers. Every match reached match end with 0 asserts and a clean exit. "Stuck" is
OnStuckplusFAIL_STUCKmove failures, without the round-start spawn crowd,SeekWeaponand dead bots; a stuck episode is one bot's stuck events less than 10 s apart. Fall deaths are deaths totrigger_hurtor the world. Intervals are 95 % bootstrap intervals (4000 draws) of the paired ratio, +1 smoothed, unless named.ntre_apparatus_dm, #157's navmesh, 24 pairs of one-round, 5-minute DM matches (48 matches):FAIL_FELL_OFFmove failuresFAIL_NO_PATHmove failuresFall deaths drop by about 25 a match and player kills rise by about 5; stuck does not move. A second run of 24 pairs on the same mesh (part of the 9-map run below) gave fall deaths 27.9 -> 5.1 (x0.18 [0.14, 0.23]) and stuck episodes 9.9 -> 10.0 (x1.01 [0.86, 1.18]).
At 12 bots (6v6, same map, 8 pairs of one-round, 5-minute DM matches, Release): fall deaths 11.5 -> 3.1 a match (x0.27 [0.16, 0.42], 8 of 8 pairs down),
FAIL_FELL_OFF17.4 -> 8.3 (x0.47 [0.25, 0.76]), stuck episodes 10.5 -> 8.8 (x0.83 [0.62, 1.13]), player kills with fall deaths excluded 61.6 -> 65.3 (x1.06 [0.96, 1.14]);FAIL_NO_PATH3.5 -> 6.4 (x1.82 [1.00, 3.61]), a count too small at this size to tell from noise, against x1.02 [0.90, 1.16] at 30 bots.Every other map. The 28 other
ntre_maps in scope, each on its neoAssets PR's navmesh at its head: CTG maps 1 round of 3 minutes with the ghost pinned at up to 4 spawns, each twice (ntre_isolation_ctg's one spawn 8 times); JGR maps 1 round of 5 minutes, the juggernaut pinned the same way (ntre_yard_jgr's 3 spawns 3 times);ntre_bullet_tdm1 round of 5 minutes, unpinned, 8 times. 225 pairs (450 matches), then 16 more pairs each onntre_transit_ctgandntre_skyline_ctg(below). One row a map; numbers are a match, master / this PR; thentre_apparatus_dmrow is the 24 pairs above.ntre_apparatus_dmntre_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_jgrOver the 28 maps: kills 38 / 38 a match (difference -0.2 [-1.0, +0.7]); three maps' kill differences exclude 0 (threadplate up, sentinel CTG and disengage down), about what chance gives across 28. Ghost captures 24 / 20 in 192 CTG rounds.
FAIL_FELL_OFF2.33 / 2.32 a match; it fell onntre_ballistrade_ctg(0.50 -> 0.12, x0.77 [0.59, 0.92]) andntre_rogue_ctg(1.62 -> 1.00, x0.73 [0.54, 0.92]).Fall deaths stay rare off apparatus: none in either arm on 25 of the 28 maps. On
ntre_skyline_ctg, with its 16-pair rerun, they were 20 / 30 over 24 pairs (0.83 / 1.25 a match, x1.11 [0.77, 1.63]), spread over about 12 areas of the pit floor in both arms, with no spot that only this PR has.Second run, 9 maps.
ntre_apparatus_dm,ntre_bullet_tdm,ntre_dusk_ctg,ntre_ghost_ctg,ntre_isolation_ctg,ntre_redlight_ctg,ntre_skyline_ctg,ntre_tarmac_ctgandntre_vtol_ctg, 8 pairs each (72 pairs, 144 matches), set up as above; intervals here are of the ratio of summed counts over pairs. Fall deaths 3.08 -> 0.72 a match (x0.23 [0.15, 0.38]), all on apparatus andntre_skyline_ctg; stuck episodes 3.10 -> 2.82 (x0.91 [0.77, 1.08]);FAIL_FELL_OFF6.01 -> 5.12 (x0.85 [0.59, 1.30]); kills -1.4 [-3.1, +0.2] a match, all of it fall deaths that no longer count as kills: without them, apparatus 237.5 -> 245.4 and the other 64 matches 45.2 / 45.2.Stuck episodes: the two runs disagree, and neither points at a map or spot. The 28-map screen measured 2.36 -> 2.72 a match in its first 225 pairs and x1.10 [1.01, 1.18] over 257 pairs with the two reruns (x1.07 [0.99, 1.16] without transit's first pass, x1.09 [0.94, 1.27] for stuck events); the 9-map run measured x0.91 [0.77, 1.08]. In the screen, transit's first pass (x2.33) did not repeat in its 16-pair rerun (x0.99), and no other map's interval excludes 1, though many sit 10-30 % up. The five spots with over 2 stuck events a match on this PR and under 0.5 on master are mostly one bot held for tens of seconds, and master has eight such spots of its own; one of the five (
ntre_yard_jgrnear (-487, 241, -74), 5 of 9 matches) recurs and has not been watched. Episodes that start near a link down across a gap: x0.89 [0.52, 1.44] in the 9-map run.Earlier measurements of the same two changes, on an older base and navmesh
The code is the same; the base and the navmesh differ. These ran on Debug builds of upstream master at #2180 and of our research build (upstream plus our open bot PRs, #2164 among them, since closed, with each change behind a test-only switch), on
ntre_apparatus_dm's research navmesh, which has the same spawn-hall gap links as #157's. 30 bots, default weapon damage, 15 s freeze, 5-minute DM rounds on apparatus.The touching-area drops run the same code in both arms, so their ±20 is the walker's run-to-run noise. Weighted by how often bots cross each link in ordinary matches, the gap links fixed carry 25 crossings a match and the ones broken 0.2 (one link on
ntre_vtol_ctg, for Assault). Fixed links are mostly on apparatus, isolation, vtol, sentinel (CTG and JGR), skyline, subsurface, saitama and terminal.Approaches tried and dropped
Considered, not included
Both were measured in the 9-map run above (plus 16 more apparatus pairs), against this PR:
FAIL_FELL_OFFx1.02 [0.77, 1.32], stuck episodes x1.19 [1.00, 1.41] pooled and x1.00 [0.82, 1.24] on apparatus over 24 pairs. No measured benefit, and pooled stuck episodes leaned up, so the behavior change is left out.FAIL_FELL_OFFx0.71 [0.46, 1.06] pooled and x1.02 [0.67, 1.52] on apparatus, stuck episodes x1.04 [0.88, 1.22]. Left out: the walk-off was not shown to cost anything.Toolchain
Linked Issues