Navmesh obstacles: tiled navmeshes that doors and crates change (ENG-F70) #119
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "khyretos/claude/dynamic-navmesh"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Requested by Kees · project thread
What (ENG-F70)
A navmesh that changes while the game runs: a door that shuts, a crate pushed into a doorway, a parked cart.
NavMesh::buildDynamic(vertices, indices, settings, {tileSize, maxObstacles})builds the level as square tiles (16 m by default) in a DetourTileCache. DetourTileCache was already compiled in as part of recastnavigation and is wrapped now. Every tile's floors become layers the cache keeps, stored uncompressed (a byte or two per cell).addBoxObstacle(centre, halfExtents, yaw)(the engine's yaw, asglm::rotateabout +y) andaddCylinderObstacle(base, radius, height)cut a hole.removeObstacle(id)fills it again (once).update()rebuilds only the touched tiles, a few per call, so call it every frame;updateAll()loops.revision()goes up when the surface changed. AlsoobstacleCount()andtileCount().findPath,walkable,moveAlongSurface,nearestPoint,randomPointNear, debug triangles) works unchanged on either kind.save()is empty for a dynamic mesh, because it is rebuilt from the level.AiWorldfinds an agent's path again when the mesh's revision changes.Tests
test_ai.cpp:Ai*tests pass (36).🤖 Generated with Claude Code
https://claude.ai/code/session_019rkS6wq7fV9fWdiLwxyyc7
Title
Navmesh obstacles: tiled navmeshes that doors and crates change (ENG-F70)
User description
Requested by Kees · project thread
What (ENG-F70)
A navmesh that changes while the game runs: a door that shuts, a crate pushed into a doorway, a parked cart.
NavMesh::buildDynamic(vertices, indices, settings, {tileSize, maxObstacles})builds the level as square tiles (16 m by default) in a DetourTileCache. DetourTileCache was already compiled in as part of recastnavigation and is wrapped now. Every tile's floors become layers the cache keeps, stored uncompressed (a byte or two per cell).addBoxObstacle(centre, halfExtents, yaw)(the engine's yaw, asglm::rotateabout +y) andaddCylinderObstacle(base, radius, height)cut a hole.removeObstacle(id)fills it again (once).update()rebuilds only the touched tiles, a few per call, so call it every frame;updateAll()loops.revision()goes up when the surface changed. AlsoobstacleCount()andtileCount().findPath,walkable,moveAlongSurface,nearestPoint,randomPointNear, debug triangles) works unchanged on either kind.save()is empty for a dynamic mesh, because it is rebuilt from the level.AiWorldfinds an agent's path again when the mesh's revision changes.Tests
test_ai.cpp:Ai*tests pass (36).🤖 Generated with Claude Code
https://claude.ai/code/session_019rkS6wq7fV9fWdiLwxyyc7
PR Type
Bug fix, Enhancement
Description
Adds tiled dynamic navmeshes via DetourTileCache
Enables runtime obstacles like doors and crates
Agents re-path when the walkable surface changes
Diagram Walkthrough
File Walkthrough
3 files
Declares buildDynamic, obstacle methods and revisionAdds pathRevision to Agent for dynamic mesh trackingImplements tiled dynamic navmesh and obstacle management1 files
Re-finds paths when NavMesh revision changes1 files
Tests dynamic mesh, obstacles and agent re-pathing2 files
Documents dynamic navmesh usage and limitationsUpdates ENG-F70 status to done for tiled navmeshPR Reviewer Guide 🔍
📋 Focus areas: 🔴 2 open · ✅ 0 handled. Every push re-checks them; a handled area moves to Resolved findings at the bottom.
Potential Infinite Loop in update()
In the
update()method of NavMesh, ifm->cache->update()returns a failure status (DT_FAILURE), the function returns false immediately. However, thedirtyflag is only set to true ifm->dirtywas already true before this call. If an obstacle is added or removed,dirtybecomes true, but if the subsequentupdate()call fails, the revision counter is not incremented and the dirty state remains true. This could lead to repeated failed update attempts without progress, potentially causing agents to get stuck or the system to hang if called in a tight loop without checking the return value. The logic should ensure that a failed update does not leave the mesh in a permanently dirty state without attempting recovery or signaling an error condition more explicitly.Missing Validation for Dynamic Mesh Obstacles
The
addBoxObstacleandaddCylinderObstaclefunctions check for!m->cachebefore adding obstacles. However, there is no explicit check to ensure the dynamic mesh is actually valid or initialized correctly beyond just having a non-null cache pointer. IfbuildDynamicfails partially or leaves the cache in an inconsistent state, these functions might still attempt to add obstacles, leading to undefined behavior or silent failures. Additionally, theremoveObstaclefunction checks!m->obstacles.count(id)but does not verify if the obstacle is currently active in the tile cache, which could lead to attempting to remove an obstacle that was never successfully added or has already been removed.PR Code Suggestions ✨
No code suggestions found for the PR.
🧹 Lint report · ❌ 37 problems in 6 checks · 🧹 3 formatting checks to tidy
Nothing here blocks the merge (report only).
MARKDOWN · markdownlint: first lines of the output
CPP · cppcheck: first lines of the output
YAML · yamllint: first lines of the output
BASH · shellcheck: first lines of the output
DOCKERFILE · hadolint: first lines of the output
REPOSITORY · trivy: first lines of the output
🧹 Formatting only (layout, no behaviour; the nightly lint-fix job applies these as a PR): YAML · prettier (56 files checked), BASH · shfmt (11 files checked), JSON · prettier (145 files checked)
✅ Clean: actionlint, git_diff, gitleaks, grype, jsonlint, secretlint, stylelint, trufflehog, xmllint
MegaLinter on soucouyant · full logs in the Actions run · comment
/improvefor AI fix suggestions