Terrain for open levels: heightmaps, sculpt and paint, LOD, collision (F75-F77) #123

Merged
kompas-review merged 2 commits from khyretos/claude/terrain into main 2026-10-25 19:01:20 +01:00 AGit
Owner

Requested by Kees · project thread

What (ENG-F75, F76, F77: terrain for open levels)

  • kke::Terrain (kke/Terrain.h): a square heightmap. It comes from seeded gradient noise (rolling hills, or ridged mountains with ridged), from a greyscale picture (8- or 16-bit), or flat. Brushes: raise/lower, smooth, flatten (a level pad), and paint across four colour layers (grass, rock, dirt, snow by default), with rock showing by itself on steep slopes. heightAt, normalAt, slopeAt.
  • kke::TerrainChunks: 32-cell chunks (bigger on terrains over 513², so there are never more than 16 × 16), each drawn only when its bounds are in the camera's view (and in the sun's shadow view for shadows). Their detail halves with distance (every 1st, 2nd, 4th, 8th height). Skirts on every edge mean neighbours never crack. Sculpted chunks rebuild at once, LOD changes rebuild nearest first, and a new terrain builds whole on its first frame.
  • RigidWorld::Shape::HeightField (Jolt HeightFieldShape): it splits each cell on the same diagonal as Terrain::heightAt, so the ground you see is the ground you stand on. Bodies, characters, rays, AI ground height and nav.build (#121) all see it.
  • Lua terrain.* (kke::ScriptTerrain, in its own file so it stays out of the way of #121's ScriptModule changes): create{size, resolution, pos, height, seed, feature, octaves, roughness, ridged, image, flat, layers, rock, rockSlope}, height, ground, normal, slope, raise, lower, smooth, flatten, paint, layer, info, remove. There is one terrain per game, owned by the script that made it; others can read it. Collision catches up a quarter of a second after sculpting stops.
  • Docs: new docs/TERRAIN.md (with a "Not yet": textured layers, holes, saving and multiplayer sculpting, foliage), SCRIPTING.md table row, docs README, mkdocs nav, GAPS F75/F76/F77 🟡.

Tests

  • New test_terrain.cpp (8 tests): the same seed gives the same land, the range comes out exact, and ridged differs; heightAt follows the collision's two triangles; brushes report the rectangle they changed and refuse NaN or a bad radius; paint weights always add up to 255 and cliffs show rock; mesh triangles face up, match the heights, and the skirts face outward; a greyscale PNG loads (and a missing one says which); chunk LOD by distance and only touched chunks rebuild; the Lua API end to end (ownership, errors, release on unload, clamping, path refusal).
  • RigidWorld.TerrainHeightFieldMatchesTheTerrain: rays land within 3 cm of heightAt (heights are packed), a ball comes to rest on it, and moving or mis-sized height fields are refused.
  • Full kke_tests: 1254 passed, no warnings at -Wall -Wextra. mkdocs build --strict is clean, and check_std_includes/check_dependencies pass.
  • In starter_game, a 240 m terrain at 257² with a flattened start, a painted dirt path and a raised hill: the player stands and walks on it, red balls dropped from 30 m land and roll down, and the screenshot shows grass, the dirt path and rock on the steep slopes.

🤖 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)_ ## What (ENG-F75, F76, F77: terrain for open levels) - `kke::Terrain` (`kke/Terrain.h`): a square heightmap. It comes from seeded gradient noise (rolling hills, or ridged mountains with `ridged`), from a greyscale picture (8- or 16-bit), or flat. Brushes: raise/lower, smooth, flatten (a level pad), and paint across four colour layers (grass, rock, dirt, snow by default), with rock showing by itself on steep slopes. `heightAt`, `normalAt`, `slopeAt`. - `kke::TerrainChunks`: 32-cell chunks (bigger on terrains over 513², so there are never more than 16 × 16), each drawn only when its bounds are in the camera's view (and in the sun's shadow view for shadows). Their detail halves with distance (every 1st, 2nd, 4th, 8th height). Skirts on every edge mean neighbours never crack. Sculpted chunks rebuild at once, LOD changes rebuild nearest first, and a new terrain builds whole on its first frame. - `RigidWorld::Shape::HeightField` (Jolt `HeightFieldShape`): it splits each cell on the same diagonal as `Terrain::heightAt`, so the ground you see is the ground you stand on. Bodies, characters, rays, AI ground height and `nav.build` (#121) all see it. - Lua `terrain.*` (`kke::ScriptTerrain`, in its own file so it stays out of the way of #121's ScriptModule changes): `create{size, resolution, pos, height, seed, feature, octaves, roughness, ridged, image, flat, layers, rock, rockSlope}`, `height`, `ground`, `normal`, `slope`, `raise`, `lower`, `smooth`, `flatten`, `paint`, `layer`, `info`, `remove`. There is one terrain per game, owned by the script that made it; others can read it. Collision catches up a quarter of a second after sculpting stops. - Docs: new docs/TERRAIN.md (with a "Not yet": textured layers, holes, saving and multiplayer sculpting, foliage), SCRIPTING.md table row, docs README, mkdocs nav, GAPS F75/F76/F77 🟡. ## Tests - New `test_terrain.cpp` (8 tests): the same seed gives the same land, the range comes out exact, and ridged differs; `heightAt` follows the collision's two triangles; brushes report the rectangle they changed and refuse NaN or a bad radius; paint weights always add up to 255 and cliffs show rock; mesh triangles face up, match the heights, and the skirts face outward; a greyscale PNG loads (and a missing one says which); chunk LOD by distance and only touched chunks rebuild; the Lua API end to end (ownership, errors, release on unload, clamping, path refusal). - `RigidWorld.TerrainHeightFieldMatchesTheTerrain`: rays land within 3 cm of `heightAt` (heights are packed), a ball comes to rest on it, and moving or mis-sized height fields are refused. - Full `kke_tests`: 1254 passed, no warnings at `-Wall -Wextra`. `mkdocs build --strict` is clean, and `check_std_includes`/`check_dependencies` pass. - In starter_game, a 240 m terrain at 257² with a flattened start, a painted dirt path and a raised hill: the player stands and walks on it, red balls dropped from 30 m land and roll down, and the screenshot shows grass, the dirt path and rock on the steep slopes. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_019rkS6wq7fV9fWdiLwxyyc7
kke::Terrain is a square heightmap: seeded gradient-noise hills or
ridged mountains, or a greyscale picture; raise, lower, smooth, flatten
and paint brushes; four colour layers with rock on steep slopes.
TerrainChunks draws it in 32-cell chunks whose detail halves with
distance, with skirts so neighbours never crack. RigidWorld gains a
HeightField shape that splits cells on the same diagonal as
Terrain::heightAt, so bodies, characters, rays, AI ground height and
nav.build all see the same ground.

Lua terrain.* (ScriptTerrain): create, height, ground, normal, slope,
raise, lower, smooth, flatten, paint, layer, info, remove. One terrain
per game, owned by the script that made it; collision catches up a
quarter second after sculpting stops. docs/TERRAIN.md.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019rkS6wq7fV9fWdiLwxyyc7
Terrain: draw only the chunks in view, bigger chunks on big terrains
All checks were successful
CI / dependencies (pull_request) Successful in 4s
CI / std-includes (pull_request) Successful in 13s
review / pr-agent PR-Agent reviewed this commit
Benchmarks / benchmarks (pull_request) Successful in 4m16s
CI / android (pull_request) Successful in 6m45s
Docs site / site (pull_request) Successful in 12s
lint / megalinter (pull_request) Successful in 1m10s
Windows (native) / build (pull_request) Successful in 9m42s
Windows (native) / test (pull_request) Successful in 6m5s
Windows (native) / publish (pull_request) Has been skipped
CI / shipping-build (pull_request) Successful in 31m33s
CI / release-build (pull_request) Successful in 40m26s
CI / build-test-and-coverage (pull_request) Successful in 1h8m32s
kk / all-checks All 13 checks passed
4a0c291bfc
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-25 17:52:35 +01:00
Collaborator

Title

Terrain for open levels: heightmaps, sculpt and paint, LOD, collision (F75-F77)


User description

Requested by Kees · project thread

What (ENG-F75, F76, F77: terrain for open levels)

  • kke::Terrain (kke/Terrain.h): a square heightmap. It comes from seeded gradient noise (rolling hills, or ridged mountains with ridged), from a greyscale picture (8- or 16-bit), or flat. Brushes: raise/lower, smooth, flatten (a level pad), and paint across four colour layers (grass, rock, dirt, snow by default), with rock showing by itself on steep slopes. heightAt, normalAt, slopeAt.
  • kke::TerrainChunks: 32-cell chunks (bigger on terrains over 513², so there are never more than 16 × 16), each drawn only when its bounds are in the camera's view (and in the sun's shadow view for shadows). Their detail halves with distance (every 1st, 2nd, 4th, 8th height). Skirts on every edge mean neighbours never crack. Sculpted chunks rebuild at once, LOD changes rebuild nearest first, and a new terrain builds whole on its first frame.
  • RigidWorld::Shape::HeightField (Jolt HeightFieldShape): it splits each cell on the same diagonal as Terrain::heightAt, so the ground you see is the ground you stand on. Bodies, characters, rays, AI ground height and nav.build (#121) all see it.
  • Lua terrain.* (kke::ScriptTerrain, in its own file so it stays out of the way of #121's ScriptModule changes): create{size, resolution, pos, height, seed, feature, octaves, roughness, ridged, image, flat, layers, rock, rockSlope}, height, ground, normal, slope, raise, lower, smooth, flatten, paint, layer, info, remove. There is one terrain per game, owned by the script that made it; others can read it. Collision catches up a quarter of a second after sculpting stops.
  • Docs: new docs/TERRAIN.md (with a "Not yet": textured layers, holes, saving and multiplayer sculpting, foliage), SCRIPTING.md table row, docs README, mkdocs nav, GAPS F75/F76/F77 🟡.

Tests

  • New test_terrain.cpp (8 tests): the same seed gives the same land, the range comes out exact, and ridged differs; heightAt follows the collision's two triangles; brushes report the rectangle they changed and refuse NaN or a bad radius; paint weights always add up to 255 and cliffs show rock; mesh triangles face up, match the heights, and the skirts face outward; a greyscale PNG loads (and a missing one says which); chunk LOD by distance and only touched chunks rebuild; the Lua API end to end (ownership, errors, release on unload, clamping, path refusal).
  • RigidWorld.TerrainHeightFieldMatchesTheTerrain: rays land within 3 cm of heightAt (heights are packed), a ball comes to rest on it, and moving or mis-sized height fields are refused.
  • Full kke_tests: 1254 passed, no warnings at -Wall -Wextra. mkdocs build --strict is clean, and check_std_includes/check_dependencies pass.
  • In starter_game, a 240 m terrain at 257² with a flattened start, a painted dirt path and a raised hill: the player stands and walks on it, red balls dropped from 30 m land and roll down, and the screenshot shows grass, the dirt path and rock on the steep slopes.

🤖 Generated with Claude Code

https://claude.ai/code/session_019rkS6wq7fV9fWdiLwxyyc7


PR Type

Enhancement, Tests


Description

  • Adds heightmap terrain with sculpting, painting and LOD chunks

  • Integrates Jolt HeightField collision matching the visual mesh

  • Exposes terrain.* Lua API for generation, brushes and layers

  • Includes comprehensive C++ tests for terrain and physics


Diagram Walkthrough

flowchart LR
  A["ScriptTerrain"] -->|"create"| B["Terrain"]
  A -->|"chunks"| C["TerrainChunks"]
  B -->|"heights"| D["RigidWorld HeightField"]
  C -.->|"draws"| E["Meshes"]
  D -.->|"collides"| F["Bodies/Rays/AI"]

File Walkthrough

Relevant files
Enhancement
5 files
Terrain.cpp
Implements Terrain heightmap generation, sculpting and painting
+405/-0 
ScriptTerrain.cpp
Lua terrain API and chunk rendering logic                               
+334/-0 
ScriptModule.cpp
Initializes ScriptTerrain and calls update/render               
+18/-1   
RigidWorld.h
Adds HeightField shape enum and BodyDesc fields                   
+7/-1     
RigidWorld.cpp
Implements HeightFieldShape creation from terrain data     
+9/-0     
Documentation
8 files
Terrain.h
Declares Terrain struct and TerrainChunks class                   
+154/-0 
ScriptTerrain.h
Declares ScriptTerrain wrapper class                                         
+79/-0   
ScriptModule.h
Adds terrain() accessor to ScriptModule                                   
+7/-0     
TERRAIN.md
Documents terrain creation, brushes and Lua API                   
+96/-0   
README.md
Links new TERRAIN.md in navigation                                             
+1/-0     
GAPS.md
Updates ENG-F75/F76/F77 status to yellow                                 
+3/-3     
mkdocs.yml
Adds Terrain to documentation navigation                                 
+1/-0     
SCRIPTING.md
Documents terrain.* functions in Lua API table                     
+1/-0     
Tests
2 files
test_terrain.cpp
Tests terrain generation, brushes, paint and chunks           
+267/-0 
test_rigid_world.cpp
Tests HeightField collision matches Terrain heights           
+39/-0   
Configuration changes
2 files
CMakeLists.txt
Adds Terrain and ScriptTerrain source files                           
+2/-0     
CMakeLists.txt
Adds test_terrain.cpp to test executable                                 
+1/-0     

## Title Terrain for open levels: heightmaps, sculpt and paint, LOD, collision (F75-F77) ___ ### **User description** <!-- claude-projects-attribution: {"github_login":"Khyretos"} --> _Requested by **Kees** · [project thread](https://claude.ai/code/project/chan_01Ds4PKvSLCNCCKr5TJxrV2A?thread=cmsg_01Ds4PKvSLCNCCKr5TJxrV2ABb2WaYyLkc7eUMNFVK1p2Z)_ ## What (ENG-F75, F76, F77: terrain for open levels) - `kke::Terrain` (`kke/Terrain.h`): a square heightmap. It comes from seeded gradient noise (rolling hills, or ridged mountains with `ridged`), from a greyscale picture (8- or 16-bit), or flat. Brushes: raise/lower, smooth, flatten (a level pad), and paint across four colour layers (grass, rock, dirt, snow by default), with rock showing by itself on steep slopes. `heightAt`, `normalAt`, `slopeAt`. - `kke::TerrainChunks`: 32-cell chunks (bigger on terrains over 513², so there are never more than 16 × 16), each drawn only when its bounds are in the camera's view (and in the sun's shadow view for shadows). Their detail halves with distance (every 1st, 2nd, 4th, 8th height). Skirts on every edge mean neighbours never crack. Sculpted chunks rebuild at once, LOD changes rebuild nearest first, and a new terrain builds whole on its first frame. - `RigidWorld::Shape::HeightField` (Jolt `HeightFieldShape`): it splits each cell on the same diagonal as `Terrain::heightAt`, so the ground you see is the ground you stand on. Bodies, characters, rays, AI ground height and `nav.build` (#121) all see it. - Lua `terrain.*` (`kke::ScriptTerrain`, in its own file so it stays out of the way of #121's ScriptModule changes): `create{size, resolution, pos, height, seed, feature, octaves, roughness, ridged, image, flat, layers, rock, rockSlope}`, `height`, `ground`, `normal`, `slope`, `raise`, `lower`, `smooth`, `flatten`, `paint`, `layer`, `info`, `remove`. There is one terrain per game, owned by the script that made it; others can read it. Collision catches up a quarter of a second after sculpting stops. - Docs: new docs/TERRAIN.md (with a "Not yet": textured layers, holes, saving and multiplayer sculpting, foliage), SCRIPTING.md table row, docs README, mkdocs nav, GAPS F75/F76/F77 🟡. ## Tests - New `test_terrain.cpp` (8 tests): the same seed gives the same land, the range comes out exact, and ridged differs; `heightAt` follows the collision's two triangles; brushes report the rectangle they changed and refuse NaN or a bad radius; paint weights always add up to 255 and cliffs show rock; mesh triangles face up, match the heights, and the skirts face outward; a greyscale PNG loads (and a missing one says which); chunk LOD by distance and only touched chunks rebuild; the Lua API end to end (ownership, errors, release on unload, clamping, path refusal). - `RigidWorld.TerrainHeightFieldMatchesTheTerrain`: rays land within 3 cm of `heightAt` (heights are packed), a ball comes to rest on it, and moving or mis-sized height fields are refused. - Full `kke_tests`: 1254 passed, no warnings at `-Wall -Wextra`. `mkdocs build --strict` is clean, and `check_std_includes`/`check_dependencies` pass. - In starter_game, a 240 m terrain at 257² with a flattened start, a painted dirt path and a raised hill: the player stands and walks on it, red balls dropped from 30 m land and roll down, and the screenshot shows grass, the dirt path and rock on the steep slopes. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_019rkS6wq7fV9fWdiLwxyyc7 ___ ### **PR Type** Enhancement, Tests ___ ### **Description** - Adds heightmap terrain with sculpting, painting and LOD chunks - Integrates Jolt HeightField collision matching the visual mesh - Exposes terrain.* Lua API for generation, brushes and layers - Includes comprehensive C++ tests for terrain and physics ___ ### Diagram Walkthrough ```mermaid flowchart LR A["ScriptTerrain"] -->|"create"| B["Terrain"] A -->|"chunks"| C["TerrainChunks"] B -->|"heights"| D["RigidWorld HeightField"] C -.->|"draws"| E["Meshes"] D -.->|"collides"| F["Bodies/Rays/AI"] ``` <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>5 files</summary><table> <tr> <td><strong>Terrain.cpp</strong><dd><code>Implements Terrain heightmap generation, sculpting and painting</code></dd></td> <td><a href="https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/123/head/engine/src/Terrain.cpp">+405/-0</a>&nbsp; </td> </tr> <tr> <td><strong>ScriptTerrain.cpp</strong><dd><code>Lua terrain API and chunk rendering logic</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/123/head/engine/src/ScriptTerrain.cpp">+334/-0</a>&nbsp; </td> </tr> <tr> <td><strong>ScriptModule.cpp</strong><dd><code>Initializes ScriptTerrain and calls update/render</code>&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/123/head/engine/src/modules/ScriptModule.cpp">+18/-1</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>RigidWorld.h</strong><dd><code>Adds HeightField shape enum and BodyDesc 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/123/head/engine/include/kke/RigidWorld.h">+7/-1</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>RigidWorld.cpp</strong><dd><code>Implements HeightFieldShape creation from terrain data</code>&nbsp; &nbsp; &nbsp; </dd></td> <td><a href="https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/123/head/engine/src/RigidWorld.cpp">+9/-0</a>&nbsp; &nbsp; &nbsp; </td> </tr> </table></details></td></tr><tr><td><strong>Documentation</strong></td><td><details><summary>8 files</summary><table> <tr> <td><strong>Terrain.h</strong><dd><code>Declares Terrain struct and TerrainChunks class</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/123/head/engine/include/kke/Terrain.h">+154/-0</a>&nbsp; </td> </tr> <tr> <td><strong>ScriptTerrain.h</strong><dd><code>Declares ScriptTerrain wrapper class</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &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/123/head/engine/include/kke/ScriptTerrain.h">+79/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>ScriptModule.h</strong><dd><code>Adds terrain() accessor to ScriptModule</code>&nbsp; &nbsp; &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/123/head/engine/include/kke/modules/ScriptModule.h">+7/-0</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>TERRAIN.md</strong><dd><code>Documents terrain creation, brushes and Lua API</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/123/head/docs/TERRAIN.md">+96/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>README.md</strong><dd><code>Links new TERRAIN.md in navigation</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &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/123/head/docs/README.md">+1/-0</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>GAPS.md</strong><dd><code>Updates ENG-F75/F76/F77 status to yellow</code>&nbsp; &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/123/head/docs/GAPS.md">+3/-3</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>mkdocs.yml</strong><dd><code>Adds Terrain to documentation navigation</code>&nbsp; &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/123/head/mkdocs.yml">+1/-0</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>SCRIPTING.md</strong><dd><code>Documents terrain.* functions in Lua API table</code>&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/123/head/docs/SCRIPTING.md">+1/-0</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_terrain.cpp</strong><dd><code>Tests terrain generation, brushes, paint and chunks</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td> <td><a href="https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/123/head/tests/test_terrain.cpp">+267/-0</a>&nbsp; </td> </tr> <tr> <td><strong>test_rigid_world.cpp</strong><dd><code>Tests HeightField collision matches Terrain heights</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td> <td><a href="https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/123/head/tests/test_rigid_world.cpp">+39/-0</a>&nbsp; &nbsp; </td> </tr> </table></details></td></tr><tr><td><strong>Configuration changes</strong></td><td><details><summary>2 files</summary><table> <tr> <td><strong>CMakeLists.txt</strong><dd><code>Adds Terrain and ScriptTerrain source files</code>&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/123/head/engine/CMakeLists.txt">+2/-0</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>CMakeLists.txt</strong><dd><code>Adds test_terrain.cpp to test executable</code>&nbsp; &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/123/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: 4 🔵🔵🔵🔵⚪
🧪 PR contains tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Potential Data Loss in Image Loading

In loadImage, the code reads 16-bit PNGs into a stbi_us buffer and divides by 65535.0f to get a float between 0 and 1. However, if the image contains values greater than 65535 (which shouldn't happen for 16-bit images but could occur with malformed data or future extensions), the division would be incorrect. More critically, if the image is 8-bit, the values are already in the 0-255 range, and dividing by 65535.0f will result in very small numbers (0.0039 to 0.000039), effectively flattening the terrain to near-zero heights unless the height parameter compensates significantly. The comment says "8- or 16-bit", but the logic assumes 16-bit scaling for all cases. This could lead to unexpected terrain heights when loading 8-bit images.

auto at = [&](int a, int b) { return float(px.get()[size_t(b) * size_t(w) + size_t(a)]) / 65535.0f; };
const float top = at(ix, iz) + u * (at(ix + 1, iz) - at(ix, iz));
const float bottom = at(ix, iz + 1) + u * (at(ix + 1, iz + 1) - at(ix, iz + 1));
m_heights[size_t(z) * size_t(m_n) + size_t(x)] = (top + v * (bottom - top)) * height;
Missing Validation for Terrain Creation Parameters

In terrain.create, the size parameter is clamped to [4.0f, 16384.0f], but there is no validation for the resolution parameter beyond clamping it to [kMinSamples, maxSamples]. If a user provides a resolution that results in a spacing smaller than the smallest representable float difference or causes integer overflow in calculations (e.g., m_n * m_n in heights()), it could lead to undefined behavior or memory issues. Additionally, the pos parameter is not validated to ensure it doesn't cause the terrain to be placed outside reasonable world bounds, which could affect collision detection and rendering.

const float size = std::clamp(ScriptVM::fieldNumber(L, 1, "size", 256.0f), 4.0f, 16384.0f);
const int samples = std::clamp(int(ScriptVM::fieldNumber(L, 1, "resolution", 257.0f)), Terrain::kMinSamples, std::min(maxSamples, Terrain::kMaxSamples));
auto t = std::make_unique<Terrain>(size, samples, ScriptVM::fieldVec3(L, 1, "pos", glm::vec3(0.0f)));
Potential Memory Leak in HeightField Shape Creation

In the HeightField case of add, the code creates a JPH::HeightFieldShapeSettings object and then calls fromSettings(s) to create the shape. However, if fromSettings fails or returns an invalid shape, the allocated resources might not be properly cleaned up, leading to a memory leak. The code should check the return value of fromSettings and handle errors appropriately.

case Shape::HeightField: {
    if (d.motion != Motion::Static || d.heightSamples < 4 || d.heights.size() != size_t(d.heightSamples) * d.heightSamples) return kNoBody;
    JPH::HeightFieldShapeSettings s(d.heights.data(), JPH::Vec3::sZero(), JPH::Vec3(d.heightSpacing, 1.0f, d.heightSpacing), d.heightSamples);
    s.mBlockSize = 4;
    s.mBitsPerSample = 8;
    shape = fromSettings(s);
    break;
}
## 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>: 4 🔵🔵🔵🔵⚪</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/123/head/engine/src/Terrain.cpp#L172-L175'><strong>Potential Data Loss in Image Loading</strong></a> In `loadImage`, the code reads 16-bit PNGs into a `stbi_us` buffer and divides by 65535.0f to get a float between 0 and 1. However, if the image contains values greater than 65535 (which shouldn't happen for 16-bit images but could occur with malformed data or future extensions), the division would be incorrect. More critically, if the image is 8-bit, the values are already in the 0-255 range, and dividing by 65535.0f will result in very small numbers (0.0039 to 0.000039), effectively flattening the terrain to near-zero heights unless the `height` parameter compensates significantly. The comment says "8- or 16-bit", but the logic assumes 16-bit scaling for all cases. This could lead to unexpected terrain heights when loading 8-bit images. </summary> ```c++ auto at = [&](int a, int b) { return float(px.get()[size_t(b) * size_t(w) + size_t(a)]) / 65535.0f; }; const float top = at(ix, iz) + u * (at(ix + 1, iz) - at(ix, iz)); const float bottom = at(ix, iz + 1) + u * (at(ix + 1, iz + 1) - at(ix, iz + 1)); m_heights[size_t(z) * size_t(m_n) + size_t(x)] = (top + v * (bottom - top)) * height; ``` </details> <details><summary><a href='https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/123/head/engine/src/ScriptTerrain.cpp#L174-L176'><strong>Missing Validation for Terrain Creation Parameters</strong></a> In `terrain.create`, the `size` parameter is clamped to [4.0f, 16384.0f], but there is no validation for the `resolution` parameter beyond clamping it to [kMinSamples, maxSamples]. If a user provides a resolution that results in a spacing smaller than the smallest representable float difference or causes integer overflow in calculations (e.g., `m_n * m_n` in `heights()`), it could lead to undefined behavior or memory issues. Additionally, the `pos` parameter is not validated to ensure it doesn't cause the terrain to be placed outside reasonable world bounds, which could affect collision detection and rendering. </summary> ```c++ const float size = std::clamp(ScriptVM::fieldNumber(L, 1, "size", 256.0f), 4.0f, 16384.0f); const int samples = std::clamp(int(ScriptVM::fieldNumber(L, 1, "resolution", 257.0f)), Terrain::kMinSamples, std::min(maxSamples, Terrain::kMaxSamples)); auto t = std::make_unique<Terrain>(size, samples, ScriptVM::fieldVec3(L, 1, "pos", glm::vec3(0.0f))); ``` </details> <details><summary><a href='https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/123/head/engine/src/RigidWorld.cpp#L291-L298'><strong>Potential Memory Leak in HeightField Shape Creation</strong></a> In the `HeightField` case of `add`, the code creates a `JPH::HeightFieldShapeSettings` object and then calls `fromSettings(s)` to create the shape. However, if `fromSettings` fails or returns an invalid shape, the allocated resources might not be properly cleaned up, leading to a memory leak. The code should check the return value of `fromSettings` and handle errors appropriately. </summary> ```c++ case Shape::HeightField: { if (d.motion != Motion::Static || d.heightSamples < 4 || d.heights.size() != size_t(d.heightSamples) * d.heightSamples) return kNoBody; JPH::HeightFieldShapeSettings s(d.heights.data(), JPH::Vec3::sZero(), JPH::Vec3(d.heightSpacing, 1.0f, d.heightSpacing), d.heightSamples); s.mBlockSize = 4; s.mBitsPerSample = 8; shape = fromSettings(s); break; } ``` </details> </td></tr> </table> <!-- pr-agent-review-state:v1 {"findings":[{"body":"**Potential Data Loss in Image Loading**\n\nIn `loadImage`, the code reads 16-bit PNGs into a `stbi_us` buffer and divides by 65535.0f to get a float between 0 and 1. However, if the image contains values greater than 65535 (which shouldn't happen for 16-bit images but could occur with malformed data or future extensions), the division would be incorrect. More critically, if the image is 8-bit, the values are already in the 0-255 range, and dividing by 65535.0f will result in very small numbers (0.0039 to 0.000039), effectively flattening the terrain to near-zero heights unless the `height` parameter compensates significantly. The comment says \"8- or 16-bit\", but the logic assumes 16-bit scaling for all cases. This could lead to unexpected terrain heights when loading 8-bit images.","finding_id":"30e6a69b04f6","first_seen":"2026-10-25T16:54:02.119247Z","last_seen":"2026-10-25T16:54:02.119247Z","last_seen_head_sha":"4a0c291bfc73c3db9c21309ea6ed967d5645f26a","line_end":175,"line_start":172,"path":"engine/src/Terrain.cpp","state":"ACTIVE"},{"body":"**Potential Memory Leak in HeightField Shape Creation**\n\nIn the `HeightField` case of `add`, the code creates a `JPH::HeightFieldShapeSettings` object and then calls `fromSettings(s)` to create the shape. However, if `fromSettings` fails or returns an invalid shape, the allocated resources might not be properly cleaned up, leading to a memory leak. The code should check the return value of `fromSettings` and handle errors appropriately.","finding_id":"a3858fcb5ecf","first_seen":"2026-10-25T16:54:02.119247Z","last_seen":"2026-10-25T16:54:02.119247Z","last_seen_head_sha":"4a0c291bfc73c3db9c21309ea6ed967d5645f26a","line_end":298,"line_start":291,"path":"engine/src/RigidWorld.cpp","state":"ACTIVE"},{"body":"**Missing Validation for Terrain Creation Parameters**\n\nIn `terrain.create`, the `size` parameter is clamped to [4.0f, 16384.0f], but there is no validation for the `resolution` parameter beyond clamping it to [kMinSamples, maxSamples]. If a user provides a resolution that results in a spacing smaller than the smallest representable float difference or causes integer overflow in calculations (e.g., `m_n * m_n` in `heights()`), it could lead to undefined behavior or memory issues. Additionally, the `pos` parameter is not validated to ensure it doesn't cause the terrain to be placed outside reasonable world bounds, which could affect collision detection and rendering.","finding_id":"c51f76bd76b6","first_seen":"2026-10-25T16:54:02.119247Z","last_seen":"2026-10-25T16:54:02.119247Z","last_seen_head_sha":"4a0c291bfc73c3db9c21309ea6ed967d5645f26a","line_end":176,"line_start":174,"path":"engine/src/ScriptTerrain.cpp","state":"ACTIVE"}],"last_run":{"complete":true,"excluded_files":[],"head_sha":"4a0c291bfc73c3db9c21309ea6ed967d5645f26a","kind":"full","run_id":"https://git.kreative-kompas.com/khyretos/kk-engine/commit/4a0c291bfc73c3db9c21309ea6ed967d5645f26a"},"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 · ❌ 37 problems in 6 checks · 🧹 3 formatting checks to tidy

Nothing here blocks the merge (report only).

Check Errors Warnings Files
❌ MARKDOWN · markdownlint 23 0 251
❌ CPP · cppcheck 9 0 900
❌ YAML · yamllint 2 0 56
❌ 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/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]
games/racing/README.md:78:32 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/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
✅ [SUCCESS] engine/include/kke/Buffer.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-25T17:03:48Z	INFO	[vulndb] Need to update DB
2026-10-25T17:03:48Z	INFO	[vulndb] Downloading vulnerability DB...
2026-10-25T17:03:48Z	INFO	[vulndb] Downloading artifact...	repo="mirror.gcr.io/aquasec/trivy-db:2"
18.41 MiB / 129.84 MiB [-------->___________________________________________________] 14.18% ? p/s ?31.71 MiB / 129.84 MiB [-------------->_____________________________________________] 24.42% ? p/s ?43.73 MiB / 129.84 MiB [-------------------->_______________________________________] 33.68% ? p/s ?55.08 MiB / 129.84 MiB [------------------->___________________________] 42.42% 61.04 MiB p/s ETA 1s66.33 MiB / 129.84 MiB [------------------------>______________________] 51.09% 61.04 MiB p/s ETA 1s77.66 MiB / 129.84 MiB [---------------------------->__________________] 59.82% 61.04 MiB p/s ETA 0s89.07 MiB / 129.84 MiB [-------------------------------->______________] 68.60% 60.76 MiB p/s ETA 0s100.69 MiB / 129.84 MiB [----------------------------------->__________] 77.55% 60.76 MiB p/s ETA 0s112.14 MiB / 129.84 MiB [--------------------------------------->______] 86.37% 60.76 MiB p/s ETA 0s123.77 MiB / 129.84 MiB [------------------------------------------->__] 95.33% 60.57 MiB p/s ETA 0s129.84 MiB / 129.84 MiB [-------------------------------------------->] 100.00% 60.57 MiB p/s ETA 0s129.84 MiB / 129.84 MiB [-------------------------------------------->] 100.00% 60.57 MiB p/s ETA 0s129.84 MiB / 129.84 MiB [-------------------------------------------->] 100.00% 57.32 MiB p/s ETA 0s129.84 MiB / 129.84 MiB [-------------------------------------------->] 100.00% 57.32 MiB p/s ETA 0s129.84 MiB / 129.84 MiB [-------------------------------------------->] 100.00% 57.32 MiB p/s ETA 0s129.84 MiB / 129.84 MiB [-------------------------------------------->] 100.00% 53.62 MiB p/s ETA 0s129.84 MiB / 129.84 MiB [-------------------------------------------->] 100.00% 53.62 MiB p/s ETA 0s129.84 MiB / 129.84 MiB [-------------------------------------------->] 100.00% 53.62 MiB p/s ETA 0s129.84 MiB / 129.84 MiB [-------------------------------------------->] 100.00% 50.16 MiB p/s ETA 0s129.84 MiB / 129.84 MiB [-------------------------------------------->] 100.00% 50.16 MiB p/s ETA 0s129.84 MiB / 129.84 MiB [-------------------------------------------->] 100.00% 50.16 MiB p/s ETA 0s129.84 MiB / 129.84 MiB [-------------------------------------------->] 100.00% 46.92 MiB p/s ETA 0s129.84 MiB / 129.84 MiB [-------------------------------------------->] 100.00% 46.92 MiB p/s ETA 0s129.84 MiB / 129.84 MiB [-------------------------------------------->] 100.00% 46.92 MiB p/s ETA 0s129.84 MiB / 129.84 MiB [-------------------------------------------->] 100.00% 43.90 MiB p/s ETA 0s129.84 MiB / 129.84 MiB [-------------------------------------------->] 100.00% 43.90 MiB p/s ETA 0s129.84 MiB / 129.84 MiB [-------------------------------------------->] 100.00% 43.90 MiB p/s ETA 0s129.84 MiB / 129.84 MiB [-------------------------------------------->] 100.00% 41.06 MiB p/s ETA 0s129.84 MiB / 129.84 MiB [-------------------------------------------->] 100.00% 41.06 MiB p/s ETA 0s129.84 MiB / 129.84 MiB [-----------------------------------------------] 100.00% 23.11 MiB p/s 5.8s2026-10-25T17:03:54Z	INFO	[vulndb] Artifact successfully downloaded	repo="mirror.gcr.io/aquasec/trivy-db:2"
2026-10-25T17:03:54Z	INFO	[vuln] Vulnerability scanning is enabled
2026-10-25T17:03:54Z	INFO	[misconfig] Misconfiguration scanning is enabled
2026-10-25T17:03:54Z	INFO	[checks-client] Need to update the checks bundle
2026-10-25T17:03:54Z	INFO	[checks-client] Downloading the checks bundle...
234.57 KiB / 234.57 KiB [------------------------------------------------------] 100.00% ? p/s 100ms2026-10-25T17:03:56Z	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-25T17:03:56Z	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-25T17:03:56Z	INFO	Number of language-specific files	num=0
2026-10-25T17:03:56Z	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 (56 files checked), BASH · shfmt (11 files checked), JSON · prettier (145 files checked)

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

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

<!-- kk-lint --> ## 🧹 Lint report · ❌ 37 problems in 6 checks · 🧹 3 formatting checks to tidy Nothing here blocks the merge (report only). | | Check | Errors | Warnings | Files | |---|---|---:|---:|---:| | ❌ | MARKDOWN · markdownlint | 23 | 0 | 251 | | ❌ | CPP · cppcheck | 9 | 0 | 900 | | ❌ | YAML · yamllint | 2 | 0 | 56 | | ❌ | 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/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] games/racing/README.md:78:32 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/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 ✅ [SUCCESS] engine/include/kke/Buffer.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-25T17:03:48Z INFO [vulndb] Need to update DB 2026-10-25T17:03:48Z INFO [vulndb] Downloading vulnerability DB... 2026-10-25T17:03:48Z INFO [vulndb] Downloading artifact... repo="mirror.gcr.io/aquasec/trivy-db:2" 18.41 MiB / 129.84 MiB [-------->___________________________________________________] 14.18% ? p/s ?31.71 MiB / 129.84 MiB [-------------->_____________________________________________] 24.42% ? p/s ?43.73 MiB / 129.84 MiB [-------------------->_______________________________________] 33.68% ? p/s ?55.08 MiB / 129.84 MiB [------------------->___________________________] 42.42% 61.04 MiB p/s ETA 1s66.33 MiB / 129.84 MiB [------------------------>______________________] 51.09% 61.04 MiB p/s ETA 1s77.66 MiB / 129.84 MiB [---------------------------->__________________] 59.82% 61.04 MiB p/s ETA 0s89.07 MiB / 129.84 MiB [-------------------------------->______________] 68.60% 60.76 MiB p/s ETA 0s100.69 MiB / 129.84 MiB [----------------------------------->__________] 77.55% 60.76 MiB p/s ETA 0s112.14 MiB / 129.84 MiB [--------------------------------------->______] 86.37% 60.76 MiB p/s ETA 0s123.77 MiB / 129.84 MiB [------------------------------------------->__] 95.33% 60.57 MiB p/s ETA 0s129.84 MiB / 129.84 MiB [-------------------------------------------->] 100.00% 60.57 MiB p/s ETA 0s129.84 MiB / 129.84 MiB [-------------------------------------------->] 100.00% 60.57 MiB p/s ETA 0s129.84 MiB / 129.84 MiB [-------------------------------------------->] 100.00% 57.32 MiB p/s ETA 0s129.84 MiB / 129.84 MiB [-------------------------------------------->] 100.00% 57.32 MiB p/s ETA 0s129.84 MiB / 129.84 MiB [-------------------------------------------->] 100.00% 57.32 MiB p/s ETA 0s129.84 MiB / 129.84 MiB [-------------------------------------------->] 100.00% 53.62 MiB p/s ETA 0s129.84 MiB / 129.84 MiB [-------------------------------------------->] 100.00% 53.62 MiB p/s ETA 0s129.84 MiB / 129.84 MiB [-------------------------------------------->] 100.00% 53.62 MiB p/s ETA 0s129.84 MiB / 129.84 MiB [-------------------------------------------->] 100.00% 50.16 MiB p/s ETA 0s129.84 MiB / 129.84 MiB [-------------------------------------------->] 100.00% 50.16 MiB p/s ETA 0s129.84 MiB / 129.84 MiB [-------------------------------------------->] 100.00% 50.16 MiB p/s ETA 0s129.84 MiB / 129.84 MiB [-------------------------------------------->] 100.00% 46.92 MiB p/s ETA 0s129.84 MiB / 129.84 MiB [-------------------------------------------->] 100.00% 46.92 MiB p/s ETA 0s129.84 MiB / 129.84 MiB [-------------------------------------------->] 100.00% 46.92 MiB p/s ETA 0s129.84 MiB / 129.84 MiB [-------------------------------------------->] 100.00% 43.90 MiB p/s ETA 0s129.84 MiB / 129.84 MiB [-------------------------------------------->] 100.00% 43.90 MiB p/s ETA 0s129.84 MiB / 129.84 MiB [-------------------------------------------->] 100.00% 43.90 MiB p/s ETA 0s129.84 MiB / 129.84 MiB [-------------------------------------------->] 100.00% 41.06 MiB p/s ETA 0s129.84 MiB / 129.84 MiB [-------------------------------------------->] 100.00% 41.06 MiB p/s ETA 0s129.84 MiB / 129.84 MiB [-----------------------------------------------] 100.00% 23.11 MiB p/s 5.8s2026-10-25T17:03:54Z INFO [vulndb] Artifact successfully downloaded repo="mirror.gcr.io/aquasec/trivy-db:2" 2026-10-25T17:03:54Z INFO [vuln] Vulnerability scanning is enabled 2026-10-25T17:03:54Z INFO [misconfig] Misconfiguration scanning is enabled 2026-10-25T17:03:54Z INFO [checks-client] Need to update the checks bundle 2026-10-25T17:03:54Z INFO [checks-client] Downloading the checks bundle... 234.57 KiB / 234.57 KiB [------------------------------------------------------] 100.00% ? p/s 100ms2026-10-25T17:03:56Z 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-25T17:03:56Z 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-25T17:03:56Z INFO Number of language-specific files num=0 2026-10-25T17:03:56Z 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 (56 files checked), BASH · shfmt (11 files checked), JSON · prettier (145 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!123
No description provided.