Skip to content

Re-path a bot that stands above its path - #2214

Draft
sunzenshen wants to merge 1 commit into
NeotokyoRebuild:masterfrom
sunzenshen:bot-repath-from-own-height
Draft

sunzenshen wants to merge 1 commit into
NeotokyoRebuild:masterfrom
sunzenshen:bot-repath-from-own-height

Conversation

@sunzenshen

@sunzenshen sunzenshen commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

Description

PathFollower steers along the path's line in two dimensions. A bot that walks onto a floor over its path, such as a grate above the corridor the path runs along, keeps walking the line on the upper floor, steps off onto whatever lies ahead at that height, and pushes at a goal it cannot step down to until the stuck monitor fires. The follower already re-paths a bot that lands more than a step below its path (HasFallenBelowPath); nothing handles the bot that ends up above it.

ntre_apparatus_dm hits this at the blast door at (-6616, 0, 805): bots whose path runs through the doorway (z 712) cross the grate east of it (z 824), step west onto the top of the door frame (nav area 8604, marked AVOID), and stand there facing the doorway 95 u below. On upstream master with the navmesh merged in NeotokyoRebuild/neoAssets#157 it is about 1.9 stuck episodes a 12-bot match; with this PR, 0.3. No navmesh edit removed this spot in #157: deleting the frame's nav area left bots walking onto it and raised falls.

What this PR does

A bot standing above its path re-paths (NextBotPathFollow.cpp, #ifdef NEO). HasRisenAbovePath is the mirror of HasFallenBelowPath: on any update the bot is on the ground, when the bot's area is not on the path, is not connected to either area of the leg it is walking, and its feet are more than a step above the nearest point of the path, the follower runs the landing check's existing branch (OnMoveToFailure( FAIL_FELL_OFF ), invalidate, clear the stuck status), and the behavior computes a new path from the bot's own area. Planned drops, gap jumps, climbs and ladders (a goal or previous segment that is not ON_GROUND, which includes #2176's ladder-cap climbs) are left alone, as is a bot climbing, jumping or on a ladder.

How to test

  1. map ntre_apparatus_dm (upstream neoAssets navmesh), sv_cheats 1, neo_bot_quota 12, nb_debug behavior, and join spectator.
  2. Watch the top of the blast door at (-6616, 0, 805), reached from the grate east of it. Without this PR, a bot in SeekAndDestroy stands on the frame's top facing west for several seconds before its stuck handling moves it. With this PR, the follower re-paths such a bot while it is still on the grate (developer 1 with nb_debug path prints the follower's FAIL_FELL_OFF once), and few bots reach the frame.

Why FAIL_FELL_OFF goes up

Each re-path from above the path is reported as FAIL_FELL_OFF, the event the landing check already sends, so that count is the old falls below the path plus this PR's re-paths: 2.3 -> 11.0 a 30-bot match over the screen below, of which 9.2 are this PR's.

  • At the door, the re-path is the fix: 67 of the 169 re-paths in the 12-bot door runs were in the door's box, 41 of them on the grate (area 1073, 112 u above the doorway's path); only 2 of those bots were stuck at the door within 15 s afterwards.
  • On outdoor terrain it fires where it does not need to: the path's line between its points is a straight chord, and a bot that cuts over a rise on a neighboring area stands more than a step above that chord. ntre_vtol_ctg had 355 re-paths in 14 matches (25 a match, 200 bots), 85 % of them less than 32 u above the line, spread over the map (no 128-u cell had more than 6). ntre_isolation_ctg: 19 in its one match, 11 bots, no two at the same place. Each costs the owning behavior one path computation. No stuck, kill or fall change follows them: over the screen, 9 of 368 stuck events with this PR came within 10 s after the same bot's re-path, and 0 of 19 fall deaths.
  • Also requiring the feet to be a step above the goal area's surface would keep all 83 door re-paths but drop only a third of vtol's, so this PR keeps the single check.

Risk

  • About 9 more FAIL_FELL_OFF a 30-bot match, mostly on maps with uneven outdoor terrain (25 on ntre_vtol_ctg, 19 on ntre_isolation_ctg, 1 to 3 on the other CTG maps measured); a behavior reacts to it as it does to a fall below the path.
  • A bot beside its leg on a connected area (a landing next to a ramp it is about to walk down) is left to walk on; without that exception, such bots on ntre_apparatus_dm were sent back through a ramp link where bots stall without this PR too.
  • Fall deaths on ntre_apparatus_dm at 12 bots read 2.2 -> 2.9 a match, x1.32 [0.98, 1.82]; at 30 bots 19 v 19. The added falls were spread over the pits (z 0), at most 5 more in any 256-u cell, and 3 of 70 had the victim's own re-path in the 10 s before, so the re-path is not what put them there on this read. Earlier runs on Transmit the location of the capzon regardless if its visible to player #157's navmesh before merge read x1.05 [0.92, 1.20] over 56 pairs.

Cost

Per update, only when the bot's area is neither of its current leg's areas: two IsConnected checks, a walk over the path's segments and one MoveCursorToClosestPosition; no trace.

Measured in one 12-bot ntre_apparatus_dm match (Release, a timing build that wraps each call in CFastTimer, so the numbers include the timer): 17,042 calls over 20,114 server ticks, 0.17 us a call on average, 95 % under 0.5 us, 99 % under 1 us, the slowest 12.5 us. 17 % of calls reached the segment walk. Summed over all bots that is 2.9 ms in the 5-minute match, 0.14 us a server tick.

Results

Linux dedicated servers, Release, built from upstream master f426c222b without and with this PR (no other PR in either), both with the same test-only logging (kills, stuck events, move failures, round and match end, and in the second build a line naming which height check fired). Navmeshes as on neoAssets master, except ntre_vtol_ctg, which ran a local revision of its navmesh (pit shaft rims marked; the same in both builds). Default weapon damage (1.485), 15 s pre-round freeze, 1 round a match, XP rank held at one tier for all players and rotated across the matches; both builds of each pair in the same batch of 8 parallel servers. Every match reached match end with 0 asserts and a clean exit. "Stuck" is OnStuck plus FAIL_STUCK during play (after the round's end is dropped); an episode is one bot's stuck events less than 10 s apart. Fall deaths are deaths to trigger_hurt or the world. Intervals are 95 % bootstrap intervals (4000 draws) of the paired ratio or difference.

ntre_apparatus_dm, the door top. 12 bots, 5-minute DM, 24 pairs:

a match without with paired
stuck episodes at the door top 1.92 0.29 -1.62 [-2.12, -1.12]
stuck episodes, whole map 8.0 6.6 x0.82 [0.66, 1.00]
fall deaths 2.2 2.9 x1.32 [0.98, 1.82]
FAIL_FELL_OFF (includes this PR's re-paths) 2.6 11.0
FAIL_NO_PATH 3.7 4.4 x1.19 [0.87, 1.66]
player kills 66.0 66.8 +0.8 [-2.4, +4.5]

By XP tier (6 pairs each), whole-map stuck episodes x0.70 to x0.94 at every tier; player kills move by +4.8 (Corporal), -0.3 (Lieutenant), +1.8 (Private) and -3.0 (Sergeant) a match.

Without this PR, 122 stuck events at the door in 21 of 24 matches, 91 of them on the frame's top (area 8604), almost all in plain SeekAndDestroy with a goal through the doorway below. With it, none on the frame's top.

Spawn screen. 30 bots, CTG maps with the ghost pinned to each spawn once (3-minute round), ntre_apparatus_dm 4 matches; totals without -> with this PR:

map pairs stuck episodes fall deaths kills FAIL_FELL_OFF of which re-paths from above
ntre_apparatus_dm 4 28 -> 29 19 -> 19 965 -> 964 36 -> 76 49
ntre_dusk_ctg 5 10 -> 10 0 -> 0 116 -> 118 11 -> 18 13
ntre_ghost_ctg 10 9 -> 17 0 -> 0 269 -> 249 39 -> 41 9
ntre_isolation_ctg 1 0 -> 0 0 -> 0 26 -> 24 0 -> 20 19
ntre_redlight_ctg 6 10 -> 7 0 -> 0 149 -> 154 1 -> 9 7
ntre_tarmac_ctg 10 9 -> 15 0 -> 0 246 -> 244 2 -> 12 9
ntre_vtol_ctg 14 47 -> 46 0 -> 0 345 -> 367 27 -> 376 355
all 50 x1.10 [0.86, 1.40] 19 -> 19 +0.1 [-1.4, +1.4] a match 2.3 -> 11.0 a match 9.2 a match

By XP tier, stuck episodes x1.35 at Corporal (14 pairs), x1.46 at Lieutenant (10), x0.88 at Private (16), x0.87 at Sergeant (10); player kills move by +0.3, +0.2, +1.7 and -2.9 a match, no tier past 3.

Rounds: 44 -> 42 eliminations, 1 -> 3 captures. On ntre_ghost_ctg none of the stuck events with this PR followed the bot's own re-path (9 re-paths in 10 matches); the stuck places there occur in both builds.

Debug build (upstream master with this PR and the test-only logging): ntre_apparatus_dm, ntre_vtol_ctg and ntre_isolation_ctg (ghost spawn 0), 30 bots: 3 of 3 at match end, 0 asserts, 0 crashes.

The commit message quotes an earlier measurement, taken before the rebase onto f426c222b (door episodes 1.71 -> 0.62 a match at 12 bots, 0.67 -> 0.25 at 30); the numbers above replace it.

Not run: TDM and JGR maps, the other ntre_ maps, and a Windows build.

Toolchain

  • Linux GCC Distro Native (Linux Mint 22.3, GCC 13.3.0)

Linked Issues

@sunzenshen

sunzenshen commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor Author

To be honest, looking at these numbers, I don't think this is worth it. What immediately comes to mind is that terrain and stairs, and even the roof of bullet end to have navareas that are below the actual collision volume. The other one seemed more worth it to me, because falling into an area accidentally is fairly common for the bots on maps like transit, but I'm not so sure about the opposite.

I think what I'm going to do is leave this in draft and I might try to run more A/B comparisons with this on/off with whatever we merge before for apparatus.

@sunzenshen
sunzenshen force-pushed the bot-repath-from-own-height branch from f53b361 to f3d3e86 Compare October 12, 2026 03:53
A path follower steers by the path's line in two dimensions. A bot
that walks onto a floor over its path, such as a grate above the
corridor the path runs along, keeps walking the line on the upper
floor, steps onto whatever lies ahead at that height, and pushes at a
goal it cannot step down to until the stuck monitor fires.

The landing check already re-paths a bot that lands off the path more
than a step below it. The follower now does the same on any grounded
update for a bot that stands off the path's areas more than a step
above the nearest point of the path, leaving planned drops, gap
jumps, climbs and ladders alone. The behavior's next path starts from
the bot's own area.

A bot on an area connected to either area of the leg it walks, such
as a landing beside a ramp the path goes down, reaches the leg by
walking on and is left alone. Without that exception, bots on such a
landing on ntre_apparatus_dm were sent back through the ramp's link,
where they stall.

On ntre_apparatus_dm, bots crossing the grate over the blast-door
corridor step onto the door frame's top at (-6616, 0, 805) and stand
there with a path through the doorway 95 u below. Stuck episodes
there, 24 pairs a roster: 1.71 -> 0.62 a match at 12 bots, 0.67 ->
0.25 at 30; whole-map stuck episodes x0.83 and x0.97, kills x1.02 and
x1.00.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant