Character creator screen and demo (MakeHuman) #116

Merged
kompas-review merged 1 commit from khyretos/claude/character-creator into main 2026-10-25 14:44:48 +01:00 AGit
Owner

Builds on the human pack PR (branch claude/human-pack): merge that first; this one then shows only its own commit.

What changes

  • kke::CharacterCreatorModule: a character creator screen any game can add (RmlUi, assets/ui/character_creator/). Tabs of about 300 sliders (body type, shape, torso, arms and legs, head and neck, eyes, nose, mouth and jaw, ears, skin), mirrored sides, undo/redo, a believable randomise, Body/Face/Profile/Back/turntable views, save/load (.kkhuman JSON), copy/paste share code. While a slider moves only vertices move (setDeformedVertices); bakeSkinned() gives the game a skinned, animatable body.
  • kke/HumanLook.h: the look that travels in multiplayer. Only slider values and colours, as a share code (kh1. + base64url: one bit per slider plus a byte per moved one; about 90 to 250 letters), tied to the pack's slider list. quantised() rounds the local player like the code so every machine bakes the same vertices. Also reads MakeHuman .mhm saves.
  • Human: feet on the floor (y = 0), macro-shape cache, setAll.
  • games/character_creator: the demo; added to the headless smoke tests (both CI files) plus a self-test (KKE_CREATOR_SELFTEST=1: random person → share code → back, must be identical and on the floor). The self-test found two codes for one look; fixed, with a unit test.

How I verified it

  • ./build/bin/kke_tests: 1257 passed
  • New files compile with -Wall -Wextra -O2 -Werror
  • character_creator under Xvfb + lavapipe with VK_LAYER_KHRONOS_validation and sync validation: no errors; self-test passed on repeated runs
  • Screenshots of every tab and view checked (body, face, profile, random people)

Performance

Dragging a slider re-shapes ~19k vertices on the CPU and uploads them; no new buffers. In a game the body is one skinned model like any other.

Docs

  • docs/HUMANS.md (creator screen, multiplayer), games/character_creator/README.md with screenshots, ROADMAP, README demo table, docs/demos/index.md, mkdocs, DEPENDENCIES row for the screen files

UI change: holds for Kees's look-OK on the screenshots before merging.

🤖 Generated with Claude Code

https://claude.ai/code/session_014ehJmtmmZLukS7FKi1aYb6

Builds on the human pack PR (branch claude/human-pack): merge that first; this one then shows only its own commit. ## What changes - `kke::CharacterCreatorModule`: a character creator screen any game can add (RmlUi, `assets/ui/character_creator/`). Tabs of about 300 sliders (body type, shape, torso, arms and legs, head and neck, eyes, nose, mouth and jaw, ears, skin), mirrored sides, undo/redo, a believable randomise, Body/Face/Profile/Back/turntable views, save/load (`.kkhuman` JSON), copy/paste share code. While a slider moves only vertices move (`setDeformedVertices`); `bakeSkinned()` gives the game a skinned, animatable body. - `kke/HumanLook.h`: the look that travels in multiplayer. Only slider values and colours, as a share code (`kh1.` + base64url: one bit per slider plus a byte per moved one; about 90 to 250 letters), tied to the pack's slider list. `quantised()` rounds the local player like the code so every machine bakes the same vertices. Also reads MakeHuman `.mhm` saves. - `Human`: feet on the floor (y = 0), macro-shape cache, `setAll`. - `games/character_creator`: the demo; added to the headless smoke tests (both CI files) plus a self-test (`KKE_CREATOR_SELFTEST=1`: random person → share code → back, must be identical and on the floor). The self-test found two codes for one look; fixed, with a unit test. ## How I verified it - [x] `./build/bin/kke_tests`: 1257 passed - [x] New files compile with `-Wall -Wextra -O2 -Werror` - [x] `character_creator` under Xvfb + lavapipe with `VK_LAYER_KHRONOS_validation` and sync validation: no errors; self-test passed on repeated runs - [x] Screenshots of every tab and view checked (body, face, profile, random people) ## Performance Dragging a slider re-shapes ~19k vertices on the CPU and uploads them; no new buffers. In a game the body is one skinned model like any other. ## Docs - [x] docs/HUMANS.md (creator screen, multiplayer), games/character_creator/README.md with screenshots, ROADMAP, README demo table, docs/demos/index.md, mkdocs, DEPENDENCIES row for the screen files UI change: holds for Kees's look-OK on the screenshots before merging. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_014ehJmtmmZLukS7FKi1aYb6
tools/makehuman/cook_human.py cooks MakeHuman's CC0 data (base mesh,
1,280 morph targets, 291 modifiers, default rig and weights) into
assets/humans/makehuman.kkhpack: exact to the .target files' 0.1 mm
steps, sparse and zlib-packed, 127 MB of text to 9.8 MB. Only the data
files are read; none of MakeHuman's AGPL code is used.

kke::HumanPack reads it; kke::Human sets sliders by MakeHuman's own
names, blends the macro targets (gender, age, muscle, weight, height,
proportions, ancestry, breast), and bakes a skinned ModelData whose
skeleton is fitted to the shaped body; reshape() moves a bake in place
while a slider is dragged.

Tests: 8 in tests/test_human.cpp (incl. the rest pose reproducing the
shape exactly and reshape matching a fresh bake), 7 for the cooker,
run in CI. docs/HUMANS.md, ROADMAP, DEPENDENCIES updated.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014ehJmtmmZLukS7FKi1aYb6
Human test: one copy of the target weights, not two
All checks were successful
review / pr-agent PR-Agent reviewed this commit
CI / dependencies (pull_request) Successful in 5s
CI / std-includes (pull_request) Successful in 11s
Benchmarks / benchmarks (pull_request) Successful in 2m50s
CI / shipping-build (pull_request) Successful in 13m43s
Docs site / site (pull_request) Successful in 32s
lint / megalinter (pull_request) Successful in 3m2s
Windows (native) / build (pull_request) Successful in 23m35s
CI / build-test-and-coverage (pull_request) Successful in 38m52s
CI / android (pull_request) Successful in 33m50s
Windows (native) / test (pull_request) Successful in 6m21s
CI / release-build (pull_request) Successful in 46m8s
Windows (native) / publish (pull_request) Has been skipped
kk / all-checks All 13 checks passed
4e34257c6a
begin() and end() came from two temporary vectors; the full suite
crashed on it, a filtered run happened not to.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014ehJmtmmZLukS7FKi1aYb6
Character creator: a screen any game can add, and its demo
All checks were successful
review / pr-agent PR-Agent reviewed this commit
CI / dependencies (pull_request) Successful in 8s
CI / std-includes (pull_request) Successful in 11s
Benchmarks / benchmarks (pull_request) Successful in 2m52s
CI / shipping-build (pull_request) Successful in 12m42s
Docs site / site (pull_request) Successful in 47s
CI / android (pull_request) Successful in 12m31s
lint / megalinter (pull_request) Successful in 3m3s
CI / build-test-and-coverage (pull_request) Successful in 39m20s
CI / release-build (pull_request) Successful in 43m44s
Windows (native) / build (pull_request) Successful in 55m44s
Windows (native) / test (pull_request) Successful in 6m27s
Windows (native) / publish (pull_request) Has been skipped
kk / all-checks All 13 checks passed
8894eb17ff
kke::CharacterCreatorModule is the screen: tabs of MakeHuman's sliders
(body type, shape, torso, arms and legs, head and neck, eyes, nose,
mouth and jaw, ears, skin), mirrored sides, undo and redo, a believable
randomise, camera views, save and load, and a share code. While a slider
moves only vertices move; bakeSkinned() gives the game a skinned body.

kke/HumanLook.h is the look that travels: slider values and colours as
JSON saves (and MakeHuman's .mhm), and as a share code of one bit per
slider plus a byte per moved one (about 90 to 250 letters), tied to the
pack's slider list. quantised() rounds the player's own body like the
code, so every machine bakes the same vertices.

Human now stands on the floor (feet at y = 0), keeps the macro shape
between changes, and gains setAll for whole looks.

games/character_creator is the demo. CI runs it under validation and a
self-test (a random person through a share code and back), which found
two codes for one look; fixed, with a unit test.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014ehJmtmmZLukS7FKi1aYb6
kompas-review scheduled this pull request to auto merge when all checks succeed 2026-10-25 04:06:06 +01:00
Collaborator

Title

Character creator screen and demo (MakeHuman)


User description

Builds on the human pack PR (branch claude/human-pack): merge that first; this one then shows only its own commit.

What changes

  • kke::CharacterCreatorModule: a character creator screen any game can add (RmlUi, assets/ui/character_creator/). Tabs of about 300 sliders (body type, shape, torso, arms and legs, head and neck, eyes, nose, mouth and jaw, ears, skin), mirrored sides, undo/redo, a believable randomise, Body/Face/Profile/Back/turntable views, save/load (.kkhuman JSON), copy/paste share code. While a slider moves only vertices move (setDeformedVertices); bakeSkinned() gives the game a skinned, animatable body.
  • kke/HumanLook.h: the look that travels in multiplayer. Only slider values and colours, as a share code (kh1. + base64url: one bit per slider plus a byte per moved one; about 90 to 250 letters), tied to the pack's slider list. quantised() rounds the local player like the code so every machine bakes the same vertices. Also reads MakeHuman .mhm saves.
  • Human: feet on the floor (y = 0), macro-shape cache, setAll.
  • games/character_creator: the demo; added to the headless smoke tests (both CI files) plus a self-test (KKE_CREATOR_SELFTEST=1: random person → share code → back, must be identical and on the floor). The self-test found two codes for one look; fixed, with a unit test.

How I verified it

  • ./build/bin/kke_tests: 1257 passed
  • New files compile with -Wall -Wextra -O2 -Werror
  • character_creator under Xvfb + lavapipe with VK_LAYER_KHRONOS_validation and sync validation: no errors; self-test passed on repeated runs
  • Screenshots of every tab and view checked (body, face, profile, random people)

Performance

Dragging a slider re-shapes ~19k vertices on the CPU and uploads them; no new buffers. In a game the body is one skinned model like any other.

Docs

  • docs/HUMANS.md (creator screen, multiplayer), games/character_creator/README.md with screenshots, ROADMAP, README demo table, docs/demos/index.md, mkdocs, DEPENDENCIES row for the screen files

UI change: holds for Kees's look-OK on the screenshots before merging.

🤖 Generated with Claude Code

https://claude.ai/code/session_014ehJmtmmZLukS7FKi1aYb6


PR Type

Enhancement, Tests, Documentation


Description

  • Adds character creator screen with sliders and views

  • Implements Human class for skinned models and packs

  • Introduces share codes and MakeHuman pack support

  • Includes demo app, unit tests and CI validation


Diagram Walkthrough

flowchart LR
  A["Character Creator Module"] -->|"UI & Logic"| B["Human Class"]
  A -->|"Save/Load"| C["HumanLook"]
  A -->|"Packs"| D["HumanPack"]
  B -->|"Skinned Model"| E["Human Demo"]
  C -->|"Share Code"| F["Multiplayer"]
  D -->|"MakeHuman .mhm"| G["Cooker Tool"]
  H["Unit Tests"] -.->|"Validate"| B
  I["CI Smoke Test"] -.->|"Run"| E

File Walkthrough

Relevant files
Enhancement
12 files
CharacterCreatorModule.cpp
Implements character creator screen with sliders, views, save/load and
share codes
+619/-0 
Human.cpp
Adds feet-on-floor positioning, macro-shape cache and setAll for whole
looks
+433/-0 
HumanPack.cpp
Parses MakeHuman CC0 human pack file format with positions, targets,
bones and modifiers
+299/-0 
HumanLook.cpp
Implements JSON saves, MakeHuman .mhm reads and short share codes for
character looks
+220/-0 
main.cpp
Creates demo application showing the character creator module
standalone
+40/-0   
HumanPack.h
Defines HumanPack data structures for positions, quads, targets, bones
and modifiers
+109/-0 
cook_human.py
Cooks MakeHuman CC0 data into a .kkhpack binary format     
+400/-0 
CharacterCreatorModule.h
Adds CharacterCreatorModule with sliders, views and share codes
+134/-0 
Human.h
Implements Human class to blend morph targets into skinned models
+93/-0   
HumanLook.h
Defines HumanLook struct and functions for multiplayer share codes
+58/-0   
creator.rcss
Stylesheet for the character creator UI with night theme 
+71/-0   
creator.rml
RML layout for the character creator screen with tabs and sliders
+60/-0   
Tests
4 files
test_human.cpp
Adds unit tests for human pack loading, two-sided sliders, ancestry
sums and share codes
+278/-0 
ci.yml
Adds character_creator to CI smoke test loop under validation layers
+1/-1     
test_cook_human.py
Unit tests for the human pack cooker and reader                   
+182/-0 
ci.yml
Adds CI job to run cooker tests and character creator self-test
+13/-1   
Configuration changes
8 files
new_game.cmake
Registers character_creator as a reserved game name in the new game
tool
+1/-1     
DEPENDENCIES.md
Registers MakeHuman assets and creator screen in dependencies list
+2/-0     
CMakeLists.txt
Adds kke_use_character_creator function and character_creator demo
+20/-0   
mkdocs.yml
Adds Humans and Character Creator pages to navigation       
+2/-0     
credits.json
Credits file listing MakeHuman authors and CC0 licence     
+16/-0   
CMakeLists.txt
Adds Human and CharacterCreatorModule source files to engine build
+4/-0     
CMakeLists.txt
CMakeLists for character_creator demo linking engine and UI
+6/-0     
CMakeLists.txt
Adds test_human.cpp unit tests to kke_tests executable     
+1/-0     
Documentation
6 files
HUMANS.md
Documents the MakeHuman integration, pack format and creator screen
+153/-0 
ROADMAP.md
Updates roadmap to mark Humans feature as in progress       
+1/-0     
README.md
README for the character creator demo with controls and build notes
+73/-0   
index.md
Links character_creator demo in demos index page                 
+1/-0     
README.md
Lists character_creator demo in main README table               
+1/-0     
README.md
Links HUMANS.md in docs README sidebar                                     
+1/-0     
Additional files
4 files
makehuman.kkhpack [link]   
game.json +9/-0     
character-creator-face.webp [link]   
character-creator.webp [link]   

## Title Character creator screen and demo (MakeHuman) ___ ### **User description** Builds on the human pack PR (branch claude/human-pack): merge that first; this one then shows only its own commit. ## What changes - `kke::CharacterCreatorModule`: a character creator screen any game can add (RmlUi, `assets/ui/character_creator/`). Tabs of about 300 sliders (body type, shape, torso, arms and legs, head and neck, eyes, nose, mouth and jaw, ears, skin), mirrored sides, undo/redo, a believable randomise, Body/Face/Profile/Back/turntable views, save/load (`.kkhuman` JSON), copy/paste share code. While a slider moves only vertices move (`setDeformedVertices`); `bakeSkinned()` gives the game a skinned, animatable body. - `kke/HumanLook.h`: the look that travels in multiplayer. Only slider values and colours, as a share code (`kh1.` + base64url: one bit per slider plus a byte per moved one; about 90 to 250 letters), tied to the pack's slider list. `quantised()` rounds the local player like the code so every machine bakes the same vertices. Also reads MakeHuman `.mhm` saves. - `Human`: feet on the floor (y = 0), macro-shape cache, `setAll`. - `games/character_creator`: the demo; added to the headless smoke tests (both CI files) plus a self-test (`KKE_CREATOR_SELFTEST=1`: random person → share code → back, must be identical and on the floor). The self-test found two codes for one look; fixed, with a unit test. ## How I verified it - [x] `./build/bin/kke_tests`: 1257 passed - [x] New files compile with `-Wall -Wextra -O2 -Werror` - [x] `character_creator` under Xvfb + lavapipe with `VK_LAYER_KHRONOS_validation` and sync validation: no errors; self-test passed on repeated runs - [x] Screenshots of every tab and view checked (body, face, profile, random people) ## Performance Dragging a slider re-shapes ~19k vertices on the CPU and uploads them; no new buffers. In a game the body is one skinned model like any other. ## Docs - [x] docs/HUMANS.md (creator screen, multiplayer), games/character_creator/README.md with screenshots, ROADMAP, README demo table, docs/demos/index.md, mkdocs, DEPENDENCIES row for the screen files UI change: holds for Kees's look-OK on the screenshots before merging. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_014ehJmtmmZLukS7FKi1aYb6 ___ ### **PR Type** Enhancement, Tests, Documentation ___ ### **Description** - Adds character creator screen with sliders and views - Implements Human class for skinned models and packs - Introduces share codes and MakeHuman pack support - Includes demo app, unit tests and CI validation ___ ### Diagram Walkthrough ```mermaid flowchart LR A["Character Creator Module"] -->|"UI & Logic"| B["Human Class"] A -->|"Save/Load"| C["HumanLook"] A -->|"Packs"| D["HumanPack"] B -->|"Skinned Model"| E["Human Demo"] C -->|"Share Code"| F["Multiplayer"] D -->|"MakeHuman .mhm"| G["Cooker Tool"] H["Unit Tests"] -.->|"Validate"| B I["CI Smoke Test"] -.->|"Run"| E ``` <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>12 files</summary><table> <tr> <td><strong>CharacterCreatorModule.cpp</strong><dd><code>Implements character creator screen with sliders, views, save/load and </code><br><code>share codes</code></dd></td> <td><a href="https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/116/head/engine/src/modules/CharacterCreatorModule.cpp">+619/-0</a>&nbsp; </td> </tr> <tr> <td><strong>Human.cpp</strong><dd><code>Adds feet-on-floor positioning, macro-shape cache and setAll for whole </code><br><code>looks</code></dd></td> <td><a href="https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/116/head/engine/src/Human.cpp">+433/-0</a>&nbsp; </td> </tr> <tr> <td><strong>HumanPack.cpp</strong><dd><code>Parses MakeHuman CC0 human pack file format with positions, targets, </code><br><code>bones and modifiers</code></dd></td> <td><a href="https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/116/head/engine/src/HumanPack.cpp">+299/-0</a>&nbsp; </td> </tr> <tr> <td><strong>HumanLook.cpp</strong><dd><code>Implements JSON saves, MakeHuman .mhm reads and short share codes for </code><br><code>character looks</code></dd></td> <td><a href="https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/116/head/engine/src/HumanLook.cpp">+220/-0</a>&nbsp; </td> </tr> <tr> <td><strong>main.cpp</strong><dd><code>Creates demo application showing the character creator module </code><br><code>standalone</code></dd></td> <td><a href="https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/116/head/games/character_creator/main.cpp">+40/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>HumanPack.h</strong><dd><code>Defines HumanPack data structures for positions, quads, targets, bones </code><br><code>and modifiers</code></dd></td> <td><a href="https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/116/head/engine/include/kke/HumanPack.h">+109/-0</a>&nbsp; </td> </tr> <tr> <td><strong>cook_human.py</strong><dd><code>Cooks MakeHuman CC0 data into a .kkhpack binary format</code>&nbsp; &nbsp; &nbsp; </dd></td> <td><a href="https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/116/head/tools/makehuman/cook_human.py">+400/-0</a>&nbsp; </td> </tr> <tr> <td><strong>CharacterCreatorModule.h</strong><dd><code>Adds CharacterCreatorModule with sliders, views and share codes</code></dd></td> <td><a href="https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/116/head/engine/include/kke/modules/CharacterCreatorModule.h">+134/-0</a>&nbsp; </td> </tr> <tr> <td><strong>Human.h</strong><dd><code>Implements Human class to blend morph targets into skinned models</code></dd></td> <td><a href="https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/116/head/engine/include/kke/Human.h">+93/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>HumanLook.h</strong><dd><code>Defines HumanLook struct and functions for multiplayer share codes</code></dd></td> <td><a href="https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/116/head/engine/include/kke/HumanLook.h">+58/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>creator.rcss</strong><dd><code>Stylesheet for the character creator UI with night theme</code>&nbsp; </dd></td> <td><a href="https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/116/head/assets/ui/character_creator/creator.rcss">+71/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>creator.rml</strong><dd><code>RML layout for the character creator screen with tabs and sliders</code></dd></td> <td><a href="https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/116/head/assets/ui/character_creator/creator.rml">+60/-0</a>&nbsp; &nbsp; </td> </tr> </table></details></td></tr><tr><td><strong>Tests</strong></td><td><details><summary>4 files</summary><table> <tr> <td><strong>test_human.cpp</strong><dd><code>Adds unit tests for human pack loading, two-sided sliders, ancestry </code><br><code>sums and share codes</code></dd></td> <td><a href="https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/116/head/tests/test_human.cpp">+278/-0</a>&nbsp; </td> </tr> <tr> <td><strong>ci.yml</strong><dd><code>Adds character_creator to CI smoke test loop under validation layers</code></dd></td> <td><a href="https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/116/head/.github/workflows/ci.yml">+1/-1</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>test_cook_human.py</strong><dd><code>Unit tests for the human pack cooker and reader</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/116/head/tools/makehuman/test_cook_human.py">+182/-0</a>&nbsp; </td> </tr> <tr> <td><strong>ci.yml</strong><dd><code>Adds CI job to run cooker tests and character creator self-test</code></dd></td> <td><a href="https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/116/head/.forgejo/workflows/ci.yml">+13/-1</a>&nbsp; &nbsp; </td> </tr> </table></details></td></tr><tr><td><strong>Configuration changes</strong></td><td><details><summary>8 files</summary><table> <tr> <td><strong>new_game.cmake</strong><dd><code>Registers character_creator as a reserved game name in the new game </code><br><code>tool</code></dd></td> <td><a href="https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/116/head/tools/new_game.cmake">+1/-1</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>DEPENDENCIES.md</strong><dd><code>Registers MakeHuman assets and creator screen in dependencies list</code></dd></td> <td><a href="https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/116/head/docs/DEPENDENCIES.md">+2/-0</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>CMakeLists.txt</strong><dd><code>Adds kke_use_character_creator function and character_creator demo</code></dd></td> <td><a href="https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/116/head/CMakeLists.txt">+20/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>mkdocs.yml</strong><dd><code>Adds Humans and Character Creator pages to navigation</code>&nbsp; &nbsp; &nbsp; &nbsp; </dd></td> <td><a href="https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/116/head/mkdocs.yml">+2/-0</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>credits.json</strong><dd><code>Credits file listing MakeHuman authors and CC0 licence</code>&nbsp; &nbsp; &nbsp; </dd></td> <td><a href="https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/116/head/assets/humans/credits.json">+16/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>CMakeLists.txt</strong><dd><code>Adds Human and CharacterCreatorModule source files to engine build</code></dd></td> <td><a href="https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/116/head/engine/CMakeLists.txt">+4/-0</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>CMakeLists.txt</strong><dd><code>CMakeLists for character_creator demo linking engine and UI</code></dd></td> <td><a href="https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/116/head/games/character_creator/CMakeLists.txt">+6/-0</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>CMakeLists.txt</strong><dd><code>Adds test_human.cpp unit tests to kke_tests executable</code>&nbsp; &nbsp; &nbsp; </dd></td> <td><a href="https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/116/head/tests/CMakeLists.txt">+1/-0</a>&nbsp; &nbsp; &nbsp; </td> </tr> </table></details></td></tr><tr><td><strong>Documentation</strong></td><td><details><summary>6 files</summary><table> <tr> <td><strong>HUMANS.md</strong><dd><code>Documents the MakeHuman integration, pack format and creator screen</code></dd></td> <td><a href="https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/116/head/docs/HUMANS.md">+153/-0</a>&nbsp; </td> </tr> <tr> <td><strong>ROADMAP.md</strong><dd><code>Updates roadmap to mark Humans feature as in progress</code>&nbsp; &nbsp; &nbsp; &nbsp; </dd></td> <td><a href="https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/116/head/ROADMAP.md">+1/-0</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>README.md</strong><dd><code>README for the character creator demo with controls and build notes</code></dd></td> <td><a href="https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/116/head/games/character_creator/README.md">+73/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>index.md</strong><dd><code>Links character_creator demo in demos index page</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td> <td><a href="https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/116/head/docs/demos/index.md">+1/-0</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>README.md</strong><dd><code>Lists character_creator demo in main README table</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/116/head/README.md">+1/-0</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>README.md</strong><dd><code>Links HUMANS.md in docs README sidebar</code>&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/116/head/docs/README.md">+1/-0</a>&nbsp; &nbsp; &nbsp; </td> </tr> </table></details></td></tr><tr><td><strong>Additional files</strong></td><td><details><summary>4 files</summary><table> <tr> <td><strong>makehuman.kkhpack</strong></td> <td><a href="https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/116/head/assets/humans/makehuman.kkhpack">[link]</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>game.json</strong></td> <td><a href="https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/116/head/games/character_creator/game.json">+9/-0</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>character-creator-face.webp</strong></td> <td><a href="https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/116/head/website/static/media/character-creator-face.webp">[link]</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>character-creator.webp</strong></td> <td><a href="https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/116/head/website/static/media/character-creator.webp">[link]</a>&nbsp; &nbsp; </td> </tr> </table></details></td></tr></tr></tbody></table> </details> ___
Collaborator

PR Reviewer Guide 🔍

📋 Focus areas: 🔴 6 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

Missing Error Handling for Save Path

In the save() function, if SDL_GetPrefPath returns nullptr, the code returns early without setting a status message. This leaves the user unaware that the save operation failed due to missing preferences directory. The load() function has similar behavior but at least logs "No saved character yet". The save() function should log an error or set a status message when the path is empty.

if (path.empty()) return setStatus("This device has no save folder");
std::ofstream out(path, std::ios::binary);
Potential Null Pointer Dereference

In the update() function, line 476 checks if a slider name matches ancestry macros and then iterates over m_sliders to update their values. However, if m_sliders is empty (e.g., if no modifiers match the current tab), this loop will not execute, which is fine. But more critically, on line 486, setView(m_view) is called unconditionally after checking m_shapeDirty. If m_view was never initialized or cleared unexpectedly, it might cause issues, though it's initialized in init(). A more significant concern is that m_orbit is used on line 486 without a null check before calling setView, although setView itself checks for !m_orbit on line 520. The real issue is that m_view is accessed directly without ensuring it's valid in all contexts, but since it's initialized in init(), this is less critical. The main issue is the lack of bounds checking or validation when accessing m_sliders in the ancestry update block.

if (s.name == "macrodetails/African" || s.name == "macrodetails/Asian" || s.name == "macrodetails/Caucasian") {
    for (SliderView& o : m_sliders) o.value = o.shown = m_human->get(o.name);
    m_handle.DirtyVariable("sliders");
}
Division by Zero Risk

In the jointPosition() function, line 270 divides by it->second.size(). If a joint has no associated vertices (empty vector), this will cause a division by zero, resulting in undefined behavior (NaN). While this might be a rare edge case, it's better to add a check to ensure the size is greater than zero before performing the division.

return sum / static_cast<float>(it->second.size());
Potential Integer Overflow in Base64 Encoding

In the base64url function, the variable n is constructed by shifting three bytes into a uint32_t. While this fits within 32 bits, the subsequent bitwise operations and shifts are performed on this value. If the input vector contains more than 3 elements, the logic correctly processes them in chunks of 3. However, if the input size is not a multiple of 3, the padding logic for the output string is incomplete. The function does not append '=' padding characters as per standard Base64 encoding when the input length modulo 3 is 1 or 2. This could lead to incorrect decoding in strict parsers that expect padding, although the custom unbase64url function handles missing padding gracefully by checking bit counts. A more robust implementation should explicitly handle padding to ensure compatibility with standard decoders.

std::string base64url(const std::vector<uint8_t>& in) {
    std::string out;
    for (size_t i = 0; i < in.size(); i += 3) {
        const uint32_t n = (uint32_t(in[i]) << 16) | (i + 1 < in.size() ? uint32_t(in[i + 1]) << 8 : 0) |
                           (i + 2 < in.size() ? in[i + 2] : 0);
        out += kB64[(n >> 18) & 63];
        out += kB64[(n >> 12) & 63];
        if (i + 1 < in.size()) out += kB64[(n >> 6) & 63];
        if (i + 2 < in.size()) out += kB64[n & 63];
    }
    return out;
}
Missing Error Handling for Non-UTF8 Strings

In the read_obj function, the file is opened with encoding="utf-8". If the OBJ file contains non-UTF8 characters (e.g., binary data or special symbols not in UTF-8), the open call will raise a UnicodeDecodeError. While this stops execution, it does not provide a clear error message indicating whether the issue is with the file format or encoding. It would be better to catch this exception and provide a more descriptive error message, or validate the file encoding before attempting to read it.

with open(path, encoding="utf-8") as f:
    for raw in f:
        line = strip_comment(raw)
        if not line:
            continue
        parts = line.split()
        kind = parts[0]
        if kind == "v":
            positions.append(tuple(float(x) for x in parts[1:4]))
        elif kind == "vt":
            uvs.append((float(parts[1]), float(parts[2])))
        elif kind == "g":
            groups.append(parts[1] if len(parts) > 1 else "")
            group = len(groups) - 1
        elif kind == "f":
            corners = parts[1:]
            if len(corners) == 3:
                corners.append(corners[2])  # a triangle as a quad with its last corner twice
            if len(corners) != 4:
                raise ValueError(f"{path}: face with {len(corners)} corners")
            vs, ts = [], []
            for c in corners:
                bits = c.split("/")
                vs.append(int(bits[0]) - 1)
                ts.append(int(bits[1]) - 1 if len(bits) > 1 and bits[1] else 0)
            if group < 0:
                groups.append("")
                group = 0
            faces.append((vs, ts, group))
return positions, uvs, faces, groups
Hardcoded Step Size Assumption

The quantise function assumes that all target offsets are exact multiples of STEP = 0.001. While this is documented in the code comments, there is no validation in the cook function to ensure that all .target files adhere to this precision before quantization occurs. If a .target file contains an offset that is not a multiple of 0.001 (within floating-point tolerance), the quantise function will raise a ValueError. This error handling is present but could be improved by providing a clearer warning or allowing for a fallback mechanism if the precision requirement is violated, rather than failing the entire cook process.

def quantise(value, name):
    q = round(value / STEP)
    if not -32768 <= q <= 32767 or abs(q * STEP - value) > 1e-6:
        raise ValueError(f"{name}: offset {value} is not a whole number of {STEP} steps within 16 bits")
    return q

ℹ️ Chunked review: the diff exceeded the model token budget, so it was reviewed in 3 chunks and the per-chunk results were merged.

## PR Reviewer Guide 🔍 <!-- pr-agent:review:full --> 📋 **Focus areas:** 🔴 6 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/116/head/engine/src/modules/CharacterCreatorModule.cpp#L592-L593'><strong>Missing Error Handling for Save Path</strong></a> In the `save()` function, if `SDL_GetPrefPath` returns `nullptr`, the code returns early without setting a status message. This leaves the user unaware that the save operation failed due to missing preferences directory. The `load()` function has similar behavior but at least logs "No saved character yet". The `save()` function should log an error or set a status message when the path is empty. </summary> ```c++ if (path.empty()) return setStatus("This device has no save folder"); std::ofstream out(path, std::ios::binary); ``` </details> <details><summary><a href='https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/116/head/engine/src/modules/CharacterCreatorModule.cpp#L476-L479'><strong>Potential Null Pointer Dereference</strong></a> In the `update()` function, line 476 checks if a slider name matches ancestry macros and then iterates over `m_sliders` to update their values. However, if `m_sliders` is empty (e.g., if no modifiers match the current tab), this loop will not execute, which is fine. But more critically, on line 486, `setView(m_view)` is called unconditionally after checking `m_shapeDirty`. If `m_view` was never initialized or cleared unexpectedly, it might cause issues, though it's initialized in `init()`. A more significant concern is that `m_orbit` is used on line 486 without a null check before calling `setView`, although `setView` itself checks for `!m_orbit` on line 520. The real issue is that `m_view` is accessed directly without ensuring it's valid in all contexts, but since it's initialized in `init()`, this is less critical. The main issue is the lack of bounds checking or validation when accessing `m_sliders` in the ancestry update block. </summary> ```c++ if (s.name == "macrodetails/African" || s.name == "macrodetails/Asian" || s.name == "macrodetails/Caucasian") { for (SliderView& o : m_sliders) o.value = o.shown = m_human->get(o.name); m_handle.DirtyVariable("sliders"); } ``` </details> <details><summary><a href='https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/116/head/engine/src/Human.cpp#L270-L270'><strong>Division by Zero Risk</strong></a> In the `jointPosition()` function, line 270 divides by `it->second.size()`. If a joint has no associated vertices (empty vector), this will cause a division by zero, resulting in undefined behavior (NaN). While this might be a rare edge case, it's better to add a check to ensure the size is greater than zero before performing the division. </summary> ```c++ return sum / static_cast<float>(it->second.size()); ``` </details> <details><summary><a href='https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/116/head/engine/src/HumanLook.cpp#L42-L53'><strong>Potential Integer Overflow in Base64 Encoding</strong></a> In the `base64url` function, the variable `n` is constructed by shifting three bytes into a `uint32_t`. While this fits within 32 bits, the subsequent bitwise operations and shifts are performed on this value. If the input vector contains more than 3 elements, the logic correctly processes them in chunks of 3. However, if the input size is not a multiple of 3, the padding logic for the output string is incomplete. The function does not append '=' padding characters as per standard Base64 encoding when the input length modulo 3 is 1 or 2. This could lead to incorrect decoding in strict parsers that expect padding, although the custom `unbase64url` function handles missing padding gracefully by checking bit counts. A more robust implementation should explicitly handle padding to ensure compatibility with standard decoders. </summary> ```c++ std::string base64url(const std::vector<uint8_t>& in) { std::string out; for (size_t i = 0; i < in.size(); i += 3) { const uint32_t n = (uint32_t(in[i]) << 16) | (i + 1 < in.size() ? uint32_t(in[i + 1]) << 8 : 0) | (i + 2 < in.size() ? in[i + 2] : 0); out += kB64[(n >> 18) & 63]; out += kB64[(n >> 12) & 63]; if (i + 1 < in.size()) out += kB64[(n >> 6) & 63]; if (i + 2 < in.size()) out += kB64[n & 63]; } return out; } ``` </details> <details><summary><a href='https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/116/head/tools/makehuman/cook_human.py#L66-L95'><strong>Missing Error Handling for Non-UTF8 Strings</strong></a> In the `read_obj` function, the file is opened with `encoding="utf-8"`. If the OBJ file contains non-UTF8 characters (e.g., binary data or special symbols not in UTF-8), the `open` call will raise a `UnicodeDecodeError`. While this stops execution, it does not provide a clear error message indicating whether the issue is with the file format or encoding. It would be better to catch this exception and provide a more descriptive error message, or validate the file encoding before attempting to read it. </summary> ```python with open(path, encoding="utf-8") as f: for raw in f: line = strip_comment(raw) if not line: continue parts = line.split() kind = parts[0] if kind == "v": positions.append(tuple(float(x) for x in parts[1:4])) elif kind == "vt": uvs.append((float(parts[1]), float(parts[2]))) elif kind == "g": groups.append(parts[1] if len(parts) > 1 else "") group = len(groups) - 1 elif kind == "f": corners = parts[1:] if len(corners) == 3: corners.append(corners[2]) # a triangle as a quad with its last corner twice if len(corners) != 4: raise ValueError(f"{path}: face with {len(corners)} corners") vs, ts = [], [] for c in corners: bits = c.split("/") vs.append(int(bits[0]) - 1) ts.append(int(bits[1]) - 1 if len(bits) > 1 and bits[1] else 0) if group < 0: groups.append("") group = 0 faces.append((vs, ts, group)) return positions, uvs, faces, groups ``` </details> <details><summary><a href='https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/116/head/tools/makehuman/cook_human.py#L133-L137'><strong>Hardcoded Step Size Assumption</strong></a> The `quantise` function assumes that all target offsets are exact multiples of `STEP = 0.001`. While this is documented in the code comments, there is no validation in the `cook` function to ensure that all `.target` files adhere to this precision before quantization occurs. If a `.target` file contains an offset that is not a multiple of 0.001 (within floating-point tolerance), the `quantise` function will raise a `ValueError`. This error handling is present but could be improved by providing a clearer warning or allowing for a fallback mechanism if the precision requirement is violated, rather than failing the entire cook process. </summary> ```python def quantise(value, name): q = round(value / STEP) if not -32768 <= q <= 32767 or abs(q * STEP - value) > 1e-6: raise ValueError(f"{name}: offset {value} is not a whole number of {STEP} steps within 16 bits") return q ``` </details> </td></tr> </table> <hr> ℹ️ **Chunked review:** the diff exceeded the model token budget, so it was reviewed in 3 chunks and the per-chunk results were merged. <!-- pr-agent-review-state:v1 {"findings":[{"body":"**Potential Null Pointer Dereference**\n\nIn the `update()` function, line 476 checks if a slider name matches ancestry macros and then iterates over `m_sliders` to update their values. However, if `m_sliders` is empty (e.g., if no modifiers match the current tab), this loop will not execute, which is fine. But more critically, on line 486, `setView(m_view)` is called unconditionally after checking `m_shapeDirty`. If `m_view` was never initialized or cleared unexpectedly, it might cause issues, though it's initialized in `init()`. A more significant concern is that `m_orbit` is used on line 486 without a null check before calling `setView`, although `setView` itself checks for `!m_orbit` on line 520. The real issue is that `m_view` is accessed directly without ensuring it's valid in all contexts, but since it's initialized in `init()`, this is less critical. The main issue is the lack of bounds checking or validation when accessing `m_sliders` in the ancestry update block.","finding_id":"2977d434ff50","first_seen":"2026-10-25T03:13:46.455419Z","last_seen":"2026-10-25T03:13:46.455419Z","last_seen_head_sha":"8894eb17ff8c75eb4f8ab831230b8674f6117bd6","line_end":479,"line_start":476,"path":"engine/src/modules/CharacterCreatorModule.cpp","state":"ACTIVE"},{"body":"**Missing Error Handling for Save Path**\n\nIn the `save()` function, if `SDL_GetPrefPath` returns `nullptr`, the code returns early without setting a status message. This leaves the user unaware that the save operation failed due to missing preferences directory. The `load()` function has similar behavior but at least logs \"No saved character yet\". The `save()` function should log an error or set a status message when the path is empty.","finding_id":"517c93fdb85e","first_seen":"2026-10-25T03:13:46.455419Z","last_seen":"2026-10-25T03:13:46.455419Z","last_seen_head_sha":"8894eb17ff8c75eb4f8ab831230b8674f6117bd6","line_end":593,"line_start":592,"path":"engine/src/modules/CharacterCreatorModule.cpp","state":"ACTIVE"},{"body":"**Missing Error Handling for Non-UTF8 Strings**\n\nIn the `read_obj` function, the file is opened with `encoding=\"utf-8\"`. If the OBJ file contains non-UTF8 characters (e.g., binary data or special symbols not in UTF-8), the `open` call will raise a `UnicodeDecodeError`. While this stops execution, it does not provide a clear error message indicating whether the issue is with the file format or encoding. It would be better to catch this exception and provide a more descriptive error message, or validate the file encoding before attempting to read it.","finding_id":"544ce6011706","first_seen":"2026-10-25T03:13:46.455419Z","last_seen":"2026-10-25T03:13:46.455419Z","last_seen_head_sha":"8894eb17ff8c75eb4f8ab831230b8674f6117bd6","line_end":95,"line_start":66,"path":"tools/makehuman/cook_human.py","state":"ACTIVE"},{"body":"**Hardcoded Step Size Assumption**\n\nThe `quantise` function assumes that all target offsets are exact multiples of `STEP = 0.001`. While this is documented in the code comments, there is no validation in the `cook` function to ensure that all `.target` files adhere to this precision before quantization occurs. If a `.target` file contains an offset that is not a multiple of 0.001 (within floating-point tolerance), the `quantise` function will raise a `ValueError`. This error handling is present but could be improved by providing a clearer warning or allowing for a fallback mechanism if the precision requirement is violated, rather than failing the entire cook process.","finding_id":"58968fa71baf","first_seen":"2026-10-25T03:13:46.455419Z","last_seen":"2026-10-25T03:13:46.455419Z","last_seen_head_sha":"8894eb17ff8c75eb4f8ab831230b8674f6117bd6","line_end":137,"line_start":133,"path":"tools/makehuman/cook_human.py","state":"ACTIVE"},{"body":"**Division by Zero Risk**\n\nIn the `jointPosition()` function, line 270 divides by `it->second.size()`. If a joint has no associated vertices (empty vector), this will cause a division by zero, resulting in undefined behavior (NaN). While this might be a rare edge case, it's better to add a check to ensure the size is greater than zero before performing the division.","finding_id":"79f2a591ff27","first_seen":"2026-10-25T03:13:46.455419Z","last_seen":"2026-10-25T03:13:46.455419Z","last_seen_head_sha":"8894eb17ff8c75eb4f8ab831230b8674f6117bd6","line_end":270,"line_start":270,"path":"engine/src/Human.cpp","state":"ACTIVE"},{"body":"**Potential Integer Overflow in Base64 Encoding**\n\nIn the `base64url` function, the variable `n` is constructed by shifting three bytes into a `uint32_t`. While this fits within 32 bits, the subsequent bitwise operations and shifts are performed on this value. If the input vector contains more than 3 elements, the logic correctly processes them in chunks of 3. However, if the input size is not a multiple of 3, the padding logic for the output string is incomplete. The function does not append '=' padding characters as per standard Base64 encoding when the input length modulo 3 is 1 or 2. This could lead to incorrect decoding in strict parsers that expect padding, although the custom `unbase64url` function handles missing padding gracefully by checking bit counts. A more robust implementation should explicitly handle padding to ensure compatibility with standard decoders.","finding_id":"f2113960e48d","first_seen":"2026-10-25T03:13:46.455419Z","last_seen":"2026-10-25T03:13:46.455419Z","last_seen_head_sha":"8894eb17ff8c75eb4f8ab831230b8674f6117bd6","line_end":53,"line_start":42,"path":"engine/src/HumanLook.cpp","state":"ACTIVE"}],"last_run":{"complete":false,"excluded_files":[],"head_sha":"8894eb17ff8c75eb4f8ab831230b8674f6117bd6","kind":"partial","run_id":"https://git.kreative-kompas.com/khyretos/kk-engine/commit/8894eb17ff8c75eb4f8ab831230b8674f6117bd6"},"schema_version":1} -->
Collaborator

PR Code Suggestions ✨

No code suggestions found for the PR.

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

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

Nothing here blocks the merge (report only).

Check Errors Warnings Files
❌ MARKDOWN · markdownlint 24 0 252
❌ CPP · cppcheck 9 0 905
❌ 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/HUMANS.md:143 error MD032/blanks-around-lists Lists should be surrounded by blank lines [Context: "1) with MakeHuman's slider nam..."]
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/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-25T09:00:29Z	INFO	[vulndb] Need to update DB
2026-10-25T09:00:29Z	INFO	[vulndb] Downloading vulnerability DB...
2026-10-25T09:00:29Z	INFO	[vulndb] Downloading artifact...	repo="mirror.gcr.io/aquasec/trivy-db:2"
1.57 MiB / 129.70 MiB [>_____________________________________________________________] 1.21% ? p/s ?7.26 MiB / 129.70 MiB [--->__________________________________________________________] 5.60% ? p/s ?14.81 MiB / 129.70 MiB [------>_____________________________________________________] 11.42% ? p/s ?23.60 MiB / 129.70 MiB [-------->______________________________________] 18.19% 36.68 MiB p/s ETA 2s28.16 MiB / 129.70 MiB [---------->____________________________________] 21.71% 36.68 MiB p/s ETA 2s36.88 MiB / 129.70 MiB [------------->_________________________________] 28.43% 36.68 MiB p/s ETA 2s40.97 MiB / 129.70 MiB [-------------->________________________________] 31.59% 36.18 MiB p/s ETA 2s47.25 MiB / 129.70 MiB [----------------->_____________________________] 36.43% 36.18 MiB p/s ETA 2s58.68 MiB / 129.70 MiB [--------------------->_________________________] 45.24% 36.18 MiB p/s ETA 1s66.25 MiB / 129.70 MiB [------------------------>______________________] 51.08% 36.56 MiB p/s ETA 1s76.69 MiB / 129.70 MiB [--------------------------->___________________] 59.13% 36.56 MiB p/s ETA 1s85.41 MiB / 129.70 MiB [------------------------------>________________] 65.85% 36.56 MiB p/s ETA 1s96.56 MiB / 129.70 MiB [---------------------------------->____________] 74.45% 37.47 MiB p/s ETA 0s105.38 MiB / 129.70 MiB [------------------------------------->________] 81.25% 37.47 MiB p/s ETA 0s115.57 MiB / 129.70 MiB [---------------------------------------->_____] 89.11% 37.47 MiB p/s ETA 0s125.91 MiB / 129.70 MiB [-------------------------------------------->_] 97.08% 38.17 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 38.17 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 38.17 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 36.12 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 36.12 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 36.12 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 33.79 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 33.79 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 33.79 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 31.61 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 31.61 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 31.61 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 29.57 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 29.57 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 29.57 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 27.66 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 27.66 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 27.66 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 25.88 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 25.88 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 25.88 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 24.21 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 24.21 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 24.21 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 22.64 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 22.64 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 22.64 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 21.18 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 21.18 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 21.18 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 19.82 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 19.82 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 19.82 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 18.54 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 18.54 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 18.54 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 17.34 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 17.34 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 17.34 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 16.22 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 16.22 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 16.22 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 15.18 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 15.18 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 15.18 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 14.20 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 14.20 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 14.20 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 13.28 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 13.28 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 13.28 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 12.43 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 12.43 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 12.43 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 11.62 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 11.62 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 11.62 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 10.87 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 10.87 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 10.87 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 10.17 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 10.17 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 10.17 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [--------------------------------------------->] 100.00% 9.52 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [--------------------------------------------->] 100.00% 9.52 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [--------------------------------------------->] 100.00% 9.52 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [--------------------------------------------->] 100.00% 8.90 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [--------------------------------------------->] 100.00% 8.90 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [--------------------------------------------->] 100.00% 8.90 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [--------------------------------------------->] 100.00% 8.33 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [--------------------------------------------->] 100.00% 8.33 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [--------------------------------------------->] 100.00% 8.33 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [--------------------------------------------->] 100.00% 7.79 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [--------------------------------------------->] 100.00% 7.79 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [--------------------------------------------->] 100.00% 7.79 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [--------------------------------------------->] 100.00% 7.29 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [--------------------------------------------->] 100.00% 7.29 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [--------------------------------------------->] 100.00% 7.29 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [--------------------------------------------->] 100.00% 6.82 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [--------------------------------------------->] 100.00% 6.82 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [--------------------------------------------->] 100.00% 6.82 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [--------------------------------------------->] 100.00% 6.38 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [--------------------------------------------->] 100.00% 6.38 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [--------------------------------------------->] 100.00% 6.38 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------------] 100.00% 6.59 MiB p/s 20s2026-10-25T09:00:50Z	INFO	[vulndb] Artifact successfully downloaded	repo="mirror.gcr.io/aquasec/trivy-db:2"
2026-10-25T09:00:50Z	INFO	[vuln] Vulnerability scanning is enabled
2026-10-25T09:00:50Z	INFO	[misconfig] Misconfiguration scanning is enabled
2026-10-25T09:00:50Z	INFO	[checks-client] Need to update the checks bundle
2026-10-25T09:00:50Z	INFO	[checks-client] Downloading the checks bundle...
234.57 KiB / 234.57 KiB [------------------------------------------------------] 100.00% ? p/s 200ms2026-10-25T09:00:53Z	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-25T09:00:54Z	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-25T09:00:54Z	INFO	Number of language-specific files	num=0
2026-10-25T09:00:54Z	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 (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 | 252 | | ❌ | CPP · cppcheck | 9 | 0 | 905 | | ❌ | 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/HUMANS.md:143 error MD032/blanks-around-lists Lists should be surrounded by blank lines [Context: "1) with MakeHuman's slider nam..."] 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/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-25T09:00:29Z INFO [vulndb] Need to update DB 2026-10-25T09:00:29Z INFO [vulndb] Downloading vulnerability DB... 2026-10-25T09:00:29Z INFO [vulndb] Downloading artifact... repo="mirror.gcr.io/aquasec/trivy-db:2" 1.57 MiB / 129.70 MiB [>_____________________________________________________________] 1.21% ? p/s ?7.26 MiB / 129.70 MiB [--->__________________________________________________________] 5.60% ? p/s ?14.81 MiB / 129.70 MiB [------>_____________________________________________________] 11.42% ? p/s ?23.60 MiB / 129.70 MiB [-------->______________________________________] 18.19% 36.68 MiB p/s ETA 2s28.16 MiB / 129.70 MiB [---------->____________________________________] 21.71% 36.68 MiB p/s ETA 2s36.88 MiB / 129.70 MiB [------------->_________________________________] 28.43% 36.68 MiB p/s ETA 2s40.97 MiB / 129.70 MiB [-------------->________________________________] 31.59% 36.18 MiB p/s ETA 2s47.25 MiB / 129.70 MiB [----------------->_____________________________] 36.43% 36.18 MiB p/s ETA 2s58.68 MiB / 129.70 MiB [--------------------->_________________________] 45.24% 36.18 MiB p/s ETA 1s66.25 MiB / 129.70 MiB [------------------------>______________________] 51.08% 36.56 MiB p/s ETA 1s76.69 MiB / 129.70 MiB [--------------------------->___________________] 59.13% 36.56 MiB p/s ETA 1s85.41 MiB / 129.70 MiB [------------------------------>________________] 65.85% 36.56 MiB p/s ETA 1s96.56 MiB / 129.70 MiB [---------------------------------->____________] 74.45% 37.47 MiB p/s ETA 0s105.38 MiB / 129.70 MiB [------------------------------------->________] 81.25% 37.47 MiB p/s ETA 0s115.57 MiB / 129.70 MiB [---------------------------------------->_____] 89.11% 37.47 MiB p/s ETA 0s125.91 MiB / 129.70 MiB [-------------------------------------------->_] 97.08% 38.17 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 38.17 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 38.17 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 36.12 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 36.12 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 36.12 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 33.79 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 33.79 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 33.79 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 31.61 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 31.61 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 31.61 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 29.57 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 29.57 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 29.57 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 27.66 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 27.66 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 27.66 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 25.88 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 25.88 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 25.88 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 24.21 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 24.21 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 24.21 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 22.64 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 22.64 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 22.64 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 21.18 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 21.18 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 21.18 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 19.82 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 19.82 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 19.82 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 18.54 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 18.54 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 18.54 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 17.34 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 17.34 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 17.34 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 16.22 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 16.22 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 16.22 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 15.18 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 15.18 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 15.18 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 14.20 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 14.20 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 14.20 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 13.28 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 13.28 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 13.28 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 12.43 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 12.43 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 12.43 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 11.62 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 11.62 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 11.62 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 10.87 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 10.87 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 10.87 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 10.17 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 10.17 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------->] 100.00% 10.17 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [--------------------------------------------->] 100.00% 9.52 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [--------------------------------------------->] 100.00% 9.52 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [--------------------------------------------->] 100.00% 9.52 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [--------------------------------------------->] 100.00% 8.90 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [--------------------------------------------->] 100.00% 8.90 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [--------------------------------------------->] 100.00% 8.90 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [--------------------------------------------->] 100.00% 8.33 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [--------------------------------------------->] 100.00% 8.33 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [--------------------------------------------->] 100.00% 8.33 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [--------------------------------------------->] 100.00% 7.79 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [--------------------------------------------->] 100.00% 7.79 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [--------------------------------------------->] 100.00% 7.79 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [--------------------------------------------->] 100.00% 7.29 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [--------------------------------------------->] 100.00% 7.29 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [--------------------------------------------->] 100.00% 7.29 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [--------------------------------------------->] 100.00% 6.82 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [--------------------------------------------->] 100.00% 6.82 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [--------------------------------------------->] 100.00% 6.82 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [--------------------------------------------->] 100.00% 6.38 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [--------------------------------------------->] 100.00% 6.38 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [--------------------------------------------->] 100.00% 6.38 MiB p/s ETA 0s129.70 MiB / 129.70 MiB [-------------------------------------------------] 100.00% 6.59 MiB p/s 20s2026-10-25T09:00:50Z INFO [vulndb] Artifact successfully downloaded repo="mirror.gcr.io/aquasec/trivy-db:2" 2026-10-25T09:00:50Z INFO [vuln] Vulnerability scanning is enabled 2026-10-25T09:00:50Z INFO [misconfig] Misconfiguration scanning is enabled 2026-10-25T09:00:50Z INFO [checks-client] Need to update the checks bundle 2026-10-25T09:00:50Z INFO [checks-client] Downloading the checks bundle... 234.57 KiB / 234.57 KiB [------------------------------------------------------] 100.00% ? p/s 200ms2026-10-25T09:00:53Z 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-25T09:00:54Z 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-25T09:00:54Z INFO Number of language-specific files num=0 2026-10-25T09:00:54Z 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 (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!116
No description provided.