Repository navigation
Bots detour around physics/animation props - #2197
Conversation
e2c925e to
292042a
Compare
A navmesh is generated without entities, so it cannot know where a physics prop or an animated prop's bone followers stand. A bot's path runs straight through such a prop, the stock Avoid probe leans away only from props narrower than the bot, and the bot walks into the prop until the stuck monitor fires. CNEOBotPropDetour, a member of PathFollower under NEO, looks along the bot's path every 0.25 s for movable props it can see and sweeps its body against them. When one is in the way, it searches a grid of cells over the nav mesh around the props for a route to a point on the path past them, and the follower steers through that route's waypoints, heading for the path segment past the props. Light props that physics moves freely are shoved as a player does: a bot walks around one where there is room, and keeps its path through them when only light props are in the way. When the props run on past the ones the path crosses, as a wall or a row does, the region grows over them to 1536 u, one such wide search a server tick between all bots. A detour is searched again to follow props that move; a wide one only when its props wake or the bot leaves its route. With no way around, the bot keeps the path it had, as before.
e7639e4 to
e600c5a
Compare
|
Demos of before/after bots avoid physics props, as well as the visualization of their physics prop detection tracelines: (Maps created from a template where the navmesh is naive grid, and props are placed in the map without regard to that navmesh) Chicane: chicane-ingame.mp4chicane-replay.mp4Tiny gap in a wall of physics crates: gap48-ingame.mp4gap48-replay.mp4Maze: maze-ingame.mp4maze-replay.mp4One can verify that the navmeshes are not cut to accommodate the props with
|
|
Map pack of the test maps: |
The breakable check traced the bot's hull to its current path goal on every update. The prop detour already looks along the path ahead; its partition query now also keeps breakables, and the first one the body would meet within the look-ahead range is what the check returns, so the separate hull trace goes. The look runs every 0.3 s instead of 0.25 s. While detouring or waiting to retry a plan, breakables are looked for alone on the same cadence.
At the look-ahead range of 256 u bots found glass panes about 240 u away and stopped there to shoot them. 128 u is still beyond the distance a bot runs between two looks, and keeps the find near where the hull trace used to make it (65-73 u from the pane on the test maps), close enough for the knife.
|
Breakables map included for testing optimization of prop+breakables detection: |
The component now looks ahead for breakables as well as props, so it
is named for that shared job: CNEOBotPathObstacles in
neo_bot_path_obstacles.{h,cpp}, PathFollower::m_pathObstacles. The
look's constants become PATH_OBSTACLE_*; the prop-only detour search
keeps its prop-detour names.
CNEOBotPathObstacles keeps the look ahead every 0.3 s and the
breakable find; CNEOBotPropDetour (neo_bot_prop_detour.{h,cpp}) holds
the grid, the route and wide searches, and the detour with its
replans. The look hands a plan request to the detour and learns when
to look again; neither reaches into the other. The prop queries both
use (body box, movable props, resting count) are in
neo_bot_obstacle_props.{h,cpp}. No behavior change.
One constant prefix per file: OBSTACLE_PROP_* for the shared prop query, which no longer names the detour's grid; the detour defines its grid margin and floor tolerance from the query's, and adds the margin to the region the look hands it. The query's two optional arguments become FindMovableProps and FindBreakables over one enumerator that asserts a breaker for a breakables list; the cheap health test runs before IsAbleToBreak. BodyMeetsProp is built on BodyMeetsEntity, now in the shared file. A breakable outside the box of the first 128 u is not swept; the breakable-only look no longer builds lists it does not read. Comments cut to two lines.
The look clipped the body against each breakable on its own, so a pane behind a crate, a door or a wall was reported and the bot stood still shooting the obstacle. When the look has a breakable, the body hull is traced along the legs up to it, and the breakable is kept only if it is the first thing met. The trace runs only on looks that found one.
Two bots side by side at a pane each met the other first in the hull check, so neither reported the pane and both stood there until the stuck monitor fired. Players move, and the clear behavior already backs off from a teammate in its line of fire.
Replace the research form of NeotokyoRebuild#2197 (build 4) with the files at bot-avoid-moved-props be96024: the hardened detour, the CNEOBotPathObstacles look-ahead (neo_bot_path_obstacles, renamed from the detour's integration) and the breakable look that replaces GetBreakableInPath's hull trace. Harness hooks re-ported: neo_harness_propavoid (now in NextBotPathFollow.cpp; 0 also stops the breakable look) and the detour= field on NEO_FORENSIC_STUCK. Dropped: neo_harness_propavoid_log (NEO_FORENSIC_PROPDETOUR plans), which the hardened code does not carry.
Replace the research form of NeotokyoRebuild#2197 (build 4) with the files at bot-avoid-moved-props be96024: the hardened detour, the CNEOBotPathObstacles look-ahead (neo_bot_path_obstacles, renamed from the detour's integration) and the breakable look that replaces GetBreakableInPath's hull trace. Harness hooks re-ported: neo_harness_propavoid (now in NextBotPathFollow.cpp; 0 also stops the breakable look) and the detour= field on NEO_FORENSIC_STUCK. Dropped: neo_harness_propavoid_log (NEO_FORENSIC_PROPDETOUR plans), which the hardened code does not carry.
Replace the research form of NeotokyoRebuild#2197 (build 4) with the files at bot-avoid-moved-props be96024: the hardened detour, the CNEOBotPathObstacles look-ahead (neo_bot_path_obstacles, renamed from the detour's integration) and the breakable look that replaces GetBreakableInPath's hull trace. Harness hooks re-ported: neo_harness_propavoid (now in NextBotPathFollow.cpp; 0 also stops the breakable look) and the detour= field on NEO_FORENSIC_STUCK. Dropped: neo_harness_propavoid_log (NEO_FORENSIC_PROPDETOUR plans), which the hardened code does not carry.
Replace the research form of NeotokyoRebuild#2197 (build 4) with the files at bot-avoid-moved-props be96024: the hardened detour, the CNEOBotPathObstacles look-ahead (neo_bot_path_obstacles, renamed from the detour's integration) and the breakable look that replaces GetBreakableInPath's hull trace. Harness hooks re-ported: neo_harness_propavoid (now in NextBotPathFollow.cpp; 0 also stops the breakable look) and the detour= field on NEO_FORENSIC_STUCK. Dropped: neo_harness_propavoid_log (NEO_FORENSIC_PROPDETOUR plans), which the hardened code does not carry.
Replace the research form of NeotokyoRebuild#2197 (build 4) with the files at bot-avoid-moved-props be96024: the hardened detour, the CNEOBotPathObstacles look-ahead (neo_bot_path_obstacles, renamed from the detour's integration) and the breakable look that replaces GetBreakableInPath's hull trace. Harness hooks re-ported: neo_harness_propavoid (now in NextBotPathFollow.cpp; 0 also stops the breakable look) and the detour= field on NEO_FORENSIC_STUCK. Dropped: neo_harness_propavoid_log (NEO_FORENSIC_PROPDETOUR plans), which the hardened code does not carry.
sunmachine
left a comment
There was a problem hiding this comment.
Just some stale points that weren't described I think (see below), but otherwise this is good to go.
The GetHealth() > 0 gate on breakables (harmless), detour reset on every repath (costly but correct), per-search allocations every 0.3 s, bounded O(n²) Straighten/GrowOverProps, and the stale resumeGoal on give-up (stated as intended)
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.

Description
If a bot meets a physics prop in its path and the prop is not trivially pushable, it maps out a prop-aware grid and routes past the obstacle. Marking
NAV_MESH_AVOIDor cutting around problematic physics props in the navmesh is still recommended, so bots don't have to route around them regularly. This change lets maps contain physics props without trapping bots when props move out of their expected position. The same look ahead also finds glass and other breakables in the bot's way, in place of the breakable check's own hull trace.Dependencies and status
Needs (code PRs this one depends on):
Helped by:
Pending before review:
Navmesh PRs that rely on this
None of the open navmesh PRs needs this: each beats master without it. It acts on props the meshes deliberately do not cut out because they move: the two office chairs on
ntre_dusk_ctgandntre_dawn_ctg(NeotokyoRebuild/neoAssets#150 and #151) and the hanging robobags onntre_ghost_ctg(NeotokyoRebuild/neoAssets#163).Problem
A navmesh is generated without regard to physics prop entities, so it cannot know where a prop that moves will end up. This applies to:
prop_physics*,func_physbox*), which players push and shoot about;prop_dynamic's collision.A bot's path runs straight through such a prop. The stock
PathFollower::Avoidprobes 50 u ahead with two boxes, one on each side of the bot's center line, and leans away from the side that hits. A prop wider than the bot hits both, so the bot walks into it until the stuck monitor fires.Problem areas on official maps:
ntre_ghost_ctg: rows of hangingrobobagphysics props, 86 u tall.ntre_dusk_ctgandntre_dawn_ctgoffice chairs, and the scooter onntre_redlight_ctg: bots walk into the prop or climb onto it.prop_dynamic, collision on 7 bone followers) lands on the capture zone of dusk and dawn in the first 10 s of every round and parks there. Decom, saitama and rise have the same vehicle.What this PR does
Adds
CNEOBotPathObstacles(neo/bot/neo_bot_path_obstacles.{h,cpp}), a member ofPathFollowerunder#ifdef NEO(m_pathObstacles).PathFollower::Updateruns it before ledge climbing andAvoid. It owns the look ahead every 0.3 s (steps 1, 2 and 7) and aCNEOBotPropDetour(neo/bot/neo_bot_prop_detour.{h,cpp}: the grid search and the detour with its replans, steps 3-6); both use the prop query inneo/bot/neo_bot_obstacle_props.{h,cpp}.ClipRayToEntity), both where the prop is and where it will be.IsAbleToBreak:func_breakablewith health,func_breakable_surf, breakable props), and the same body box, swept over the first 128 u, finds the nearest one the body would meet. A hull trace along the path up to it then keeps it only if nothing else (a crate, a door, a wall; players aside) stands in front of it.CNEOBotPathClearBreakable::GetBreakableInPathkeeps its gates (freeze time, a threat seen recently,neo_bot_fire_weapon_allowed, a valid path) and returns that breakable, instead of tracing the bot's hull to its current path goal on every bot update.How to test
The 16 test maps and the glass map,
dev_prop_glass_glass2_ctg, are attached asntre-proplab-maps-glass.zip(about 4.3 MB: each map's.bspwith its.nav, anddev_prop_README.txtwith one line per map). They are not committed to any repo.Each map has two 2048 u obstacle courses, one per team, joined by hallways to a ghost room. Both capture zones sit on the ghost, so the first pickup ends the round, and the teams swap courses every round. The navmesh runs under every prop, so a bot's path goes straight through them.
Download: ntre-proplab-maps-glass.zip
neofolder (the one withgameinfo.txt). It addsmaps/dev_prop_*.bspandmaps/nav/dev_prop_*.nav.map dev_prop_maze_crowd_ctg, thenbot_add 3 jinrai; bot_add 3 nsf, and join spectator.sv_cheats 1; nb_blind 1; sv_neo_wep_dmg_modifier 0.Try
dev_prop_maze_crowd_ctg,dev_prop_chicane_pillars_ctganddev_prop_gap48_gap30_ctgfirst.dev_prop_empty_single_ctgis the control. For breakables,dev_prop_glass_glass2_ctg: one course has a wall with three doorways shut by glass panes, two crates before it and a chair past it; the other a corridor shut by two panes, with office chairs between them. Bots knife or shoot the panes; the test runs usednb_blind 1with weapon damage left on. A build without this PR shows the upstream behavior on the same maps.Risk
Low to moderate.
PathFollower::CheckProgress) without conflict.Cost
Every 0.3 s a bot looks 384 u along its path: one spatial partition query around those segments, a sight trace to each movable prop it keeps, a body sweep against each over the first 256 u, and a body sweep against each breakable over the first 128 u.
The breakable check it replaces traced the bot's hull to its path goal on every bot update that reached it: about 6 calls and 4 hull traces a bot-second, 30-44 us a bot-second, 1.8-2.3 % of the bot update (debug build,
ntre_ghost_ctg,ntre_dusk_ctg,ntre_rise_ctg, 12 bots). The look, measured every 0.25 s on those maps, was 64-83 us a bot-second, 2.7-4.6 % of the bot update. On the glass map the two together cost 89 us a bot-second, and the merged look every 0.3 s 61 us (-31 %).Measured with the look every 0.25 s: with no prop in the way, it costs a median 14-15 us (p99 45-50 us, debug build). That is about 40 ms per 3-minute round for 12 bots on
ntre_ghost_ctgandntre_dusk_ctg. There, where detours are rare, it is two-thirds to three-quarters of this change's CPU, mostly the path walk and the partition query.Only when a prop meets its body does the bot plan:
ClipRayToEntityper cell under a prop's footprint;With no way around, one bot a server tick runs the wide search: one query over the box its region could grow into, a sight trace for each prop there, and a second grid. A bot whose way is through light props, or that has no way around at all, looks again after 1 s.
While a bot detours near its path, the query, the grid and the search are redone every 0.3 s. A detour the wide search found is checked every 0.3 s with at most one query that counts its region's props and whether one is awake. It is searched again only when they changed, the bot left its route, or the last search is 1 s old, and then on a tick no other bot used for a wide search.
Plan time in the debug build, measured with the look every 0.25 s, for each check that met a prop in the way or checked a detour:
Results
Obstacle courses
Measured with the look every 0.25 s, before breakables used it; the maze and the glass map were rerun at 0.3 s (below).
Setup: the 16 test maps, 3 bots a team (one of each class), blind and with no weapon damage so they only move. 8-round matches in the debug build, with both arms on one build. The unit is one bot's run across its course. "Stuck" counts its
OnStuckandFAIL_STUCKevents in the run. Intervals are 95 % intervals from resampling whole rounds.Against upstream, 16 maps, 2 matches a map and arm (64 matches, every one reached match end with 0 asserts). Stuck events a run, this PR minus upstream;
*= 95 % interval clear of 0:dev_prop_maze_crowd_ctgdev_prop_chicane_pillars_ctgdev_prop_gap48_gap30_ctg(48),dev_prop_gap40_gap34_ctg(40 / 34)dev_prop_peek_doorplug_ctg(doorway),dev_prop_hang_street_ctg(row)dev_prop_row2_row3_ctgdev_prop_cluster_cup_ctgdev_prop_fall_pushwall_ctgdev_prop_wall0_wall448_ctgdev_prop_diag_rotated_ctgdev_prop_row5_wall160_ctgdev_prop_asteroids_slalom_ctgdev_prop_cluster_cup_ctg;dev_prop_gap48_gap30_ctg;dev_prop_chicane_pillars_ctg;dev_prop_wall0_wall448_ctgdev_prop_litter_pushables_ctgdev_prop_maze_crowd_ctgdev_prop_office_mess_ctg;dev_prop_litter_pushables_ctgNo course read worse than upstream. Follow-up runs on the final code:
With the look every 0.3 s and breakables in it (2 matches): the maze 0 stuck a run; the office chairs packed wall to wall 5.4, against 3.7 at 0.25 s and 4.4 upstream, a course whose figure swings by about 3 between batches.
On the course of office chairs packed wall to wall, the PR reads about 0.7 more stuck events per bot run than upstream over five batches pooled (+0.67 [-0.11, +1.44]). But there 99 % of plans keep the upstream path, and 191 of 195 stuck episodes begin before any bot on that course has detoured, so we read it as noise.
Glass map
dev_prop_glass_glass2_ctg, 6 bots, blind, weapons on, 2 matches with the merged look (13 rounds) against 4 with the old hull trace (24 rounds), every one at match end with 0 asserts: 53 panes broken against 112, none left standing when a bot gave up, no stuck event at the glass in either. From the bot's first touch to the broken pane: median 1.14 s, p90 1.55 s (old trace 1.28 s, 1.55 s). The merged look finds a pane at a median 117 u from its middle (old trace 65 u).Shipped maps: sanity games
Measured with the look every 0.25 s, before breakables used it. 8 scenarios (ghost at two ghost spawns, dusk, dawn, redlight, tarmac, engage, ballistrade), 12 bots in normal play, 2 rounds of 3 minutes, one match a scenario and build.
collisionproperty.cpp (1310)fired. We have seen this assert twice before at map load on other branches.Earlier large batches (measured on an earlier revision of this change)
These runs predate several fixes (the wide search, the corner rule for props, the light-prop rule, the route-start rule). The code then used at most 64 cells a side and 4096 cells expanded, with no wide search, light and heavy props alike, and a corner rule for cells off the mesh only. They are kept as evidence on the shipped maps.
Setup: upstream plus #2176 and #2186, both arms on the same server build, with harness logging and a test-only switch for this change (this PR has no switch). 30 bots, default weapon damage (1.485), 8 matches at a time, the two arms of a pair side by side in the same batch.
Every match reached match end with 0 asserts and no crash. "Stuck" is
OnStuckplusFAIL_STUCKmove failures, without the round-start spawn crowd and without weapon scavenging. A "prop stuck" event is one whose blocker (a hull trace toward the path goal) is a movable prop. Intervals are 95 % bootstrap intervals of the paired ratio.The meshes have no prop cut-outs. Our dusk, dawn and redlight navmeshes had cut out the floor under the office chairs, the scooter and the parked rabbitfrog. These runs use the same meshes with those cut-outs undone (on dusk and dawn, the two areas under the vehicle are back with their links). Ghost's robobags were never cut out. The other maps use the navmeshes of their open or planned neoAssets PRs.
Test cases, 52 pairs (ghost 20, dusk 10, dawn 10, redlight 12):
FAIL_NO_PATH_EXISTSThe rabbitfrog's landing, 40 pairs: dusk and dawn, each with the vehicle's areas cut out and restored, with the pre-round freeze cut to 3 s so bots are on the move while it comes down:
All 29
ntre_maps, 112 pairs (up to 4 ghost or juggernaut spawns each):FAIL_NO_PATH_EXISTSntre_terminal_jgr(24.5 -> 129) andntre_sentinel_jgr(23 -> 47) a few bots logged most of the events, on maps where the detour hardly acts (3.5 and 0 plans a match). Maps with no detour at all read anywhere from x0.4 to x2.5 at 4 pairs: that is the noise of a single map's figure here.ntre_tarmac_ctg(x2.69 at 4 pairs, one bot in one match, nowhere near a detour): x0.89 [0.37, 1.86] over 12 more pairs.ntre_bullet_tdm(x1.62 at 4 pairs, its extra events along the train corridor, away from the food carts the bots detour around): x0.84 [0.57, 1.25] over 16 more pairs, x1.10 [0.83, 1.47] over all 32.FAIL_NO_PATH_EXISTSisChasePathfailing to reach an enemy, mostly one standing off the mesh. Over every batch of that version it rose 7 % (1741 -> 1863), each interval across 1. Nothing ties it to a detour.Test cases that shaped the design
Each of these obstacle-course findings drove a rule above.
Approaches tried and dropped
On the shipped maps:
IsAtGoalthen needs the next segment to be traversable, which the chair blocks, so the follower kept the goal behind the bot. Every replan swung between the leg back to that goal and the leg on, and the bot rocked against the chairs. Fix: the goal moves to the segment past the rejoin point.Climbingtook the scooter for a ledge and jumped onto it. On its sloping seat the bot is not on the ground, so it can neither walk nor jump off (5 of 12 matches). Fix: no climbing while detouring.ntre_engage_ctgread x1.40 [0.81, 2.54] (16 pairs), and bots still ended up on the redlight scooter. A new plan every 0.25 s ran its line from the bot straight to the goal the detour had moved past the props. That line put the rejoin point among the props or behind a wall, the search found no way around, and the detour was dropped with the follower still aimed at a goal around the corner. Bots walked into walls (engage's cardboard boxes) or climbed the scooter. Fix: replans keep the rejoin point, and a detour given up hands the follower back its own goal.Avoidboxes hit, with no check that the way around was clear or on the mesh: x0.87 over 5 maps, but ghost +24 % andntre_pissalley_ctg+33 %.prop_dynamics got worse (subsurface's spider tank). With static props too, most maps were x2.03 worse: they are already in the mesh.On the obstacle courses (stuck a run unless noted):
Toolchain
Linked Issues