Creatures and pathfinding in Lua games: nav.* and ai.* (ENG-F70) #121
Closed
khyretos
wants to merge 3 commits from
khyretos/claude/lua-nav into main
AGit
pull from: khyretos/claude/lua-nav
merge into: khyretos:main
khyretos:main
khyretos:renovate-hold-imgui-rnnoise
khyretos:gh-pages
khyretos:windows-results
khyretos:renovate/font-awesome-7.x
khyretos:lint-fix/yaml_prettier
khyretos:renovate/https-code.forgejo.org-forgejo-upload-artifact-5.x
khyretos:renovate/https-code.forgejo.org-forgejo-download-artifact-7.x
khyretos:renovate/https-code.forgejo.org-actions-cache-6.x
khyretos:renovate/xiph-rnnoise-0.x
khyretos:renovate/cmake-mirrors-non-major
khyretos:lint-fix/json_prettier
khyretos:lint-fix/bash_shfmt
khyretos:ci/coverage-ccache
khyretos:ci/speed-ccache-jobs
khyretos:claude/integration
khyretos:claude/creator-vitruvian-wip
khyretos:claude/park-engine-foundations
khyretos:claude/park-parity
khyretos:claude/jiggle-skeleton-base
khyretos:claude/creator-polish-base
khyretos:claude/creator-polish
khyretos:claude/creator-wardrobe
khyretos:claude/creator-anime
khyretos:claude/creator-clothes
khyretos:pre-release
khyretos:release
khyretos:claude/mpm-water
khyretos:lint-fix/markdown_markdownlint
khyretos:claude/lua-nav
khyretos:claude/creator-hair
khyretos:claude/dynamic-navmesh
khyretos:claude/lua-audio
khyretos:claude/save-slots
khyretos:archive/2026-10-24/clothref
khyretos:archive/2026-10-24/f01aref
khyretos:archive/2026-10-24/f02aref
khyretos:archive/2026-10-24/f12ref
khyretos:archive/2026-10-24/g18eref
khyretos:archive/2026-10-24/g19aref
khyretos:archive/2026-10-24/g19b2
khyretos:archive/2026-10-24/g19bref
khyretos:archive/2026-10-24/g19cref
khyretos:archive/2026-10-24/g19dref
khyretos:archive/2026-10-24/reflA
khyretos:archive/2026-10-24/reflB
khyretos:khyretos/rhi-shaders
khyretos:claude/rhi-light-sets
khyretos:claude/rhi-pipeline-state
khyretos:archive/2026-10-24/r1gref
khyretos:archive/2026-10-24/ciref
khyretos:archive/2026-10-24/r1eref
khyretos:archive/2026-10-24/r1dref
khyretos:archive/2026-10-24/r1cref
khyretos:archive/2026-10-24/r1bref
khyretos:archive/2026-10-24/r1aref
khyretos:archive/2026-10-24/f13ref
khyretos:archive/2026-10-24/f11ref
khyretos:archive/2026-10-24/f10dref
khyretos:khyretos/ci-on-prs
khyretos:archive/2026-10-24/f10cref
khyretos:archive/2026-10-24/f10bref
khyretos:claude/bloom
khyretos:archive/2026-10-24/f10ref
khyretos:archive/2026-10-24/f08ref
khyretos:archive/2026-10-24/f07ref
khyretos:archive/2026-10-24/f05bref
khyretos:archive/2026-10-24/f05ref
khyretos:archive/2026-10-24/f03ref
khyretos:archive/2026-10-24/f02cref
khyretos:benchmark-data
khyretos:claude/cloth-passes
khyretos:archive/2026-10-24/f02bref
khyretos:archive/2026-10-24/refl-merge-base
khyretos:archive/2026-10-24/claude-b087-ref
khyretos:archive/2026-10-24/claude-macos-cross
khyretos:archive/2026-10-24/claude-macos-ref
khyretos:archive/2026-10-24/stash-macos-ref
khyretos:archive/2026-10-24/claude-win-probe
khyretos:patches/inbox
khyretos:tennis-feedback-wip
khyretos:archive/2026-10-24/runner-kit-check
No reviewers
Labels
No labels
deps
deps-major
hold
security
Milestone
Clear milestone
No items
No milestone
Projects
Clear projects
No items
No project
Assignees
Clear assignees
No assignees
3 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".
No due date set.
Dependencies
No dependencies set.
Reference
khyretos/kk-engine!121
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "khyretos/claude/lua-nav"
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
Stacked on #119 (moving-door navmesh): merge that first; this PR then shows only its own two commits.
What (ENG-F70: pathfinding and creatures for Lua games)
nav.*for scripts:nav.build{radius, height, climb, slope, cell, tile, dynamic}makes a navmesh from the level's collision (every static body: script boxes, scenes, terrain), through the newRigidWorld::staticTriangles. Thenpath(from, to)→ corners andpartial,walkable,nearest,move(slides along walls),random, andobstacle{pos, size, yaw}/{pos, radius, height}→ id,remove(id). A script's obstacles and navmesh go when it unloads (256 obstacles per script).ai.*creatures for scripts:ScriptModulekeeps oneai::AiWorldwhose agents are the script's models.ai.add(model, "dog")gives it a mind; every frame it walks the navmesh, stands on the ground (static raycast), sees only what isn't behind a wall, turns where it goes, and plays the clip thatai.anims(inst, {idle=, walk=, run=, attack=, ...})names for what it does (run falls back to walk, anything else to idle; attacks play once).ai.actor(id, pos [, species, velocity])places someone with no model, such as the player (ids from 1,000,000). Newmodels.position(inst)→ pos, yaw.models.removeand script unload take the mind away too.AiWorld:Arrivedfired every frame between reaching a goTo/fetch goal and the next think tick. It now fires once.nav,aiandmodels.positionin the table), AI.md "Lua", GAPS F70.Tests
RigidWorld.StaticTrianglesAreTheLevelsCollision;AiWorld.OrdersWinAndReportArrivalnow asserts exactly oneArrivedper order.kke_tests: 1250 passed, no warnings at-Wall -Wextra.nav.build()made 28 polygons in 7.8 ms from the template level. Three UAL mannequins givenai.add(inst, "dog")andai.goTowalked 12 m across the level on the navmesh playing Walk, faced their way, spotted each other and theai.actor, firedArrivedonce each, then held their spot playing Idle.🤖 Generated with Claude Code
https://claude.ai/code/session_019rkS6wq7fV9fWdiLwxyyc7
Title
Creatures and pathfinding in Lua games: nav.* and ai.* (ENG-F70)
User description
Requested by Kees · project thread
Stacked on #119 (moving-door navmesh): merge that first; this PR then shows only its own two commits.
What (ENG-F70: pathfinding and creatures for Lua games)
nav.*for scripts:nav.build{radius, height, climb, slope, cell, tile, dynamic}makes a navmesh from the level's collision (every static body: script boxes, scenes, terrain), through the newRigidWorld::staticTriangles. Thenpath(from, to)→ corners andpartial,walkable,nearest,move(slides along walls),random, andobstacle{pos, size, yaw}/{pos, radius, height}→ id,remove(id). A script's obstacles and navmesh go when it unloads (256 obstacles per script).ai.*creatures for scripts:ScriptModulekeeps oneai::AiWorldwhose agents are the script's models.ai.add(model, "dog")gives it a mind; every frame it walks the navmesh, stands on the ground (static raycast), sees only what isn't behind a wall, turns where it goes, and plays the clip thatai.anims(inst, {idle=, walk=, run=, attack=, ...})names for what it does (run falls back to walk, anything else to idle; attacks play once).ai.actor(id, pos [, species, velocity])places someone with no model, such as the player (ids from 1,000,000). Newmodels.position(inst)→ pos, yaw.models.removeand script unload take the mind away too.AiWorld:Arrivedfired every frame between reaching a goTo/fetch goal and the next think tick. It now fires once.nav,aiandmodels.positionin the table), AI.md "Lua", GAPS F70.Tests
RigidWorld.StaticTrianglesAreTheLevelsCollision;AiWorld.OrdersWinAndReportArrivalnow asserts exactly oneArrivedper order.kke_tests: 1250 passed, no warnings at-Wall -Wextra.nav.build()made 28 polygons in 7.8 ms from the template level. Three UAL mannequins givenai.add(inst, "dog")andai.goTowalked 12 m across the level on the navmesh playing Walk, faced their way, spotted each other and theai.actor, firedArrivedonce each, then held their spot playing Idle.🤖 Generated with Claude Code
https://claude.ai/code/session_019rkS6wq7fV9fWdiLwxyyc7
PR Type
Enhancement
Description
Adds dynamic tiled navmeshes with obstacles for Lua games
Implements
nav.*pathfinding andai.*creature systemEnables agents to re-path around moving crates and doors
Diagram Walkthrough
File Walkthrough
4 files
Extracts static level collision as triangles for navmesh buildingImplements dynamic tiled navmeshes with obstacle supportExposes nav.* pathfinding and ai.* creature APIs to LuaIntegrates navmesh and creatures into script lifecycle1 files
Fixes Arrived event firing once per order instead of every frame2 files
Adds tests for dynamic navmeshes, obstacles, and agent behaviorTests staticTriangles extraction from rigid bodies7 files
Declares staticTriangles method for extracting level geometryAdds pathRevision field to track navmesh updatesDeclares dynamic build methods and obstacle managementAdds navmesh and AiWorld accessors plus creature clip storageDocuments dynamic navmeshes and Lua creature integrationUpdates ENG-F70 status to reflect completed pathfinding workDocuments nav.* and ai.* functions for game scriptsPR Reviewer Guide 🔍
(Review updated until commit
cf0885bd9a)📋 Focus areas: 🔴 3 open · ✅ 2 handled. Every push re-checks them; a handled area moves to Resolved findings at the bottom.
Potential Logic Error in Creature Cleanup
In
releaseScript, the code callsforgetCreature(m.id)and thenmodels->remove(m.id). However,forgetCreatureremoves the agent fromm_aiand clips fromm_creatureClips, but it does not remove the model instance fromm_models. The subsequent loop that erases models fromm_modelsmight be affected if the logic for tracking which models belong to the script relies onm_modelsbeing consistent. Specifically, if a model is removed fromm_modelsbeforeforgetCreatureis called (which happens in the loop immediately after), there could be a mismatch. The current order seems intentional (cleanup AI first, then remove model), but it's worth verifying thatforgetCreaturedoesn't depend on the model still being inm_modelsto function correctly. IfforgetCreatureonly usesm.idandm.source(which are available in the loop), this should be fine. However, the comment "Its obstacles; the navmesh with the script that built it" suggests that the navmesh cleanup is also part of this process. Ensure that the order of operations (AI cleanup -> Model removal -> Navmesh cleanup) is correct and doesn't cause issues if a model is referenced by multiple scripts or if the navmesh cleanup depends on the model's state.Missing Validation for Actor ID in ai.actor
The
ai.actorfunction checks if the ID is less than 1,000,000 or greater than 0x7fffffff, but it does not check if the ID is already in use by another actor or model. This could lead to overwriting existing actors or models, causing unexpected behavior. Additionally, the function does not validate that the ID is positive, which could lead to undefined behavior if a negative ID is passed.Potential Memory Leak in Dynamic NavMesh
In
buildDynamic, ifdtAllocTileCache()ordtAllocNavMesh()fails, the allocated memory is not freed before returning an error. This could lead to a memory leak if the allocation fails multiple times. The code should ensure that any allocated resources are properly cleaned up in the error paths.✅ Resolved findings
engine/src/ai/AiWorld.cpp:1046-1049
Potential Logic Error in Interact Order Handling
Similar to the previous issue, in
AiWorld::act, the conditionif (a.order.kind != Order::Kind::Interact)is used to check if an agent has arrived at its interaction target. The logic here assumes that if the order is notInteract, the agent should brake and go idle. However, this could lead to incorrect behavior if an agent arrives while under a different order type (e.g.,MoveTo). The code structure suggests thatMoveToandInteractare handled separately, but the early exit for non-Interactorders could interfere with the intended flow if an agent arrives while under a different order type. This needs verification against the expected behavior where agents should transition to idle only after completing their specific task.engine/src/ai/AiWorld.cpp:984-987
Potential Logic Error in Order Handling
In
AiWorld::act, the conditionif (a.order.kind != Order::Kind::MoveTo)is used to detect when an agent has arrived and should stop animating. However, this logic assumes that onlyMoveToorders are active when the agent is not at its goal. If other order types (likeInteract) are also considered "arrived" states, they might incorrectly trigger the idle animation logic before the specific handling for those orders occurs. The code structure suggests thatMoveToandInteractare handled separately, but the early exit for non-MoveToorders could interfere with the intended flow if an agent arrives while under a different order type. This needs verification against the expected behavior where agents should transition to idle only after completing their specific task.PR Code Suggestions ✨
No code suggestions found for the PR.
This PR needs
mainmerged in (a plain merge works), but it was pushed to refs/for/main, so only its author can update it: merge main in and push it again.This PR needs
mainmerged in (a plain merge works), but it was pushed to refs/for/main, so only its author can update it: merge main in and push it again.🧹 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 suggestionsThis PR needs
mainmerged in (a plain merge works), but it was pushed to refs/for/main, so only its author can update it: merge main in and push it again.This PR needs
mainmerged in (a plain merge works), but it was pushed to refs/for/main, so only its author can update it: merge main in and push it again.This PR needs
mainmerged in (a plain merge works), but it was pushed to refs/for/main, so only its author can update it: merge main in and push it again.This PR conflicts with
mainand the bot could not merge it in by itself:engine/include/kke/modules/ScriptModule.hengine/src/modules/ScriptModule.cppReply
unionto keep both sides in every file (CI then checks the result), or merge main in by hand.Pull request closed