Cutscene cameras from Lua: camera.path, hold, release, shake (F27) #133

Merged
kompas-review merged 1 commit from khyretos/claude/cutscenes-2 into main 2026-10-27 14:33:18 +01:00 AGit
Owner

Requested by Kees · project thread

Rebased onto main fcee8fab (after #131 foliage and #132 lint fix). Terrain chunks now follow the camera that's shown, so a cutscene's shot gets the right detail.

What

Cutscene cameras for every game, from Lua (GAPS F27, camera part).

  • kke::CameraDirector: a path through shots (position, look-at point, optional field of view, time; Catmull-Rom), or one held shot. It blends in from what was on screen and back out to gameplay, never jumping, even when a new path starts during another or a release comes half way through a blend. Plus a trauma shake on top of any camera.
  • Application::setCameraDirector: runs after every module's update with the camera gameplay made; camera() is gameplay's again before the next updates, so the player's camera code keeps running underneath.
  • Lua: camera.path(shots, {loop, hold, blend, blendOut, seconds}), camera.hold, camera.release, camera.directing, camera.shake(strength, seconds), camera.fov, and the CameraDone hook. camera.position/target/forward report the shot on screen. A script's cutscene ends when the script unloads.
  • explosion.lua now shakes in every game (camera.shake), new cutscene.lua recipe with a picture, cookbook "Cutscenes in any game", SCRIPTING rows, GAPS F27 🟡.

Tests

  • 10 new CameraDirector.* tests (passes through every shot, smooth, blend in/out without jumps, finishes once, hold, loop, fov, shake fades and caps). Full kke_tests: 1334 passed (23 skipped: valkey and postgres backends) on main fcee8fab.
  • cutscene.lua played in starter_game (tools/check_game, 12 s): fly-around, close-up hold, CameraDone logged, no errors. Recipe runner shots for cutscene and explosion.
  • check_std_includes, check_dependencies, check_game_modules, mkdocs --strict: clean.

🤖 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)_ Rebased onto main fcee8fab (after #131 foliage and #132 lint fix). Terrain chunks now follow the camera that's shown, so a cutscene's shot gets the right detail. ## What Cutscene cameras for every game, from Lua (GAPS F27, camera part). - `kke::CameraDirector`: a path through shots (position, look-at point, optional field of view, time; Catmull-Rom), or one held shot. It blends in from what was on screen and back out to gameplay, never jumping, even when a new path starts during another or a release comes half way through a blend. Plus a trauma shake on top of any camera. - `Application::setCameraDirector`: runs after every module's update with the camera gameplay made; `camera()` is gameplay's again before the next updates, so the player's camera code keeps running underneath. - Lua: `camera.path(shots, {loop, hold, blend, blendOut, seconds})`, `camera.hold`, `camera.release`, `camera.directing`, `camera.shake(strength, seconds)`, `camera.fov`, and the `CameraDone` hook. `camera.position/target/forward` report the shot on screen. A script's cutscene ends when the script unloads. - `explosion.lua` now shakes in every game (`camera.shake`), new `cutscene.lua` recipe with a picture, cookbook "Cutscenes in any game", SCRIPTING rows, GAPS F27 🟡. ## Tests - 10 new `CameraDirector.*` tests (passes through every shot, smooth, blend in/out without jumps, finishes once, hold, loop, fov, shake fades and caps). Full kke_tests: 1334 passed (23 skipped: valkey and postgres backends) on main fcee8fab. - `cutscene.lua` played in starter_game (`tools/check_game`, 12 s): fly-around, close-up hold, `CameraDone` logged, no errors. Recipe runner shots for cutscene and explosion. - check_std_includes, check_dependencies, check_game_modules, mkdocs --strict: clean. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_019rkS6wq7fV9fWdiLwxyyc7
Cutscene cameras from Lua: camera.path, hold, release, shake (F27)
All checks were successful
CI / dependencies (pull_request) Successful in 7s
CI / std-includes (pull_request) Successful in 14s
review / pr-agent PR-Agent reviewed this commit
Benchmarks / benchmarks (pull_request) Successful in 4m42s
CI / android (pull_request) Successful in 7m43s
Docs site / site (pull_request) Successful in 10s
CI / shipping-build (pull_request) Successful in 13m0s
lint / megalinter (pull_request) Successful in 1m0s
Windows (native) / build (pull_request) Successful in 23m46s
Windows (native) / test (pull_request) Successful in 6m15s
Windows (native) / publish (pull_request) Has been skipped
CI / release-build (pull_request) Successful in 45m27s
kk / all-checks All 13 checks passed
CI / build-test-and-coverage (pull_request) Successful in 1h43m42s
934dc18f9b
kke::CameraDirector takes the camera from gameplay for a path through
shots (Catmull-Rom, optional field of view per shot) or a held shot,
blends in from what was on screen and back out to gameplay, and adds a
trauma shake on top of any camera. Application runs it after every
module's update (setCameraDirector) and gives camera() back to gameplay
before the next frame's updates, so the player's camera code keeps
running underneath.

Lua: camera.path/hold/release/directing/shake/fov, the CameraDone hook,
and camera.position/target/forward now report the shot on screen. The
explosion recipe shakes in every game; new cutscene.lua recipe and a
cookbook section. GAPS F27 partly done.

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-27 12:49:12 +01:00
Collaborator

Title

Cutscene cameras from Lua: camera.path, hold, release, shake (F27)


User description

Requested by Kees · project thread

Rebased onto main fcee8fab (after #131 foliage and #132 lint fix). Terrain chunks now follow the camera that's shown, so a cutscene's shot gets the right detail.

What

Cutscene cameras for every game, from Lua (GAPS F27, camera part).

  • kke::CameraDirector: a path through shots (position, look-at point, optional field of view, time; Catmull-Rom), or one held shot. It blends in from what was on screen and back out to gameplay, never jumping, even when a new path starts during another or a release comes half way through a blend. Plus a trauma shake on top of any camera.
  • Application::setCameraDirector: runs after every module's update with the camera gameplay made; camera() is gameplay's again before the next updates, so the player's camera code keeps running underneath.
  • Lua: camera.path(shots, {loop, hold, blend, blendOut, seconds}), camera.hold, camera.release, camera.directing, camera.shake(strength, seconds), camera.fov, and the CameraDone hook. camera.position/target/forward report the shot on screen. A script's cutscene ends when the script unloads.
  • explosion.lua now shakes in every game (camera.shake), new cutscene.lua recipe with a picture, cookbook "Cutscenes in any game", SCRIPTING rows, GAPS F27 🟡.

Tests

  • 10 new CameraDirector.* tests (passes through every shot, smooth, blend in/out without jumps, finishes once, hold, loop, fov, shake fades and caps). Full kke_tests: 1334 passed (23 skipped: valkey and postgres backends) on main fcee8fab.
  • cutscene.lua played in starter_game (tools/check_game, 12 s): fly-around, close-up hold, CameraDone logged, no errors. Recipe runner shots for cutscene and explosion.
  • check_std_includes, check_dependencies, check_game_modules, mkdocs --strict: clean.

🤖 Generated with Claude Code

https://claude.ai/code/session_019rkS6wq7fV9fWdiLwxyyc7


PR Type

Enhancement


Description

  • Adds CameraDirector for Lua-driven cutscenes with smooth blending.

  • Implements camera.path, hold, release, and shake functions.

  • Introduces CameraDone hook and terrain updates during shots.


Diagram Walkthrough

flowchart LR
  A["Lua Script"] -->|"camera.path"| B["CameraDirector"]
  B -->|"update"| C["Application"]
  C -->|"setCameraDirector"| D["Game Loop"]
  D -.->|"shownCamera"| E["Terrain Update"]
  D -.->|"shownCamera"| F["Collision Check"]

File Walkthrough

Relevant files
Enhancement
8 files
CameraDirector.h
Defines CameraDirector class for cutscene camera control 
+84/-0   
CameraDirector.cpp
Implements Catmull-Rom interpolation and blending logic   
+187/-0 
Application.h
Adds setCameraDirector callback and gameplay camera storage
+11/-0   
Application.cpp
Integrates CameraDirector into main loop with blend handling
+6/-0     
ScriptModule.h
Declares camera bindings and director state variables       
+14/-2   
ScriptCamera.cpp
Exposes camera.path, hold, release, shake to Lua scripts 
+116/-0 
ScriptModule.cpp
Calls CameraDone hook and uses shownCamera for terrain     
+10/-10 
explosion.lua
Updates to use universal camera.shake instead of view.shake
+2/-2     
Bug fix
1 files
ScriptBindings.cpp
Releases director when script unloads to restore gameplay
+4/-0     
Tests
1 files
test_camera_director.cpp
Tests path smoothness, blending, holding, looping, shake 
+174/-0 
Documentation
4 files
cutscene.lua
Demonstrates fly-around cutscene with camera.path and hold
+36/-0   
cameras.md
Documents cutscene usage and shows cutscene.lua example   
+25/-2   
SCRIPTING.md
Adds camera.* API reference and CameraDone hook entry       
+2/-1     
GAPS.md
Marks ENG-F27 as partly done with camera cutscenes             
+1/-1     
Configuration changes
1 files
run_recipes.py
Registers cutscene.lua recipe for automated testing           
+1/-0     
Additional files
2 files
CMakeLists.txt +2/-1     
CMakeLists.txt +1/-0     

## Title Cutscene cameras from Lua: camera.path, hold, release, shake (F27) ___ ### **User description** <!-- claude-projects-attribution: {"github_login":"Khyretos"} --> _Requested by **Kees** · [project thread](https://claude.ai/code/project/chan_01Ds4PKvSLCNCCKr5TJxrV2A?thread=cmsg_01Ds4PKvSLCNCCKr5TJxrV2ABb2WaYyLkc7eUMNFVK1p2Z)_ Rebased onto main fcee8fab (after #131 foliage and #132 lint fix). Terrain chunks now follow the camera that's shown, so a cutscene's shot gets the right detail. ## What Cutscene cameras for every game, from Lua (GAPS F27, camera part). - `kke::CameraDirector`: a path through shots (position, look-at point, optional field of view, time; Catmull-Rom), or one held shot. It blends in from what was on screen and back out to gameplay, never jumping, even when a new path starts during another or a release comes half way through a blend. Plus a trauma shake on top of any camera. - `Application::setCameraDirector`: runs after every module's update with the camera gameplay made; `camera()` is gameplay's again before the next updates, so the player's camera code keeps running underneath. - Lua: `camera.path(shots, {loop, hold, blend, blendOut, seconds})`, `camera.hold`, `camera.release`, `camera.directing`, `camera.shake(strength, seconds)`, `camera.fov`, and the `CameraDone` hook. `camera.position/target/forward` report the shot on screen. A script's cutscene ends when the script unloads. - `explosion.lua` now shakes in every game (`camera.shake`), new `cutscene.lua` recipe with a picture, cookbook "Cutscenes in any game", SCRIPTING rows, GAPS F27 🟡. ## Tests - 10 new `CameraDirector.*` tests (passes through every shot, smooth, blend in/out without jumps, finishes once, hold, loop, fov, shake fades and caps). Full kke_tests: 1334 passed (23 skipped: valkey and postgres backends) on main fcee8fab. - `cutscene.lua` played in starter_game (`tools/check_game`, 12 s): fly-around, close-up hold, `CameraDone` logged, no errors. Recipe runner shots for cutscene and explosion. - check_std_includes, check_dependencies, check_game_modules, mkdocs --strict: clean. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_019rkS6wq7fV9fWdiLwxyyc7 ___ ### **PR Type** Enhancement ___ ### **Description** - Adds `CameraDirector` for Lua-driven cutscenes with smooth blending. - Implements `camera.path`, `hold`, `release`, and `shake` functions. - Introduces `CameraDone` hook and terrain updates during shots. ___ ### Diagram Walkthrough ```mermaid flowchart LR A["Lua Script"] -->|"camera.path"| B["CameraDirector"] B -->|"update"| C["Application"] C -->|"setCameraDirector"| D["Game Loop"] D -.->|"shownCamera"| E["Terrain Update"] D -.->|"shownCamera"| F["Collision Check"] ``` <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>8 files</summary><table> <tr> <td><strong>CameraDirector.h</strong><dd><code>Defines CameraDirector class for cutscene camera control</code>&nbsp; </dd></td> <td><a href="https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/133/head/engine/include/kke/CameraDirector.h">+84/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>CameraDirector.cpp</strong><dd><code>Implements Catmull-Rom interpolation and blending logic</code>&nbsp; &nbsp; </dd></td> <td><a href="https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/133/head/engine/src/CameraDirector.cpp">+187/-0</a>&nbsp; </td> </tr> <tr> <td><strong>Application.h</strong><dd><code>Adds setCameraDirector callback and gameplay camera storage</code></dd></td> <td><a href="https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/133/head/engine/include/kke/Application.h">+11/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>Application.cpp</strong><dd><code>Integrates CameraDirector into main loop with blend handling</code></dd></td> <td><a href="https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/133/head/engine/src/Application.cpp">+6/-0</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>ScriptModule.h</strong><dd><code>Declares camera bindings and director state variables</code>&nbsp; &nbsp; &nbsp; &nbsp; </dd></td> <td><a href="https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/133/head/engine/include/kke/modules/ScriptModule.h">+14/-2</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>ScriptCamera.cpp</strong><dd><code>Exposes camera.path, hold, release, shake to Lua scripts</code>&nbsp; </dd></td> <td><a href="https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/133/head/engine/src/modules/ScriptCamera.cpp">+116/-0</a>&nbsp; </td> </tr> <tr> <td><strong>ScriptModule.cpp</strong><dd><code>Calls CameraDone hook and uses shownCamera for terrain</code>&nbsp; &nbsp; &nbsp; </dd></td> <td><a href="https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/133/head/engine/src/modules/ScriptModule.cpp">+10/-10</a>&nbsp; </td> </tr> <tr> <td><strong>explosion.lua</strong><dd><code>Updates to use universal camera.shake instead of view.shake</code></dd></td> <td><a href="https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/133/head/docs/cookbook/recipes/explosion.lua">+2/-2</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>ScriptBindings.cpp</strong><dd><code>Releases director when script unloads to restore gameplay</code></dd></td> <td><a href="https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/133/head/engine/src/modules/ScriptBindings.cpp">+4/-0</a>&nbsp; &nbsp; &nbsp; </td> </tr> </table></details></td></tr><tr><td><strong>Tests</strong></td><td><details><summary>1 files</summary><table> <tr> <td><strong>test_camera_director.cpp</strong><dd><code>Tests path smoothness, blending, holding, looping, shake</code>&nbsp; </dd></td> <td><a href="https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/133/head/tests/test_camera_director.cpp">+174/-0</a>&nbsp; </td> </tr> </table></details></td></tr><tr><td><strong>Documentation</strong></td><td><details><summary>4 files</summary><table> <tr> <td><strong>cutscene.lua</strong><dd><code>Demonstrates fly-around cutscene with camera.path and hold</code></dd></td> <td><a href="https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/133/head/docs/cookbook/recipes/cutscene.lua">+36/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>cameras.md</strong><dd><code>Documents cutscene usage and shows cutscene.lua example</code>&nbsp; &nbsp; </dd></td> <td><a href="https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/133/head/docs/cookbook/cameras.md">+25/-2</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>SCRIPTING.md</strong><dd><code>Adds camera.* API reference and CameraDone hook entry</code>&nbsp; &nbsp; &nbsp; &nbsp; </dd></td> <td><a href="https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/133/head/docs/SCRIPTING.md">+2/-1</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>GAPS.md</strong><dd><code>Marks ENG-F27 as partly done with camera cutscenes</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/133/head/docs/GAPS.md">+1/-1</a>&nbsp; &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>run_recipes.py</strong><dd><code>Registers cutscene.lua recipe for automated testing</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td> <td><a href="https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/133/head/tools/docs_site/run_recipes.py">+1/-0</a>&nbsp; &nbsp; &nbsp; </td> </tr> </table></details></td></tr><tr><td><strong>Additional files</strong></td><td><details><summary>2 files</summary><table> <tr> <td><strong>CMakeLists.txt</strong></td> <td><a href="https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/133/head/engine/CMakeLists.txt">+2/-1</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>CMakeLists.txt</strong></td> <td><a href="https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/133/head/tests/CMakeLists.txt">+1/-0</a>&nbsp; &nbsp; &nbsp; </td> </tr> </table></details></td></tr></tr></tbody></table> </details> ___
Collaborator

PR Reviewer Guide 🔍

📋 Focus areas: 🔴 3 open · ✅ 0 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 Empty Shot Lists

The play method in CameraDirector.cpp (lines 67-80) calls hold with the first shot if the list has exactly one element, but does not validate that the input vector is non-empty before accessing elements. While play checks for empty at the start, the logic flow could be improved to ensure robustness against unexpected empty inputs passed from Lua scripts. Additionally, the sample function (lines 34-54) accesses shots.front() and shots.back() without checking if the vector is empty, which would cause undefined behavior if called with an empty list. This could lead to crashes if a script accidentally passes an empty shot list to camera.path.

CameraDirector::Shot CameraDirector::sample(const std::vector<Shot>& shots, float time) {
    if (shots.empty()) return {};
    if (shots.size() == 1) return shots[0];
    const float t = std::clamp(time, shots.front().time, shots.back().time);
    size_t i = 0;
    while (i + 2 < shots.size() && shots[i + 1].time < t) ++i;
    const Shot& k1 = shots[i];
    const Shot& k2 = shots[i + 1];
    const Shot& k0 = i > 0 ? shots[i - 1] : k1;
    const Shot& k3 = i + 2 < shots.size() ? shots[i + 2] : k2;
    const float u = std::clamp((t - k1.time) / std::max(1e-6f, k2.time - k1.time), 0.0f, 1.0f);
    Shot s;
    s.position = catmullRom(k0.position, k1.position, k2.position, k3.position, u);
    s.target = catmullRom(k0.target, k1.target, k2.target, k3.target, u);
    // Field of view eases between its two shots; 0 (gameplay's) on either
    // side stays 0 so the gameplay value fills it in.
    s.fovDegrees = (k1.fovDegrees > 0.0f && k2.fovDegrees > 0.0f) ? k1.fovDegrees + (k2.fovDegrees - k1.fovDegrees) * smooth(u)
                                                                  : (u < 0.5f ? k1.fovDegrees : k2.fovDegrees);
    s.time = t;
    return s;
}
Potential Division by Zero in Time Calculation

In the camera.path binding (lines 81-84), when shots are not explicitly timed (timed == 0), the code calculates time values using division by n - 1. If a script passes exactly one shot to camera.path, this results in division by zero, causing undefined behavior or a crash. Although the PR description mentions handling single shots via hold, the current implementation does not prevent the division by zero error in the general case. A check should be added to handle the single-shot case before performing the division.

if (timed == 0 && n > 1) {
    if (seconds <= 0.0f) luaL_error(L, "camera.path: seconds must be more than 0");
    for (size_t i = 0; i < n; ++i) shots[i].time = seconds * static_cast<float>(i) / static_cast<float>(n - 1);
}
Race Condition in Camera Director State Management

In Application::run() (lines 850-858), the camera director state is updated after module updates. However, there is a potential race condition if multiple modules attempt to set or modify the camera director simultaneously. The m_cameraDirected flag is set based on the result of m_cameraDirector, but if m_cameraDirector is modified concurrently (e.g., by another module calling setCameraDirector), the state could become inconsistent. Additionally, the m_gameplayCamera is only updated once per frame, which might not reflect the latest gameplay camera state if multiple modules modify it. This could lead to unexpected camera behavior during rapid scene changes.

if (m_cameraDirected) m_camera = m_gameplayCamera; // last frame's cutscene shot was for that frame only
for (Module* m : m_initOrder) {
    safeInvoke(m, "update", [&] { m->update(updateCtx); });
}
m_cameraDirected = false;
if (m_cameraDirector) {
    m_gameplayCamera = m_camera;
    m_cameraDirected = m_cameraDirector(m_camera, effectiveDt);
}
## PR Reviewer Guide 🔍 <!-- pr-agent:review:full --> 📋 **Focus areas:** 🔴 3 open · ✅ 0 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/133/head/engine/src/CameraDirector.cpp#L34-L54'><strong>Missing Error Handling for Empty Shot Lists</strong></a> The `play` method in CameraDirector.cpp (lines 67-80) calls `hold` with the first shot if the list has exactly one element, but does not validate that the input vector is non-empty before accessing elements. While `play` checks for empty at the start, the logic flow could be improved to ensure robustness against unexpected empty inputs passed from Lua scripts. Additionally, the `sample` function (lines 34-54) accesses `shots.front()` and `shots.back()` without checking if the vector is empty, which would cause undefined behavior if called with an empty list. This could lead to crashes if a script accidentally passes an empty shot list to `camera.path`. </summary> ```c++ CameraDirector::Shot CameraDirector::sample(const std::vector<Shot>& shots, float time) { if (shots.empty()) return {}; if (shots.size() == 1) return shots[0]; const float t = std::clamp(time, shots.front().time, shots.back().time); size_t i = 0; while (i + 2 < shots.size() && shots[i + 1].time < t) ++i; const Shot& k1 = shots[i]; const Shot& k2 = shots[i + 1]; const Shot& k0 = i > 0 ? shots[i - 1] : k1; const Shot& k3 = i + 2 < shots.size() ? shots[i + 2] : k2; const float u = std::clamp((t - k1.time) / std::max(1e-6f, k2.time - k1.time), 0.0f, 1.0f); Shot s; s.position = catmullRom(k0.position, k1.position, k2.position, k3.position, u); s.target = catmullRom(k0.target, k1.target, k2.target, k3.target, u); // Field of view eases between its two shots; 0 (gameplay's) on either // side stays 0 so the gameplay value fills it in. s.fovDegrees = (k1.fovDegrees > 0.0f && k2.fovDegrees > 0.0f) ? k1.fovDegrees + (k2.fovDegrees - k1.fovDegrees) * smooth(u) : (u < 0.5f ? k1.fovDegrees : k2.fovDegrees); s.time = t; return s; } ``` </details> <details><summary><a href='https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/133/head/engine/src/modules/ScriptCamera.cpp#L81-L84'><strong>Potential Division by Zero in Time Calculation</strong></a> In the `camera.path` binding (lines 81-84), when shots are not explicitly timed (`timed == 0`), the code calculates time values using division by `n - 1`. If a script passes exactly one shot to `camera.path`, this results in division by zero, causing undefined behavior or a crash. Although the PR description mentions handling single shots via `hold`, the current implementation does not prevent the division by zero error in the general case. A check should be added to handle the single-shot case before performing the division. </summary> ```c++ if (timed == 0 && n > 1) { if (seconds <= 0.0f) luaL_error(L, "camera.path: seconds must be more than 0"); for (size_t i = 0; i < n; ++i) shots[i].time = seconds * static_cast<float>(i) / static_cast<float>(n - 1); } ``` </details> <details><summary><a href='https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/133/head/engine/src/Application.cpp#L850-L858'><strong>Race Condition in Camera Director State Management</strong></a> In Application::run() (lines 850-858), the camera director state is updated after module updates. However, there is a potential race condition if multiple modules attempt to set or modify the camera director simultaneously. The `m_cameraDirected` flag is set based on the result of `m_cameraDirector`, but if `m_cameraDirector` is modified concurrently (e.g., by another module calling `setCameraDirector`), the state could become inconsistent. Additionally, the `m_gameplayCamera` is only updated once per frame, which might not reflect the latest gameplay camera state if multiple modules modify it. This could lead to unexpected camera behavior during rapid scene changes. </summary> ```c++ if (m_cameraDirected) m_camera = m_gameplayCamera; // last frame's cutscene shot was for that frame only for (Module* m : m_initOrder) { safeInvoke(m, "update", [&] { m->update(updateCtx); }); } m_cameraDirected = false; if (m_cameraDirector) { m_gameplayCamera = m_camera; m_cameraDirected = m_cameraDirector(m_camera, effectiveDt); } ``` </details> </td></tr> </table> <!-- pr-agent-review-state:v1 {"findings":[{"body":"**Race Condition in Camera Director State Management**\n\nIn Application::run() (lines 850-858), the camera director state is updated after module updates. However, there is a potential race condition if multiple modules attempt to set or modify the camera director simultaneously. The `m_cameraDirected` flag is set based on the result of `m_cameraDirector`, but if `m_cameraDirector` is modified concurrently (e.g., by another module calling `setCameraDirector`), the state could become inconsistent. Additionally, the `m_gameplayCamera` is only updated once per frame, which might not reflect the latest gameplay camera state if multiple modules modify it. This could lead to unexpected camera behavior during rapid scene changes.","finding_id":"5155c7d7ea03","first_seen":"2026-10-27T11:50:28.592873Z","last_seen":"2026-10-27T11:50:28.592873Z","last_seen_head_sha":"934dc18f9badf8f182e7e0c76d4fb9128a0ed901","line_end":858,"line_start":850,"path":"engine/src/Application.cpp","state":"ACTIVE"},{"body":"**Missing Error Handling for Empty Shot Lists**\n\nThe `play` method in CameraDirector.cpp (lines 67-80) calls `hold` with the first shot if the list has exactly one element, but does not validate that the input vector is non-empty before accessing elements. While `play` checks for empty at the start, the logic flow could be improved to ensure robustness against unexpected empty inputs passed from Lua scripts. Additionally, the `sample` function (lines 34-54) accesses `shots.front()` and `shots.back()` without checking if the vector is empty, which would cause undefined behavior if called with an empty list. This could lead to crashes if a script accidentally passes an empty shot list to `camera.path`.","finding_id":"6e8bce95b0b0","first_seen":"2026-10-27T11:50:28.592873Z","last_seen":"2026-10-27T11:50:28.592873Z","last_seen_head_sha":"934dc18f9badf8f182e7e0c76d4fb9128a0ed901","line_end":54,"line_start":34,"path":"engine/src/CameraDirector.cpp","state":"ACTIVE"},{"body":"**Potential Division by Zero in Time Calculation**\n\nIn the `camera.path` binding (lines 81-84), when shots are not explicitly timed (`timed == 0`), the code calculates time values using division by `n - 1`. If a script passes exactly one shot to `camera.path`, this results in division by zero, causing undefined behavior or a crash. Although the PR description mentions handling single shots via `hold`, the current implementation does not prevent the division by zero error in the general case. A check should be added to handle the single-shot case before performing the division.","finding_id":"9cd6edf017bf","first_seen":"2026-10-27T11:50:28.592873Z","last_seen":"2026-10-27T11:50:28.592873Z","last_seen_head_sha":"934dc18f9badf8f182e7e0c76d4fb9128a0ed901","line_end":84,"line_start":81,"path":"engine/src/modules/ScriptCamera.cpp","state":"ACTIVE"}],"last_run":{"complete":true,"excluded_files":[],"head_sha":"934dc18f9badf8f182e7e0c76d4fb9128a0ed901","kind":"full","run_id":"https://git.kreative-kompas.com/khyretos/kk-engine/commit/934dc18f9badf8f182e7e0c76d4fb9128a0ed901"},"schema_version":1} -->
Collaborator

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
General
Clarify camera API availability

The documentation mentions that view belongs to the cookbook game, but it doesn't
clarify that camera. functions are available in all games. Add a sentence to
explicitly state that camera.
is part of the engine API and available in every
game, while view.* is specific to the cookbook game.

docs/cookbook/cameras.md [94-107]

 ## Cutscenes in any game
 
 `view` belongs to the cookbook game. Every game has the engine's
 `camera.*` for cutscenes: `camera.path` flies through a list of shots
 and blends in from (and back to) whatever camera the game has, so the
 player's own camera code keeps running underneath. `camera.hold` parks
 the camera on one shot until `camera.release`, and the `CameraDone` hook
 says when a path is over. Press **C**:
 
+Note that `camera.*` functions are part of the engine API and available in every game, while `view.*` is specific to the cookbook game.
+
Suggestion importance[1-10]: 6

__

Why: The suggestion correctly identifies a minor documentation gap by clarifying that camera.* is an engine-wide API while view.* is cookbook-specific, improving clarity without changing functionality.

Low
## PR Code Suggestions ✨ <!-- pr-agent:improve:summary --> <!-- 934dc18 --> Explore these optional code suggestions: <table><thead><tr><td><strong>Category</strong></td><td align=left><strong>Suggestion&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </strong></td><td align=center><strong>Impact</strong></td></tr><tbody><tr><td rowspan=1>General</td> <td> <details><summary>Clarify camera API availability</summary> ___ **The documentation mentions that <code>view</code> belongs to the cookbook game, but it doesn't <br>clarify that <code>camera.*</code> functions are available in all games. Add a sentence to <br>explicitly state that <code>camera.*</code> is part of the engine API and available in every <br>game, while <code>view.*</code> is specific to the cookbook game.** [docs/cookbook/cameras.md [94-107]](https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/133/head/docs/cookbook/cameras.md#L94-L107) ```diff ## Cutscenes in any game `view` belongs to the cookbook game. Every game has the engine's `camera.*` for cutscenes: `camera.path` flies through a list of shots and blends in from (and back to) whatever camera the game has, so the player's own camera code keeps running underneath. `camera.hold` parks the camera on one shot until `camera.release`, and the `CameraDone` hook says when a path is over. Press **C**: +Note that `camera.*` functions are part of the engine API and available in every game, while `view.*` is specific to the cookbook game. + ``` <details><summary>Suggestion importance[1-10]: 6</summary> __ Why: The suggestion correctly identifies a minor documentation gap by clarifying that `camera.*` is an engine-wide API while `view.*` is cookbook-specific, improving clarity without changing functionality. </details></details></td><td align=center>Low </td></tr></tr></tbody></table>

🧹 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 254
❌ CPP · cppcheck 9 0 942
❌ 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-27T12:01:59Z	INFO	[vulndb] Need to update DB
2026-10-27T12:01:59Z	INFO	[vulndb] Downloading vulnerability DB...
2026-10-27T12:01:59Z	INFO	[vulndb] Downloading artifact...	repo="mirror.gcr.io/aquasec/trivy-db:2"
12.57 MiB / 129.80 MiB [----->_______________________________________________________] 9.68% ? p/s ?26.17 MiB / 129.80 MiB [------------>_______________________________________________] 20.16% ? p/s ?40.09 MiB / 129.80 MiB [------------------>_________________________________________] 30.89% ? p/s ?54.09 MiB / 129.80 MiB [------------------->___________________________] 41.67% 69.28 MiB p/s ETA 1s68.43 MiB / 129.80 MiB [------------------------>______________________] 52.72% 69.28 MiB p/s ETA 0s82.52 MiB / 129.80 MiB [----------------------------->_________________] 63.57% 69.28 MiB p/s ETA 0s96.82 MiB / 129.80 MiB [----------------------------------->___________] 74.59% 69.39 MiB p/s ETA 0s110.35 MiB / 129.80 MiB [--------------------------------------->______] 85.02% 69.39 MiB p/s ETA 0s124.09 MiB / 129.80 MiB [------------------------------------------->__] 95.60% 69.39 MiB p/s ETA 0s129.80 MiB / 129.80 MiB [-------------------------------------------->] 100.00% 68.47 MiB p/s ETA 0s129.80 MiB / 129.80 MiB [-------------------------------------------->] 100.00% 68.47 MiB p/s ETA 0s129.80 MiB / 129.80 MiB [-------------------------------------------->] 100.00% 68.47 MiB p/s ETA 0s129.80 MiB / 129.80 MiB [-------------------------------------------->] 100.00% 64.05 MiB p/s ETA 0s129.80 MiB / 129.80 MiB [-------------------------------------------->] 100.00% 64.05 MiB p/s ETA 0s129.80 MiB / 129.80 MiB [-------------------------------------------->] 100.00% 64.05 MiB p/s ETA 0s129.80 MiB / 129.80 MiB [-------------------------------------------->] 100.00% 59.92 MiB p/s ETA 0s129.80 MiB / 129.80 MiB [-------------------------------------------->] 100.00% 59.92 MiB p/s ETA 0s129.80 MiB / 129.80 MiB [-------------------------------------------->] 100.00% 59.92 MiB p/s ETA 0s129.80 MiB / 129.80 MiB [-------------------------------------------->] 100.00% 56.05 MiB p/s ETA 0s129.80 MiB / 129.80 MiB [-------------------------------------------->] 100.00% 56.05 MiB p/s ETA 0s129.80 MiB / 129.80 MiB [-------------------------------------------->] 100.00% 56.05 MiB p/s ETA 0s129.80 MiB / 129.80 MiB [-------------------------------------------->] 100.00% 52.44 MiB p/s ETA 0s129.80 MiB / 129.80 MiB [-----------------------------------------------] 100.00% 30.57 MiB p/s 4.4s2026-10-27T12:02:04Z	INFO	[vulndb] Artifact successfully downloaded	repo="mirror.gcr.io/aquasec/trivy-db:2"
2026-10-27T12:02:04Z	INFO	[vuln] Vulnerability scanning is enabled
2026-10-27T12:02:04Z	INFO	[misconfig] Misconfiguration scanning is enabled
2026-10-27T12:02:04Z	INFO	[checks-client] Need to update the checks bundle
2026-10-27T12:02:04Z	INFO	[checks-client] Downloading the checks bundle...
234.57 KiB / 234.57 KiB [---------------------------------------------------------] 100.00% ? p/s 0s2026-10-27T12:02:05Z	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-27T12:02:06Z	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-27T12:02:06Z	INFO	Number of language-specific files	num=0
2026-10-27T12:02:06Z	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 | 254 | | ❌ | CPP · cppcheck | 9 | 0 | 942 | | ❌ | 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-27T12:01:59Z INFO [vulndb] Need to update DB 2026-10-27T12:01:59Z INFO [vulndb] Downloading vulnerability DB... 2026-10-27T12:01:59Z INFO [vulndb] Downloading artifact... repo="mirror.gcr.io/aquasec/trivy-db:2" 12.57 MiB / 129.80 MiB [----->_______________________________________________________] 9.68% ? p/s ?26.17 MiB / 129.80 MiB [------------>_______________________________________________] 20.16% ? p/s ?40.09 MiB / 129.80 MiB [------------------>_________________________________________] 30.89% ? p/s ?54.09 MiB / 129.80 MiB [------------------->___________________________] 41.67% 69.28 MiB p/s ETA 1s68.43 MiB / 129.80 MiB [------------------------>______________________] 52.72% 69.28 MiB p/s ETA 0s82.52 MiB / 129.80 MiB [----------------------------->_________________] 63.57% 69.28 MiB p/s ETA 0s96.82 MiB / 129.80 MiB [----------------------------------->___________] 74.59% 69.39 MiB p/s ETA 0s110.35 MiB / 129.80 MiB [--------------------------------------->______] 85.02% 69.39 MiB p/s ETA 0s124.09 MiB / 129.80 MiB [------------------------------------------->__] 95.60% 69.39 MiB p/s ETA 0s129.80 MiB / 129.80 MiB [-------------------------------------------->] 100.00% 68.47 MiB p/s ETA 0s129.80 MiB / 129.80 MiB [-------------------------------------------->] 100.00% 68.47 MiB p/s ETA 0s129.80 MiB / 129.80 MiB [-------------------------------------------->] 100.00% 68.47 MiB p/s ETA 0s129.80 MiB / 129.80 MiB [-------------------------------------------->] 100.00% 64.05 MiB p/s ETA 0s129.80 MiB / 129.80 MiB [-------------------------------------------->] 100.00% 64.05 MiB p/s ETA 0s129.80 MiB / 129.80 MiB [-------------------------------------------->] 100.00% 64.05 MiB p/s ETA 0s129.80 MiB / 129.80 MiB [-------------------------------------------->] 100.00% 59.92 MiB p/s ETA 0s129.80 MiB / 129.80 MiB [-------------------------------------------->] 100.00% 59.92 MiB p/s ETA 0s129.80 MiB / 129.80 MiB [-------------------------------------------->] 100.00% 59.92 MiB p/s ETA 0s129.80 MiB / 129.80 MiB [-------------------------------------------->] 100.00% 56.05 MiB p/s ETA 0s129.80 MiB / 129.80 MiB [-------------------------------------------->] 100.00% 56.05 MiB p/s ETA 0s129.80 MiB / 129.80 MiB [-------------------------------------------->] 100.00% 56.05 MiB p/s ETA 0s129.80 MiB / 129.80 MiB [-------------------------------------------->] 100.00% 52.44 MiB p/s ETA 0s129.80 MiB / 129.80 MiB [-----------------------------------------------] 100.00% 30.57 MiB p/s 4.4s2026-10-27T12:02:04Z INFO [vulndb] Artifact successfully downloaded repo="mirror.gcr.io/aquasec/trivy-db:2" 2026-10-27T12:02:04Z INFO [vuln] Vulnerability scanning is enabled 2026-10-27T12:02:04Z INFO [misconfig] Misconfiguration scanning is enabled 2026-10-27T12:02:04Z INFO [checks-client] Need to update the checks bundle 2026-10-27T12:02:04Z INFO [checks-client] Downloading the checks bundle... 234.57 KiB / 234.57 KiB [---------------------------------------------------------] 100.00% ? p/s 0s2026-10-27T12:02:05Z 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-27T12:02:06Z 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-27T12:02:06Z INFO Number of language-specific files num=0 2026-10-27T12:02:06Z 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>
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!133
No description provided.