Repository navigation
Bots skip weapons they cannot reach when scavenging - #2186
Merged
sunzenshen merged 1 commit intoOct 3, 2026
Merged
Conversation
This was referenced Oct 1, 2026
sunzenshen
force-pushed
the
bot-seek-weapon-reach
branch
2 times, most recently
from
October 1, 2026 05:49
00c5635 to
dbf883c
Compare
sunzenshen
marked this pull request as ready for review
October 1, 2026 05:50
sunzenshen
added a commit
to sunzenshen/neo
that referenced
this pull request
Oct 1, 2026
…the research branch
This was referenced Oct 2, 2026
SeekWeapon checks that its path ends within 150 u of the weapon, but Path::Compute appends the goal position to a path that failed to reach it, so the check always passes for a weapon lying off the nav mesh: the bot walks the last leg straight at the weapon, over whatever lies between. On ntre_saitama_ctg that leg runs off the fire pit's grate rim into its trigger_hurt, and each death drops another weapon for the next scavenger. Giving up, a failed path, OnStuck and OnMoveToFailure also left the weapon unmarked, so the next scavenge picked it again. FindNearestPrimaryWeapon now skips a weapon with no nav area within 40 u of it (brush line of sight), or more than 72 u above / 32 u below that area. A weapon the bot failed to reach goes into the ignore cache the behavior already keeps. Seek only weapons on the mesh with a full path The first change still let a weapon near the nav mesh draw a bot: the path to it could stop at the mesh's edge and end with a straight walk to the weapon, and a weapon on an area with no links in (a pit floor cut off from the mesh) gets that walk from the area beside it. FindNearestPrimaryWeapon now takes a weapon only if it lies over a nav area or at most half a player's width past its edge (generated areas stop that short of walls), at most 32 u above the area's surface or 8 u below it. SeekWeapon follows only a full path to that area, ending at the point on it nearest the weapon, so the bot never leaves the mesh for a weapon; a bot at the area's edge still touches one lying against the wall. This is checked when the behavior starts (before a ghost carrier drops the ghost), on every repath and whenever it picks a new weapon. A weapon without such a path goes into the ignore cache. CNEOBotPathCompute now does what its comment says: with includeGoalIfPathFails false it returns the full path or nothing. No caller passed false before.
sunzenshen
force-pushed
the
bot-seek-weapon-reach
branch
from
October 3, 2026 17:51
dbf883c to
ab3ae94
Compare
sunmachine
approved these changes
Oct 3, 2026
sunmachine
left a comment
Contributor
There was a problem hiding this comment.
Took a bit, but it LGTM
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
Bots skip weapons they cannot reach when scavenging
CNEOBotSeekWeaponchecks that its path ends within 150 u of the weapon, but that check never fails for a weapon lying off the nav mesh. When no nav area near the weapon can be reached,Path::Computebuilds a partial path to the closest area and, withincludeGoalIfPathFailsat its default, appends the weapon's own position as the last segment. The path therefore always "ends at the weapon", and the bot walks the last leg in a straight line from the mesh's edge to the weapon, over whatever lies between.FindNearestPrimaryWeapon's visibility check is skipped for a weapon that has no nav area, so nothing else filters these weapons out. Giving up after 10 s,OnStuckandOnMoveToFailurealso leave the weapon unmarked, so the next scavenge, a second later for a bot without primary ammo, picks the same weapon again.ntre_saitama_ctgshows it every time the ghost spawns by the fire pit. A dead bot's weapon lands on the pit floor, 160 u below the grate rim; the next bot to scavenge sprints off the rim towards it and dies in the pit'strigger_hurt, dropping another weapon there. Maps with ladders tend to have such drops.A weapon is now a scavenging target only if it lies on the nav mesh and the bot has a full path to it. Changes, in
neo_bot_seek_weapon.cppandneo_bot_path_compute.cpp:FindNearestPrimaryWeapontakes a weapon only if it lies over its nearest nav area (found with brush line of sight), or at most half a player's width (16 u) past the area's edge, which is how far generated areas stop short of walls; and at most 32 u above the area's surface (a desk, a low crate) or 8 u below it (an area's plane can pass above a stairway's treads). The visibility check uses that area.SeekWeaponfollows only a path that reaches that area, and it ends at the point on the area nearest the weapon; a bot there still touches a weapon lying against a wall. The path is checked when the behavior starts (before a ghost carrier drops the ghost for the weapon), at every repath (the weapon may have been knocked off the mesh meanwhile) and whenever the behavior picks another weapon.CNEOBotPathComputereturns the full path or nothing when asked to. Its comment says so forincludeGoalIfPathFailsfalse, but it returned any valid path, andPath::Computeleaves a partial path valid.Path::Compute's own result is the only reliable signal: when the bot's own area is the closest one it reaches, it builds a trivial path straight at the goal and reports it asCOMPLETE_PATH. No caller passedfalsebefore this change.OnStuckandOnMoveToFailureadd the weapon to the ignore cache the behavior already keeps for its "path does not get close enough" check. Both callers already own such a cache and reset it when their behavior starts (the tactical monitor's once a life).Evidence
Upstream master (at #2168) plus the unwanted-ladder release half of the draft #2176, with harness logging added and a test-only switch for this change. 30 bots, default weapon damage, 8 matches at a time; each run is paired with a stock run of the same map, ghost spawn and repeat, back to back in the same batch. CTG: 2 rounds of 3 minutes with the ghost pinned at one ghost spawn; TDM: one 5-minute round. All 306 matches reached match end with 0 asserts and no crash. "Scavenging stuck" is
OnStuckplusFAIL_STUCKmove failures inside the weapon-scavenging behavior; "other stuck" is the rest, without the round-start spawn crowd. Intervals are 95 % bootstrap intervals of the paired ratio.ntre_saitama_ctg, 46 pairs: the reworked navmesh from the companion neoAssets PR at every ghost spawn (34) and at spawn 9 (6), and master's navmesh at spawn 9 (6):FAIL_NO_PATH_EXISTSAn earlier version of this change took weapons up to 40 u off the mesh and let the path end at the weapon itself. It still lost two bots in the pit in the same 14 spawn-9 matches, both scavenging a weapon lying just past the rim.
The rise in other stuck (x1.27 [0.98, 1.67] over all 46 pairs; the earlier version measured x1.21 [1.00, 1.49] over 109 pairs) comes from where the bots go instead. Most of it is in the map's west ladder tower (x1.53 [0.89, 2.55] there, x1.21 [0.85, 1.72] elsewhere): a descent of its lower ladder ends back on the top floor about 95 % of the time, and the bot then walks its floor-level path into a wall until the stuck monitor fires; one such bot can log dozens of stuck events in a round. Bots no longer busy with, or killed by, unreachable weapons spend more of the round on objectives and walk into that trap more often. The ladder-descent half of the draft #2176 targets it.
Other maps, 56 pairs (5 ghost spawns each, 2 runs;
ntre_bullet_tdm6 TDM matches), on the navmeshes of their companion or planned neoAssets PRs:ntre_tarmac_ctgntre_transit_ctgntre_subsurface_ctgntre_marketa_ctgntre_bullet_tdmntre_rogue_ctgPooled over those maps:
FAIL_NO_PATH_EXISTS10.1 -> 5.4 a match, "fell off the path" 2.0 -> 0.7, kills 65.8 -> 65.8, deaths to hurt volumes and falls 8 -> 8 in 56 matches (none while scavenging: rogue's canal water and tank, bullet's track). Bots start scavenging 17 % less often and pick up 11 % fewer weapons (17.8 -> 15.9 a match); the weapons they no longer go for lie off the mesh. Onntre_saitama_ctgmost of those were seen only while still falling after a death (and taken once they landed), 0.3 a match lay in the pit, and about 0.5 a match lay 16-40 u past an area's edge.Cost
One nearest-area search with line-of-sight traces per candidate weapon that would otherwise win, during a scavenge search (at most once a second per bot, and only while a bot looks for a weapon), and the same search at each repath of the behavior (every 1-2 s while it runs).
Toolchain
Linked Issues