Repository navigation
Bots walk off a ledge they land on part way down a drop - #2201
Merged
sunzenshen merged 4 commits intoOct 17, 2026
Merged
Conversation
A bot that lands on a ledge part way down a planned drop failed the "goal is high above us" check every tick: its diagonal trace from the feet to the landing hits the ledge's rim, so it reported a fall, re-pathed to the same drop and had its stuck status cleared, and never moved. Test the landing level from the feet instead, and keep steering at the drop, which walks the bot off the rim. Fell-off re-paths that leave the bot where it was (3 within 2 s, under 8 u apart) no longer clear its stuck status, so the stuck monitor can act on a bot off the mesh with a path it cannot follow.
This was referenced Oct 8, 2026
sunzenshen
force-pushed
the
bot-path-drop-land-pr
branch
from
October 12, 2026 04:09
91c9767 to
e4ca44e
Compare
…embers) Both this branch and NeotokyoRebuild#2176 add constants, constructor lines and header members next to the same places in NextBotPathFollow; keep both.
A bot that drops onto a ledge wide enough to have a nav area of its own, part way down a drop, also passed the level test: the drop's top stands over that ledge, so steering at it circled the bot on the spot until the stuck monitor fired (10 s on ntre_ghost_ctg at (-850, 1570, 128), area 4883). Such a ledge is a place to plan from, so keep the stock fell-off re-path there; the steer-on is for rims and sills with no nav area, and for a bot still falling.
Fold the steer-on into the stock fell-off test as one keepDropGoal condition instead of an empty if/else, pass the landing segment the caller already has, word the helper's comment as the caller's precondition, and say why the column range matters for a stuck bot. IsFellOffLoop counts as well as tests, so call it CountFellOffRepath. Constants are constexpr; header comment aligned.
This was referenced Oct 15, 2026
sunzenshen
marked this pull request as ready for review
October 15, 2026 04:04
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
When a path drops off a ledge,
Path::Computeputs aDROP_DOWNpoint at the top of the drop and the landing straight below it. If the bot lands on something part way down that has no nav area (a rim, a sill, a narrow ledge), the path follower's "goal is high above us" check sees the drop top more than a jump above it. It then tests whether the landing is reachable withIsPotentiallyTraversablefrom the feet to the landing. That trace runs diagonally down through the ledge the bot stands on, so it fails. The follower reportsFAIL_FELL_OFF, invalidates the path, clears the stuck status and returns before steering. The behavior re-paths from the bot's last known area, which is still the area it dropped from, and gets the same drop again. The bot never moves: oneFAIL_FELL_OFFa tick, and the stuck monitor is reset every tick, soOnStucknever fires either.This was found on
ntre_ghost_ctg, where bots that ran off the upper walkway into a vent shaft and landed on the 12 u rim beside the hole stood there, ducked, for the rest of the round (46-57 s). The loop can happen at any drop with a narrow ledge part way down, on any map, so it is fixed in the path follower rather than in one navmesh.On the current neoAssets meshes no planned path drops onto such a ledge (the merged ghost mesh has no drop link from the walkway's west end), so the spawn sweep below shows no change on them, and the loop guard had nothing to act on either. The fix keeps the follower from looping when a mesh does plan such a drop, which any
nav_generatepass or link edit can bring back; the replays below show the loop on a mesh revision that has it.What this PR changes
DROP_DOWNtop, the landing is below the feet and within 50 u of them in 2D (the bot is in the drop's column, on a ledge or still falling), and there is no nav area under the feet, the landing is tested level from the feet: can the bot step toward it? If it can, the drop top stays the goal and the bot steers at it, which walks it off the ledge, and it falls to the landing. It uses only the bot's own planned path.OnStuckhandling can act.A bot whose landing is not clear at its own height, that is not over the drop's column, or that stands on a nav area gets the stock behavior. The change is in
NextBotPathFollow.cpp/.hunder#ifdef NEO, with the values as named constants.How to test
The vent drop on
ntre_ghost_ctgis no longer planned with the merged navmesh (NeotokyoRebuild/neoAssets#163 has no drop link from the walkway's west end), so the loop needs a mesh that plans a drop onto a rim. To see it, use a ghost mesh revision that keeps the walkway drop (area 4314 to 225), such asneo/maps/nav/ntre_ghost_ctg.navat commit2975aa4on thebot-navmesh-generation-researchbranch ofsunzenshen/neoAssets,map ntre_ghost_ctg,neo_bot_quota 30, join spectator, and watch the vent shaft at the west end of the upper walkway near (-1425, 920, 255). A bot that lands on the rim beside the hole (z 163) stays there without this PR (nb_debug PATHshowsFAIL_FELL_OFFevery tick); with it, it steps off within about a second and lands in the room below.Risk
ClearStuckStatuscall. It never re-paths or moves the bot itself.Merging with #2200
#2200 adds code at the same four places in these two files (the constants block, the constructor, the helpers after
IsNearNavMesh, the header'sNEOmembers). Whichever of the two merges second resolves those conflicts by keeping both sides; nothing else changes. A trial merge of both branches builds clean. The logic does not overlap: this PR acts inside the "goal is high above us" check on aDROP_DOWNgoal, and #2200's re-check leaves any goal that is not an ordinary ground waypoint alone.Results
All on this branch alone against master
f426c222b(both with the harness's logging and match-end support only), Release builds, neoAssets master navmeshes, 30 bots, default weapon damage (1.485), 1 round, 15 s freeze, every player at one XP tier rotated over the spawns, 8 wide. Every match reached match end with 0 asserts; one Debug match onntre_ghost_ctghad 0 asserts.Replays at the ghost vent. A test-only harness puts a bot on the walkway (three start points 40-75 u east of the drop, small random jitter on each repeat) with a floor point in the room below as its goal, 3 classes, 24 repeats a start. On the merged ghost mesh the planned route does not use the drop (16-24 s routes, no rim stops in either arm). On the ghost mesh revision named in How to test, which keeps the walkway drop, used as a test rig (same
.bsp):FAIL_FELL_OFFThe rim stops are on both rims (east x -1408, z 163; west x -1440, z 163-177), about 1-1.5 s after the start. With the PR every trial reached the floor (3.3-4.2 s median by start and class).
Spawn sweep,
ntre_ghost_ctg(10 ghost spawns) andntre_vtol_ctg(14), each spawn once, arms paired by spawn:OnStuck,FAIL_STUCK)FAIL_FELL_OFFThe steer-on acted 30 times, mostly bots still falling down ordinary drops, and no stuck episode followed any of them. No bot in either arm looped on a ledge, so on master's meshes the change has nothing to fix in this sweep: it guards a loop that the navmesh decides whether to expose.
An earlier sweep of the first commit alone found one new spot: on
ntre_ghost_ctgat (-850, 1570, 128), a bot dropping from the walkway landed on a 20 u ledge with its own nav area (4883), part way down to the floor. The level test passed, the drop top stood over the ledge, and the bot circled under it;OnStuckfired after 6.6 s and kept firing for 10 s more (2 bots in 10 matches). The second commit leaves a bot on a nav area to the stock re-path, and the sweep above has no stuck after a steer-on there.Earlier evidence, on the harness research build
Measured on our research build (master plus other open bot PRs, #2200 on), with the change behind a test-only switch, on the research navmeshes of the time: the same replays gave 14 rim stops in 336 trials without the change and 0 in 216 with it; a survey of 29
ntre_maps (212 matches an arm) went from 8.92 to 1.46FAIL_FELL_OFFa match, 1659 of the 1892 without it from 3 bots on the ghost vent rims. Onntre_vtol_ctg's pit under the map the loop guard let the stuck monitor fire after about 6 s (3 bots in 14 matches); the merged vtol mesh had no bot fall in during the sweep above.Approaches considered and dropped
NAV_MESH_AVOIDon the walkway's west end. It would make fewer paths use that drop, but a bot that still drops there would loop as before, and the same loop can happen at any drop onto a narrow ledge.Toolchain
Linked Issues