Skip to content

Duck under low headroom the navmesh does not mark as crouch - #2235

Draft
sunzenshen wants to merge 2 commits into
NeotokyoRebuild:masterfrom
sunzenshen:bot-duck-low-headroom
Draft

sunzenshen wants to merge 2 commits into
NeotokyoRebuild:masterfrom
sunzenshen:bot-duck-low-headroom

Conversation

@sunzenshen

Copy link
Copy Markdown
Contributor

Description

Bots crouch to move only where the navmesh marks an area NAV_MESH_CROUCH. nav_generate decides crouch from brush-only traces, so headroom that a static prop or a thin beam takes away (a pipe, a lintel, a seat box overhead) is left unmarked, and a bot of any class walks into it upright and presses until its stuck handling gives up. CNEOBotLocomotion overrides AdjustPosture with an empty body ("never crouch to navigate", from the TF / HL2MP bot template), so nothing else makes it duck.

ntre_terminal_jgr's spawn exit has such a lintel (area 147): on its master navmesh bots were stuck under it about 100 times a match in an older test (September 30). On the research navmesh used below no bot was stuck there in either arm, and with this PR bots ducked under it 168 times in 5 matches.

What this PR does

neo_bot_locomotion.{cpp,h} only. AdjustPosture now sweeps the bot's standing hull from step height towards its move goal; when that hits the world or a prop and the bot's crouched hull does not, the bot holds crouch. Players are ignored, since they move. The check runs:

  • every 0.2 s, not every movement update, looking as far as the bot moves before the next check plus a hull width (at least 32 u); one crouch press lasts 0.3 s, so a duck holds from one check to the next;
  • only on the ground and off ladders;
  • not in a crouch area, where the bot already holds crouch, and not in an area at a ladder's foot or top, where a ducking bot used to fail to line up with the ladder.

Its class's own hull heights are used, so a Support ducks only where 59 u fits. This is the half of "too tall to fit" that the planner cannot see: #2199 makes Support and the Juggernaut plan around crouch areas their ducked hull does not fit, which needs the mesh's crouch marks; this PR handles the low headroom that has no mark.

How to test

  1. map ntre_terminal_jgr, neo_bot_quota 12, join spectator.
  2. Watch the spawn exit lintel near area 147 (nav_goto_area 147 with sv_cheats 1): bots crouch as they pass under it. On ntre_tarmac_ctg (the largest drop in stuck below) compare 1-round matches with and without this PR.

Risk

  • A bot ducks for something it did not need to duck under only when its standing hull would hit it within its next 0.2 s of movement; ducking costs speed for that moment.
  • A bot that now reaches a crouch-height window or gap it used to stop short of can meet what is in it. On ntre_rogue_ctg a crouch window on the upper floor (around (-245, -670, 200)) holds a glass pane over a drop: with this PR alone, bots in one of 5 matches ducked onto the sill and pressed the glass (3 stuck events in the round; without this PR no bot reached the sill). Bots break a vent cover in a crawl gap along their path #2231 ("Break a vent cover in a crawl gap", draft) breaks such a pane; with both, 0 stuck there in 10 matches (5 visits to the sill).
  • The test is the bot's hull against world and props; it does not see displacements or brush entities that are not solid to traces.

Cost

Two hull traces per moving bot every 0.2 s at most (the crouched sweep runs only when the standing one hits), instead of up to two every movement update in an ungated version.

Results

Linux dedicated servers, Release builds of upstream master (at #2203) with test-only logging (kills, stuck events, positions, round and match end), without and with this PR (the "with" build also logged each duck); our research navmeshes. A spawn sweep: every ghost / juggernaut spawn of 9 maps once, ntre_bullet_tdm 4 matches; 1 round, 30 bots, default damage 1.485, 8 wide, both arms of a pair back to back; XP held at one rank tier per match, rotated across spawns. 146 of 146 matches reached match end with 0 asserts and a clean exit. Stuck is OnStuck plus FAIL_STUCK move failures without the round-start spawn crowd and weapon pickups; ratios are paired with a bootstrap 95 % interval.

a match without with
stuck, all 10 maps (73 pairs) 13.97 8.75 (x0.80 [0.60, 1.05], 36 pairs down / 30 up)
stuck, ntre_tarmac_ctg (10) 6.9 2.1 (x0.46 [0.23, 0.74], 8 / 0)
stuck, ntre_terminal_jgr (5) 42.0 11.8 (x0.52 [0.17, 2.31], 4 / 1)
stuck, ntre_rogue_ctg (5) 6.6 9.8 (x1.64 [1.19, 2.58], 0 / 5)
stuck events while approaching a ladder, all 73 matches 21 7
no-path failures 3.49 3.25 (x0.93 [0.74, 1.18])
fell off the path 1.71 2.23 (x1.08 [0.92, 1.26])
kills 46.2 46.5 (x1.01 [0.95, 1.07])

Where the bots ducked (a match): ntre_terminal_jgr 128 (168 of them at the spawn exit lintel, area 147, over 5 matches), ntre_sentinel_jgr 102, ntre_subsurface_ctg 61, ntre_bullet_tdm 55 (the seat boxes), 7 to 28 on the others. On ntre_rogue_ctg the rise is one bot at a wall on the ground floor that never ducked (20 events), plus the window above. ntre_saitama_ctg area 544, where an ungated version added stuck, had 2 events without and 6 with (3 bots).

Debug build (upstream master with this PR and the same logging): one 30-bot match on ntre_terminal_jgr, match end, 0 asserts, 149 ducks.

Not run: a Windows build.

Approaches tried and dropped

  • The same check every movement update, everywhere. Lowered stuck where a head-height obstacle was the fault (terminal's lintel 103.7 -> 0.2 a match, the bullet seats 3.7 -> 1.9) but added stuck at ladder feet on ntre_tarmac_ctg (bots approaching a ladder ducked) and on two other maps; stuck while approaching a ladder now goes down instead.
  • Marking these spots CROUCH in each navmesh. Works per spot and per map, but nav_generate would drop the marks on the next regeneration, and every map with a prop overhead needs its own edit.

Toolchain

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

Linked Issues

CNEOBotLocomotion overrode AdjustPosture with an empty body, carried
over from the TF / HL2MP bot template, so bots never crouched to get
under something unless the nav area was marked CROUCH. A bot standing
under a beam, a light fitting or a lintel it could duck under pushed
into it until the stuck monitor fired.

AdjustPosture now sweeps a standing hull from step height 32 u towards
the move goal. When that hits the world or a prop and a crouched hull
does not, the bot holds crouch. Players are ignored, since they move.
The headroom check swept two hulls on every Approach call, every tick
for every moving bot. Run it every 0.2 s instead, looking as far ahead
as the bot moves before the next check plus a hull width, and let one
crouch press (0.3 s) last until the next check.

Skip it in a crouch area, where the bot already holds crouch, and in an
area at a ladder's foot or top: bots approaching a ladder ducked under
the ladder's own frame and failed to mount.
@sunzenshen sunzenshen added the Bots Related to bot players label Oct 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Bots Related to bot players

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant