Repository navigation
Bots skip crouch gaps their hull cannot fit - #2199
Open
sunzenshen wants to merge 5 commits into
Open
sunzenshen wants to merge 5 commits into
sunzenshen wants to merge 5 commits into
Conversation
sunzenshen
added a commit
to sunzenshen/neo
that referenced
this pull request
Oct 4, 2026
The per-map reset compared string_t pointers, and the engine keeps gpGlobals->mapname in a fixed buffer, so it never fired: after a changelevel the old map's verdicts would apply to the new map's area ids. A small game system now clears the cache in LevelShutdownPostEntity, as in NeotokyoRebuild#2199. Research cvars unchanged.
This was referenced Oct 4, 2026
sunzenshen
added a commit
to sunzenshen/neo
that referenced
this pull request
Oct 7, 2026
The per-map reset compared string_t pointers, and the engine keeps gpGlobals->mapname in a fixed buffer, so it never fired: after a changelevel the old map's verdicts would apply to the new map's area ids. A small game system now clears the cache in LevelShutdownPostEntity, as in NeotokyoRebuild#2199. Research cvars unchanged.
sunzenshen
added a commit
to sunzenshen/neo
that referenced
this pull request
Oct 8, 2026
The per-map reset compared string_t pointers, and the engine keeps gpGlobals->mapname in a fixed buffer, so it never fired: after a changelevel the old map's verdicts would apply to the new map's area ids. A small game system now clears the cache in LevelShutdownPostEntity, as in NeotokyoRebuild#2199. Research cvars unchanged.
sunzenshen
added a commit
to sunzenshen/neo
that referenced
this pull request
Oct 8, 2026
The per-map reset compared string_t pointers, and the engine keeps gpGlobals->mapname in a fixed buffer, so it never fired: after a changelevel the old map's verdicts would apply to the new map's area ids. A small game system now clears the cache in LevelShutdownPostEntity, as in NeotokyoRebuild#2199. Research cvars unchanged.
sunzenshen
added a commit
to sunzenshen/neo
that referenced
this pull request
Oct 8, 2026
The per-map reset compared string_t pointers, and the engine keeps gpGlobals->mapname in a fixed buffer, so it never fired: after a changelevel the old map's verdicts would apply to the new map's area ids. A small game system now clears the cache in LevelShutdownPostEntity, as in NeotokyoRebuild#2199. Research cvars unchanged.
This was referenced Oct 8, 2026
sunzenshen
force-pushed
the
bot-support-duck-lane
branch
from
October 12, 2026 03:55
d42472a to
6644c55
Compare
sunzenshen
added a commit
to sunzenshen/neo
that referenced
this pull request
Oct 13, 2026
The per-map reset compared string_t pointers, and the engine keeps gpGlobals->mapname in a fixed buffer, so it never fired: after a changelevel the old map's verdicts would apply to the new map's area ids. A small game system now clears the cache in LevelShutdownPostEntity, as in NeotokyoRebuild#2199. Research cvars unchanged.
sunzenshen
marked this pull request as ready for review
October 15, 2026 04:10
A crouch nav area promises HumanCrouchHeight (55 u) of headroom, but Support ducks to 59 u and the Juggernaut to 75 u. They planned through crouch gaps their ducked hull does not fit and got stuck there. For a bot whose ducked hull is taller than HumanCrouchHeight, the path cost now refuses a level step into or out of a crouch area when no lane across the portal holds that hull. There is one lane per 4 u of portal width; the hull is swept a step above the floor against the world and static props, and each verdict is kept until the level changes. Climbs and drops are not tested. Recon and Assault plan as before.
The duck lane rule derived the bot's ducked hull from its class and model scale on every crouch edge the search expanded. The path cost now reads it once in its constructor and tests a single bool first, so Recon and Assault pay one check per edge and never read area attributes for this rule. The level-step test uses the class's m_stepHeight, as the rest of operator() does.
The verdicts are keyed by area id and were cleared only at level shutdown. nav_load, nav_generate_incremental and the nav editing commands (split, merge, delete) renumber areas inside a running level, so old verdicts would apply to new areas. Clear the cache when the area count it was filled on changes. Players never hit this; a nav editing session with bots can.
The lane sweep comment said it raises the hull the way CGameMovement steps. StepMove lifts the whole hull, while the lane sweep keeps the top at the ducked height and ignores the lowest step of the hull, so say what it does instead. Break comment lines at clause boundaries, and name the key struct with the _t suffix used for plain structs.
sunzenshen
force-pushed
the
bot-support-duck-lane
branch
from
October 15, 2026 04:10
8170a50 to
f94a437
Compare
Resolve conflict in neo_bot_path_cost.cpp: - Keep duck lane hull cache code (PR NeotokyoRebuild#2199) - Adopt upstream's hurry-hazard multiplier logic (m_bInAHurry)
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
Support and Juggernaut bots skip crouch gaps their ducked hull does not fit
A navmesh marks an area
NAV_MESH_CROUCHwhen a standing player does not fit there but a crouching one does, measured with the stock crouch height,HumanCrouchHeight(55 u). NT;RE's classes duck to different heights: Recon 46 u and Assault 48 u fit under that, but Support ducks to 59 u and the Juggernaut to 75 u.CNEOBotPathCosttreats every crouch area as open to every class, so a Support plans through a duct it cannot enter, presses into it until the stuck monitor fires, and the next path often sends it the same way.ntre_yard_jgrhits this every match: the low gaps beside its prop staircases (around (120, -1361) and (-310, -749)) are crouch areas Support's ducked hull does not fit. On upstream master Supports get stuck at the first of them about once a match (1.1), and on our research build, which carries our other open bot PRs, 2.6 times.Navmesh PRs that rely on this
None of the open navmesh PRs needs this. The crouch gaps it closes are on master's navmeshes too (see "Evidence"), so it acts without any navmesh PR. NeotokyoRebuild/neoAssets#154 (
ntre_yard_jgr) lists the two crawlspaces under "Known, not changed" as this PR's case, and NeotokyoRebuild/neoAssets#165 (ntre_sentinel_jgr) its north culvert pipe, where the Juggernaut's stuck went 1.00 -> 0.56 a match with this PR (see "Risk").What this PR changes
With this PR, a bot whose ducked hull is taller than
HumanCrouchHeight(Support, Juggernaut) refuses a level step (a rise within a step, 18 u) into or out of a crouch area when no lane across the portal holds its ducked hull:nav_load, nav editing); moving props are not part of it.One clear lane keeps the portal open. Climbs and drops into crouch areas are not tested. Recon and Assault plan exactly as before: the path cost reads the bot's ducked hull once, and for them the rule is one bool per step. Two files,
neo_bot_path_cost.{cpp,h}, +193.Why the test runs only at crouch areas
The traces run only for a step that touches a
NAV_MESH_CROUCHarea, which is where the mesh says a standing player does not fit, and each portal is traced once a map. The test belongs to path planning rather than to the bot's surroundings: A* has to know that a gap anywhere on the route is closed when it plans, or the plan goes through the gap and the bot finds out only when it arrives. So it does not wait until the bot is near the gap.Low headroom that the mesh does not mark as crouch (a pipe, a beam or a seat box overhead, which
nav_generate's brush-only crouch test does not see) is a different case and affects every class: there the bot has to duck as it walks. That is #2235, separate from this PR.How to test
map ntre_yard_jgr(its navmesh on neoAssets master has the gaps), thenneo_bot_quota 12, and join spectator.Evidence
Current upstream, October 14. The PR's base (upstream a497c98, with #2198) against this PR's head, both with harness logging only (kill, stuck and position lines), on our research navmeshes (yard's has the same gaps as master's, see below). A spawn sweep: every ghost or juggernaut spawn of 13 maps once,
ntre_yard_jgr's three juggernaut spawns 4 times andntre_isolation_ctg's 8 times (they have the gaps this PR is about),ntre_apparatus_dmtwice; 1 round, every player held at one XP rank tier, rotated across the spawns. 109 pairs, 218 of 218 matches reached match end with 0 asserts.Recon and Assault stuck read higher on yard (11.1 -> 26.7 and 12.6 -> 17.5 a match, x1.81 [0.81, 3.68] and x1.31 [0.58, 3.07]) and on some single maps (decom, rogue, dawn Assault, a few events a match). The rule never runs for them (the build logged no portal test for their heights). On yard the extra events sit at spots both arms share: a wall row at (640 to 1115, -3160) on the way to area 55 (12 bots stuck there over the base arm's 12 matches, 15 with this PR, in longer episodes; 10 of this PR's events there had a player as the blocker, 0 in the base arm) and the ladder at (-629, -652). Supports were not there more often (501 and 449 position samples), but Recon and Assault were there 2.7 times as long in this PR's matches (402 and 1096 samples), and their stuck there scales with that time. We found no spot that only this PR's arm has, and no way for the rule to move Recon or Assault, so we read it as how the 12 juggernaut matches played out; the many-map sweep has Recon x1.13 [0.89, 1.44] and Assault x1.14 [0.88, 1.45]. Elsewhere the single-map rises are single bots at a different area each match.
The tables below are from before October 11, when test matches still started XP at zero, so their numbers are not directly comparable with the one above.
All bot matches: 30 bots, default weapon damage (1.485); both arms of each pair run back to back in the same 8-wide batch. Every match reached match end with 0 asserts. "Stuck" is
OnStuckplusFAIL_STUCKmove failures, without the round-start spawn crowd and weapon pickups. Class is read from the stuck bot's hull. Ratios are paired B/A with a bootstrap 95 % interval.This branch on
ntre_yard_jgrandntre_isolation_ctg. Upstream master (at #2180) against upstream master with this PR, both with harness logging only (kill, stuck and position lines); the maps' research navmeshes. The gaps this PR closes on those maps are the same crouch areas, with the same ids and portals, on the navmeshes in neoAssets master (yard from #121): one match of this branch on each of those navmeshes refused the same gaps, plus one more on yard and three portals into one more crouch area on isolation. So the effect does not wait on a navmesh PR. 24 pairs of 5-minute JGR matches on yard (its three juggernaut spawns pinned in turn) and 16 pairs of CTG matches (2 rounds of 3 minutes) on isolation; 80 of 80 matches reached match end with 0 asserts.Supports spent 10.2 position samples a match (about one a second) on the areas of the yard gaps this closes on master, and 0.8 with this PR. Master's yard stuck is about three times the research build's below, which carries our other open bot PRs. Some of yard's remaining Support stuck moved to ladders: Support stuck while approaching or on a ladder went 0.9 -> 1.8 a match (the rest 15.1 -> 11.3), all of it on an upper floor of the research navmesh that has one of the closed gaps; master's yard navmesh has neither that floor nor ladders. The Juggernaut had one such episode at a ladder there (0 -> 0.5 a match). On isolation the gap closes from one side only: the crouch area behind the hut's low gap (21922) also connects to a neighbor (21923) whose portal has a clear lane, so a Support that comes in that way still sticks inside it, in both arms (20 events in one master match, 13 in two with this PR).
Every crouch link, 28 maps. On our bot research build (upstream master plus our open bot PRs), research navmeshes, the change switched by a test-only cvar. A link walker (bots cross one nav link at a time from a standing start) walked all 2569 crouch links of the 28
ntre_maps that have one, three times with each class, with and without this change:The one refused crossing is a 16 u step down under a pipe on
ntre_apparatus_dmwhose way back up Support fails.13 maps, 240 pairs, research build (as the walker;
ntre_yard_jgr48 pairs, isolation, apparatus, decom, skyline, ridgeline, rogue, dawn, pissalley, saitama, tarmac, sentinel CTG and JGR; 8 to 48 pairs a map, CTG and JGR from up to 4 pinned spawns):On the other 11 maps Supports rarely reach a gap this closes in ordinary matches (0 to 1 position samples a match on those areas, against 16 to 19 on yard and 11 on isolation), so they test that nothing else changes. Three of them rose in a first 8-pair batch (decom, ridgeline Support, rogue); 16 fresh pairs each did not repeat it, and no Support stood at a closed gap there in either arm.
Cost
Path computation only, and only for Support and Juggernaut on level crouch portals: the first test of a portal sweeps up to one lane per 4 u of portal width (two short traces and three hull sweeps each, stopping at the first clear lane); later tests read the per-map cache. Recon and Assault pay one bool per step.
Measured with a test-only timer on the October 14 sweep (wall clock, 8 servers at once at 10x speed, so an upper bound): 29 to 271 portal tests a match (Support and Juggernaut heights), 217 to 2448 traces, 0.7 to 4.9 ms in a whole match. Most of it falls in one tick, the bots' first plans after they spawn, which is before the round starts: at most 3.4 ms (
ntre_yard_jgr), elsewhere 0.5 to 1.8 ms. After that the cache answers.Risk
ntre_subsurface_ctg, a physics prop, a brush entity such asfunc_brush, a low beam deeper inside a crouch area than the lanes reach), the lanes read clear and the portal stays open, as today.ntre_sentinel_jgrits stuck went 1.00 -> 0.56 a match (16 pairs, research build).Ordering
Adds an early return in
CNEOBotPathCost::operator(); #2170 (theNAV_MESH_AVOIDcost, draft) changes the same function on other lines. Independent of the per-map navmesh PRs.Approaches tried and dropped
Toolchain
Linked Issues