Creatures and pathfinding in Lua games: nav.* and ai.* (ENG-F70) #127

Merged
kompas-review merged 3 commits from khyretos/claude/lua-nav-2 into main 2026-10-26 11:25:54 +01:00 AGit
Owner

Requested by Kees · project thread

Stacked on #119 (moving-door navmesh): merge that first; this PR then shows only its own two commits.

Replaces #121 (closed with merge conflicts): the same change rebased onto main ccdddb3, now that #119 is merged; the terrain and nav hooks sit side by side in ScriptModule. All 1295 tests pass locally.

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 new RigidWorld::staticTriangles. Then path(from, to) → corners and partial, walkable, nearest, move (slides along walls), random, and obstacle{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: ScriptModule keeps one ai::AiWorld whose 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 that ai.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). New models.position(inst) → pos, yaw. models.remove and script unload take the mind away too.
  • Fix in AiWorld: Arrived fired every frame between reaching a goTo/fetch goal and the next think tick. It now fires once.
  • Docs: SCRIPTING.md "Finding the way" and "Creatures" (plus nav, ai and models.position in the table), AI.md "Lua", GAPS F70.

Tests

  • New RigidWorld.StaticTrianglesAreTheLevelsCollision; AiWorld.OrdersWinAndReportArrival now asserts exactly one Arrived per order.
  • Full kke_tests: 1250 passed, no warnings at -Wall -Wextra.
  • In starter_game with a test script: nav.build() made 28 polygons in 7.8 ms from the template level. Three UAL mannequins given ai.add(inst, "dog") and ai.goTo walked 12 m across the level on the navmesh playing Walk, faced their way, spotted each other and the ai.actor, fired Arrived once each, then held their spot playing Idle.

🤖 Generated with Claude Code

https://claude.ai/code/session_019rkS6wq7fV9fWdiLwxyyc7

<!-- claude-projects-attribution: {"github_login":"Khyretos"} --> _Requested by **Kees** · [project thread](https://claude.ai/code/project/chan_01Ds4PKvSLCNCCKr5TJxrV2A?thread=cmsg_01Ds4PKvSLCNCCKr5TJxrV2ABb2WaYyLkc7eUMNFVK1p2Z)_ **Stacked on #119** (moving-door navmesh): merge that first; this PR then shows only its own two commits. Replaces #121 (closed with merge conflicts): the same change rebased onto main ccdddb3, now that #119 is merged; the terrain and nav hooks sit side by side in ScriptModule. All 1295 tests pass locally. ## 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 new `RigidWorld::staticTriangles`. Then `path(from, to)` → corners and `partial`, `walkable`, `nearest`, `move` (slides along walls), `random`, and `obstacle{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: `ScriptModule` keeps one `ai::AiWorld` whose 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 that `ai.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). New `models.position(inst)` → pos, yaw. `models.remove` and script unload take the mind away too. - Fix in `AiWorld`: `Arrived` fired every frame between reaching a goTo/fetch goal and the next think tick. It now fires once. - Docs: SCRIPTING.md "Finding the way" and "Creatures" (plus `nav`, `ai` and `models.position` in the table), AI.md "Lua", GAPS F70. ## Tests - New `RigidWorld.StaticTrianglesAreTheLevelsCollision`; `AiWorld.OrdersWinAndReportArrival` now asserts exactly one `Arrived` per order. - Full `kke_tests`: 1250 passed, no warnings at `-Wall -Wextra`. - In starter_game with a test script: `nav.build()` made 28 polygons in 7.8 ms from the template level. Three UAL mannequins given `ai.add(inst, "dog")` and `ai.goTo` walked 12 m across the level on the navmesh playing Walk, faced their way, spotted each other and the `ai.actor`, fired `Arrived` once each, then held their spot playing Idle. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_019rkS6wq7fV9fWdiLwxyyc7
Lua games had no navigation. nav.build makes a navmesh (dynamic by
default) from every static body's shape, via the new
RigidWorld::staticTriangles. nav.path, walkable, nearest, move and random
query it. nav.obstacle (a box by its middle, or a post) and nav.remove
change it, and ScriptModule updates it every frame. A script's obstacles go
when it unloads, and the mesh goes with the script that built it.
ScriptModule::navMesh() hands it to C++.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019rkS6wq7fV9fWdiLwxyyc7
ScriptModule keeps one ai::AiWorld whose agents are the script's models.
ai.add gives a model a mind; each frame it walks the nav.build navmesh,
stands on the level's collision, turns where it goes and plays the clip
ai.anims named for what it does (run falls back to walk, the rest to
idle). ai.actor places someone without a model (the player).
models.position reads where a model is. Removing a model or unloading
the script takes its mind away.

AiWorld fired Arrived every frame between reaching a goTo or fetch goal
and the next think; it now fires once (test asserts it).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019rkS6wq7fV9fWdiLwxyyc7
CI check: add a missing standard include
Some checks failed
review / pr-agent PR-Agent reviewed this commit
Windows (native) / build (pull_request) Failing after -1s
Docs site / site (pull_request) Failing after -1s
Windows (native) / test (pull_request) Has been skipped
Windows (native) / publish (pull_request) Has been skipped
lint / megalinter (pull_request) Successful in 3m7s
Benchmarks / benchmarks (pull_request) Successful in 3m39s
CI / dependencies (pull_request) Successful in 3s
kk / all-checks Failed: Docs site / site (pull_request), Windows (native) / build (pull_request)
CI / std-includes (pull_request) Successful in 6s
CI / shipping-build (pull_request) Successful in 5m21s
CI / release-build (pull_request) Successful in 7m28s
CI / android (pull_request) Successful in 5m20s
CI / build-test-and-coverage (pull_request) Has been cancelled
3ff618a4f7
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019rkS6wq7fV9fWdiLwxyyc7
kompas-review scheduled this pull request to auto merge when all checks succeed 2026-10-26 00:45:43 +01:00
Collaborator

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.

Replaces #121 (closed with merge conflicts): the same change rebased onto main ccdddb3, now that #119 is merged; the terrain and nav hooks sit side by side in ScriptModule. All 1295 tests pass locally.

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 new RigidWorld::staticTriangles. Then path(from, to) → corners and partial, walkable, nearest, move (slides along walls), random, and obstacle{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: ScriptModule keeps one ai::AiWorld whose 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 that ai.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). New models.position(inst) → pos, yaw. models.remove and script unload take the mind away too.
  • Fix in AiWorld: Arrived fired every frame between reaching a goTo/fetch goal and the next think tick. It now fires once.
  • Docs: SCRIPTING.md "Finding the way" and "Creatures" (plus nav, ai and models.position in the table), AI.md "Lua", GAPS F70.

Tests

  • New RigidWorld.StaticTrianglesAreTheLevelsCollision; AiWorld.OrdersWinAndReportArrival now asserts exactly one Arrived per order.
  • Full kke_tests: 1250 passed, no warnings at -Wall -Wextra.
  • In starter_game with a test script: nav.build() made 28 polygons in 7.8 ms from the template level. Three UAL mannequins given ai.add(inst, "dog") and ai.goTo walked 12 m across the level on the navmesh playing Walk, faced their way, spotted each other and the ai.actor, fired Arrived once each, then held their spot playing Idle.

🤖 Generated with Claude Code

https://claude.ai/code/session_019rkS6wq7fV9fWdiLwxyyc7


PR Type

Enhancement


Description

  • Adds Lua nav.* functions to build pathfinding from level collision

  • Introduces ai.* system for creatures walking navmeshes with clips

  • Fixes AI arrival event firing only once per order instead of every frame

  • Exposes static level triangles via new RigidWorld::staticTriangles method


Diagram Walkthrough

flowchart LR
  Level["Level Collision"] --> NavMesh["NavMesh"]
  NavMesh --> Path["Path Query"]
  NavMesh --> Obstacles["Dynamic Obstacles"]
  Models["Script Models"] --> Creatures["AI Agents"]
  Creatures --> NavMesh
  Creatures --> Clips["Animation Clips"]
  Events["Arrived Event"] -.-> Once["Fires Once"]

File Walkthrough

Relevant files
Enhancement
3 files
RigidWorld.cpp
Implements staticTriangles to extract level collision as triangles
+32/-0   
ScriptBindings.cpp
Binds nav.* and ai.* Lua functions for pathfinding and creatures
+322/-1 
ScriptModule.cpp
Integrates nav and creature systems into script lifecycle
+4/-0     
Bug fix
1 files
AiWorld.cpp
Fixes Arrived event to fire once per order completion       
+9/-2     
Tests
2 files
test_ai.cpp
Asserts Arrived event fires exactly once per order             
+10/-0   
test_rigid_world.cpp
Tests staticTriangles output correctness and limits           
+36/-0   
Documentation
4 files
RigidWorld.h
Declares staticTriangles method signature                               
+5/-0     
AI.md
Documents Lua nav.* and ai.* integration details                 
+8/-3     
GAPS.md
Updates ENG-F70 status to reflect Lua pathfinding completion
+1/-1     
SCRIPTING.md
Adds nav.* and ai.* function reference sections                   
+69/-1   
Configuration changes
1 files
ScriptModule.h
Adds navMesh, aiWorld, and creature clip fields                   
+20/-0   

## Title Creatures and pathfinding in Lua games: nav.* and ai.* (ENG-F70) ___ ### **User description** <!-- claude-projects-attribution: {"github_login":"Khyretos"} --> _Requested by **Kees** · [project thread](https://claude.ai/code/project/chan_01Ds4PKvSLCNCCKr5TJxrV2A?thread=cmsg_01Ds4PKvSLCNCCKr5TJxrV2ABb2WaYyLkc7eUMNFVK1p2Z)_ **Stacked on #119** (moving-door navmesh): merge that first; this PR then shows only its own two commits. Replaces #121 (closed with merge conflicts): the same change rebased onto main ccdddb3, now that #119 is merged; the terrain and nav hooks sit side by side in ScriptModule. All 1295 tests pass locally. ## 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 new `RigidWorld::staticTriangles`. Then `path(from, to)` → corners and `partial`, `walkable`, `nearest`, `move` (slides along walls), `random`, and `obstacle{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: `ScriptModule` keeps one `ai::AiWorld` whose 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 that `ai.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). New `models.position(inst)` → pos, yaw. `models.remove` and script unload take the mind away too. - Fix in `AiWorld`: `Arrived` fired every frame between reaching a goTo/fetch goal and the next think tick. It now fires once. - Docs: SCRIPTING.md "Finding the way" and "Creatures" (plus `nav`, `ai` and `models.position` in the table), AI.md "Lua", GAPS F70. ## Tests - New `RigidWorld.StaticTrianglesAreTheLevelsCollision`; `AiWorld.OrdersWinAndReportArrival` now asserts exactly one `Arrived` per order. - Full `kke_tests`: 1250 passed, no warnings at `-Wall -Wextra`. - In starter_game with a test script: `nav.build()` made 28 polygons in 7.8 ms from the template level. Three UAL mannequins given `ai.add(inst, "dog")` and `ai.goTo` walked 12 m across the level on the navmesh playing Walk, faced their way, spotted each other and the `ai.actor`, fired `Arrived` once each, then held their spot playing Idle. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_019rkS6wq7fV9fWdiLwxyyc7 ___ ### **PR Type** Enhancement ___ ### **Description** - Adds Lua `nav.*` functions to build pathfinding from level collision - Introduces `ai.*` system for creatures walking navmeshes with clips - Fixes AI arrival event firing only once per order instead of every frame - Exposes static level triangles via new `RigidWorld::staticTriangles` method ___ ### Diagram Walkthrough ```mermaid flowchart LR Level["Level Collision"] --> NavMesh["NavMesh"] NavMesh --> Path["Path Query"] NavMesh --> Obstacles["Dynamic Obstacles"] Models["Script Models"] --> Creatures["AI Agents"] Creatures --> NavMesh Creatures --> Clips["Animation Clips"] Events["Arrived Event"] -.-> Once["Fires Once"] ``` <details> <summary><h3> File Walkthrough</h3></summary> <table><thead><tr><th></th><th align="left">Relevant files</th></tr></thead><tbody><tr><td><strong>Enhancement</strong></td><td><details><summary>3 files</summary><table> <tr> <td><strong>RigidWorld.cpp</strong><dd><code>Implements staticTriangles to extract level collision as triangles</code></dd></td> <td><a href="https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/127/head/engine/src/RigidWorld.cpp">+32/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>ScriptBindings.cpp</strong><dd><code>Binds nav.* and ai.* Lua functions for pathfinding and creatures</code></dd></td> <td><a href="https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/127/head/engine/src/modules/ScriptBindings.cpp">+322/-1</a>&nbsp; </td> </tr> <tr> <td><strong>ScriptModule.cpp</strong><dd><code>Integrates nav and creature systems into script lifecycle</code></dd></td> <td><a href="https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/127/head/engine/src/modules/ScriptModule.cpp">+4/-0</a>&nbsp; &nbsp; &nbsp; </td> </tr> </table></details></td></tr><tr><td><strong>Bug fix</strong></td><td><details><summary>1 files</summary><table> <tr> <td><strong>AiWorld.cpp</strong><dd><code>Fixes Arrived event to fire once per order completion</code>&nbsp; &nbsp; &nbsp; &nbsp; </dd></td> <td><a href="https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/127/head/engine/src/ai/AiWorld.cpp">+9/-2</a>&nbsp; &nbsp; &nbsp; </td> </tr> </table></details></td></tr><tr><td><strong>Tests</strong></td><td><details><summary>2 files</summary><table> <tr> <td><strong>test_ai.cpp</strong><dd><code>Asserts Arrived event fires exactly once per order</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td> <td><a href="https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/127/head/tests/test_ai.cpp">+10/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>test_rigid_world.cpp</strong><dd><code>Tests staticTriangles output correctness and limits</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td> <td><a href="https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/127/head/tests/test_rigid_world.cpp">+36/-0</a>&nbsp; &nbsp; </td> </tr> </table></details></td></tr><tr><td><strong>Documentation</strong></td><td><details><summary>4 files</summary><table> <tr> <td><strong>RigidWorld.h</strong><dd><code>Declares staticTriangles method signature</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td> <td><a href="https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/127/head/engine/include/kke/RigidWorld.h">+5/-0</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>AI.md</strong><dd><code>Documents Lua nav.* and ai.* integration details</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td> <td><a href="https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/127/head/docs/AI.md">+8/-3</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>GAPS.md</strong><dd><code>Updates ENG-F70 status to reflect Lua pathfinding completion</code></dd></td> <td><a href="https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/127/head/docs/GAPS.md">+1/-1</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>SCRIPTING.md</strong><dd><code>Adds nav.* and ai.* function reference sections</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td> <td><a href="https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/127/head/docs/SCRIPTING.md">+69/-1</a>&nbsp; &nbsp; </td> </tr> </table></details></td></tr><tr><td><strong>Configuration changes</strong></td><td><details><summary>1 files</summary><table> <tr> <td><strong>ScriptModule.h</strong><dd><code>Adds navMesh, aiWorld, and creature clip fields</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td> <td><a href="https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/127/head/engine/include/kke/modules/ScriptModule.h">+20/-0</a>&nbsp; &nbsp; </td> </tr> </table></details></td></tr></tr></tbody></table> </details> ___
Collaborator

PR Reviewer Guide 🔍

(Review updated until commit 97d897675d)

📋 Focus areas: 🔴 2 open · ✅ 2 handled. Every push re-checks them; a handled area moves to Resolved findings at the bottom.

⏱️ Estimated effort to review: 3 🔵🔵🔵⚪⚪
🧪 PR contains tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Missing Error Handling for Raycast

In RigidWorld::staticTriangles, the code iterates over all bodies and attempts to lock each one. If a body is locked by another thread or in an invalid state, lock.Succeeded() returns false and the loop continues without any diagnostic logging. While this prevents a crash, it silently skips processing potentially valid static bodies that are temporarily locked, leading to missing geometry in the navmesh. Adding a log message when a lock fails would help diagnose race conditions or performance bottlenecks during navmesh generation.

if (!lock.Succeeded()) continue;
const JPH::Body& body = lock.GetBody();
Potential Resource Leak on Script Unload

In ScriptModule::releaseScript, the code removes obstacles from the navmesh (m_nav->removeObstacle(o.id)) but only if the obstacle belongs to the script being released. However, if m_nav is null or invalid at the time of release (e.g., if the navmesh was destroyed earlier), the code will not attempt cleanup. More critically, if m_nav exists but m_nav->removeObstacle throws an exception or fails silently due to internal state corruption, the obstacle ID remains in the system. The current code does not check the return value or handle exceptions from removeObstacle, which could lead to memory leaks or undefined behavior if the navmesh implementation does not guarantee exception safety.

if (m_nav)
    for (const NavObstacle& o : m_navObstacles)
        if (mine(o.source)) m_nav->removeObstacle(o.id);
✅ Resolved findings

engine/src/RigidWorld.cpp

Missing Error Handling for Raycast

In the groundHeight lambda (lines 1064-1069), if the raycast fails to hit anything (e.g., the agent is too high or in an empty void), the function returns false without setting y. This leaves y uninitialized, which will cause undefined behavior when used later. The code should initialize y to a safe default (like 0.0f) before the raycast call.

engine/src/RigidWorld.cpp

Potential Memory Leak in Dynamic Navmesh

In ScriptModule::releaseScript (lines 1214-1224), obstacles are removed from the navmesh and then cleared from m_navObstacles. However, if m_nav is not valid or m_navSource does not match the current script, the navmesh itself (m_nav) is only reset if mine(m_navSource) is true. If a script unloads but m_navSource was set to a different script (due to a logic error or race condition), the navmesh might persist while its associated obstacles are cleaned up, leading to a mismatch. More critically, if m_nav is non-null but mine(m_navSource) is false, the navmesh is NOT reset, potentially leaving stale data. The logic at line 1219 ensures the navmesh is only cleared if it belongs to the releasing script, which is correct, but the comment at line 1213 implies all obstacles are removed regardless of ownership check before the conditional block. The code correctly checks ownership for obstacle removal (line 1215-1216) and navmesh reset (line 1219). However, there is a subtle issue: if m_nav exists but m_navSource is empty or doesn't match, the navmesh persists. This is likely intended behavior (don't destroy global navs), but the lack of explicit logging or assertion makes debugging difficult if m_navSource gets out of sync. A more robust approach would be to ensure m_navSource is always updated atomically with m_nav creation and cleared immediately upon destruction, which seems to be happening, but the potential for m_nav to point to a mesh built by a script that has already been partially unloaded (if releaseScript is called multiple times or in parallel) is a risk. Given the single-threaded nature of Lua scripts, this is less likely, but the lack of validation on m_navSource before accessing m_nav in other parts of the code could be problematic.

## PR Reviewer Guide 🔍 <!-- pr-agent:review:full --> #### (Review updated until commit https://git.kreative-kompas.com/khyretos/kk-engine/commit/97d897675d015dcb370406b0ed6b5f9ed386eb4c) 📋 **Focus areas:** 🔴 2 open · ✅ 2 handled. Every push re-checks them; a handled area moves to *Resolved findings* at the bottom. <table> <tr><td>⏱️&nbsp;<strong>Estimated effort to review</strong>: 3 🔵🔵🔵⚪⚪</td></tr> <tr><td>🧪&nbsp;<strong>PR contains tests</strong></td></tr> <tr><td>🔒&nbsp;<strong>No security concerns identified</strong></td></tr> <tr><td>⚡&nbsp;<strong>Recommended focus areas for review</strong><br><br> <details><summary><a href='https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/127/head/engine/src/RigidWorld.cpp#L387-L388'><strong>Missing Error Handling for Raycast</strong></a> In `RigidWorld::staticTriangles`, the code iterates over all bodies and attempts to lock each one. If a body is locked by another thread or in an invalid state, `lock.Succeeded()` returns false and the loop continues without any diagnostic logging. While this prevents a crash, it silently skips processing potentially valid static bodies that are temporarily locked, leading to missing geometry in the navmesh. Adding a log message when a lock fails would help diagnose race conditions or performance bottlenecks during navmesh generation. </summary> ```c++ if (!lock.Succeeded()) continue; const JPH::Body& body = lock.GetBody(); ``` </details> <details><summary><a href='https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/127/head/engine/src/modules/ScriptBindings.cpp#L1502-L1504'><strong>Potential Resource Leak on Script Unload</strong></a> In `ScriptModule::releaseScript`, the code removes obstacles from the navmesh (`m_nav->removeObstacle(o.id)`) but only if the obstacle belongs to the script being released. However, if `m_nav` is null or invalid at the time of release (e.g., if the navmesh was destroyed earlier), the code will not attempt cleanup. More critically, if `m_nav` exists but `m_nav->removeObstacle` throws an exception or fails silently due to internal state corruption, the obstacle ID remains in the system. The current code does not check the return value or handle exceptions from `removeObstacle`, which could lead to memory leaks or undefined behavior if the navmesh implementation does not guarantee exception safety. </summary> ```c++ if (m_nav) for (const NavObstacle& o : m_navObstacles) if (mine(o.source)) m_nav->removeObstacle(o.id); ``` </details> </td></tr> </table> <details> <summary>✅ Resolved findings</summary> ### engine/src/RigidWorld.cpp **Missing Error Handling for Raycast** In the `groundHeight` lambda (lines 1064-1069), if the raycast fails to hit anything (e.g., the agent is too high or in an empty void), the function returns `false` without setting `y`. This leaves `y` uninitialized, which will cause undefined behavior when used later. The code should initialize `y` to a safe default (like `0.0f`) before the raycast call. ### engine/src/RigidWorld.cpp **Potential Memory Leak in Dynamic Navmesh** In `ScriptModule::releaseScript` (lines 1214-1224), obstacles are removed from the navmesh and then cleared from `m_navObstacles`. However, if `m_nav` is not valid or `m_navSource` does not match the current script, the navmesh itself (`m_nav`) is only reset if `mine(m_navSource)` is true. If a script unloads but `m_navSource` was set to a different script (due to a logic error or race condition), the navmesh might persist while its associated obstacles are cleaned up, leading to a mismatch. More critically, if `m_nav` is non-null but `mine(m_navSource)` is false, the navmesh is NOT reset, potentially leaving stale data. The logic at line 1219 ensures the navmesh is only cleared if it belongs to the releasing script, which is correct, but the comment at line 1213 implies all obstacles are removed regardless of ownership check before the conditional block. The code correctly checks ownership for obstacle removal (line 1215-1216) and navmesh reset (line 1219). However, there is a subtle issue: if `m_nav` exists but `m_navSource` is empty or doesn't match, the navmesh persists. This is likely intended behavior (don't destroy global navs), but the lack of explicit logging or assertion makes debugging difficult if `m_navSource` gets out of sync. A more robust approach would be to ensure `m_navSource` is always updated atomically with `m_nav` creation and cleared immediately upon destruction, which seems to be happening, but the potential for `m_nav` to point to a mesh built by a script that has already been partially unloaded (if `releaseScript` is called multiple times or in parallel) is a risk. Given the single-threaded nature of Lua scripts, this is less likely, but the lack of validation on `m_navSource` before accessing `m_nav` in other parts of the code could be problematic. </details> <!-- pr-agent-review-state:v1 {"findings":[{"body":"**Potential Memory Leak in Dynamic Navmesh**\n\nIn `ScriptModule::releaseScript` (lines 1214-1224), obstacles are removed from the navmesh and then cleared from `m_navObstacles`. However, if `m_nav` is not valid or `m_navSource` does not match the current script, the navmesh itself (`m_nav`) is only reset if `mine(m_navSource)` is true. If a script unloads but `m_navSource` was set to a different script (due to a logic error or race condition), the navmesh might persist while its associated obstacles are cleaned up, leading to a mismatch. More critically, if `m_nav` is non-null but `mine(m_navSource)` is false, the navmesh is NOT reset, potentially leaving stale data. The logic at line 1219 ensures the navmesh is only cleared if it belongs to the releasing script, which is correct, but the comment at line 1213 implies all obstacles are removed regardless of ownership check before the conditional block. The code correctly checks ownership for obstacle removal (line 1215-1216) and navmesh reset (line 1219). However, there is a subtle issue: if `m_nav` exists but `m_navSource` is empty or doesn't match, the navmesh persists. This is likely intended behavior (don't destroy global navs), but the lack of explicit logging or assertion makes debugging difficult if `m_navSource` gets out of sync. A more robust approach would be to ensure `m_navSource` is always updated atomically with `m_nav` creation and cleared immediately upon destruction, which seems to be happening, but the potential for `m_nav` to point to a mesh built by a script that has already been partially unloaded (if `releaseScript` is called multiple times or in parallel) is a risk. Given the single-threaded nature of Lua scripts, this is less likely, but the lack of validation on `m_navSource` before accessing `m_nav` in other parts of the code could be problematic.","finding_id":"125b65f07cf0","first_seen":"2026-10-25T23:48:03.753361Z","last_seen":"2026-10-25T23:48:03.753361Z","last_seen_head_sha":"3ff618a4f72e053892043c2c67e2f63f43a8d5b4","path":"engine/src/RigidWorld.cpp","resolution_run_id":"https://git.kreative-kompas.com/khyretos/kk-engine/commit/97d897675d015dcb370406b0ed6b5f9ed386eb4c","resolved_at":"2026-10-26T09:12:02.407224Z","resolved_head_sha":"97d897675d015dcb370406b0ed6b5f9ed386eb4c","state":"RESOLVED"},{"body":"**Missing Error Handling for Raycast**\n\nIn `RigidWorld::staticTriangles`, the code iterates over all bodies and attempts to lock each one. If a body is locked by another thread or in an invalid state, `lock.Succeeded()` returns false and the loop continues without any diagnostic logging. While this prevents a crash, it silently skips processing potentially valid static bodies that are temporarily locked, leading to missing geometry in the navmesh. Adding a log message when a lock fails would help diagnose race conditions or performance bottlenecks during navmesh generation.","finding_id":"a724a323de29","first_seen":"2026-10-26T09:12:02.407224Z","last_seen":"2026-10-26T09:12:02.407224Z","last_seen_head_sha":"97d897675d015dcb370406b0ed6b5f9ed386eb4c","line_end":388,"line_start":387,"path":"engine/src/RigidWorld.cpp","state":"ACTIVE"},{"body":"**Missing Error Handling for Raycast**\n\nIn the `groundHeight` lambda (lines 1064-1069), if the raycast fails to hit anything (e.g., the agent is too high or in an empty void), the function returns `false` without setting `y`. This leaves `y` uninitialized, which will cause undefined behavior when used later. The code should initialize `y` to a safe default (like `0.0f`) before the raycast call.","finding_id":"c8ff28ffd33c","first_seen":"2026-10-25T23:48:03.753361Z","last_seen":"2026-10-25T23:48:03.753361Z","last_seen_head_sha":"3ff618a4f72e053892043c2c67e2f63f43a8d5b4","path":"engine/src/RigidWorld.cpp","resolution_run_id":"https://git.kreative-kompas.com/khyretos/kk-engine/commit/97d897675d015dcb370406b0ed6b5f9ed386eb4c","resolved_at":"2026-10-26T09:12:02.407224Z","resolved_head_sha":"97d897675d015dcb370406b0ed6b5f9ed386eb4c","state":"RESOLVED"},{"body":"**Potential Resource Leak on Script Unload**\n\nIn `ScriptModule::releaseScript`, the code removes obstacles from the navmesh (`m_nav->removeObstacle(o.id)`) but only if the obstacle belongs to the script being released. However, if `m_nav` is null or invalid at the time of release (e.g., if the navmesh was destroyed earlier), the code will not attempt cleanup. More critically, if `m_nav` exists but `m_nav->removeObstacle` throws an exception or fails silently due to internal state corruption, the obstacle ID remains in the system. The current code does not check the return value or handle exceptions from `removeObstacle`, which could lead to memory leaks or undefined behavior if the navmesh implementation does not guarantee exception safety.","finding_id":"e676f1c5b046","first_seen":"2026-10-26T09:12:02.407224Z","last_seen":"2026-10-26T09:12:02.407224Z","last_seen_head_sha":"97d897675d015dcb370406b0ed6b5f9ed386eb4c","line_end":1504,"line_start":1502,"path":"engine/src/modules/ScriptBindings.cpp","state":"ACTIVE"}],"last_run":{"complete":true,"excluded_files":[],"head_sha":"97d897675d015dcb370406b0ed6b5f9ed386eb4c","kind":"full","run_id":"https://git.kreative-kompas.com/khyretos/kk-engine/commit/97d897675d015dcb370406b0ed6b5f9ed386eb4c"},"schema_version":1} -->
Collaborator

PR Code Suggestions ✨

No code suggestions found for the PR.

## PR Code Suggestions ✨ <!-- pr-agent:improve:no-suggestions --> No code suggestions found for the PR.

🧹 Lint report · ❌ 38 problems in 6 checks · 🧹 3 formatting checks to tidy

Nothing here blocks the merge (report only).

Check Errors Warnings Files
❌ MARKDOWN · markdownlint 24 0 253
❌ CPP · cppcheck 9 0 930
❌ YAML · yamllint 2 0 57
❌ BASH · shellcheck 1 0 11
❌ DOCKERFILE · hadolint 1 0 9
❌ REPOSITORY · trivy 1 0 –
MARKDOWN · markdownlint: first lines of the output
See documentation on https://megalinter.io/9.4.0/descriptors/markdown_markdownlint/
-----------------------------------------------
❌ [ERROR] for workspace /workspace/khyretos/kk-engine
Linter raw log:
docs/ANDROID.md:87 error MD040/fenced-code-language Fenced code blocks should have a language specified [Context: "```"]
docs/ANDROID.md:128 error MD040/fenced-code-language Fenced code blocks should have a language specified [Context: "```"]
docs/ANDROID.md:136 error MD040/fenced-code-language Fenced code blocks should have a language specified [Context: "```"]
docs/ANDROID.md:149 error MD040/fenced-code-language Fenced code blocks should have a language specified [Context: "```"]
docs/ANDROID.md:163 error MD040/fenced-code-language Fenced code blocks should have a language specified [Context: "```"]
docs/ANDROID.md:186 error MD040/fenced-code-language Fenced code blocks should have a language specified [Context: "```"]
docs/ASSETS.md:155 error MD046/code-block-style Code block style [Expected: fenced; Actual: indented]
docs/HISTORY.md:21 error MD025/single-title/single-h1 Multiple top-level headings in the same document [Context: " Kreative Kompas Engine (KKE)"]
docs/HISTORY.md:520 error MD040/fenced-code-language Fenced code blocks should have a language specified [Context: "```"]
docs/HISTORY.md:727 error MD040/fenced-code-language Fenced code blocks should have a language specified [Context: "```"]
docs/HISTORY.md:734 error MD040/fenced-code-language Fenced code blocks should have a language specified [Context: "```"]
docs/HISTORY.md:2031 error MD040/fenced-code-language Fenced code blocks should have a language specified [Context: "```"]
docs/HISTORY.md:2090 error MD040/fenced-code-language Fenced code blocks should have a language specified [Context: "```"]
docs/MODDING.md:13 error MD040/fenced-code-language Fenced code blocks should have a language specified [Context: "```"]
docs/NETWORKING.md:114 error MD040/fenced-code-language Fenced code blocks should have a language specified [Context: "```"]
docs/PLATFORMS.md:48 error MD040/fenced-code-language Fenced code blocks should have a language specified [Context: "```"]
docs/PROCEDURAL_ANIMATION.md:36 error MD040/fenced-code-language Fenced code blocks should have a language specified [Context: "```"]
docs/PROCEDURAL_ANIMATION.md:284 error MD040/fenced-code-language Fenced code blocks should have a language specified [Context: "```"]
games/racing/README.md:34 error MD040/fenced-code-language Fenced code blocks should have a language specified [Context: "```"]
games/racing/README.md:76:91 error MD056/table-column-count Table column count [Expected: 4; Actual: 3; Too few cells, row will be missing data]
games/racing/README.md:77:89 error MD056/table-column-count Table column count [Expected: 4; Actual: 3; Too few cells, row will be missing data]
…
CPP · cppcheck: first lines of the output
See documentation on https://megalinter.io/9.4.0/descriptors/cpp_cppcheck/
-----------------------------------------------
✅ [SUCCESS] benchmarks/kke_bench/main.cpp
✅ [SUCCESS] benchmarks/kke_benchmark/main.cpp
✅ [SUCCESS] engine/include/engine/Buffer.h
✅ [SUCCESS] engine/include/engine/Mesh.h
✅ [SUCCESS] engine/include/engine/Pipeline.h
✅ [SUCCESS] engine/include/engine/Renderer.h
✅ [SUCCESS] engine/include/engine/SwapChain.h
✅ [SUCCESS] engine/include/engine/VulkanCheck.h
✅ [SUCCESS] engine/include/engine/VulkanDevice.h
✅ [SUCCESS] engine/include/engine/Window.h
✅ [SUCCESS] engine/include/kke/AnimEvents.h
✅ [SUCCESS] engine/include/kke/AnimRig.h
✅ [SUCCESS] engine/include/kke/Animator.h
✅ [SUCCESS] engine/include/kke/Application.h
✅ [SUCCESS] engine/include/kke/AssetCatalog.h
✅ [SUCCESS] engine/include/kke/AudioMixer.h
✅ [SUCCESS] engine/include/kke/AutoRig.h
✅ [SUCCESS] engine/include/kke/BenchRecorder.h
✅ [SUCCESS] engine/include/kke/BenchmarkReport.h
✅ [SUCCESS] engine/include/kke/Bloom.h
✅ [SUCCESS] engine/include/kke/BodyShape.h
✅ [SUCCESS] engine/include/kke/BreakGraph.h
✅ [SUCCESS] engine/include/kke/Breakables.h
…
YAML · yamllint: first lines of the output
See documentation on https://megalinter.io/9.4.0/descriptors/yaml_yamllint/
-----------------------------------------------
❌ [ERROR] for workspace /workspace/khyretos/kk-engine
Linter raw log:
games/goblin_horde/data/heroes.yml
  53:11     error    too many spaces after colon  (colons)
  63:11     error    too many spaces after colon  (colons)
BASH · shellcheck: first lines of the output
See documentation on https://megalinter.io/9.4.0/descriptors/bash_shellcheck/
-----------------------------------------------
❌ [ERROR] for workspace /workspace/khyretos/kk-engine
Linter raw log:
In tools/packaging/package.sh line 130:
	done < <(find "$stage" "$stage/benchmark" -maxdepth 1 -type f \( -name "*$exe" -o -name '*.dll' \) $([ -z "$exe" ] && echo "-perm -u+x") -print0)
                                                                                                           ^-- SC2046 (warning): Quote this to prevent word splitting.
For more information:
  https://www.shellcheck.net/wiki/SC2046 -- Quote this to prevent word splitt...
DOCKERFILE · hadolint: first lines of the output
See documentation on https://megalinter.io/9.4.0/descriptors/dockerfile_hadolint/
-----------------------------------------------
❌ [ERROR] for workspace /workspace/khyretos/kk-engine
Linter raw log:
docker/android.Dockerfile:19 DL4006 warning: Set the SHELL option -o pipefail before RUN with a pipe in it. If you are using /bin/sh in an alpine image or if your shell is symlinked to busybox then consider explicitly setting your SHELL to /bin/ash, or disable this check
docker/appimage.Dockerfile:38 SC2016 info: Expressions don't expand in single quotes, use double quotes for that.
docker/appimage.Dockerfile:51 DL3003 warning: Use WORKDIR to switch to a directory
docker/appimage.Dockerfile:57 DL3003 warning: Use WORKDIR to switch to a directory
docker/appimage.Dockerfile:63 DL4006 warning: Set the SHELL option -o pipefail before RUN with a pipe in it. If you are using /bin/sh in an alpine image or if your shell is symlinked to busybox then consider explicitly setting your SHELL to /bin/ash, or disable this check
docker/appimage.Dockerfile:63 DL3003 warning: Use WORKDIR to switch to a directory
docker/appimage.Dockerfile:73 DL4006 warning: Set the SHELL option -o pipefail before RUN with a pipe in it. If you are using /bin/sh in an alpine image or if your shell is symlinked to busybox then consider explicitly setting your SHELL to /bin/ash, or disable this check
docker/appimage.Dockerfile:73 DL3003 warning: Use WORKDIR to switch to a directory
docker/ci.Dockerfile:42 DL4006 warning: Set the SHELL option -o pipefail before RUN with a pipe in it. If you are using /bin/sh in an alpine image or if your shell is symlinked to busybox then consider explicitly setting your SHELL to /bin/ash, or disable this check
docker/ci.Dockerfile:56 DL4006 warning: Set the SHELL option -o pipefail before RUN with a pipe in it. If you are using /bin/sh in an alpine image or if your shell is symlinked to busybox then consider explicitly setting your SHELL to /bin/ash, or disable this check
docker/server/Dockerfile:22 DL4006 warning: Set the SHELL option -o pipefail before RUN with a pipe in it. If you are using /bin/sh in an alpine image or if your shell is symlinked to busybox then consider explicitly setting your SHELL to /bin/ash, or disable this check
docker/vm.Dockerfile:22 SC2086 info: Double quote to prevent globbing and word splitting.
docker/vm.Dockerfile:29 DL3003 warning: Use WORKDIR to switch to a directory
docker/vm.Dockerfile:29 DL4006 warning: Set the SHELL option -o pipefail before RUN with a pipe in it. If you are using /bin/sh in an alpine image or if your shell is symlinked to busybox then consider explicitly setting your SHELL to /bin/ash, or disable this check
website/Dockerfile:9 DL4006 warning: Set the SHELL option -o pipefail before RUN with a pipe in it. If you are using /bin/sh in an alpine image or if your shell is symlinked to busybox then consider explicitly setting your SHELL to /bin/ash, or disable this check
REPOSITORY · trivy: first lines of the output
See documentation on https://megalinter.io/9.4.0/descriptors/repository_trivy/
-----------------------------------------------
❌ [ERROR] for workspace /workspace/khyretos/kk-engine
Linter raw log:
2026-10-26T09:58:14Z	INFO	[vulndb] Need to update DB
2026-10-26T09:58:14Z	INFO	[vulndb] Downloading vulnerability DB...
2026-10-26T09:58:14Z	INFO	[vulndb] Downloading artifact...	repo="mirror.gcr.io/aquasec/trivy-db:2"
19.75 MiB / 129.73 MiB [--------->__________________________________________________] 15.22% ? p/s ?34.40 MiB / 129.73 MiB [--------------->____________________________________________] 26.52% ? p/s ?49.66 MiB / 129.73 MiB [---------------------->_____________________________________] 38.28% ? p/s ?62.17 MiB / 129.73 MiB [---------------------->________________________] 47.92% 70.76 MiB p/s ETA 0s73.95 MiB / 129.73 MiB [-------------------------->____________________] 57.00% 70.76 MiB p/s ETA 0s85.26 MiB / 129.73 MiB [------------------------------>________________] 65.72% 70.76 MiB p/s ETA 0s96.39 MiB / 129.73 MiB [---------------------------------->____________] 74.30% 69.87 MiB p/s ETA 0s107.68 MiB / 129.73 MiB [-------------------------------------->_______] 83.01% 69.87 MiB p/s ETA 0s118.71 MiB / 129.73 MiB [------------------------------------------>___] 91.51% 69.87 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 68.95 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 68.95 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 68.95 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 64.50 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 64.50 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 64.50 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 60.34 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 60.34 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 60.34 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 56.44 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 56.44 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 56.44 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 52.80 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 52.80 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 52.80 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 49.40 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 49.40 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 49.40 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 46.21 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 46.21 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 46.21 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 43.23 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 43.23 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-----------------------------------------------] 100.00% 20.31 MiB p/s 6.6s2026-10-26T09:58:21Z	INFO	[vulndb] Artifact successfully downloaded	repo="mirror.gcr.io/aquasec/trivy-db:2"
2026-10-26T09:58:21Z	INFO	[vuln] Vulnerability scanning is enabled
2026-10-26T09:58:21Z	INFO	[misconfig] Misconfiguration scanning is enabled
2026-10-26T09:58:21Z	INFO	[checks-client] Need to update the checks bundle
2026-10-26T09:58:21Z	INFO	[checks-client] Downloading the checks bundle...
234.57 KiB / 234.57 KiB [------------------------------------------------------] 100.00% ? p/s 100ms2026-10-26T09:58:22Z	WARN	[rego] Module will be skipped as current version of Trivy is older than minimum trivy version required - please update Trivy to use this module	file_path="root/.cache/trivy/policy/content/policies/cloud/policies/aws/cloudfront/enable_logging.rego" minimum_trivy_version="0.72.0"
2026-10-26T09:58:22Z	ERROR	[dockerfile scanner] Failed to parse file	file_path="docker/server/Dockerfile.dockerignore" err="parse dockerfile instruction: parse instruction \".git\": unknown instruction: .git"
2026-10-26T09:58:22Z	INFO	Number of language-specific files	num=0
2026-10-26T09:58:22Z	INFO	Detected config files	num=9
Report Summary
┌──────────────────────────────┬────────────┬─────────────────┬───────────────────┐
│            Target            │    Type    │ Vulnerabilities │ Misconfigurations │
├──────────────────────────────┼────────────┼─────────────────┼───────────────────┤
│ benchmarks/docker/Dockerfile │ dockerfile │        -        │         2         │
├──────────────────────────────┼────────────┼─────────────────┼───────────────────┤
│ docker/android.Dockerfile    │ dockerfile │        -        │         2         │
├──────────────────────────────┼────────────┼─────────────────┼───────────────────┤
│ docker/appimage.Dockerfile   │ dockerfile │        -        │         5         │
…

🧹 Formatting only (layout, no behaviour; the nightly lint-fix job applies these as a PR): YAML · prettier (57 files checked), BASH · shfmt (11 files checked), JSON · prettier (147 files checked)

✅ Clean: actionlint, git_diff, gitleaks, grype, jsonlint, secretlint, stylelint, trufflehog, xmllint

MegaLinter on soucouyant · full logs in the Actions run · comment /improve for AI fix suggestions

<!-- kk-lint --> ## 🧹 Lint report · ❌ 38 problems in 6 checks · 🧹 3 formatting checks to tidy Nothing here blocks the merge (report only). | | Check | Errors | Warnings | Files | |---|---|---:|---:|---:| | ❌ | MARKDOWN · markdownlint | 24 | 0 | 253 | | ❌ | CPP · cppcheck | 9 | 0 | 930 | | ❌ | YAML · yamllint | 2 | 0 | 57 | | ❌ | BASH · shellcheck | 1 | 0 | 11 | | ❌ | DOCKERFILE · hadolint | 1 | 0 | 9 | | ❌ | REPOSITORY · trivy | 1 | 0 | – | <details><summary>MARKDOWN · markdownlint: first lines of the output</summary> ```text See documentation on https://megalinter.io/9.4.0/descriptors/markdown_markdownlint/ ----------------------------------------------- ❌ [ERROR] for workspace /workspace/khyretos/kk-engine Linter raw log: docs/ANDROID.md:87 error MD040/fenced-code-language Fenced code blocks should have a language specified [Context: "```"] docs/ANDROID.md:128 error MD040/fenced-code-language Fenced code blocks should have a language specified [Context: "```"] docs/ANDROID.md:136 error MD040/fenced-code-language Fenced code blocks should have a language specified [Context: "```"] docs/ANDROID.md:149 error MD040/fenced-code-language Fenced code blocks should have a language specified [Context: "```"] docs/ANDROID.md:163 error MD040/fenced-code-language Fenced code blocks should have a language specified [Context: "```"] docs/ANDROID.md:186 error MD040/fenced-code-language Fenced code blocks should have a language specified [Context: "```"] docs/ASSETS.md:155 error MD046/code-block-style Code block style [Expected: fenced; Actual: indented] docs/HISTORY.md:21 error MD025/single-title/single-h1 Multiple top-level headings in the same document [Context: " Kreative Kompas Engine (KKE)"] docs/HISTORY.md:520 error MD040/fenced-code-language Fenced code blocks should have a language specified [Context: "```"] docs/HISTORY.md:727 error MD040/fenced-code-language Fenced code blocks should have a language specified [Context: "```"] docs/HISTORY.md:734 error MD040/fenced-code-language Fenced code blocks should have a language specified [Context: "```"] docs/HISTORY.md:2031 error MD040/fenced-code-language Fenced code blocks should have a language specified [Context: "```"] docs/HISTORY.md:2090 error MD040/fenced-code-language Fenced code blocks should have a language specified [Context: "```"] docs/MODDING.md:13 error MD040/fenced-code-language Fenced code blocks should have a language specified [Context: "```"] docs/NETWORKING.md:114 error MD040/fenced-code-language Fenced code blocks should have a language specified [Context: "```"] docs/PLATFORMS.md:48 error MD040/fenced-code-language Fenced code blocks should have a language specified [Context: "```"] docs/PROCEDURAL_ANIMATION.md:36 error MD040/fenced-code-language Fenced code blocks should have a language specified [Context: "```"] docs/PROCEDURAL_ANIMATION.md:284 error MD040/fenced-code-language Fenced code blocks should have a language specified [Context: "```"] games/racing/README.md:34 error MD040/fenced-code-language Fenced code blocks should have a language specified [Context: "```"] games/racing/README.md:76:91 error MD056/table-column-count Table column count [Expected: 4; Actual: 3; Too few cells, row will be missing data] games/racing/README.md:77:89 error MD056/table-column-count Table column count [Expected: 4; Actual: 3; Too few cells, row will be missing data] … ``` </details> <details><summary>CPP · cppcheck: first lines of the output</summary> ```text See documentation on https://megalinter.io/9.4.0/descriptors/cpp_cppcheck/ ----------------------------------------------- ✅ [SUCCESS] benchmarks/kke_bench/main.cpp ✅ [SUCCESS] benchmarks/kke_benchmark/main.cpp ✅ [SUCCESS] engine/include/engine/Buffer.h ✅ [SUCCESS] engine/include/engine/Mesh.h ✅ [SUCCESS] engine/include/engine/Pipeline.h ✅ [SUCCESS] engine/include/engine/Renderer.h ✅ [SUCCESS] engine/include/engine/SwapChain.h ✅ [SUCCESS] engine/include/engine/VulkanCheck.h ✅ [SUCCESS] engine/include/engine/VulkanDevice.h ✅ [SUCCESS] engine/include/engine/Window.h ✅ [SUCCESS] engine/include/kke/AnimEvents.h ✅ [SUCCESS] engine/include/kke/AnimRig.h ✅ [SUCCESS] engine/include/kke/Animator.h ✅ [SUCCESS] engine/include/kke/Application.h ✅ [SUCCESS] engine/include/kke/AssetCatalog.h ✅ [SUCCESS] engine/include/kke/AudioMixer.h ✅ [SUCCESS] engine/include/kke/AutoRig.h ✅ [SUCCESS] engine/include/kke/BenchRecorder.h ✅ [SUCCESS] engine/include/kke/BenchmarkReport.h ✅ [SUCCESS] engine/include/kke/Bloom.h ✅ [SUCCESS] engine/include/kke/BodyShape.h ✅ [SUCCESS] engine/include/kke/BreakGraph.h ✅ [SUCCESS] engine/include/kke/Breakables.h … ``` </details> <details><summary>YAML · yamllint: first lines of the output</summary> ```text See documentation on https://megalinter.io/9.4.0/descriptors/yaml_yamllint/ ----------------------------------------------- ❌ [ERROR] for workspace /workspace/khyretos/kk-engine Linter raw log: games/goblin_horde/data/heroes.yml 53:11 error too many spaces after colon (colons) 63:11 error too many spaces after colon (colons) ``` </details> <details><summary>BASH · shellcheck: first lines of the output</summary> ```text See documentation on https://megalinter.io/9.4.0/descriptors/bash_shellcheck/ ----------------------------------------------- ❌ [ERROR] for workspace /workspace/khyretos/kk-engine Linter raw log: In tools/packaging/package.sh line 130: done < <(find "$stage" "$stage/benchmark" -maxdepth 1 -type f \( -name "*$exe" -o -name '*.dll' \) $([ -z "$exe" ] && echo "-perm -u+x") -print0) ^-- SC2046 (warning): Quote this to prevent word splitting. For more information: https://www.shellcheck.net/wiki/SC2046 -- Quote this to prevent word splitt... ``` </details> <details><summary>DOCKERFILE · hadolint: first lines of the output</summary> ```text See documentation on https://megalinter.io/9.4.0/descriptors/dockerfile_hadolint/ ----------------------------------------------- ❌ [ERROR] for workspace /workspace/khyretos/kk-engine Linter raw log: docker/android.Dockerfile:19 DL4006 warning: Set the SHELL option -o pipefail before RUN with a pipe in it. If you are using /bin/sh in an alpine image or if your shell is symlinked to busybox then consider explicitly setting your SHELL to /bin/ash, or disable this check docker/appimage.Dockerfile:38 SC2016 info: Expressions don't expand in single quotes, use double quotes for that. docker/appimage.Dockerfile:51 DL3003 warning: Use WORKDIR to switch to a directory docker/appimage.Dockerfile:57 DL3003 warning: Use WORKDIR to switch to a directory docker/appimage.Dockerfile:63 DL4006 warning: Set the SHELL option -o pipefail before RUN with a pipe in it. If you are using /bin/sh in an alpine image or if your shell is symlinked to busybox then consider explicitly setting your SHELL to /bin/ash, or disable this check docker/appimage.Dockerfile:63 DL3003 warning: Use WORKDIR to switch to a directory docker/appimage.Dockerfile:73 DL4006 warning: Set the SHELL option -o pipefail before RUN with a pipe in it. If you are using /bin/sh in an alpine image or if your shell is symlinked to busybox then consider explicitly setting your SHELL to /bin/ash, or disable this check docker/appimage.Dockerfile:73 DL3003 warning: Use WORKDIR to switch to a directory docker/ci.Dockerfile:42 DL4006 warning: Set the SHELL option -o pipefail before RUN with a pipe in it. If you are using /bin/sh in an alpine image or if your shell is symlinked to busybox then consider explicitly setting your SHELL to /bin/ash, or disable this check docker/ci.Dockerfile:56 DL4006 warning: Set the SHELL option -o pipefail before RUN with a pipe in it. If you are using /bin/sh in an alpine image or if your shell is symlinked to busybox then consider explicitly setting your SHELL to /bin/ash, or disable this check docker/server/Dockerfile:22 DL4006 warning: Set the SHELL option -o pipefail before RUN with a pipe in it. If you are using /bin/sh in an alpine image or if your shell is symlinked to busybox then consider explicitly setting your SHELL to /bin/ash, or disable this check docker/vm.Dockerfile:22 SC2086 info: Double quote to prevent globbing and word splitting. docker/vm.Dockerfile:29 DL3003 warning: Use WORKDIR to switch to a directory docker/vm.Dockerfile:29 DL4006 warning: Set the SHELL option -o pipefail before RUN with a pipe in it. If you are using /bin/sh in an alpine image or if your shell is symlinked to busybox then consider explicitly setting your SHELL to /bin/ash, or disable this check website/Dockerfile:9 DL4006 warning: Set the SHELL option -o pipefail before RUN with a pipe in it. If you are using /bin/sh in an alpine image or if your shell is symlinked to busybox then consider explicitly setting your SHELL to /bin/ash, or disable this check ``` </details> <details><summary>REPOSITORY · trivy: first lines of the output</summary> ```text See documentation on https://megalinter.io/9.4.0/descriptors/repository_trivy/ ----------------------------------------------- ❌ [ERROR] for workspace /workspace/khyretos/kk-engine Linter raw log: 2026-10-26T09:58:14Z INFO [vulndb] Need to update DB 2026-10-26T09:58:14Z INFO [vulndb] Downloading vulnerability DB... 2026-10-26T09:58:14Z INFO [vulndb] Downloading artifact... repo="mirror.gcr.io/aquasec/trivy-db:2" 19.75 MiB / 129.73 MiB [--------->__________________________________________________] 15.22% ? p/s ?34.40 MiB / 129.73 MiB [--------------->____________________________________________] 26.52% ? p/s ?49.66 MiB / 129.73 MiB [---------------------->_____________________________________] 38.28% ? p/s ?62.17 MiB / 129.73 MiB [---------------------->________________________] 47.92% 70.76 MiB p/s ETA 0s73.95 MiB / 129.73 MiB [-------------------------->____________________] 57.00% 70.76 MiB p/s ETA 0s85.26 MiB / 129.73 MiB [------------------------------>________________] 65.72% 70.76 MiB p/s ETA 0s96.39 MiB / 129.73 MiB [---------------------------------->____________] 74.30% 69.87 MiB p/s ETA 0s107.68 MiB / 129.73 MiB [-------------------------------------->_______] 83.01% 69.87 MiB p/s ETA 0s118.71 MiB / 129.73 MiB [------------------------------------------>___] 91.51% 69.87 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 68.95 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 68.95 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 68.95 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 64.50 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 64.50 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 64.50 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 60.34 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 60.34 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 60.34 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 56.44 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 56.44 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 56.44 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 52.80 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 52.80 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 52.80 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 49.40 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 49.40 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 49.40 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 46.21 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 46.21 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 46.21 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 43.23 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 43.23 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-----------------------------------------------] 100.00% 20.31 MiB p/s 6.6s2026-10-26T09:58:21Z INFO [vulndb] Artifact successfully downloaded repo="mirror.gcr.io/aquasec/trivy-db:2" 2026-10-26T09:58:21Z INFO [vuln] Vulnerability scanning is enabled 2026-10-26T09:58:21Z INFO [misconfig] Misconfiguration scanning is enabled 2026-10-26T09:58:21Z INFO [checks-client] Need to update the checks bundle 2026-10-26T09:58:21Z INFO [checks-client] Downloading the checks bundle... 234.57 KiB / 234.57 KiB [------------------------------------------------------] 100.00% ? p/s 100ms2026-10-26T09:58:22Z WARN [rego] Module will be skipped as current version of Trivy is older than minimum trivy version required - please update Trivy to use this module file_path="root/.cache/trivy/policy/content/policies/cloud/policies/aws/cloudfront/enable_logging.rego" minimum_trivy_version="0.72.0" 2026-10-26T09:58:22Z ERROR [dockerfile scanner] Failed to parse file file_path="docker/server/Dockerfile.dockerignore" err="parse dockerfile instruction: parse instruction \".git\": unknown instruction: .git" 2026-10-26T09:58:22Z INFO Number of language-specific files num=0 2026-10-26T09:58:22Z INFO Detected config files num=9 Report Summary ┌──────────────────────────────┬────────────┬─────────────────┬───────────────────┐ │ Target │ Type │ Vulnerabilities │ Misconfigurations │ ├──────────────────────────────┼────────────┼─────────────────┼───────────────────┤ │ benchmarks/docker/Dockerfile │ dockerfile │ - │ 2 │ ├──────────────────────────────┼────────────┼─────────────────┼───────────────────┤ │ docker/android.Dockerfile │ dockerfile │ - │ 2 │ ├──────────────────────────────┼────────────┼─────────────────┼───────────────────┤ │ docker/appimage.Dockerfile │ dockerfile │ - │ 5 │ … ``` </details> 🧹 Formatting only (layout, no behaviour; the nightly lint-fix job applies these as a PR): YAML · prettier (57 files checked), BASH · shfmt (11 files checked), JSON · prettier (147 files checked) ✅ Clean: actionlint, git_diff, gitleaks, grype, jsonlint, secretlint, stylelint, trufflehog, xmllint <sub>MegaLinter on soucouyant · full logs in the Actions run · comment <code>/improve</code> for AI fix suggestions</sub>
Collaborator

This PR needs main merged 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.

<!-- kk-automerge:conflict --> This PR needs `main` merged 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.
Collaborator

This PR needs main merged 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.

<!-- kk-automerge:conflict --> This PR needs `main` merged 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.
Collaborator

This PR conflicts with main and the bot could not merge it in by itself:

  • engine/include/kke/modules/ScriptModule.h
  • engine/src/modules/ScriptBindings.cpp
  • engine/src/modules/ScriptModule.cpp

Reply union to keep both sides in every file (CI then checks the result), or merge main in by hand.

<!-- kk-automerge:conflict --> This PR conflicts with `main` and the bot could not merge it in by itself: - `engine/include/kke/modules/ScriptModule.h` - `engine/src/modules/ScriptBindings.cpp` - `engine/src/modules/ScriptModule.cpp` Reply `union` to keep both sides in every file (CI then checks the result), or merge main in by hand.
khyretos force-pushed khyretos/claude/lua-nav-2 from 3ff618a4f7
Some checks failed
review / pr-agent PR-Agent reviewed this commit
Windows (native) / build (pull_request) Failing after -1s
Docs site / site (pull_request) Failing after -1s
Windows (native) / test (pull_request) Has been skipped
Windows (native) / publish (pull_request) Has been skipped
lint / megalinter (pull_request) Successful in 3m7s
Benchmarks / benchmarks (pull_request) Successful in 3m39s
CI / dependencies (pull_request) Successful in 3s
kk / all-checks Failed: Docs site / site (pull_request), Windows (native) / build (pull_request)
CI / std-includes (pull_request) Successful in 6s
CI / shipping-build (pull_request) Successful in 5m21s
CI / release-build (pull_request) Successful in 7m28s
CI / android (pull_request) Successful in 5m20s
CI / build-test-and-coverage (pull_request) Has been cancelled
to 97d897675d
All checks were successful
CI / build-test-and-coverage (pull_request) Successful in 1h11m54s
kk / all-checks All 13 checks passed
review / pr-agent PR-Agent reviewed this commit
CI / dependencies (pull_request) Successful in 5s
CI / std-includes (pull_request) Successful in 11s
Benchmarks / benchmarks (pull_request) Successful in 4m48s
CI / release-build (pull_request) Successful in 7m54s
Docs site / site (pull_request) Successful in 43s
CI / android (pull_request) Successful in 5m35s
CI / shipping-build (pull_request) Successful in 26m50s
lint / megalinter (pull_request) Successful in 1m4s
Windows (native) / build (pull_request) Successful in 51m48s
Windows (native) / test (pull_request) Successful in 6m30s
Windows (native) / publish (pull_request) Has been skipped
2026-10-26 10:11:33 +01:00
Compare
Sign in to join this conversation.
No reviewers
No milestone
No project
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!127
No description provided.