Skip to content

Bots re-check a skipped-to goal when pushed off their path - #2200

Draft
sunzenshen wants to merge 4 commits into
NeotokyoRebuild:masterfrom
sunzenshen:bot-path-goal-recheck-pr
Draft

sunzenshen wants to merge 4 commits into
NeotokyoRebuild:masterfrom
sunzenshen:bot-path-goal-recheck-pr

Conversation

@sunzenshen

@sunzenshen sunzenshen commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

Description

Background

PathFollower skips ahead to a later waypoint whenever the straight line to it is clear from where the bot stands. That choice is right when it is made, but the bot keeps steering at that goal from wherever it ends up. When a bot is shoved by a teammate, carried off by a fight, or slides off a step, the straight line from its new position can run into a wall corner, a door leaf or a prop. The bot then presses into it, often for 5 s or more until the stuck monitor fires, and sometimes for 30 s or more. On ntre_rise_ctg bots pressed into an open door leaf at a stair-top landing for 5-7 s; on ntre_ghost_ctg a chasing bot stayed pinned against a wall-mounted pipe meter for 35 s. A navmesh edit does not help, because the waypoint was reachable when the path was planned.

What this PR changes

  1. Re-check. On Avoid()'s existing 0.5 s cadence, a bot on the ground whose goal is an ordinary ground waypoint, and which stands on neither the goal's area nor the area before it, runs the stock IsPotentiallyTraversable check from its feet to the goal. A bot on those two areas is still on the line the path planned, and is left alone.
  2. Back up or re-path. If the goal is blocked, the goal moves back to the nearest earlier ground waypoint that is walkable from the feet, never back over a ladder, climb or drop. If that waypoint is within the goal tolerance (the bot already stands at it, and reaching it would hand the bot straight back the blocked goal) or no earlier waypoint is walkable, the path is invalidated so the behavior re-paths from where the bot is.
  3. Loop guard. A re-path can lead back into the same prop. After each action, a goal within 16 u of the one acted on, or a bot within 32 u of where it stood, is left alone for 3 s, and after two actions there it is left to the stuck monitor.
  4. Not while detouring (second commit). Bots detour around physics/animation props #2197's prop detour moves the path goal to the waypoint past the props and steers around them to reach it, so the straight line from the bot to that goal runs through a prop on purpose. The re-check does not run while a detour is in progress, so it cannot back that goal up or re-path in the middle of a detour.

A bot that stays on its path's areas, or whose goal is still walkable, is unchanged. The change is in NextBotPathFollow.cpp / .h under #ifdef NEO, with the guard's values as named constants.

How to test

  1. map ntre_rise_ctg, neo_bot_quota 30, join spectator.
  2. Watch the stair-top landing near (-225, -492, -204). Bots shoved east off the stair corner come up the steps toward the landing. Without this PR some of them press into the open door leaf at the landing's west end for 5-7 s, until the stuck monitor's jump frees them. With this PR they turn back to the corner or re-path within about 0.5 s.

Risk

  • A re-path is the same Invalidate() the follower already uses after a fall or a failed climb; the behavior computes a new path on its next update.
  • The check runs only for bots off their path's areas, at most twice a second, and costs one hull trace, plus one per earlier waypoint tried when the goal is blocked. On-path bots cost an area comparison. Its cadence is Avoid()'s timer, which Avoid() restarts only while avoiding is on (nb_allow_avoiding, default 1); with avoiding off the re-check runs every tick for an off-path bot.
  • After two actions at one place (a goal within 16 u, or the bot within 32 u, of where it last acted) the re-check stays off there until it acts somewhere else; there is no time limit on that. A bot that comes back to the same spot later with the same goal is left to the stuck monitor.

Merging with #2201

#2201 adds code at the same four places in these two files (the constants block, the constructor, the helpers after IsNearNavMesh, the header's NEO members). 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: #2201 acts inside the "goal is high above us" check on a DROP_DOWN goal, and this 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 on ntre_rise_ctg had 0 asserts.

Spawn sweep of the four maps with the spots the re-check was built on (ntre_rise_ctg, ntre_ghost_ctg, ntre_saitama_ctg, ntre_threadplate_ctg) and the two with the movable props #2197 detours around (ntre_dusk_ctg office chairs, ntre_redlight_ctg scooter): every ghost spawn once, 51 matches an arm, paired by spawn. A third arm ran this PR without the second commit, to see what the detour condition changes.

a match master this PR ratio [95 % CI] without the detour condition
stuck (OnStuck, FAIL_STUCK) 6.6 2.9 x0.45 [0.23, 0.77] 4.0
stuck episodes (one bot, 10 s) 1.8 0.8 x0.46 [0.26, 0.71] 0.9
FAIL_FELL_OFF 1.2 1.5 x1.22 [0.89, 1.74] 0.8
no path 1.8 2.5 x1.39 [0.81, 2.31] 2.3
kills 24.9 25.7 +0.8 [-0.1, +1.7] 25.9

By map, stuck a match, master -> this PR: saitama 7.4 -> 1.2, dusk 7.6 -> 2.6, threadplate 5.1 -> 2.1, redlight 4.8 -> 2.0, ghost 6.9 -> 5.9, rise 7.2 -> 6.8. By XP tier the episodes ratio is x0.26-x0.94, lower in every tier.

At the spots (events within 128 u, all arms' matches): the rise stair-top landing 4 -> 0, the ghost pipe meter 4 -> 2. Two did not improve on master's meshes. At the ntre_saitama_ctg elevator hatch one ghost carrier in a fight logged 6 events in 4 s (2 on master). At the ntre_threadplate_ctg ramp corner one bot on the low floor beside the ramp, with its goal on the ramp 54 u up, re-pathed and backed up once each, then waited for the stuck monitor (8 events; 3 on master): the loop guard stopped the re-check after two actions, as designed, and that spot is left to the navmesh.

FAIL_FELL_OFF rose on ntre_dusk_ctg (0.4 -> 2.0 a match, 1.6 on a second pass of the same spawns) and ntre_saitama_ctg (0.6 -> 1.2, 0.4 on the second pass), and was flat or lower on the other four maps. Within dusk, apart from a drop near (2160, 4250) just after the round start that every arm has (master included), the falls are at different places, with no repeated spot, and across all six maps 10 of this arm's 77 came within 3 s of the same bot's re-check action. The two re-check arms swapped order on the second pass (1.5 and 0.8 a match on the first pass, 0.9 and 1.1 on the second), so the pooled rise reads as match-to-match spread; dusk is the one to watch.

The detour condition. In this PR's arm the re-check would have acted on 1328 of the 2839 half-second ticks bots spent detouring; the condition held it. In the arm without it, the re-check acted 479 times in the middle of a detour. Over that pass and a second pass of the same 51 spawns with only the two re-check arms (102 pairs), stuck events read 3.9 a match without the condition and 2.9 with it (x0.75 [0.45, 1.22]), stuck episodes x0.91 [0.67, 1.25], FAIL_FELL_OFF x1.24 [0.98, 1.61], kills +0.1 [-0.5, +0.7]. Neither direction is clear in play, so the condition rests on the mechanism: it keeps the re-check from undoing the detour's goal.

Earlier evidence, on the harness research build

Measured on our research build (master plus other open bot PRs) with the change behind a test-only switch, on the research navmeshes of the time, a survey of 29 ntre_ maps, every ghost and juggernaut spawn once (212 matches an arm): at the rise landing 18 stuck events (4 bots) went to 1, at the ghost pipe meter 37 + 1 fall to 3 falls, at the saitama hatch slot 62 + 2 to 8 + 1, at the threadplate ramp corner 26 to 1 fall. A same-build check before the loop guard existed: rise stuck 7.1 -> 2.2 a match, ghost pipe meter spawn 26 -> 0, ntre_bullet_tdm 27 -> 12. Without the loop guard the re-check made new repeated spots at solid props (ntre_threadplate_ctg large crate 21 events, ntre_engage_ctg wrecked hachikoma 13); with it no spot in that survey had more than 2 re-check actions a bot.

Approaches tried and dropped

  • A full-hull skip-ahead probe. The skip-ahead probe uses a quarter-width hull, so the first idea was to make it near-full width, or also full height. At the rise landing it made no difference at near-full width (4 wedges in 8 matches in both arms): the skip is decided 150-200 u earlier, where the line is clear, so no probe at that moment can catch it. Full height partly helped (1 wedge) but brought a new 77 s trap elsewhere on the map, and near-full width added a new repeated wedge on ntre_bullet_tdm. The goal itself goes stale, so this PR re-checks it.
  • Widening the nav area at the rise landing. A geometry check showed that moving or widening that area's portal changes nothing a hull meets: its near edge is already 18 u clear of the door leaf. No mesh edit was made.
  • A loop guard keyed on the goal only. It stopped the threadplate crate loop but not the engage hachikoma: there each re-path alternated between waypoints 25 u apart, so every action looked like a new goal. Keying on where the bot stands as well stopped it.
  • No guard. Without a guard the re-check made new repeated stuck spots at solid props.

Known gaps

  • A bot standing on the area before its goal is treated as on its path, so a blocked goal there is not re-checked. One ntre_tarmac_ctg spot is this case (area 3040, a path line that runs through a door frame). Widening the test to cover it would make the bot back up to a waypoint it stands at and loop. That spot is a navmesh problem.
  • The ntre_threadplate_ctg ramp corner above: a bot off the path below its goal is re-checked twice and then left to the stuck monitor.
  • The re-check sees what IsPotentiallyTraversable sees, the same quarter-width probe the skip-ahead uses, so a goal blocked for the bot's full hull but clear for the probe is not re-checked.

Toolchain

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

Linked Issues

The path follower skips ahead to a goal that is clear from where the
bot is. A bot pushed off its path keeps steering at that goal from
wherever it ends up, and when the straight line from there runs into
a wall or a prop it wedges until the stuck monitor fires.

On Avoid()'s 0.5 s cadence, when the bot stands on neither the goal's
area nor the one before it, run the stock IsPotentiallyTraversable
check to the goal. If it is blocked, back the goal up to the nearest
earlier ground waypoint that is walkable from the feet; when that
waypoint is within the goal tolerance, or none is walkable, invalidate
the path so the bot re-paths.

A re-path can lead back into the same prop, so after an action a goal
near the one acted on, or feet near where the bot stood, is held for
3 s, and after two actions it is left to the stuck monitor.
…embers)

Both this branch and NeotokyoRebuild#2176 add constants, constructor lines and header
members next to the same places in NextBotPathFollow; keep both.
NeotokyoRebuild#2197's prop detour sets the path goal to the waypoint past the props
and steers around them to reach it, so the straight line from the bot
to that goal runs through the prop on purpose. The re-check would see
it as blocked and back the goal up or re-path, undoing the detour.
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