WIP: Character creator: clothes, eyes, body types, skin, anime style #134

Draft
khyretos wants to merge 9 commits from khyretos/claude/creator-clothes into main AGit
Owner

Requested by Kees · project thread

Builds on the hair PR #120 (merged). Rebased onto main d2ee5ce6: two conflicts in the creator self-test (CharacterCreatorModule.cpp), resolved by keeping main's per-row hair check and adding this branch's clothes check.

WIP: visual work. Kees's look-OK on the creator is needed before merging; drop the WIP prefix to let auto-merge take it.

What

  • kke/HumanClothes.h: clothes that fit every MakeHuman body, cut from the two helper shapes every morph target already moves (helper-tights, helper-skirt), so no fitting step. Fitted: T-shirt, tank top, crop top, long sleeve, shorts, trousers, socks, shoes (layered 1 to 6 mm out, tops tucked under the waist). Cloth: tennis skirt, skirt, long skirt as Jolt cloth pinned at the waistband, against the body collider the hair uses, drawn with cloth.frag.
  • Character creator: a Clothes tab (one garment per slot: top, bottom, socks, shoes; 12 colours a slot; click again to take off). Fitted garments follow every slider drag; re-cut when a slider rests. Random person dresses the person.
  • HumanLook: clothes in JSON and the share code, so a peer rebuilds the same outfit. kke::dress / bakeSkinned(): the game gets the dressed person as one skinned model on the body's bones.
  • Eyes: kke/HumanEyes.h addIrises (pupil, iris, rim, tinted the chosen eye colour; swatches on Skin & eyes). Scalp under the hair is the skin a shade darker.
  • Body types: Slim, Athletic, Strong, Curvy, Heavy, Average; Random person starts from one half the time. Clothes stay over the skin on Strong bodies (keepOutside).
  • Skin: tools/makehuman/bake_skin.py bakes skin maps from MakeHuman's CC0 skin weights (lips, blush, eyelids, pores, gloss); vitruvian_export.py + bake_vitruvian.py warp CharMorph Vitruvian's CC0 scanned skin onto the body. Brows and lashes as skinned hair strips (addBrows).
  • Anime style: ModelModule::setToon (MToon-like model_toon.frag + inverted-hull outline, pipelines made on first use), Human::setStyle + Human::animeFace(), anime skin albedo, Realistic / Anime buttons; hairLocks draws the guides as tapered outlined locks.
  • Retargeting: canonicalBoneName also maps VRM humanoid bones (VRoid J_Bip_*) and MakeHuman's rig; a root counts as the pelvis only on a skeleton without one.
  • Cornrows fix from #120 kept; the CI self-test also checks every random garment is on the body. KKE_CREATOR_CLOTHES for screenshots.
  • Docs: docs/HUMANS.md "Clothes", "Brows and lashes", "Skin", "Anime style"; games/character_creator/README.md.

Tests

  • kke_tests after the rebase: 1342 passed, 0 failed (new: HumanClothes.*, HumanEyes.*, HumanStyle.AnimeFaceReshapesWithoutChangingTheLook, HumanHair.LocksTaperFromRootToTip, AnimRig.CanonicalNamesPairVrmAndMakeHuman)
  • character_creator self-test under Xvfb: "creator self-test passed (code round-trips, floor ok, hair 'two-strand twists' ok, clothes 'crop top, long skirt, socks, shoes' ok)"; a second run with 'afro' + 'crop top, shorts, …' also passed
  • check_std_includes, check_dependencies (161 listed), check_game_modules (26 games)
  • clang -Wunused-lambda-capture on every changed .cpp: no unused captures
  • Kees's look-OK on the screenshots (front, back, turning; both styles)

🤖 Generated with Claude Code

https://claude.ai/code/session_014ehJmtmmZLukS7FKi1aYb6

<!-- claude-projects-attribution: {"github_login":"Khyretos"} --> _Requested by **Kees** · [project thread](https://claude.ai/code/project/chan_01Ds4PKvSLCNCCKr5TJxrV2A?thread=cmsg_01Ds4PKvSLCNCCKr5TJxrV2ABb2WaYyLkc7eUMNFVK1p2Z)_ Builds on the hair PR #120 (merged). Rebased onto main d2ee5ce6: two conflicts in the creator self-test (`CharacterCreatorModule.cpp`), resolved by keeping main's per-row hair check and adding this branch's clothes check. **WIP: visual work. Kees's look-OK on the creator is needed before merging; drop the WIP prefix to let auto-merge take it.** ## What - `kke/HumanClothes.h`: clothes that fit every MakeHuman body, cut from the two helper shapes every morph target already moves (`helper-tights`, `helper-skirt`), so no fitting step. Fitted: T-shirt, tank top, crop top, long sleeve, shorts, trousers, socks, shoes (layered 1 to 6 mm out, tops tucked under the waist). Cloth: tennis skirt, skirt, long skirt as Jolt cloth pinned at the waistband, against the body collider the hair uses, drawn with `cloth.frag`. - Character creator: a Clothes tab (one garment per slot: top, bottom, socks, shoes; 12 colours a slot; click again to take off). Fitted garments follow every slider drag; re-cut when a slider rests. Random person dresses the person. - `HumanLook`: clothes in JSON and the share code, so a peer rebuilds the same outfit. `kke::dress` / `bakeSkinned()`: the game gets the dressed person as one skinned model on the body's bones. - Eyes: `kke/HumanEyes.h` `addIrises` (pupil, iris, rim, tinted the chosen eye colour; swatches on Skin & eyes). Scalp under the hair is the skin a shade darker. - Body types: Slim, Athletic, Strong, Curvy, Heavy, Average; Random person starts from one half the time. Clothes stay over the skin on Strong bodies (`keepOutside`). - Skin: `tools/makehuman/bake_skin.py` bakes skin maps from MakeHuman's CC0 skin weights (lips, blush, eyelids, pores, gloss); `vitruvian_export.py` + `bake_vitruvian.py` warp CharMorph Vitruvian's CC0 scanned skin onto the body. Brows and lashes as skinned hair strips (`addBrows`). - Anime style: `ModelModule::setToon` (MToon-like `model_toon.frag` + inverted-hull outline, pipelines made on first use), `Human::setStyle` + `Human::animeFace()`, anime skin albedo, Realistic / Anime buttons; `hairLocks` draws the guides as tapered outlined locks. - Retargeting: `canonicalBoneName` also maps VRM humanoid bones (VRoid `J_Bip_*`) and MakeHuman's rig; a `root` counts as the pelvis only on a skeleton without one. - Cornrows fix from #120 kept; the CI self-test also checks every random garment is on the body. `KKE_CREATOR_CLOTHES` for screenshots. - Docs: docs/HUMANS.md "Clothes", "Brows and lashes", "Skin", "Anime style"; games/character_creator/README.md. ## Tests - [x] `kke_tests` after the rebase: 1342 passed, 0 failed (new: `HumanClothes.*`, `HumanEyes.*`, `HumanStyle.AnimeFaceReshapesWithoutChangingTheLook`, `HumanHair.LocksTaperFromRootToTip`, `AnimRig.CanonicalNamesPairVrmAndMakeHuman`) - [x] `character_creator` self-test under Xvfb: "creator self-test passed (code round-trips, floor ok, hair 'two-strand twists' ok, clothes 'crop top, long skirt, socks, shoes' ok)"; a second run with 'afro' + 'crop top, shorts, …' also passed - [x] `check_std_includes`, `check_dependencies` (161 listed), `check_game_modules` (26 games) - [x] clang `-Wunused-lambda-capture` on every changed .cpp: no unused captures - [ ] Kees's look-OK on the screenshots (front, back, turning; both styles) 🤖 Generated with [Claude Code](https://claude.ai/code) https://claude.ai/code/session_014ehJmtmmZLukS7FKi1aYb6
kke/HumanClothes.h cuts garments from two shapes MakeHuman's base mesh
carries and every morph target moves with the skin: the body suit
(helper-tights) and the long skirt (helper-skirt). Tops, shorts,
trousers, socks and shoes are suit vertices picked by bone and height,
drawn as models that move with the body on every slider drag; the
tennis skirt, skirt and long skirt are Jolt cloth pinned at the
waistband, falling against the body collider the hair uses, drawn with
the fabric shader.

The creator gets a Clothes tab (one garment per slot, 12 colours),
Random person dresses the person, and the look carries the clothes in
JSON and the share code (a name and a colour each). The CI self-test
now checks every random garment is on the body.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014ehJmtmmZLukS7FKi1aYb6
kke::dress adds the worn clothes to a skinned bake as meshes on the
body's own bones (garmentMesh keeps the body suit's skin weights), so
a game animates a dressed person as one model. The creator's fitted
garments use the same garmentMesh. Unit test: one skinned mesh per
garment, weights summing to 1, in the garment's colour.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014ehJmtmmZLukS7FKi1aYb6
Kees's look-OK: the eyes were completely white and the scalp under the
hair a different colour.

- kke/HumanEyes.h addIrises: a pupil, the iris and its darker rim on
  each of MakeHuman's white helper eyeballs (32-sided domes, so round on
  the coarse ball), skinned like the eye, tinted the chosen eye colour.
  The Skin & eyes tab now shows the eye colour swatches; bakeSkinned
  includes the irises. Unit test.
- The scalp under the hair is the skin a shade darker, not the hair's
  colour (it showed as a patch with a hard edge).
- Cornrows floated over the crown in a few rows: tied styles now lie on
  the sphere fitted to the scalp, a row starting in front of the
  hairline starts further back instead of being dropped, and the ends
  sit on the skin. The self-test asked for 40 guides, which cornrows
  (a guide a row) never have; it now asks for 6.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014ehJmtmmZLukS7FKi1aYb6
Kees: every person looked alike; no voluptuous woman or strong man.
The Body type tab gets Slim, Athletic, Strong, Curvy, Heavy and
Average: each sets the build sliders (MakeHuman's own body-shape
targets, measurements, muscle and fat) for the current gender and puts
every other build slider back, leaving face, age and ancestry. Random
person starts from a body type half the time, at 50-100% strength.
KKE_CREATOR_GENDER and KKE_CREATOR_PRESET for screenshots.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014ehJmtmmZLukS7FKi1aYb6
- tools/makehuman/bake_skin.py bakes three maps in the body's UV layout
  from MakeHuman's CC0 skin weights and a fixed seed: an albedo multiplier
  (lips, blush, lids, mottling), pores (normal) and gloss (roughness).
  The creator and bakeSkinned() put them on Human_body.
- addBrows() grows brows (260 hairs each) and lashes (60 per lid) as
  thin skinned strips from the face; tinted the hair's root colour.
- skinUnder()/keepOutside() keep every garment over the skin: the tights
  helper lags the strongest muscle targets and a Strong chest showed
  through the shirt.
- The body type buttons were never shown: the string array was not
  registered with RmlUi. Registered, and the sliders moved below them.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014ehJmtmmZLukS7FKi1aYb6
- ModelModule::setToon: model_toon.frag (MToon-like two tones, a soft
  terminator, half-strength cast shadows, flattened sky light, rim light,
  a small hard highlight) and an inverted-hull outline pass
  (model_outline.vert/.frag). Pipelines made on first use; toon
  instances are never instanced.
- Human::setStyle: a layer of slider offsets used only when shaping, not
  saved; Human::animeFace() gives bigger, taller eyes set lower, a small
  nose and mouth and a softer jaw from MakeHuman's own face sliders.
- bake_skin.py also writes skin_albedo_anime.png (flat, a cheek blush,
  only a hint of lips), swapped in for the anime style.
- Creator: Realistic / Anime buttons; KKE_CREATOR_STYLE for screenshots;
  bakeSkinned() follows the style. The look and share code are the same
  in either style.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014ehJmtmmZLukS7FKi1aYb6
hairLocks() draws each simulated guide as a tapered, flattened lock
(lockWidth: 2.6x the root spacing, so they overlap), rebuilt from the
guides every frame and drawn toon with an outline. In the Anime style the
creator uses them instead of the strand renderer, raises the style's hold
to 0.35 and tints the scalp the hair colour.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014ehJmtmmZLukS7FKi1aYb6
canonicalBoneName maps VRM avatars' humanoid bones (VRoid's J_Bip_*)
and MakeHuman's default rig to the UE-style names, so the anime and the
realistic person play the same clips. MakeHuman's hips are "root":
matchBones and PoseRetarget take a root as the pelvis only on a skeleton
without one, so UE rigs keep their root.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014ehJmtmmZLukS7FKi1aYb6
Realistic skin: Vitruvian's scanned detail baked onto MakeHuman's body
All checks were successful
CI / dependencies (pull_request) Successful in 8s
CI / std-includes (pull_request) Successful in 13s
CI / shipping-build (pull_request) Successful in 5m24s
CI / android (pull_request) Successful in 5m17s
Docs site / site (pull_request) Successful in 10s
lint / megalinter (pull_request) Successful in 56s
CI / release-build (pull_request) Successful in 19m6s
Windows (native) / build (pull_request) Successful in 9m25s
Benchmarks / benchmarks (pull_request) Successful in 23m3s
Windows (native) / test (pull_request) Successful in 6m36s
Windows (native) / publish (pull_request) Has been skipped
CI / build-test-and-coverage (pull_request) Successful in 1h36m54s
4e93835c6e
tools/makehuman/vitruvian_export.py writes CharMorph's Vitruvian body
(CC0) to an npz; bake_vitruvian.py warps it onto MakeHuman's base body
(thin-plate spline through 42 shared landmarks, then six surface refits,
1.6 mm mean gap) and reads its 4K UDIM colour, height and roughness for
every texel of MakeHuman's UV layout. The skin maps are now 2048 square:
creases, wrinkles, lip lines and pores in the normal map, the scan's
colour variation in the albedo. The body keeps its 13,000 vertices.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014ehJmtmmZLukS7FKi1aYb6
Collaborator

Title

WIP: Character creator: clothes, eyes, body types, skin, anime style


User description

Requested by Kees · project thread

Builds on the hair PR #120 (merged). Rebased onto main d2ee5ce6: two conflicts in the creator self-test (CharacterCreatorModule.cpp), resolved by keeping main's per-row hair check and adding this branch's clothes check.

WIP: visual work. Kees's look-OK on the creator is needed before merging; drop the WIP prefix to let auto-merge take it.

What

  • kke/HumanClothes.h: clothes that fit every MakeHuman body, cut from the two helper shapes every morph target already moves (helper-tights, helper-skirt), so no fitting step. Fitted: T-shirt, tank top, crop top, long sleeve, shorts, trousers, socks, shoes (layered 1 to 6 mm out, tops tucked under the waist). Cloth: tennis skirt, skirt, long skirt as Jolt cloth pinned at the waistband, against the body collider the hair uses, drawn with cloth.frag.
  • Character creator: a Clothes tab (one garment per slot: top, bottom, socks, shoes; 12 colours a slot; click again to take off). Fitted garments follow every slider drag; re-cut when a slider rests. Random person dresses the person.
  • HumanLook: clothes in JSON and the share code, so a peer rebuilds the same outfit. kke::dress / bakeSkinned(): the game gets the dressed person as one skinned model on the body's bones.
  • Eyes: kke/HumanEyes.h addIrises (pupil, iris, rim, tinted the chosen eye colour; swatches on Skin & eyes). Scalp under the hair is the skin a shade darker.
  • Body types: Slim, Athletic, Strong, Curvy, Heavy, Average; Random person starts from one half the time. Clothes stay over the skin on Strong bodies (keepOutside).
  • Skin: tools/makehuman/bake_skin.py bakes skin maps from MakeHuman's CC0 skin weights (lips, blush, eyelids, pores, gloss); vitruvian_export.py + bake_vitruvian.py warp CharMorph Vitruvian's CC0 scanned skin onto the body. Brows and lashes as skinned hair strips (addBrows).
  • Anime style: ModelModule::setToon (MToon-like model_toon.frag + inverted-hull outline, pipelines made on first use), Human::setStyle + Human::animeFace(), anime skin albedo, Realistic / Anime buttons; hairLocks draws the guides as tapered outlined locks.
  • Retargeting: canonicalBoneName also maps VRM humanoid bones (VRoid J_Bip_*) and MakeHuman's rig; a root counts as the pelvis only on a skeleton without one.
  • Cornrows fix from #120 kept; the CI self-test also checks every random garment is on the body. KKE_CREATOR_CLOTHES for screenshots.
  • Docs: docs/HUMANS.md "Clothes", "Brows and lashes", "Skin", "Anime style"; games/character_creator/README.md.

Tests

  • kke_tests after the rebase: 1342 passed, 0 failed (new: HumanClothes.*, HumanEyes.*, HumanStyle.AnimeFaceReshapesWithoutChangingTheLook, HumanHair.LocksTaperFromRootToTip, AnimRig.CanonicalNamesPairVrmAndMakeHuman)
  • character_creator self-test under Xvfb: "creator self-test passed (code round-trips, floor ok, hair 'two-strand twists' ok, clothes 'crop top, long skirt, socks, shoes' ok)"; a second run with 'afro' + 'crop top, shorts, …' also passed
  • check_std_includes, check_dependencies (161 listed), check_game_modules (26 games)
  • clang -Wunused-lambda-capture on every changed .cpp: no unused captures
  • Kees's look-OK on the screenshots (front, back, turning; both styles)

🤖 Generated with Claude Code

https://claude.ai/code/session_014ehJmtmmZLukS7FKi1aYb6


PR Type

Enhancement, Tests, Documentation


Description

  • Adds fitted clothes and anime style to character creator

  • Implements realistic skin maps with Vitruvian scan detail

  • Enables VRM-to-MakeHuman bone retargeting for animations

  • Includes tests for new eyes, clothes and retargeting logic


Diagram Walkthrough

flowchart LR
  A["Character Creator"] -->|"UI & Logic"| B["Clothes System"]
  A -->|"UI & Logic"| C["Anime Style"]
  A -->|"UI & Logic"| D["Eyes & Brows"]
  B -->|"Fitted Shapes"| E["Garment Meshes"]
  C -->|"Toon Shaders"| F["Outline & Rim Light"]
  D -->|"Procedural Maps"| G["Skin Detail"]
  H["VRM Avatars"] -.->|"Bone Retargeting"| I["MakeHuman Rig"]
  J["Vitruvian Scan"] -.->|"Bake Texture"| K["MakeHuman Skin"]
  L["Tests"] -.->|"Validate"| M["Creator Features"]

File Walkthrough

Relevant files
Enhancement
19 files
CharacterCreatorModule.cpp
Adds clothes, anime style, eyes and hair locks to creator
+571/-7 
HumanEyes.cpp
Implements addIrises and addBrows for realistic eyes         
+249/-0 
HumanClothes.cpp
Implements garmentShape, dress and keepOutside functions 
+225/-0 
AnimRig.cpp
Adds VRM and MakeHuman bone name retargeting                         
+78/-4   
ModelModule.cpp
Adds toon shading and outline rendering pipeline                 
+46/-8   
HumanHair.cpp
Anime hair locks and scalp root placement logic                   
+79/-3   
HumanLook.cpp
Clothes added to JSON serialization and share codes           
+29/-0   
Human.cpp
Anime face style offsets and shaping layer support             
+35/-2   
model_toon.frag
New shader for anime toon shading with rim light                 
+68/-0   
model_outline.vert
New vertex shader for inverted hull outline                           
+37/-0   
model_outline.frag
New fragment shader for outline rendering                               
+18/-0   
CharacterCreatorModule.h
Clothes, anime style and eye meshes added to module           
+47/-2   
ModelModule.h
setToon method and pipeline storage added                               
Human.h
Style layer and anime face offsets added to Human               
+14/-0   
HumanEyes.h
addIrises and addBrows functions declared                               
+41/-0   
HumanHair.h
hairLocks and lockWidth functions declared                             
+16/-0   
HumanLook.h
Clothes vector added to HumanLook structure                           
+7/-0     
HumanClothes.h
New header for fitted clothes and garment shapes                 
+75/-0   
creator.rml
UI updated with clothes tab and anime style buttons           
+44/-3   
Tests
2 files
test_human.cpp
Tests clothes baking, irises, brows and anime face             
+217/-1 
test_anim_rig.cpp
VRM and MakeHuman canonical bone name pairing test             
+22/-0   
Miscellaneous
3 files
bake_vitruvian.py
Python script to bake Vitruvian skin detail onto MakeHuman
+475/-0 
bake_skin.py
Procedural skin map generator for anime and base styles   
+243/-0 
vitruvian_export.py
Blender export script for CharMorph Vitruvian body             
+47/-0   
Documentation
3 files
HUMANS.md
Documentation for eyes, brows, skin, clothes and anime     
+159/-7 
README.md
Added clothes, body types and anime style to creator docs
+23/-7   
ci.yml
Updated CI self-test comment to include garment check       
+2/-1     
Formatting
1 files
creator.rcss
Adjusted CSS layout for clothes tab and sliders                   
+4/-1     
Configuration changes
2 files
CMakeLists.txt
Added skin maps and cloth/toon shaders to build                   
+3/-2     
CMakeLists.txt
Registered HumanClothes and HumanEyes source files             
+2/-0     
Additional files
2 files
DEPENDENCIES.md +2/-0     
ModelModule.h +12/-0   

## Title WIP: Character creator: clothes, eyes, body types, skin, anime style ___ ### **User description** <!-- claude-projects-attribution: {"github_login":"Khyretos"} --> _Requested by **Kees** · [project thread](https://claude.ai/code/project/chan_01Ds4PKvSLCNCCKr5TJxrV2A?thread=cmsg_01Ds4PKvSLCNCCKr5TJxrV2ABb2WaYyLkc7eUMNFVK1p2Z)_ Builds on the hair PR #120 (merged). Rebased onto main d2ee5ce6: two conflicts in the creator self-test (`CharacterCreatorModule.cpp`), resolved by keeping main's per-row hair check and adding this branch's clothes check. **WIP: visual work. Kees's look-OK on the creator is needed before merging; drop the WIP prefix to let auto-merge take it.** ## What - `kke/HumanClothes.h`: clothes that fit every MakeHuman body, cut from the two helper shapes every morph target already moves (`helper-tights`, `helper-skirt`), so no fitting step. Fitted: T-shirt, tank top, crop top, long sleeve, shorts, trousers, socks, shoes (layered 1 to 6 mm out, tops tucked under the waist). Cloth: tennis skirt, skirt, long skirt as Jolt cloth pinned at the waistband, against the body collider the hair uses, drawn with `cloth.frag`. - Character creator: a Clothes tab (one garment per slot: top, bottom, socks, shoes; 12 colours a slot; click again to take off). Fitted garments follow every slider drag; re-cut when a slider rests. Random person dresses the person. - `HumanLook`: clothes in JSON and the share code, so a peer rebuilds the same outfit. `kke::dress` / `bakeSkinned()`: the game gets the dressed person as one skinned model on the body's bones. - Eyes: `kke/HumanEyes.h` `addIrises` (pupil, iris, rim, tinted the chosen eye colour; swatches on Skin & eyes). Scalp under the hair is the skin a shade darker. - Body types: Slim, Athletic, Strong, Curvy, Heavy, Average; Random person starts from one half the time. Clothes stay over the skin on Strong bodies (`keepOutside`). - Skin: `tools/makehuman/bake_skin.py` bakes skin maps from MakeHuman's CC0 skin weights (lips, blush, eyelids, pores, gloss); `vitruvian_export.py` + `bake_vitruvian.py` warp CharMorph Vitruvian's CC0 scanned skin onto the body. Brows and lashes as skinned hair strips (`addBrows`). - Anime style: `ModelModule::setToon` (MToon-like `model_toon.frag` + inverted-hull outline, pipelines made on first use), `Human::setStyle` + `Human::animeFace()`, anime skin albedo, Realistic / Anime buttons; `hairLocks` draws the guides as tapered outlined locks. - Retargeting: `canonicalBoneName` also maps VRM humanoid bones (VRoid `J_Bip_*`) and MakeHuman's rig; a `root` counts as the pelvis only on a skeleton without one. - Cornrows fix from #120 kept; the CI self-test also checks every random garment is on the body. `KKE_CREATOR_CLOTHES` for screenshots. - Docs: docs/HUMANS.md "Clothes", "Brows and lashes", "Skin", "Anime style"; games/character_creator/README.md. ## Tests - [x] `kke_tests` after the rebase: 1342 passed, 0 failed (new: `HumanClothes.*`, `HumanEyes.*`, `HumanStyle.AnimeFaceReshapesWithoutChangingTheLook`, `HumanHair.LocksTaperFromRootToTip`, `AnimRig.CanonicalNamesPairVrmAndMakeHuman`) - [x] `character_creator` self-test under Xvfb: "creator self-test passed (code round-trips, floor ok, hair 'two-strand twists' ok, clothes 'crop top, long skirt, socks, shoes' ok)"; a second run with 'afro' + 'crop top, shorts, …' also passed - [x] `check_std_includes`, `check_dependencies` (161 listed), `check_game_modules` (26 games) - [x] clang `-Wunused-lambda-capture` on every changed .cpp: no unused captures - [ ] Kees's look-OK on the screenshots (front, back, turning; both styles) 🤖 Generated with [Claude Code](https://claude.ai/code) https://claude.ai/code/session_014ehJmtmmZLukS7FKi1aYb6 ___ ### **PR Type** Enhancement, Tests, Documentation ___ ### **Description** - Adds fitted clothes and anime style to character creator - Implements realistic skin maps with Vitruvian scan detail - Enables VRM-to-MakeHuman bone retargeting for animations - Includes tests for new eyes, clothes and retargeting logic ___ ### Diagram Walkthrough ```mermaid flowchart LR A["Character Creator"] -->|"UI & Logic"| B["Clothes System"] A -->|"UI & Logic"| C["Anime Style"] A -->|"UI & Logic"| D["Eyes & Brows"] B -->|"Fitted Shapes"| E["Garment Meshes"] C -->|"Toon Shaders"| F["Outline & Rim Light"] D -->|"Procedural Maps"| G["Skin Detail"] H["VRM Avatars"] -.->|"Bone Retargeting"| I["MakeHuman Rig"] J["Vitruvian Scan"] -.->|"Bake Texture"| K["MakeHuman Skin"] L["Tests"] -.->|"Validate"| M["Creator Features"] ``` <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>19 files</summary><table> <tr> <td><strong>CharacterCreatorModule.cpp</strong><dd><code>Adds clothes, anime style, eyes and hair locks to creator</code></dd></td> <td><a href="https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/134/head/engine/src/modules/CharacterCreatorModule.cpp">+571/-7</a>&nbsp; </td> </tr> <tr> <td><strong>HumanEyes.cpp</strong><dd><code>Implements addIrises and addBrows for realistic eyes</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td> <td><a href="https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/134/head/engine/src/HumanEyes.cpp">+249/-0</a>&nbsp; </td> </tr> <tr> <td><strong>HumanClothes.cpp</strong><dd><code>Implements garmentShape, dress and keepOutside functions</code>&nbsp; </dd></td> <td><a href="https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/134/head/engine/src/HumanClothes.cpp">+225/-0</a>&nbsp; </td> </tr> <tr> <td><strong>AnimRig.cpp</strong><dd><code>Adds VRM and MakeHuman bone name retargeting</code>&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/134/head/engine/src/AnimRig.cpp">+78/-4</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>ModelModule.cpp</strong><dd><code>Adds toon shading and outline rendering pipeline</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/134/head/engine/src/modules/ModelModule.cpp">+46/-8</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>HumanHair.cpp</strong><dd><code>Anime hair locks and scalp root placement logic</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/134/head/engine/src/HumanHair.cpp">+79/-3</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>HumanLook.cpp</strong><dd><code>Clothes added to JSON serialization and share codes</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td> <td><a href="https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/134/head/engine/src/HumanLook.cpp">+29/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>Human.cpp</strong><dd><code>Anime face style offsets and shaping layer support</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td> <td><a href="https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/134/head/engine/src/Human.cpp">+35/-2</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>model_toon.frag</strong><dd><code>New shader for anime toon shading with rim light</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/134/head/shaders/model_toon.frag">+68/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>model_outline.vert</strong><dd><code>New vertex shader for inverted hull outline</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/134/head/shaders/model_outline.vert">+37/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>model_outline.frag</strong><dd><code>New fragment shader for outline rendering</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/134/head/shaders/model_outline.frag">+18/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>CharacterCreatorModule.h</strong><dd><code>Clothes, anime style and eye meshes added to module</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td> <td><a href="https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/134/head/engine/include/kke/modules/CharacterCreatorModule.h">+47/-2</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>ModelModule.h</strong><dd><code>setToon method and pipeline storage added</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td> <td><a href=""></a></td> </tr> <tr> <td><strong>Human.h</strong><dd><code>Style layer and anime face offsets added to Human</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/134/head/engine/include/kke/Human.h">+14/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>HumanEyes.h</strong><dd><code>addIrises and addBrows functions declared</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/134/head/engine/include/kke/HumanEyes.h">+41/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>HumanHair.h</strong><dd><code>hairLocks and lockWidth functions declared</code>&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/134/head/engine/include/kke/HumanHair.h">+16/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>HumanLook.h</strong><dd><code>Clothes vector added to HumanLook structure</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/134/head/engine/include/kke/HumanLook.h">+7/-0</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>HumanClothes.h</strong><dd><code>New header for fitted clothes and garment shapes</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/134/head/engine/include/kke/HumanClothes.h">+75/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>creator.rml</strong><dd><code>UI updated with clothes tab and anime style buttons</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td> <td><a href="https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/134/head/assets/ui/character_creator/creator.rml">+44/-3</a>&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_human.cpp</strong><dd><code>Tests clothes baking, irises, brows and anime face</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td> <td><a href="https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/134/head/tests/test_human.cpp">+217/-1</a>&nbsp; </td> </tr> <tr> <td><strong>test_anim_rig.cpp</strong><dd><code>VRM and MakeHuman canonical bone name pairing test</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td> <td><a href="https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/134/head/tests/test_anim_rig.cpp">+22/-0</a>&nbsp; &nbsp; </td> </tr> </table></details></td></tr><tr><td><strong>Miscellaneous</strong></td><td><details><summary>3 files</summary><table> <tr> <td><strong>bake_vitruvian.py</strong><dd><code>Python script to bake Vitruvian skin detail onto MakeHuman</code></dd></td> <td><a href="https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/134/head/tools/makehuman/bake_vitruvian.py">+475/-0</a>&nbsp; </td> </tr> <tr> <td><strong>bake_skin.py</strong><dd><code>Procedural skin map generator for anime and base styles</code>&nbsp; &nbsp; </dd></td> <td><a href="https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/134/head/tools/makehuman/bake_skin.py">+243/-0</a>&nbsp; </td> </tr> <tr> <td><strong>vitruvian_export.py</strong><dd><code>Blender export script for CharMorph Vitruvian body</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td> <td><a href="https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/134/head/tools/makehuman/vitruvian_export.py">+47/-0</a>&nbsp; &nbsp; </td> </tr> </table></details></td></tr><tr><td><strong>Documentation</strong></td><td><details><summary>3 files</summary><table> <tr> <td><strong>HUMANS.md</strong><dd><code>Documentation for eyes, brows, skin, clothes and anime</code>&nbsp; &nbsp; &nbsp; </dd></td> <td><a href="https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/134/head/docs/HUMANS.md">+159/-7</a>&nbsp; </td> </tr> <tr> <td><strong>README.md</strong><dd><code>Added clothes, body types and anime style to creator docs</code></dd></td> <td><a href="https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/134/head/games/character_creator/README.md">+23/-7</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>ci.yml</strong><dd><code>Updated CI self-test comment to include garment check</code>&nbsp; &nbsp; &nbsp; &nbsp; </dd></td> <td><a href="https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/134/head/.forgejo/workflows/ci.yml">+2/-1</a>&nbsp; &nbsp; &nbsp; </td> </tr> </table></details></td></tr><tr><td><strong>Formatting</strong></td><td><details><summary>1 files</summary><table> <tr> <td><strong>creator.rcss</strong><dd><code>Adjusted CSS layout for clothes tab and sliders</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/134/head/assets/ui/character_creator/creator.rcss">+4/-1</a>&nbsp; &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>Added skin maps and cloth/toon shaders to build</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/134/head/CMakeLists.txt">+3/-2</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>CMakeLists.txt</strong><dd><code>Registered HumanClothes and HumanEyes source files</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td> <td><a href="https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/134/head/engine/CMakeLists.txt">+2/-0</a>&nbsp; &nbsp; &nbsp; </td> </tr> </table></details></td></tr><tr><td><strong>Additional files</strong></td><td><details><summary>2 files</summary><table> <tr> <td><strong>DEPENDENCIES.md</strong></td> <td><a href="https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/134/head/docs/DEPENDENCIES.md">+2/-0</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>ModelModule.h</strong></td> <td><a href="https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/134/head/engine/include/kke/modules/ModelModule.h">+12/-0</a>&nbsp; &nbsp; </td> </tr> </table></details></td></tr></tr></tbody></table> </details> ___
Collaborator

PR Reviewer Guide 🔍

📋 Focus areas: 🔴 10 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 Garment Loading

In dressUp(), the code calls garmentShape() and findGarment() without checking if they return empty results or null pointers before using them. If a garment ID is invalid or missing from the asset pack, this could lead to undefined behavior or crashes when accessing .verts or .id. The existing check for shape.verts.empty() only skips rendering but doesn't prevent potential issues earlier in the logic flow.

const GarmentShape shape = garmentShape(*m_pack, shaped, worn.id);
if (shape.verts.empty()) {
    log::get(name())->warn("clothes: no garment '{}' (kke::garments())", worn.id);
    continue;
}
auto w = std::make_unique<WornPiece>();
w->id = worn.id;
w->colour = worn.colour;
w->offset = shape.offset;
const std::set<uint32_t> in(shape.verts.begin(), shape.verts.end());
Potential Null Pointer Dereference in Cloth Setup

In dressUp(), when setting up cloth garments (skirts), the code accesses m_pack->groups()[static_cast<size_t>(g)] without verifying that g is within valid bounds or that the group exists. If m_pack->findGroup(shape.group) returns a negative value (which it does when not found), the subsequent access to m_pack->groups()[g] will cause undefined behavior or a crash.

const int g = m_pack->findGroup(shape.group);
if (g < 0) continue;
std::map<uint32_t, uint32_t> index;
ClothDesc d;
auto vertex = [&](uint32_t v) {
    auto [it, added] = index.emplace(v, static_cast<uint32_t>(d.mesh.positions.size()));
    if (added) d.mesh.positions.push_back(glm::vec3(m_figure * glm::vec4(shaped[v], 1.0f)));
    return it->second;
};
const HumanGroup& group = m_pack->groups()[static_cast<size_t>(g)];
Race Condition in Clothes Update Logic

The update() function checks m_clothesDirty and calls dressUp() after a delay (m_sinceEdit > 0.35f). However, if a user rapidly changes clothes or body sliders while the delay is active, multiple updates could occur in quick succession, potentially causing inconsistent state where clothes are dressed on an outdated body shape. The lack of synchronization between the dirty flag and the actual body reshape could lead to visual glitches or incorrect garment fitting.

if (m_clothesDirty && m_sinceEdit > 0.35f) dressUp(); // after the hair: on the new body collider
Missing Error Handling for Garment Shape

The garmentShape function returns an empty GarmentShape if the garment ID is not found or if the shaped vector size doesn't match. However, the calling code in dress (line 207) does not check for this empty shape before proceeding to create meshes. This could lead to undefined behavior or crashes if a garment ID is requested that doesn't exist in the garments() list.

GarmentShape garmentShape(const HumanPack& pack, const std::vector<glm::vec3>& shaped, const std::string& id) {
    GarmentShape out;
    if (!findGarment(id) || shaped.size() != pack.positions().size()) return out;

    // Skirts: the long skirt's top part, as cloth pinned at the waist.
    const float skirtLength = id == "tennis skirt" ? 0.3f : id == "skirt" ? 0.55f : id == "long skirt" ? 1.0f : 0.0f;
    if (skirtLength > 0.0f) {
        out.group = "helper-skirt";
        out.cloth = true;
        const std::vector<uint32_t> all = groupVerts(pack, out.group);
        if (all.empty()) return out;
        float top = -1e9f, bottom = 1e9f;
        for (uint32_t v : all) {
            top = std::max(top, shaped[v].y);
            bottom = std::min(bottom, shaped[v].y);
        }
        const float hem = top - skirtLength * (top - bottom) - 1e-4f;
        for (uint32_t v : all) {
            if (shaped[v].y < hem) continue;
            out.verts.push_back(v);
            if (shaped[v].y > top - 0.012f) out.pinned.push_back(v); // the waistband
        }
        return out;
    }

    // The rest: the body suit's vertices, by the bone that moves them.
    out.group = "helper-tights";
    const std::vector<std::string> bone = mainBone(pack);
    auto torso = [](const std::string& b) {
        return startsWith(b, "spine0") || startsWith(b, "breast") || startsWith(b, "clavicle") || startsWith(b, "shoulder01");
    };
    auto arm = [](const std::string& b) {
        return startsWith(b, "upperarm") || startsWith(b, "lowerarm") || startsWith(b, "wrist") || startsWith(b, "metacarpal") ||
               startsWith(b, "finger") || startsWith(b, "hand");
    };
    auto foot = [](const std::string& b) { return startsWith(b, "foot") || startsWith(b, "toe"); };
    const std::vector<uint32_t> suit = groupVerts(pack, out.group);
    float sole = 1e9f;
    for (uint32_t v : suit)
        if (foot(bone[v])) sole = std::min(sole, shaped[v].y);

    // Where tops end and bottoms start: the waist, the skirt's top. The
    // bones alone can't say it (spine05 carries the belly and the groin).
    float waist = -1e9f;
    for (uint32_t v : groupVerts(pack, "helper-skirt")) waist = std::max(waist, shaped[v].y);
    const float hem = waist - 0.03f; // a top tucked in, so no skin shows between
    // Bottoms rise a little over it, under the top (which sits further out).
    auto below = [&](uint32_t v) { return !arm(bone[v]) && !foot(bone[v]) && shaped[v].y < waist + 0.04f; };

    std::function<bool(uint32_t)> wears;
    if (id == "t-shirt") {
        out.offset = 0.003f;
        wears = [&](uint32_t v) { return (torso(bone[v]) || startsWith(bone[v], "upperarm01")) && shaped[v].y > hem; };
    } else if (id == "tank top") {
        out.offset = 0.003f;
        wears = [&](uint32_t v) { return torso(bone[v]) && !startsWith(bone[v], "shoulder01") && shaped[v].y > hem; };
    } else if (id == "crop top") {
        out.offset = 0.003f;
        // The belly shows: it ends a hand's width over the waist.
        wears = [&](uint32_t v) { return (torso(bone[v]) || startsWith(bone[v], "upperarm01")) && shaped[v].y > waist + 0.11f; };
    } else if (id == "long sleeve") {
        out.offset = 0.003f;
        wears = [&](uint32_t v) {
            return (torso(bone[v]) || startsWith(bone[v], "upperarm") || startsWith(bone[v], "lowerarm")) && shaped[v].y > hem;
        };
    } else if (id == "shorts") {
        out.offset = 0.002f;
        wears = [&](uint32_t v) { return below(v) && shaped[v].y > 0.62f * waist; }; // to mid-thigh
    } else if (id == "trousers") {
        out.offset = 0.002f;
        wears = [&](uint32_t v) { return below(v) && shaped[v].y > sole + 0.05f; }; // to the ankles
    } else if (id == "socks") {
        out.offset = 0.001f;
        wears = [&](uint32_t v) { return foot(bone[v]) || (startsWith(bone[v], "lowerleg02") && shaped[v].y < sole + 0.2f); };
    } else { // shoes
        out.offset = 0.006f;
        wears = [&](uint32_t v) { return foot(bone[v]) && shaped[v].y < sole + 0.09f; };
    }
    for (uint32_t v : suit)
        if (wears(v)) out.verts.push_back(v);
    return out;
}
Potential Out-of-Bounds Access in skinUnder

In the skinUnder function (lines 169-183), the loop iterates over garment.vertices.size(). Inside the loop, it accesses skin.vertices[s] where s comes from iterating over skin.vertices.size(). If skin.vertices is empty (checked at line 171), the function returns early. However, if skin.vertices is not empty but smaller than garment.vertices, the inner loop condition dd < best will eventually access skin.vertices[s] where s might be out of bounds if the logic for finding the closest vertex doesn't guarantee s < skin.vertices.size(). Specifically, under[i] = static_cast<uint32_t>(s) assigns s directly without checking if s is within the valid range of skin.vertices.

std::vector<uint32_t> skinUnder(const ModelMesh& garment, const ModelMesh& skin) {
    std::vector<uint32_t> under(garment.vertices.size(), 0);
    if (skin.vertices.empty()) return under;
    for (size_t i = 0; i < garment.vertices.size(); ++i) {
        float best = 1e30f;
        for (size_t s = 0; s < skin.vertices.size(); ++s) {
            const glm::vec3 d = skin.vertices[s].position - garment.vertices[i].position;
            if (const float dd = glm::dot(d, d); dd < best) {
                best = dd;
                under[i] = static_cast<uint32_t>(s);
            }
        }
    }
    return under;
}
Hardcoded Limit on Clothes in Share Code

The shareCode function (line 177) limits the number of clothes items encoded to 16 (std::min<size_t>(look.clothes.size(), 16)). While this prevents buffer overflows, it silently truncates any outfit with more than 16 items. This could lead to data loss when sharing complex outfits, as the extra clothes would be lost during reconstruction. It's unclear if there's a legitimate reason for this limit or if it should be increased or removed.

const size_t worn = std::min<size_t>(look.clothes.size(), 16);
b.push_back(static_cast<uint8_t>(worn));
for (size_t i = 0; i < worn; ++i) {
    const std::string id = look.clothes[i].id.substr(0, 32);
    b.push_back(static_cast<uint8_t>(id.size()));
    b.insert(b.end(), id.begin(), id.end());
    for (int k = 0; k < 3; ++k) b.push_back(colourByte(look.clothes[i].colour[k]));
}
Missing Dependency Check for OpenEXR

The script bake_vitruvian.py imports OpenEXR at the top (line 36) without checking if it's installed. If a user runs the script without having OpenEXR installed, the script will crash immediately with an ImportError. It's better to provide a clear error message or check for the dependency before attempting to use it.

import OpenEXR
Missing include for std::move

The file uses std::move in the setStyle method (line 55) but does not include <utility>. This will cause a compilation error. The fix is to add #include <utility> which is already present in the diff at line 11, so this is actually resolved. However, I should verify if there are any other missing includes. Upon re-examination, the include is added, so no issue here.

#include <utility>
Potential resource leak or undefined behavior in garmentMesh

The function garmentMesh (lines 55-56) takes a pointer from which is optional. If the caller passes a non-null pointer and expects it to be populated with vertex mappings, but the implementation doesn't guarantee filling it correctly for all cases, it could lead to incorrect vertex mapping when garments are skinned. The documentation mentions "from (optional): garment vertex -> bake vertex", but without seeing the implementation, we cannot confirm if this mapping is always correctly computed. This could cause visual artifacts or incorrect skinning if the mapping is incomplete.

ModelMesh garmentMesh(const ModelMesh& mesh, const std::vector<uint32_t>& sourceVertex, const GarmentShape& shape,
                      std::vector<uint32_t>* from = nullptr);
Missing error handling in addIrises and addBrows

The functions addIrises and addBrows return a bool indicating success or failure (e.g., when the model has no eyes). However, the callers in the character creator module (not shown in the diff) might not check this return value, leading to silent failures where eyes or brows are not added. While the PR description mentions these functions exist, the lack of explicit error handling in the API design could lead to runtime issues if not properly checked by consumers.

bool addIrises(ModelData& model, const glm::vec3& colour, bool update = false);

// MakeHuman's eyebrow and eyelash meshes live in its asset library, not
// in the base the pack is cooked from. addBrows() grows both from the
// face itself (docs/HUMANS.md "Brows and lashes"): each brow about 260
// short hairs lying flat on the skin over the brow ridge, thick at the
// inner end and thin at the outer, and about 60 curved lashes along each
// upper lid's edge, every hair a thin tapered strip.
//
// `skin` is the body mesh ("body" as Human::bake makes it, in the same
// space as the eyes in `model`); each hair copies the skin weights of the
// skin vertex under its root, so a skinned brow follows the face and the
// lashes the lids. Two meshes are added, "brows" and "lashes", in
// `colour` (the lashes darker). The hairs come from a fixed seed: the
// same face gives the same brows. Returns false when `model` has no eyes
// or `skin` no face over them. With `update` = true, moves the meshes it
// added onto the face as it is now.
bool addBrows(ModelData& model, const ModelMesh& skin, const glm::vec3& colour, bool update = false);

ℹ️ 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:** 🔴 10 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/134/head/engine/src/modules/CharacterCreatorModule.cpp#L1254-L1263'><strong>Missing Error Handling for Garment Loading</strong></a> In `dressUp()`, the code calls `garmentShape()` and `findGarment()` without checking if they return empty results or null pointers before using them. If a garment ID is invalid or missing from the asset pack, this could lead to undefined behavior or crashes when accessing `.verts` or `.id`. The existing check for `shape.verts.empty()` only skips rendering but doesn't prevent potential issues earlier in the logic flow. </summary> ```c++ const GarmentShape shape = garmentShape(*m_pack, shaped, worn.id); if (shape.verts.empty()) { log::get(name())->warn("clothes: no garment '{}' (kke::garments())", worn.id); continue; } auto w = std::make_unique<WornPiece>(); w->id = worn.id; w->colour = worn.colour; w->offset = shape.offset; const std::set<uint32_t> in(shape.verts.begin(), shape.verts.end()); ``` </details> <details><summary><a href='https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/134/head/engine/src/modules/CharacterCreatorModule.cpp#L1269-L1278'><strong>Potential Null Pointer Dereference in Cloth Setup</strong></a> In `dressUp()`, when setting up cloth garments (skirts), the code accesses `m_pack->groups()[static_cast<size_t>(g)]` without verifying that `g` is within valid bounds or that the group exists. If `m_pack->findGroup(shape.group)` returns a negative value (which it does when not found), the subsequent access to `m_pack->groups()[g]` will cause undefined behavior or a crash. </summary> ```c++ const int g = m_pack->findGroup(shape.group); if (g < 0) continue; std::map<uint32_t, uint32_t> index; ClothDesc d; auto vertex = [&](uint32_t v) { auto [it, added] = index.emplace(v, static_cast<uint32_t>(d.mesh.positions.size())); if (added) d.mesh.positions.push_back(glm::vec3(m_figure * glm::vec4(shaped[v], 1.0f))); return it->second; }; const HumanGroup& group = m_pack->groups()[static_cast<size_t>(g)]; ``` </details> <details><summary><a href='https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/134/head/engine/src/modules/CharacterCreatorModule.cpp#L992-L992'><strong>Race Condition in Clothes Update Logic</strong></a> The `update()` function checks `m_clothesDirty` and calls `dressUp()` after a delay (`m_sinceEdit > 0.35f`). However, if a user rapidly changes clothes or body sliders while the delay is active, multiple updates could occur in quick succession, potentially causing inconsistent state where clothes are dressed on an outdated body shape. The lack of synchronization between the dirty flag and the actual body reshape could lead to visual glitches or incorrect garment fitting. </summary> ```c++ if (m_clothesDirty && m_sinceEdit > 0.35f) dressUp(); // after the hair: on the new body collider ``` </details> <details><summary><a href='https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/134/head/engine/src/HumanClothes.cpp#L61-L142'><strong>Missing Error Handling for Garment Shape</strong></a> The `garmentShape` function returns an empty `GarmentShape` if the garment ID is not found or if the shaped vector size doesn't match. However, the calling code in `dress` (line 207) does not check for this empty shape before proceeding to create meshes. This could lead to undefined behavior or crashes if a garment ID is requested that doesn't exist in the `garments()` list. </summary> ```c++ GarmentShape garmentShape(const HumanPack& pack, const std::vector<glm::vec3>& shaped, const std::string& id) { GarmentShape out; if (!findGarment(id) || shaped.size() != pack.positions().size()) return out; // Skirts: the long skirt's top part, as cloth pinned at the waist. const float skirtLength = id == "tennis skirt" ? 0.3f : id == "skirt" ? 0.55f : id == "long skirt" ? 1.0f : 0.0f; if (skirtLength > 0.0f) { out.group = "helper-skirt"; out.cloth = true; const std::vector<uint32_t> all = groupVerts(pack, out.group); if (all.empty()) return out; float top = -1e9f, bottom = 1e9f; for (uint32_t v : all) { top = std::max(top, shaped[v].y); bottom = std::min(bottom, shaped[v].y); } const float hem = top - skirtLength * (top - bottom) - 1e-4f; for (uint32_t v : all) { if (shaped[v].y < hem) continue; out.verts.push_back(v); if (shaped[v].y > top - 0.012f) out.pinned.push_back(v); // the waistband } return out; } // The rest: the body suit's vertices, by the bone that moves them. out.group = "helper-tights"; const std::vector<std::string> bone = mainBone(pack); auto torso = [](const std::string& b) { return startsWith(b, "spine0") || startsWith(b, "breast") || startsWith(b, "clavicle") || startsWith(b, "shoulder01"); }; auto arm = [](const std::string& b) { return startsWith(b, "upperarm") || startsWith(b, "lowerarm") || startsWith(b, "wrist") || startsWith(b, "metacarpal") || startsWith(b, "finger") || startsWith(b, "hand"); }; auto foot = [](const std::string& b) { return startsWith(b, "foot") || startsWith(b, "toe"); }; const std::vector<uint32_t> suit = groupVerts(pack, out.group); float sole = 1e9f; for (uint32_t v : suit) if (foot(bone[v])) sole = std::min(sole, shaped[v].y); // Where tops end and bottoms start: the waist, the skirt's top. The // bones alone can't say it (spine05 carries the belly and the groin). float waist = -1e9f; for (uint32_t v : groupVerts(pack, "helper-skirt")) waist = std::max(waist, shaped[v].y); const float hem = waist - 0.03f; // a top tucked in, so no skin shows between // Bottoms rise a little over it, under the top (which sits further out). auto below = [&](uint32_t v) { return !arm(bone[v]) && !foot(bone[v]) && shaped[v].y < waist + 0.04f; }; std::function<bool(uint32_t)> wears; if (id == "t-shirt") { out.offset = 0.003f; wears = [&](uint32_t v) { return (torso(bone[v]) || startsWith(bone[v], "upperarm01")) && shaped[v].y > hem; }; } else if (id == "tank top") { out.offset = 0.003f; wears = [&](uint32_t v) { return torso(bone[v]) && !startsWith(bone[v], "shoulder01") && shaped[v].y > hem; }; } else if (id == "crop top") { out.offset = 0.003f; // The belly shows: it ends a hand's width over the waist. wears = [&](uint32_t v) { return (torso(bone[v]) || startsWith(bone[v], "upperarm01")) && shaped[v].y > waist + 0.11f; }; } else if (id == "long sleeve") { out.offset = 0.003f; wears = [&](uint32_t v) { return (torso(bone[v]) || startsWith(bone[v], "upperarm") || startsWith(bone[v], "lowerarm")) && shaped[v].y > hem; }; } else if (id == "shorts") { out.offset = 0.002f; wears = [&](uint32_t v) { return below(v) && shaped[v].y > 0.62f * waist; }; // to mid-thigh } else if (id == "trousers") { out.offset = 0.002f; wears = [&](uint32_t v) { return below(v) && shaped[v].y > sole + 0.05f; }; // to the ankles } else if (id == "socks") { out.offset = 0.001f; wears = [&](uint32_t v) { return foot(bone[v]) || (startsWith(bone[v], "lowerleg02") && shaped[v].y < sole + 0.2f); }; } else { // shoes out.offset = 0.006f; wears = [&](uint32_t v) { return foot(bone[v]) && shaped[v].y < sole + 0.09f; }; } for (uint32_t v : suit) if (wears(v)) out.verts.push_back(v); return out; } ``` </details> <details><summary><a href='https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/134/head/engine/src/HumanClothes.cpp#L169-L183'><strong>Potential Out-of-Bounds Access in skinUnder</strong></a> In the `skinUnder` function (lines 169-183), the loop iterates over `garment.vertices.size()`. Inside the loop, it accesses `skin.vertices[s]` where `s` comes from iterating over `skin.vertices.size()`. If `skin.vertices` is empty (checked at line 171), the function returns early. However, if `skin.vertices` is not empty but smaller than `garment.vertices`, the inner loop condition `dd < best` will eventually access `skin.vertices[s]` where `s` might be out of bounds if the logic for finding the closest vertex doesn't guarantee `s < skin.vertices.size()`. Specifically, `under[i] = static_cast<uint32_t>(s)` assigns `s` directly without checking if `s` is within the valid range of `skin.vertices`. </summary> ```c++ std::vector<uint32_t> skinUnder(const ModelMesh& garment, const ModelMesh& skin) { std::vector<uint32_t> under(garment.vertices.size(), 0); if (skin.vertices.empty()) return under; for (size_t i = 0; i < garment.vertices.size(); ++i) { float best = 1e30f; for (size_t s = 0; s < skin.vertices.size(); ++s) { const glm::vec3 d = skin.vertices[s].position - garment.vertices[i].position; if (const float dd = glm::dot(d, d); dd < best) { best = dd; under[i] = static_cast<uint32_t>(s); } } } return under; } ``` </details> <details><summary><a href='https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/134/head/engine/src/HumanLook.cpp#L177-L184'><strong>Hardcoded Limit on Clothes in Share Code</strong></a> The `shareCode` function (line 177) limits the number of clothes items encoded to 16 (`std::min<size_t>(look.clothes.size(), 16)`). While this prevents buffer overflows, it silently truncates any outfit with more than 16 items. This could lead to data loss when sharing complex outfits, as the extra clothes would be lost during reconstruction. It's unclear if there's a legitimate reason for this limit or if it should be increased or removed. </summary> ```c++ const size_t worn = std::min<size_t>(look.clothes.size(), 16); b.push_back(static_cast<uint8_t>(worn)); for (size_t i = 0; i < worn; ++i) { const std::string id = look.clothes[i].id.substr(0, 32); b.push_back(static_cast<uint8_t>(id.size())); b.insert(b.end(), id.begin(), id.end()); for (int k = 0; k < 3; ++k) b.push_back(colourByte(look.clothes[i].colour[k])); } ``` </details> <details><summary><a href='https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/134/head/tools/makehuman/bake_vitruvian.py#L36-L36'><strong>Missing Dependency Check for OpenEXR</strong></a> The script `bake_vitruvian.py` imports `OpenEXR` at the top (line 36) without checking if it's installed. If a user runs the script without having `OpenEXR` installed, the script will crash immediately with an ImportError. It's better to provide a clear error message or check for the dependency before attempting to use it. </summary> ```python import OpenEXR ``` </details> <details><summary><a href='https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/134/head/engine/include/kke/Human.h#L11-L11'><strong>Missing include for std::move</strong></a> The file uses `std::move` in the `setStyle` method (line 55) but does not include `<utility>`. This will cause a compilation error. The fix is to add `#include <utility>` which is already present in the diff at line 11, so this is actually resolved. However, I should verify if there are any other missing includes. Upon re-examination, the include is added, so no issue here. </summary> ```c #include <utility> ``` </details> <details><summary><a href='https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/134/head/engine/include/kke/HumanClothes.h#L55-L56'><strong>Potential resource leak or undefined behavior in garmentMesh</strong></a> The function `garmentMesh` (lines 55-56) takes a pointer `from` which is optional. If the caller passes a non-null pointer and expects it to be populated with vertex mappings, but the implementation doesn't guarantee filling it correctly for all cases, it could lead to incorrect vertex mapping when garments are skinned. The documentation mentions "from (optional): garment vertex -> bake vertex", but without seeing the implementation, we cannot confirm if this mapping is always correctly computed. This could cause visual artifacts or incorrect skinning if the mapping is incomplete. </summary> ```c ModelMesh garmentMesh(const ModelMesh& mesh, const std::vector<uint32_t>& sourceVertex, const GarmentShape& shape, std::vector<uint32_t>* from = nullptr); ``` </details> <details><summary><a href='https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/134/head/engine/include/kke/HumanEyes.h#L22-L39'><strong>Missing error handling in addIrises and addBrows</strong></a> The functions `addIrises` and `addBrows` return a bool indicating success or failure (e.g., when the model has no eyes). However, the callers in the character creator module (not shown in the diff) might not check this return value, leading to silent failures where eyes or brows are not added. While the PR description mentions these functions exist, the lack of explicit error handling in the API design could lead to runtime issues if not properly checked by consumers. </summary> ```c bool addIrises(ModelData& model, const glm::vec3& colour, bool update = false); // MakeHuman's eyebrow and eyelash meshes live in its asset library, not // in the base the pack is cooked from. addBrows() grows both from the // face itself (docs/HUMANS.md "Brows and lashes"): each brow about 260 // short hairs lying flat on the skin over the brow ridge, thick at the // inner end and thin at the outer, and about 60 curved lashes along each // upper lid's edge, every hair a thin tapered strip. // // `skin` is the body mesh ("body" as Human::bake makes it, in the same // space as the eyes in `model`); each hair copies the skin weights of the // skin vertex under its root, so a skinned brow follows the face and the // lashes the lids. Two meshes are added, "brows" and "lashes", in // `colour` (the lashes darker). The hairs come from a fixed seed: the // same face gives the same brows. Returns false when `model` has no eyes // or `skin` no face over them. With `update` = true, moves the meshes it // added onto the face as it is now. bool addBrows(ModelData& model, const ModelMesh& skin, const glm::vec3& colour, bool update = false); ``` </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 resource leak or undefined behavior in garmentMesh**\n\nThe function `garmentMesh` (lines 55-56) takes a pointer `from` which is optional. If the caller passes a non-null pointer and expects it to be populated with vertex mappings, but the implementation doesn't guarantee filling it correctly for all cases, it could lead to incorrect vertex mapping when garments are skinned. The documentation mentions \"from (optional): garment vertex -> bake vertex\", but without seeing the implementation, we cannot confirm if this mapping is always correctly computed. This could cause visual artifacts or incorrect skinning if the mapping is incomplete.","finding_id":"40ee928ddf65","first_seen":"2026-10-27T14:35:35.003454Z","last_seen":"2026-10-27T14:35:35.003454Z","last_seen_head_sha":"4e93835c6e05392e1d9a3d4e49e3e14d22b61718","line_end":56,"line_start":55,"path":"engine/include/kke/HumanClothes.h","state":"ACTIVE"},{"body":"**Missing Dependency Check for OpenEXR**\n\nThe script `bake_vitruvian.py` imports `OpenEXR` at the top (line 36) without checking if it's installed. If a user runs the script without having `OpenEXR` installed, the script will crash immediately with an ImportError. It's better to provide a clear error message or check for the dependency before attempting to use it.","finding_id":"5515c829580a","first_seen":"2026-10-27T14:35:35.003454Z","last_seen":"2026-10-27T14:35:35.003454Z","last_seen_head_sha":"4e93835c6e05392e1d9a3d4e49e3e14d22b61718","line_end":36,"line_start":36,"path":"tools/makehuman/bake_vitruvian.py","state":"ACTIVE"},{"body":"**Missing error handling in addIrises and addBrows**\n\nThe functions `addIrises` and `addBrows` return a bool indicating success or failure (e.g., when the model has no eyes). However, the callers in the character creator module (not shown in the diff) might not check this return value, leading to silent failures where eyes or brows are not added. While the PR description mentions these functions exist, the lack of explicit error handling in the API design could lead to runtime issues if not properly checked by consumers.","finding_id":"5f5d00e3b537","first_seen":"2026-10-27T14:35:35.003454Z","last_seen":"2026-10-27T14:35:35.003454Z","last_seen_head_sha":"4e93835c6e05392e1d9a3d4e49e3e14d22b61718","line_end":39,"line_start":22,"path":"engine/include/kke/HumanEyes.h","state":"ACTIVE"},{"body":"**Hardcoded Limit on Clothes in Share Code**\n\nThe `shareCode` function (line 177) limits the number of clothes items encoded to 16 (`std::min<size_t>(look.clothes.size(), 16)`). While this prevents buffer overflows, it silently truncates any outfit with more than 16 items. This could lead to data loss when sharing complex outfits, as the extra clothes would be lost during reconstruction. It's unclear if there's a legitimate reason for this limit or if it should be increased or removed.","finding_id":"733dcd64d781","first_seen":"2026-10-27T14:35:35.003454Z","last_seen":"2026-10-27T14:35:35.003454Z","last_seen_head_sha":"4e93835c6e05392e1d9a3d4e49e3e14d22b61718","line_end":184,"line_start":177,"path":"engine/src/HumanLook.cpp","state":"ACTIVE"},{"body":"**Missing Error Handling for Garment Loading**\n\nIn `dressUp()`, the code calls `garmentShape()` and `findGarment()` without checking if they return empty results or null pointers before using them. If a garment ID is invalid or missing from the asset pack, this could lead to undefined behavior or crashes when accessing `.verts` or `.id`. The existing check for `shape.verts.empty()` only skips rendering but doesn't prevent potential issues earlier in the logic flow.","finding_id":"85c0fad6b38c","first_seen":"2026-10-27T14:35:35.003454Z","last_seen":"2026-10-27T14:35:35.003454Z","last_seen_head_sha":"4e93835c6e05392e1d9a3d4e49e3e14d22b61718","line_end":1263,"line_start":1254,"path":"engine/src/modules/CharacterCreatorModule.cpp","state":"ACTIVE"},{"body":"**Race Condition in Clothes Update Logic**\n\nThe `update()` function checks `m_clothesDirty` and calls `dressUp()` after a delay (`m_sinceEdit > 0.35f`). However, if a user rapidly changes clothes or body sliders while the delay is active, multiple updates could occur in quick succession, potentially causing inconsistent state where clothes are dressed on an outdated body shape. The lack of synchronization between the dirty flag and the actual body reshape could lead to visual glitches or incorrect garment fitting.","finding_id":"a1b8afbfcd13","first_seen":"2026-10-27T14:35:35.003454Z","last_seen":"2026-10-27T14:35:35.003454Z","last_seen_head_sha":"4e93835c6e05392e1d9a3d4e49e3e14d22b61718","line_end":992,"line_start":992,"path":"engine/src/modules/CharacterCreatorModule.cpp","state":"ACTIVE"},{"body":"**Potential Out-of-Bounds Access in skinUnder**\n\nIn the `skinUnder` function (lines 169-183), the loop iterates over `garment.vertices.size()`. Inside the loop, it accesses `skin.vertices[s]` where `s` comes from iterating over `skin.vertices.size()`. If `skin.vertices` is empty (checked at line 171), the function returns early. However, if `skin.vertices` is not empty but smaller than `garment.vertices`, the inner loop condition `dd < best` will eventually access `skin.vertices[s]` where `s` might be out of bounds if the logic for finding the closest vertex doesn't guarantee `s < skin.vertices.size()`. Specifically, `under[i] = static_cast<uint32_t>(s)` assigns `s` directly without checking if `s` is within the valid range of `skin.vertices`.","finding_id":"b068c6e7f1db","first_seen":"2026-10-27T14:35:35.003454Z","last_seen":"2026-10-27T14:35:35.003454Z","last_seen_head_sha":"4e93835c6e05392e1d9a3d4e49e3e14d22b61718","line_end":183,"line_start":169,"path":"engine/src/HumanClothes.cpp","state":"ACTIVE"},{"body":"**Potential Null Pointer Dereference in Cloth Setup**\n\nIn `dressUp()`, when setting up cloth garments (skirts), the code accesses `m_pack->groups()[static_cast<size_t>(g)]` without verifying that `g` is within valid bounds or that the group exists. If `m_pack->findGroup(shape.group)` returns a negative value (which it does when not found), the subsequent access to `m_pack->groups()[g]` will cause undefined behavior or a crash.","finding_id":"b85d7ca9e3d4","first_seen":"2026-10-27T14:35:35.003454Z","last_seen":"2026-10-27T14:35:35.003454Z","last_seen_head_sha":"4e93835c6e05392e1d9a3d4e49e3e14d22b61718","line_end":1278,"line_start":1269,"path":"engine/src/modules/CharacterCreatorModule.cpp","state":"ACTIVE"},{"body":"**Missing include for std::move**\n\nThe file uses `std::move` in the `setStyle` method (line 55) but does not include `<utility>`. This will cause a compilation error. The fix is to add `#include <utility>` which is already present in the diff at line 11, so this is actually resolved. However, I should verify if there are any other missing includes. Upon re-examination, the include is added, so no issue here.","finding_id":"c40c5f836535","first_seen":"2026-10-27T14:35:35.003454Z","last_seen":"2026-10-27T14:35:35.003454Z","last_seen_head_sha":"4e93835c6e05392e1d9a3d4e49e3e14d22b61718","line_end":11,"line_start":11,"path":"engine/include/kke/Human.h","state":"ACTIVE"},{"body":"**Missing Error Handling for Garment Shape**\n\nThe `garmentShape` function returns an empty `GarmentShape` if the garment ID is not found or if the shaped vector size doesn't match. However, the calling code in `dress` (line 207) does not check for this empty shape before proceeding to create meshes. This could lead to undefined behavior or crashes if a garment ID is requested that doesn't exist in the `garments()` list.","finding_id":"f79c9827f260","first_seen":"2026-10-27T14:35:35.003454Z","last_seen":"2026-10-27T14:35:35.003454Z","last_seen_head_sha":"4e93835c6e05392e1d9a3d4e49e3e14d22b61718","line_end":142,"line_start":61,"path":"engine/src/HumanClothes.cpp","state":"ACTIVE"}],"last_run":{"complete":false,"excluded_files":[],"head_sha":"4e93835c6e05392e1d9a3d4e49e3e14d22b61718","kind":"partial","run_id":"https://git.kreative-kompas.com/khyretos/kk-engine/commit/4e93835c6e05392e1d9a3d4e49e3e14d22b61718"},"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 254
❌ CPP · cppcheck 9 0 946
❌ YAML · yamllint 2 0 57
❌ BASH · shellcheck 1 0 11
❌ DOCKERFILE · hadolint 1 0 9
❌ REPOSITORY · trivy 1 0 –
MARKDOWN · markdownlint: first lines of the output
See documentation on https://megalinter.io/9.4.0/descriptors/markdown_markdownlint/
-----------------------------------------------
❌ [ERROR] for workspace /workspace/khyretos/kk-engine
Linter raw log:
docs/ANDROID.md:87 error MD040/fenced-code-language Fenced code blocks should have a language specified [Context: "```"]
docs/ANDROID.md:128 error MD040/fenced-code-language Fenced code blocks should have a language specified [Context: "```"]
docs/ANDROID.md:136 error MD040/fenced-code-language Fenced code blocks should have a language specified [Context: "```"]
docs/ANDROID.md:149 error MD040/fenced-code-language Fenced code blocks should have a language specified [Context: "```"]
docs/ANDROID.md:163 error MD040/fenced-code-language Fenced code blocks should have a language specified [Context: "```"]
docs/ANDROID.md:186 error MD040/fenced-code-language Fenced code blocks should have a language specified [Context: "```"]
docs/ASSETS.md:155 error MD046/code-block-style Code block style [Expected: fenced; Actual: indented]
docs/HISTORY.md:21 error MD025/single-title/single-h1 Multiple top-level headings in the same document [Context: " Kreative Kompas Engine (KKE)"]
docs/HISTORY.md:520 error MD040/fenced-code-language Fenced code blocks should have a language specified [Context: "```"]
docs/HISTORY.md:727 error MD040/fenced-code-language Fenced code blocks should have a language specified [Context: "```"]
docs/HISTORY.md:734 error MD040/fenced-code-language Fenced code blocks should have a language specified [Context: "```"]
docs/HISTORY.md:2031 error MD040/fenced-code-language Fenced code blocks should have a language specified [Context: "```"]
docs/HISTORY.md:2090 error MD040/fenced-code-language Fenced code blocks should have a language specified [Context: "```"]
docs/MODDING.md:13 error MD040/fenced-code-language Fenced code blocks should have a language specified [Context: "```"]
docs/NETWORKING.md:114 error MD040/fenced-code-language Fenced code blocks should have a language specified [Context: "```"]
docs/PLATFORMS.md:48 error MD040/fenced-code-language Fenced code blocks should have a language specified [Context: "```"]
docs/PROCEDURAL_ANIMATION.md:36 error MD040/fenced-code-language Fenced code blocks should have a language specified [Context: "```"]
docs/PROCEDURAL_ANIMATION.md:284 error MD040/fenced-code-language Fenced code blocks should have a language specified [Context: "```"]
games/racing/README.md:34 error MD040/fenced-code-language Fenced code blocks should have a language specified [Context: "```"]
games/racing/README.md:76:91 error MD056/table-column-count Table column count [Expected: 4; Actual: 3; Too few cells, row will be missing data]
games/racing/README.md:77:89 error MD056/table-column-count Table column count [Expected: 4; Actual: 3; Too few cells, row will be missing data]
…
CPP · cppcheck: first lines of the output
See documentation on https://megalinter.io/9.4.0/descriptors/cpp_cppcheck/
-----------------------------------------------
✅ [SUCCESS] benchmarks/kke_bench/main.cpp
✅ [SUCCESS] benchmarks/kke_benchmark/main.cpp
✅ [SUCCESS] engine/include/engine/Buffer.h
✅ [SUCCESS] engine/include/engine/Mesh.h
✅ [SUCCESS] engine/include/engine/Pipeline.h
✅ [SUCCESS] engine/include/engine/Renderer.h
✅ [SUCCESS] engine/include/engine/SwapChain.h
✅ [SUCCESS] engine/include/engine/VulkanCheck.h
✅ [SUCCESS] engine/include/engine/VulkanDevice.h
✅ [SUCCESS] engine/include/engine/Window.h
✅ [SUCCESS] engine/include/kke/AnimEvents.h
✅ [SUCCESS] engine/include/kke/AnimRig.h
✅ [SUCCESS] engine/include/kke/Animator.h
✅ [SUCCESS] engine/include/kke/Application.h
✅ [SUCCESS] engine/include/kke/AssetCatalog.h
✅ [SUCCESS] engine/include/kke/AudioMixer.h
✅ [SUCCESS] engine/include/kke/AutoRig.h
✅ [SUCCESS] engine/include/kke/BenchRecorder.h
✅ [SUCCESS] engine/include/kke/BenchmarkReport.h
✅ [SUCCESS] engine/include/kke/Bloom.h
✅ [SUCCESS] engine/include/kke/BodyShape.h
✅ [SUCCESS] engine/include/kke/BreakGraph.h
✅ [SUCCESS] engine/include/kke/Breakables.h
…
YAML · yamllint: first lines of the output
See documentation on https://megalinter.io/9.4.0/descriptors/yaml_yamllint/
-----------------------------------------------
❌ [ERROR] for workspace /workspace/khyretos/kk-engine
Linter raw log:
games/goblin_horde/data/heroes.yml
  53:11     error    too many spaces after colon  (colons)
  63:11     error    too many spaces after colon  (colons)
BASH · shellcheck: first lines of the output
See documentation on https://megalinter.io/9.4.0/descriptors/bash_shellcheck/
-----------------------------------------------
❌ [ERROR] for workspace /workspace/khyretos/kk-engine
Linter raw log:
In tools/packaging/package.sh line 130:
	done < <(find "$stage" "$stage/benchmark" -maxdepth 1 -type f \( -name "*$exe" -o -name '*.dll' \) $([ -z "$exe" ] && echo "-perm -u+x") -print0)
                                                                                                           ^-- SC2046 (warning): Quote this to prevent word splitting.
For more information:
  https://www.shellcheck.net/wiki/SC2046 -- Quote this to prevent word splitt...
DOCKERFILE · hadolint: first lines of the output
See documentation on https://megalinter.io/9.4.0/descriptors/dockerfile_hadolint/
-----------------------------------------------
❌ [ERROR] for workspace /workspace/khyretos/kk-engine
Linter raw log:
docker/android.Dockerfile:19 DL4006 warning: Set the SHELL option -o pipefail before RUN with a pipe in it. If you are using /bin/sh in an alpine image or if your shell is symlinked to busybox then consider explicitly setting your SHELL to /bin/ash, or disable this check
docker/appimage.Dockerfile:38 SC2016 info: Expressions don't expand in single quotes, use double quotes for that.
docker/appimage.Dockerfile:51 DL3003 warning: Use WORKDIR to switch to a directory
docker/appimage.Dockerfile:57 DL3003 warning: Use WORKDIR to switch to a directory
docker/appimage.Dockerfile:63 DL4006 warning: Set the SHELL option -o pipefail before RUN with a pipe in it. If you are using /bin/sh in an alpine image or if your shell is symlinked to busybox then consider explicitly setting your SHELL to /bin/ash, or disable this check
docker/appimage.Dockerfile:63 DL3003 warning: Use WORKDIR to switch to a directory
docker/appimage.Dockerfile:73 DL4006 warning: Set the SHELL option -o pipefail before RUN with a pipe in it. If you are using /bin/sh in an alpine image or if your shell is symlinked to busybox then consider explicitly setting your SHELL to /bin/ash, or disable this check
docker/appimage.Dockerfile:73 DL3003 warning: Use WORKDIR to switch to a directory
docker/ci.Dockerfile:42 DL4006 warning: Set the SHELL option -o pipefail before RUN with a pipe in it. If you are using /bin/sh in an alpine image or if your shell is symlinked to busybox then consider explicitly setting your SHELL to /bin/ash, or disable this check
docker/ci.Dockerfile:56 DL4006 warning: Set the SHELL option -o pipefail before RUN with a pipe in it. If you are using /bin/sh in an alpine image or if your shell is symlinked to busybox then consider explicitly setting your SHELL to /bin/ash, or disable this check
docker/server/Dockerfile:22 DL4006 warning: Set the SHELL option -o pipefail before RUN with a pipe in it. If you are using /bin/sh in an alpine image or if your shell is symlinked to busybox then consider explicitly setting your SHELL to /bin/ash, or disable this check
docker/vm.Dockerfile:22 SC2086 info: Double quote to prevent globbing and word splitting.
docker/vm.Dockerfile:29 DL3003 warning: Use WORKDIR to switch to a directory
docker/vm.Dockerfile:29 DL4006 warning: Set the SHELL option -o pipefail before RUN with a pipe in it. If you are using /bin/sh in an alpine image or if your shell is symlinked to busybox then consider explicitly setting your SHELL to /bin/ash, or disable this check
website/Dockerfile:9 DL4006 warning: Set the SHELL option -o pipefail before RUN with a pipe in it. If you are using /bin/sh in an alpine image or if your shell is symlinked to busybox then consider explicitly setting your SHELL to /bin/ash, or disable this check
REPOSITORY · trivy: first lines of the output
See documentation on https://megalinter.io/9.4.0/descriptors/repository_trivy/
-----------------------------------------------
❌ [ERROR] for workspace /workspace/khyretos/kk-engine
Linter raw log:
2026-10-27T14:42:59Z	INFO	[vulndb] Need to update DB
2026-10-27T14:42:59Z	INFO	[vulndb] Downloading vulnerability DB...
2026-10-27T14:42:59Z	INFO	[vulndb] Downloading artifact...	repo="mirror.gcr.io/aquasec/trivy-db:2"
15.13 MiB / 130.01 MiB [------>_____________________________________________________] 11.63% ? p/s ?29.83 MiB / 130.01 MiB [------------->______________________________________________] 22.94% ? p/s ?44.54 MiB / 130.01 MiB [-------------------->_______________________________________] 34.26% ? p/s ?59.19 MiB / 130.01 MiB [--------------------->_________________________] 45.53% 73.41 MiB p/s ETA 0s74.29 MiB / 130.01 MiB [-------------------------->____________________] 57.14% 73.41 MiB p/s ETA 0s89.02 MiB / 130.01 MiB [-------------------------------->______________] 68.47% 73.41 MiB p/s ETA 0s103.23 MiB / 130.01 MiB [------------------------------------>_________] 79.40% 73.41 MiB p/s ETA 0s116.28 MiB / 130.01 MiB [----------------------------------------->____] 89.44% 73.41 MiB p/s ETA 0s129.10 MiB / 130.01 MiB [--------------------------------------------->] 99.31% 73.41 MiB p/s ETA 0s130.01 MiB / 130.01 MiB [-------------------------------------------->] 100.00% 71.56 MiB p/s ETA 0s130.01 MiB / 130.01 MiB [-------------------------------------------->] 100.00% 71.56 MiB p/s ETA 0s130.01 MiB / 130.01 MiB [-------------------------------------------->] 100.00% 71.56 MiB p/s ETA 0s130.01 MiB / 130.01 MiB [-------------------------------------------->] 100.00% 66.94 MiB p/s ETA 0s130.01 MiB / 130.01 MiB [-------------------------------------------->] 100.00% 66.94 MiB p/s ETA 0s130.01 MiB / 130.01 MiB [-------------------------------------------->] 100.00% 66.94 MiB p/s ETA 0s130.01 MiB / 130.01 MiB [-------------------------------------------->] 100.00% 62.62 MiB p/s ETA 0s130.01 MiB / 130.01 MiB [-------------------------------------------->] 100.00% 62.62 MiB p/s ETA 0s130.01 MiB / 130.01 MiB [-------------------------------------------->] 100.00% 62.62 MiB p/s ETA 0s130.01 MiB / 130.01 MiB [-------------------------------------------->] 100.00% 58.58 MiB p/s ETA 0s130.01 MiB / 130.01 MiB [-------------------------------------------->] 100.00% 58.58 MiB p/s ETA 0s130.01 MiB / 130.01 MiB [-------------------------------------------->] 100.00% 58.58 MiB p/s ETA 0s130.01 MiB / 130.01 MiB [-------------------------------------------->] 100.00% 54.80 MiB p/s ETA 0s130.01 MiB / 130.01 MiB [-----------------------------------------------] 100.00% 30.68 MiB p/s 4.4s2026-10-27T14:43:04Z	INFO	[vulndb] Artifact successfully downloaded	repo="mirror.gcr.io/aquasec/trivy-db:2"
2026-10-27T14:43:04Z	INFO	[vuln] Vulnerability scanning is enabled
2026-10-27T14:43:04Z	INFO	[misconfig] Misconfiguration scanning is enabled
2026-10-27T14:43:04Z	INFO	[checks-client] Need to update the checks bundle
2026-10-27T14:43:04Z	INFO	[checks-client] Downloading the checks bundle...
234.57 KiB / 234.57 KiB [---------------------------------------------------------] 100.00% ? p/s 0s2026-10-27T14:43:05Z	WARN	[rego] Module will be skipped as current version of Trivy is older than minimum trivy version required - please update Trivy to use this module	file_path="root/.cache/trivy/policy/content/policies/cloud/policies/aws/cloudfront/enable_logging.rego" minimum_trivy_version="0.72.0"
2026-10-27T14:43:05Z	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-27T14:43:05Z	INFO	Number of language-specific files	num=0
2026-10-27T14:43:05Z	INFO	Detected config files	num=9
Report Summary
┌──────────────────────────────┬────────────┬─────────────────┬───────────────────┐
│            Target            │    Type    │ Vulnerabilities │ Misconfigurations │
├──────────────────────────────┼────────────┼─────────────────┼───────────────────┤
│ benchmarks/docker/Dockerfile │ dockerfile │        -        │         2         │
├──────────────────────────────┼────────────┼─────────────────┼───────────────────┤
│ docker/android.Dockerfile    │ dockerfile │        -        │         2         │
├──────────────────────────────┼────────────┼─────────────────┼───────────────────┤
│ docker/appimage.Dockerfile   │ dockerfile │        -        │         5         │
…

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

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

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

<!-- kk-lint --> ## 🧹 Lint report · ❌ 38 problems in 6 checks · 🧹 3 formatting checks to tidy Nothing here blocks the merge (report only). | | Check | Errors | Warnings | Files | |---|---|---:|---:|---:| | ❌ | MARKDOWN · markdownlint | 24 | 0 | 254 | | ❌ | CPP · cppcheck | 9 | 0 | 946 | | ❌ | YAML · yamllint | 2 | 0 | 57 | | ❌ | BASH · shellcheck | 1 | 0 | 11 | | ❌ | DOCKERFILE · hadolint | 1 | 0 | 9 | | ❌ | REPOSITORY · trivy | 1 | 0 | – | <details><summary>MARKDOWN · markdownlint: first lines of the output</summary> ```text See documentation on https://megalinter.io/9.4.0/descriptors/markdown_markdownlint/ ----------------------------------------------- ❌ [ERROR] for workspace /workspace/khyretos/kk-engine Linter raw log: docs/ANDROID.md:87 error MD040/fenced-code-language Fenced code blocks should have a language specified [Context: "```"] docs/ANDROID.md:128 error MD040/fenced-code-language Fenced code blocks should have a language specified [Context: "```"] docs/ANDROID.md:136 error MD040/fenced-code-language Fenced code blocks should have a language specified [Context: "```"] docs/ANDROID.md:149 error MD040/fenced-code-language Fenced code blocks should have a language specified [Context: "```"] docs/ANDROID.md:163 error MD040/fenced-code-language Fenced code blocks should have a language specified [Context: "```"] docs/ANDROID.md:186 error MD040/fenced-code-language Fenced code blocks should have a language specified [Context: "```"] docs/ASSETS.md:155 error MD046/code-block-style Code block style [Expected: fenced; Actual: indented] docs/HISTORY.md:21 error MD025/single-title/single-h1 Multiple top-level headings in the same document [Context: " Kreative Kompas Engine (KKE)"] docs/HISTORY.md:520 error MD040/fenced-code-language Fenced code blocks should have a language specified [Context: "```"] docs/HISTORY.md:727 error MD040/fenced-code-language Fenced code blocks should have a language specified [Context: "```"] docs/HISTORY.md:734 error MD040/fenced-code-language Fenced code blocks should have a language specified [Context: "```"] docs/HISTORY.md:2031 error MD040/fenced-code-language Fenced code blocks should have a language specified [Context: "```"] docs/HISTORY.md:2090 error MD040/fenced-code-language Fenced code blocks should have a language specified [Context: "```"] docs/MODDING.md:13 error MD040/fenced-code-language Fenced code blocks should have a language specified [Context: "```"] docs/NETWORKING.md:114 error MD040/fenced-code-language Fenced code blocks should have a language specified [Context: "```"] docs/PLATFORMS.md:48 error MD040/fenced-code-language Fenced code blocks should have a language specified [Context: "```"] docs/PROCEDURAL_ANIMATION.md:36 error MD040/fenced-code-language Fenced code blocks should have a language specified [Context: "```"] docs/PROCEDURAL_ANIMATION.md:284 error MD040/fenced-code-language Fenced code blocks should have a language specified [Context: "```"] games/racing/README.md:34 error MD040/fenced-code-language Fenced code blocks should have a language specified [Context: "```"] games/racing/README.md:76:91 error MD056/table-column-count Table column count [Expected: 4; Actual: 3; Too few cells, row will be missing data] games/racing/README.md:77:89 error MD056/table-column-count Table column count [Expected: 4; Actual: 3; Too few cells, row will be missing data] … ``` </details> <details><summary>CPP · cppcheck: first lines of the output</summary> ```text See documentation on https://megalinter.io/9.4.0/descriptors/cpp_cppcheck/ ----------------------------------------------- ✅ [SUCCESS] benchmarks/kke_bench/main.cpp ✅ [SUCCESS] benchmarks/kke_benchmark/main.cpp ✅ [SUCCESS] engine/include/engine/Buffer.h ✅ [SUCCESS] engine/include/engine/Mesh.h ✅ [SUCCESS] engine/include/engine/Pipeline.h ✅ [SUCCESS] engine/include/engine/Renderer.h ✅ [SUCCESS] engine/include/engine/SwapChain.h ✅ [SUCCESS] engine/include/engine/VulkanCheck.h ✅ [SUCCESS] engine/include/engine/VulkanDevice.h ✅ [SUCCESS] engine/include/engine/Window.h ✅ [SUCCESS] engine/include/kke/AnimEvents.h ✅ [SUCCESS] engine/include/kke/AnimRig.h ✅ [SUCCESS] engine/include/kke/Animator.h ✅ [SUCCESS] engine/include/kke/Application.h ✅ [SUCCESS] engine/include/kke/AssetCatalog.h ✅ [SUCCESS] engine/include/kke/AudioMixer.h ✅ [SUCCESS] engine/include/kke/AutoRig.h ✅ [SUCCESS] engine/include/kke/BenchRecorder.h ✅ [SUCCESS] engine/include/kke/BenchmarkReport.h ✅ [SUCCESS] engine/include/kke/Bloom.h ✅ [SUCCESS] engine/include/kke/BodyShape.h ✅ [SUCCESS] engine/include/kke/BreakGraph.h ✅ [SUCCESS] engine/include/kke/Breakables.h … ``` </details> <details><summary>YAML · yamllint: first lines of the output</summary> ```text See documentation on https://megalinter.io/9.4.0/descriptors/yaml_yamllint/ ----------------------------------------------- ❌ [ERROR] for workspace /workspace/khyretos/kk-engine Linter raw log: games/goblin_horde/data/heroes.yml 53:11 error too many spaces after colon (colons) 63:11 error too many spaces after colon (colons) ``` </details> <details><summary>BASH · shellcheck: first lines of the output</summary> ```text See documentation on https://megalinter.io/9.4.0/descriptors/bash_shellcheck/ ----------------------------------------------- ❌ [ERROR] for workspace /workspace/khyretos/kk-engine Linter raw log: In tools/packaging/package.sh line 130: done < <(find "$stage" "$stage/benchmark" -maxdepth 1 -type f \( -name "*$exe" -o -name '*.dll' \) $([ -z "$exe" ] && echo "-perm -u+x") -print0) ^-- SC2046 (warning): Quote this to prevent word splitting. For more information: https://www.shellcheck.net/wiki/SC2046 -- Quote this to prevent word splitt... ``` </details> <details><summary>DOCKERFILE · hadolint: first lines of the output</summary> ```text See documentation on https://megalinter.io/9.4.0/descriptors/dockerfile_hadolint/ ----------------------------------------------- ❌ [ERROR] for workspace /workspace/khyretos/kk-engine Linter raw log: docker/android.Dockerfile:19 DL4006 warning: Set the SHELL option -o pipefail before RUN with a pipe in it. If you are using /bin/sh in an alpine image or if your shell is symlinked to busybox then consider explicitly setting your SHELL to /bin/ash, or disable this check docker/appimage.Dockerfile:38 SC2016 info: Expressions don't expand in single quotes, use double quotes for that. docker/appimage.Dockerfile:51 DL3003 warning: Use WORKDIR to switch to a directory docker/appimage.Dockerfile:57 DL3003 warning: Use WORKDIR to switch to a directory docker/appimage.Dockerfile:63 DL4006 warning: Set the SHELL option -o pipefail before RUN with a pipe in it. If you are using /bin/sh in an alpine image or if your shell is symlinked to busybox then consider explicitly setting your SHELL to /bin/ash, or disable this check docker/appimage.Dockerfile:63 DL3003 warning: Use WORKDIR to switch to a directory docker/appimage.Dockerfile:73 DL4006 warning: Set the SHELL option -o pipefail before RUN with a pipe in it. If you are using /bin/sh in an alpine image or if your shell is symlinked to busybox then consider explicitly setting your SHELL to /bin/ash, or disable this check docker/appimage.Dockerfile:73 DL3003 warning: Use WORKDIR to switch to a directory docker/ci.Dockerfile:42 DL4006 warning: Set the SHELL option -o pipefail before RUN with a pipe in it. If you are using /bin/sh in an alpine image or if your shell is symlinked to busybox then consider explicitly setting your SHELL to /bin/ash, or disable this check docker/ci.Dockerfile:56 DL4006 warning: Set the SHELL option -o pipefail before RUN with a pipe in it. If you are using /bin/sh in an alpine image or if your shell is symlinked to busybox then consider explicitly setting your SHELL to /bin/ash, or disable this check docker/server/Dockerfile:22 DL4006 warning: Set the SHELL option -o pipefail before RUN with a pipe in it. If you are using /bin/sh in an alpine image or if your shell is symlinked to busybox then consider explicitly setting your SHELL to /bin/ash, or disable this check docker/vm.Dockerfile:22 SC2086 info: Double quote to prevent globbing and word splitting. docker/vm.Dockerfile:29 DL3003 warning: Use WORKDIR to switch to a directory docker/vm.Dockerfile:29 DL4006 warning: Set the SHELL option -o pipefail before RUN with a pipe in it. If you are using /bin/sh in an alpine image or if your shell is symlinked to busybox then consider explicitly setting your SHELL to /bin/ash, or disable this check website/Dockerfile:9 DL4006 warning: Set the SHELL option -o pipefail before RUN with a pipe in it. If you are using /bin/sh in an alpine image or if your shell is symlinked to busybox then consider explicitly setting your SHELL to /bin/ash, or disable this check ``` </details> <details><summary>REPOSITORY · trivy: first lines of the output</summary> ```text See documentation on https://megalinter.io/9.4.0/descriptors/repository_trivy/ ----------------------------------------------- ❌ [ERROR] for workspace /workspace/khyretos/kk-engine Linter raw log: 2026-10-27T14:42:59Z INFO [vulndb] Need to update DB 2026-10-27T14:42:59Z INFO [vulndb] Downloading vulnerability DB... 2026-10-27T14:42:59Z INFO [vulndb] Downloading artifact... repo="mirror.gcr.io/aquasec/trivy-db:2" 15.13 MiB / 130.01 MiB [------>_____________________________________________________] 11.63% ? p/s ?29.83 MiB / 130.01 MiB [------------->______________________________________________] 22.94% ? p/s ?44.54 MiB / 130.01 MiB [-------------------->_______________________________________] 34.26% ? p/s ?59.19 MiB / 130.01 MiB [--------------------->_________________________] 45.53% 73.41 MiB p/s ETA 0s74.29 MiB / 130.01 MiB [-------------------------->____________________] 57.14% 73.41 MiB p/s ETA 0s89.02 MiB / 130.01 MiB [-------------------------------->______________] 68.47% 73.41 MiB p/s ETA 0s103.23 MiB / 130.01 MiB [------------------------------------>_________] 79.40% 73.41 MiB p/s ETA 0s116.28 MiB / 130.01 MiB [----------------------------------------->____] 89.44% 73.41 MiB p/s ETA 0s129.10 MiB / 130.01 MiB [--------------------------------------------->] 99.31% 73.41 MiB p/s ETA 0s130.01 MiB / 130.01 MiB [-------------------------------------------->] 100.00% 71.56 MiB p/s ETA 0s130.01 MiB / 130.01 MiB [-------------------------------------------->] 100.00% 71.56 MiB p/s ETA 0s130.01 MiB / 130.01 MiB [-------------------------------------------->] 100.00% 71.56 MiB p/s ETA 0s130.01 MiB / 130.01 MiB [-------------------------------------------->] 100.00% 66.94 MiB p/s ETA 0s130.01 MiB / 130.01 MiB [-------------------------------------------->] 100.00% 66.94 MiB p/s ETA 0s130.01 MiB / 130.01 MiB [-------------------------------------------->] 100.00% 66.94 MiB p/s ETA 0s130.01 MiB / 130.01 MiB [-------------------------------------------->] 100.00% 62.62 MiB p/s ETA 0s130.01 MiB / 130.01 MiB [-------------------------------------------->] 100.00% 62.62 MiB p/s ETA 0s130.01 MiB / 130.01 MiB [-------------------------------------------->] 100.00% 62.62 MiB p/s ETA 0s130.01 MiB / 130.01 MiB [-------------------------------------------->] 100.00% 58.58 MiB p/s ETA 0s130.01 MiB / 130.01 MiB [-------------------------------------------->] 100.00% 58.58 MiB p/s ETA 0s130.01 MiB / 130.01 MiB [-------------------------------------------->] 100.00% 58.58 MiB p/s ETA 0s130.01 MiB / 130.01 MiB [-------------------------------------------->] 100.00% 54.80 MiB p/s ETA 0s130.01 MiB / 130.01 MiB [-----------------------------------------------] 100.00% 30.68 MiB p/s 4.4s2026-10-27T14:43:04Z INFO [vulndb] Artifact successfully downloaded repo="mirror.gcr.io/aquasec/trivy-db:2" 2026-10-27T14:43:04Z INFO [vuln] Vulnerability scanning is enabled 2026-10-27T14:43:04Z INFO [misconfig] Misconfiguration scanning is enabled 2026-10-27T14:43:04Z INFO [checks-client] Need to update the checks bundle 2026-10-27T14:43:04Z INFO [checks-client] Downloading the checks bundle... 234.57 KiB / 234.57 KiB [---------------------------------------------------------] 100.00% ? p/s 0s2026-10-27T14:43:05Z WARN [rego] Module will be skipped as current version of Trivy is older than minimum trivy version required - please update Trivy to use this module file_path="root/.cache/trivy/policy/content/policies/cloud/policies/aws/cloudfront/enable_logging.rego" minimum_trivy_version="0.72.0" 2026-10-27T14:43:05Z 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-27T14:43:05Z INFO Number of language-specific files num=0 2026-10-27T14:43:05Z INFO Detected config files num=9 Report Summary ┌──────────────────────────────┬────────────┬─────────────────┬───────────────────┐ │ Target │ Type │ Vulnerabilities │ Misconfigurations │ ├──────────────────────────────┼────────────┼─────────────────┼───────────────────┤ │ benchmarks/docker/Dockerfile │ dockerfile │ - │ 2 │ ├──────────────────────────────┼────────────┼─────────────────┼───────────────────┤ │ docker/android.Dockerfile │ dockerfile │ - │ 2 │ ├──────────────────────────────┼────────────┼─────────────────┼───────────────────┤ │ docker/appimage.Dockerfile │ dockerfile │ - │ 5 │ … ``` </details> 🧹 Formatting only (layout, no behaviour; the nightly lint-fix job applies these as a PR): YAML · prettier (57 files checked), BASH · shfmt (11 files checked), JSON · prettier (147 files checked) ✅ Clean: actionlint, git_diff, gitleaks, grype, jsonlint, secretlint, stylelint, trufflehog, xmllint <sub>MegaLinter on soucouyant · full logs in the Actions run · comment <code>/improve</code> for AI fix suggestions</sub>
Some checks are pending
CI / dependencies (pull_request) Successful in 8s
Required
Details
CI / std-includes (pull_request) Successful in 13s
Required
Details
CI / shipping-build (pull_request) Successful in 5m24s
Required
Details
CI / android (pull_request) Successful in 5m17s
Required
Details
Docs site / site (pull_request) Successful in 10s
Required
Details
lint / megalinter (pull_request) Successful in 56s
Required
Details
CI / release-build (pull_request) Successful in 19m6s
Required
Details
Windows (native) / build (pull_request) Successful in 9m25s
Required
Details
Benchmarks / benchmarks (pull_request) Successful in 23m3s
Required
Details
Windows (native) / test (pull_request) Successful in 6m36s
Required
Details
Windows (native) / publish (pull_request) Has been skipped
CI / build-test-and-coverage (pull_request) Successful in 1h36m54s
Required
Details
review / pr-agent
Required
kk / all-checks
Required
This pull request is marked as a work in progress.
This branch is out-of-date with the base branch
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin +refs/pull/134/head:khyretos/claude/creator-clothes
git switch khyretos/claude/creator-clothes
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!134
No description provided.