Skip to content

ntre_marketa_ctg: navmesh rework - #147

Closed
sunzenshen wants to merge 4 commits into
NeotokyoRebuild:masterfrom
sunzenshen:navmesh-marketa-release
Closed

sunzenshen wants to merge 4 commits into
NeotokyoRebuild:masterfrom
sunzenshen:navmesh-marketa-release

Conversation

@sunzenshen

@sunzenshen sunzenshen commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

ntre_marketa_ctg: navmesh rework

One file, neo/maps/nav/ntre_marketa_ctg.nav. It was measured on upstream master (at #2168) plus the draft NeotokyoRebuild/neo#2176 (then only its unwanted-ladder release, now its changes 1 and 7) and NeotokyoRebuild/neo#2186 ("Bots skip weapons they cannot reach", merged since), with harness logging added.

Dependencies and status

Needs (worse than master, or broken, without it):

  • None. No open bot code PR has to be reviewed or merged before this mesh: the mesh passes on upstream code alone. The map's one nav ladder is master's, and no bot on this map died going for a weapon.

Helped by (measured better with it; the mesh does not wait on it):

  • None measured.

Pending before review:

  • Nothing: ready for review.

At a glance

master this PR
nav areas 1051 834
file size 605,273 bytes 354,152 bytes (-41 %)
areas no spawn can reach (climbs over 72 u not counted) 198 90 (the upper level, see "Known, not changed")
areas the engine blocks at every round start 15 0
one-way walk links between level floor 1 0
ghost spawns bots fetch the ghost from in every match (of 20) 14 18
nav ladders 1 1 (unchanged)
bot stuck events a match (see below) 37.6 11.4

Four commits:

  1. Rework - the mesh itself.
  2. Id alignment with master - ids only, so an id-keyed diff against master reads sensibly.
  3. The back alleys - new areas where ghost spawns 5 and 14 sit, unmeshed on master.
  4. Review fixes - one-way links, lost drop backs, and areas inside props (see below).

1. What changed

  • Sliver areas merged into their neighbors on the same floor.
  • The room around ghost spawn 1 (470,-599), which master leaves unmeshed, is meshed: seven areas, with door strips through its west and east doorways. On master bots never fetch the ghost from that spawn (0 of 4 matches with it pinned there); here they do in every match (4 of 4, pickup in about 22 s).
  • The room's two doors. Each doorway holds a hinged physics door (func_physbox on a phys_hinge). After every round restart the engine tests a hull-sized box at the center of each nav area and blocks the area for the round if the box is in solid; brush entities count, and only func_doors and breakables are walked through. The closed door stood in the middle of both door strips, so both were blocked until a later re-test caught a door pushed open. A bot that picked up the ghost inside with both doors shut had no path out: in an earlier revision one of eight rounds with the ghost at that spawn ended in a stalemate, the carrier standing in the room for 165 s. Each door strip is now split at the closed door's plane, so no center test lands on a door; the door lies on the edge between the two pieces, and bots push it open walking through, as they do going in for the ghost.
  • The kiosk ghost spawn (679,1331), stranded on master, is linked to the market through the 40 u gap in its wall by a strip area. Bots fetch the ghost from it in every match (4 of 4, about 29 s). On master a bot got in once in 4 matches, 291 s into the match, and with the ghost had no path back out.
  • Props. Floor under props bots stood pressed against is cut out: market stalls, diagonal props and poles in the main corridor, the table and chair sets on the market floor, and the dock plank near the attacker spawn (see "The dock plank").
  • Areas the engine blocks at every round start (15 on master, checked in game) are removed or reshaped; none are left.
  • Walk links a player hull cannot cross are cut: a market stand (90 stuck events in earlier test runs), a fence door, a scooter, recycle bins, a firebox, world brushes and a link between two areas that touch at one corner. The five areas left with no way in are removed.
  • Pockets nothing can enter are removed: a sealed 18-area island behind a wall at (-854, 781), and 40 areas in 12 pockets whose way in is an 87-337 u climb, or (two areas under the upper level) a gap where the engine's round-start test finds solid.
  • Re-analyzed in game after the edits, so the per-area visibility lists bots use for what they can see are current.

2. Id alignment

804 of the 833 areas carry the id of the master area they match; master's ladder keeps id 1. Areas are matched only within 64 u of height. Geometry, connections, attributes and the analysis data are unchanged by this step (verified topology-identical). Commit 3 keeps every existing id; its new areas take 2994-3005.

3. The back alleys

West of the market, the back alleys hold ghost spawns 5 (-721, -1007) and 14 (-258, -1038). On master the nearest nav area is 521 u from spawn 5, and for spawn 14 it is the market floor 160 u below its landing, behind the alleys' wall, so bots never fetched the ghost from them (0 of 10 matches each with the ghost pinned there). 12 areas are added, drawn over the floor a player can stand on (16 u in from each wall), every link both ways:

  • a 40 u wide passage (y -872..-833) from the market floor at area 516, which is extended 10 u to meet it, then a corridor west to the alley;
  • the north-south alley, with spawn 5 at its south end, where a chain-link fence door that cannot move or break closes it;
  • from the corridor's west end, a stairwell up 160 u to the walled landing that holds spawn 14.

Not meshed: the alley's north exit, a 34 u gap between a wall and the back of a market stall, which leaves 1 u a side for a player over 90 u (the engine's own nav_generate does not link it either), and a side room off the alley with two movable stools. Nothing on the new areas can be fallen from. With the ghost pinned at either spawn, bots now fetch it in every match (10 of 10 at each, first pickup after about 25 s).

4. Review fixes

An outside review of the mesh listed walk links that run one way only and climbs whose drop back was cut. Every one-way pair was swept with a player hull both ways by an engine hull audit (a harness-only test build: a hull is stood every 4 u on every area and swept every 4 u across every link), and the mesh's level links were checked the same way.

From the review:

  • One-way level links. The missing direction of three pairs clear on every lane is restored. Two more are restored though the audit reads them blocked, because only movable physics props block them, which bots meet as they stand at run time: the crate at (167, 1035) (bots crossed there 225 times in 112 matches on this mesh) and the stools at the stall front near (-300, -212). The pair through the concrete wall near (-100, -2225) is cut both ways. Five half cuts ended in areas no player hull fits in, which are removed: inside a fire box near (12, 388), the parked scooter near (962, 1062) (a static prop), two wall slivers by the kitchen near (568, 1050), and beside the stairwell handrail near (362, -2234).
  • Climbs whose drop back was cut. The drop back is restored onto seven wall-top strips (5-10 u wide) and onto two ledges by the kitchen near (612, 1012); the climbs that run into a post near (212, -2000) and a neon sign near (50, 162) (no lane clear) are cut, and both tops keep their other links.

Beyond the review, from the same audit (bot positions from 112 matches on this mesh):

  • Six more areas no player hull fits in, each with every stand sample blocked, no more than 4 bot position samples: the fire box's other half, two inside the awning near (338, 1075), a recycle bin (a fixed prop_dynamic), the security bank near (620, -604), and the wall by the stairwell near (212, -2234).
  • Two pairs blocked both ways are cut: past a market stand near (-88, -1625) (4 crossings, 3 stuck events across it) and a luggage rack near (100, -526) (1 crossing).
  • Kept: a link by the kitchen counter near (825, 1101) with one 4 u lane blocked (44 crossings); the planter of sakura trees near (1060, 1660), whose strips are linked only through the trunks (no bot went there), reached by a 64.8 u climb only a Recon makes; a -17.8 u slope near (550, 488).
  • Left for the runtime avoidance of physics props (a separate bot change): the floor and links blocked at round start by the stall-front stools, the crates at (167, 1035) and (250, -1250), the parked scooters near (-550, 2140) and (-175, -2180) (physics props, unlike the one above), a signage stand, a stool, and the room's two hinged doors (see commit 1).

Every kept area keeps its id. Re-analyzed in game.

How it was tested

  • Reachability:
    • every spawn can reach every other spawn
    • the kiosk ghost spawn, stranded on master, is reachable
    • both back-alley ghost spawns are reachable
    • no area is blocked at round start (checked in game: master 15)
    • the only areas out of reach are the upper level's 90, as on master (see below)
    • by each class's climb limit (the most a bot plans: Recon 68 u, Assault 48 u, Support 43 u), Recon reaches every area but those 90; Assault and Support also miss the sakura planter (10 areas, behind a 64.8 u climb)
    • no area a class can enter and not leave; no one-way link between level floor
  • Engine hull audit of the final mesh: BLOCKED_LINKS map=ntre_marketa_ctg level_pairs=1237 oneway=0 keep=0 cut=1 area=7 check=1 entity=23: the cut is the kitchen counter link and the area rows the sakura planter's trunks, both kept above; the entities are the physics props and the hinged doors
  • Falls. The map has no trigger_hurt and no drop that kills. The only deaths not caused by another player are bots standing on a movable crate at (167, 1035): 11 in 80 matches on master, 3 in 80 here. No area needs PRECISE or CLIFF marks.
  • Master against this mesh, same bot code. Byte-identical copies of the map, one carrying each .nav, run back to back. CTG, 2 rounds of 3 minutes with the ghost pinned at each of the 20 ghost spawns in turn, 4 runs each, 30 bots, default weapon damage, 8 matches at a time; every match reached match end with 0 asserts and no crash. The stuck count is OnStuck plus FAIL_STUCK move failures, excluding the spawn crush at round start and the weapon-scavenging behavior.
    • stuck events 37.6 -> 11.4 a match (paired x0.30, 95 % interval [0.25, 0.36], 78 of 80 pairs down, 2 up); all OnStuck 73.9 -> 39.4
    • FAIL_NO_PATH_EXISTS move failures 5.5 -> 3.3 a match (paired x1.05 [0.83, 1.34], 31 pairs down, 35 up). Master's mean carries one match with 237: a bot got into the kiosk, which master strands, picked up the ghost and had no path out. Without the kiosk spawn, 2.6 -> 3.3 (x1.12 [0.90, 1.39]). None of this mesh's are in the alleys; they come in bursts, 5-8 bots at once as a ghost carrier runs or drops the ghost, and more rounds here have a carrier. "Fell off the path" failures 0.1 -> 0.1
    • kills 51.0 -> 51.0 a match; ghost captures 12 -> 13 of 160 rounds; team kill ratio (Jinrai / NSF enemy kills) 0.91 -> 0.96
    • bots still see and shoot across the mesh: 86 % of kills are between bots in different nav areas and 64 % in non-adjacent ones, against 91 % / 70 % on master (kill distance median 276 u against 289 u); merged areas are larger, so more fights span two neighboring areas, and kills per match are unchanged
    • the dock plank: 3.8 -> 0.4 stuck events a match there (65 of 80 pairs down, 1 up)
    • the ghost fetched in every match from 18 of the 20 spawns, master 14 (see the table)
    • the back alleys: no stuck event on the new areas in 94 matches; on master, with the ghost pinned at spawn 5 or 14, bots pressed against the alleys' outer wall at (-197, -1052) with no path in: 147 stuck events in those 8 matches, none here
  • The review fixes against commit 3, the same design, 40 pairs: stuck events 12.1 -> 12.6 a match (paired x1.11, 95 % interval [0.86, 1.45], 16 down / 23 up); at the edited spots 33 -> 27; kills 50.7 -> 50.8; ghost captures 9 -> 9 of 80 rounds; deaths on the movable crate 4 -> 0; FAIL_NO_PATH_EXISTS 3.4 -> 2.8; every match reached match end with 0 asserts and no crash. The rise sits at a spot no edit touches, the table and chairs at (800, 1270) (physics props with their motion disabled), already this mesh's busiest spot (206 stuck events in the 112 earlier matches): 56 -> 98 here, spread over 18 / 19 runs. Without that area, 10.7 -> 10.1 a match, x1.03 [0.78, 1.36].
  • Earlier revisions, the same comparison: commits 1-2 alone, on the same build, 40 pairs, x0.34 [0.27, 0.42]; the back alleys against them x0.85 [0.63, 1.14] (24 down / 15 up), so they add no stuck events. On the build without #2186: commits 1-2, 40 pairs, x0.31 [0.23, 0.39]; before the door split, x0.31 [0.25, 0.38]; the plank carve alone against the same mesh uncarved, x0.70 [0.53, 0.92] (25 down / 15 up), the plank 3.6 -> 0.3 stuck events a match; the ghost pinned in the g1 room, 6 more matches each: master 0 of 6 fetched, this mesh 6 of 6, stuck events with the ghost there 49.5 -> 8.5 a match.

The dock plank

The board by the attacker spawn, a prop_physics (dockplank01b at (383, -1754, 57)), lies across the floor from the ground at x 285 to 50 u up at x 481. Bots leaving the spawn crossed it where it stands 14-21 u up, more than a step, and stuck: it was the top stuck spot on master. It is a physics prop, but it cannot move: its motion is disabled (spawn flag 8), and it has no input or damage threshold that enables motion. It can break (50 health). So for pathing it is static until it is shot apart, and after that the carved band is 80 x 70 u of floor bots do not use. The band is cut where the board stands more than a step up; paths cross over its low end.

Known, not changed

  • The upper level (90 areas over the west half of the map, z 136-259, with the ghost spawns at (203, -1171) and (557, -485)): as on master, the only nav link up is a 133 u rise onto an obstacle top (41 -> 2898), which no class can climb. In the map's collision the lowest way up is a 68 u jump from a 36 u ledge near (200, -376), at the limit of a Recon's jump and above what bot pathing accepts, so the mesh does not offer it. Bots fetch the ghost from neither spawn, on master or here.
  • The back alleys' north exit and the side room off the alley (see commit 3).
  • Movable physics props. Bots still stall against a set of stools at (-392, 1367) and climb the movable crate at (167, 1035). The stools are kicked around, so their count swings between runs: 0.4 stuck events a match on master and 1.3 here in the comparison above, 2.0 and 0.7 in the earlier one; about 1 a match on both over the two. Movable props are left to runtime handling in bot code rather than cut out of the mesh, since a cut would bake in one position of something that moves.

One file. Against master: 1051 -> 833 areas, 605,273 -> 359,509
bytes; areas the engine blocks at every round start 15 -> 0; the
kiosk ghost spawn, stranded on master, is reachable.

- Sliver areas merged into their neighbors on the same floor.
- The room around ghost spawn 1 (470,-599), which master leaves
  unmeshed, is meshed. Its two doorways hold hinged physics doors;
  each door strip is split at the closed door, so the engine's
  round-start test does not block the doorways.
- The kiosk ghost spawn (679,1331) is linked to the market through
  the 40 u gap in its wall by a strip area.
- Props: floor under props bots stood pressed against is cut out
  (market stalls, diagonal props and poles, table and chair sets, and
  the dock plank near the attacker spawn - motion disabled, it can
  only break). Areas the engine blocks at every round start removed.
- Walk links a player hull cannot cross are cut (a market stand, a
  fence door, a scooter, recycle bins, a firebox, world brushes, a
  corner-only link), and the areas left with no way in removed.
- Pockets nothing can enter are removed: a sealed 18-area island
  behind a wall at (-854,781), and 40 areas in 12 pockets whose way
  in is an 87-337 u climb or blocked by the engine.
- Re-analyzed in game after the edits.
Renumbers area and ladder ids only, so an id-keyed diff against
master reads sensibly: 804 of the 833 areas carry the id of the
master area they match; master's ladder keeps id 1. Areas are matched
only within 64 u of height. Geometry, connections, attributes and the
analyzed visibility data are unchanged (verified topology-identical).
The back alleys west of the market hold ghost spawns 5 (-721,-1007)
and 14 (-258,-1038) and had no nav areas, on master either, so bots
never fetched the ghost there. 12 areas are added, drawn on the floor
a player hull can stand on, all links both ways:

- a 40 u passage (y -872..-833) from the market floor at area 516
  (extended 10 u to meet it), a corridor west to the alley, and the
  north-south alley itself with spawn 5 at its south end (a fixed
  chain-link fence door closes it there);
- a stairwell from the corridor's west end up 160 u to the walled
  landing that holds spawn 14.

Not meshed: the alley's north exit, a 34 u gap between a wall and a
stall's back (1 u clearance a side for a player over 90 u), and a
side room off the alley. No fall anywhere on the new areas.

With the ghost pinned at either spawn bots now fetch it in every
match (10 of 10 at each, about 25 s; master 0 of 10).

Re-analyzed in game; ids of existing areas unchanged, new areas take
2994-3005.
An outside review of this mesh found walk links that run one way
only and climbs whose drop back was cut. Each one-way pair was swept
with a player hull in both directions by the engine's hull audit:

- Restore the missing direction of the three pairs clear both ways,
  and of the two that only movable props block (a crate, the stools
  at a stall), which bots meet as they stand at run time.
- Cut both ways the pair through the concrete wall near (-100,
  -2225). Delete five areas the half cuts ended in that no player
  hull fits in: inside a fire box, a parked scooter (a static prop),
  two wall slivers, and beside a stairwell handrail.
- Restore the drop from nine wall-top strips and ledges whose climb
  was kept; cut the two climbs that run into a post and a neon sign.

Beyond the review, from the same audit: delete six more areas no
player hull fits in (the fire box's other half, inside an awning, a
recycle bin, a security bank, a wall by the stairwell), and cut two
pairs blocked both ways by a market stand and a luggage rack.

Existing areas keep their ids. Re-analyzed in game (same graph,
fresh visibility).
@sunzenshen
sunzenshen requested a review from sunmachine October 9, 2026 04:56
@sunzenshen
sunzenshen marked this pull request as ready for review October 9, 2026 04:56
@sunzenshen
sunzenshen marked this pull request as draft October 9, 2026 04:59
@sunzenshen

Copy link
Copy Markdown
Contributor Author

@sunmachine this map is still not really considered "fixed" but I think it's a illustrative example of how just fixing OnStuck events isn't quite enough, as there's tons of winding elevated shortcuts that the nav_generate automated navmeshes miss, like this elevated loft that has no connections:

image

This would be an interesting use case for nav editing functionality in Wireframe.

@sunzenshen

Copy link
Copy Markdown
Contributor Author

@sunmachine this map is still not really considered "fixed" but I think it's a illustrative example of how just fixing OnStuck events isn't quite enough, as there's tons of winding elevated shortcuts that the nav_generate automated navmeshes miss, like this elevated loft that has no connections:
image

This would be an interesting use case for nav editing functionality in Wireframe.

Actually @DommHavaiGreyish fixed a lot of these edge cases already in #159 , and I was able to verify that his fixes cover the same areas I had concerns with:

image

There's some edge cases left like the board crossings:

image

... but these diagonal areas are actually hell to navmesh by hand (and don't have a way to toggle based on destructability), so I'm going to close this PR in favor of Domm's version.

sunzenshen added a commit that referenced this pull request Oct 12, 2026
Add rooftop navmesh areas to the Marketa CTG navmesh. Builds on the
base mesh from #147 (sunzenshen), which these changes are based on.

Co-authored-by: sunzenshen <sunzenshen@gmail.com>
@sunzenshen

Copy link
Copy Markdown
Contributor Author

Closed in favor of #159

@sunzenshen sunzenshen closed this Oct 12, 2026
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