Day/night clock: the sun, sky and light follow the hour (ENG-F82) #130

Merged
kompas-review merged 2 commits from khyretos/claude/day-night-2 into main 2026-10-26 14:08:48 +01:00 AGit
Owner

Requested by Kees · project thread

Rebased onto main after #127 (Lua pathfinding), which touches the same ScriptModule lines. All 1316 tests pass locally.

What (ENG-F82: day/night cycle)

  • kke::DayNight (kke/DayNight.h), pure maths: the sun rises in the east at sunrise, crosses the south at noonElevation and sets in the west at sunset. For any hour it gives a gradient sky (zenith, horizon, sun colour and glow), the key light's direction, colour and strength, and the exposure, keyed on the sun's elevation from noon through golden hour, sunset and dusk to night. By night the key light becomes a pale blue moon, opposite the sun and at least 25° up. advance(dt) moves the clock and returns the hours, dawns and dusks it passed, in order and across midnight. apply() lights a Lighting, and changes the sky only when asked.
  • Sky refreshes are throttled. A new sky makes SkyResolver prefilter reflections and ambient SH again (plus a device wait for the upload), so ScriptClock moves the sun every frame but refreshes the sky at most every 0.5 s, and only when a colour changed by more than 3%.
  • Lua clock.* (kke::ScriptClock): start{hour, dayLength, noon, sunrise, sunset}, set(hour), pause, resume, stop, hour, time → "07:30", isDay, running, sun → direction, elevation, dayLength. Hooks Dawn, Dusk and Hour (e.hour). Choosing a mood stops the clock, as does unloading the script that started it.
  • Docs: MOODS.md "Time of day" (with costs and a "Not yet"), the SCRIPTING.md table, GAPS F82.

Tests

  • New test_day_night.cpp (5 tests): sunrise is east at 0°, noon is south at noonElevation, sunset is west, hours wrap, and a short low winter day works. Light: a white sun and blue sky at noon, a warm weaker sun at golden hour, blue moonlight with more exposure at night, and no jump in key strength minute by minute over 24 h. advance order around dawn, dusk and midnight, with a huge step capped at one day. apply turns a mood's fill off and leaves the sky alone unless asked. skyChanged says no for a midday minute and yes for an hour at dusk. The Lua API end to end: hooks fire in order, plus pause, set, time text, sun direction, release on unload, and NaN refused.
  • Full kke_tests: 1316 passed after the rebase, no warnings; mkdocs build --strict clean; the CI include and dependency checks pass.
  • In starter_game on the terrain: screenshots at 07:00, 12:00, 17:36, 18:36 and 01:00 show a morning, a midday, a golden, a dusk and a moonlit scene. Running at an hour a second for 8 s, Hour and Dusk fired on time (dusk at 18:04 game time, one frame after 18:00). The average frame went from 104 to 116 ms in the software-rendered headless check, with the sky refreshed at most twice a second. At a real game speed it refreshes far less often.

🤖 Generated with Claude Code

https://claude.ai/code/session_019rkS6wq7fV9fWdiLwxyyc7

<!-- claude-projects-attribution: {"github_login":"Khyretos"} --> _Requested by **Kees** · [project thread](https://claude.ai/code/project/chan_01Ds4PKvSLCNCCKr5TJxrV2A?thread=cmsg_01Ds4PKvSLCNCCKr5TJxrV2ABb2WaYyLkc7eUMNFVK1p2Z)_ Rebased onto main after #127 (Lua pathfinding), which touches the same ScriptModule lines. All 1316 tests pass locally. ## What (ENG-F82: day/night cycle) - `kke::DayNight` (`kke/DayNight.h`), pure maths: the sun rises in the east at `sunrise`, crosses the south at `noonElevation` and sets in the west at `sunset`. For any hour it gives a gradient sky (zenith, horizon, sun colour and glow), the key light's direction, colour and strength, and the exposure, keyed on the sun's elevation from noon through golden hour, sunset and dusk to night. By night the key light becomes a pale blue moon, opposite the sun and at least 25° up. `advance(dt)` moves the clock and returns the hours, dawns and dusks it passed, in order and across midnight. `apply()` lights a `Lighting`, and changes the sky only when asked. - Sky refreshes are throttled. A new sky makes `SkyResolver` prefilter reflections and ambient SH again (plus a device wait for the upload), so `ScriptClock` moves the sun every frame but refreshes the sky at most every 0.5 s, and only when a colour changed by more than 3%. - Lua `clock.*` (`kke::ScriptClock`): `start{hour, dayLength, noon, sunrise, sunset}`, `set(hour)`, `pause`, `resume`, `stop`, `hour`, `time` → "07:30", `isDay`, `running`, `sun` → direction, elevation, `dayLength`. Hooks `Dawn`, `Dusk` and `Hour` (e.hour). Choosing a mood stops the clock, as does unloading the script that started it. - Docs: MOODS.md "Time of day" (with costs and a "Not yet"), the SCRIPTING.md table, GAPS F82. ## Tests - New `test_day_night.cpp` (5 tests): sunrise is east at 0°, noon is south at `noonElevation`, sunset is west, hours wrap, and a short low winter day works. Light: a white sun and blue sky at noon, a warm weaker sun at golden hour, blue moonlight with more exposure at night, and no jump in key strength minute by minute over 24 h. `advance` order around dawn, dusk and midnight, with a huge step capped at one day. `apply` turns a mood's fill off and leaves the sky alone unless asked. `skyChanged` says no for a midday minute and yes for an hour at dusk. The Lua API end to end: hooks fire in order, plus pause, set, time text, sun direction, release on unload, and NaN refused. - Full `kke_tests`: 1316 passed after the rebase, no warnings; `mkdocs build --strict` clean; the CI include and dependency checks pass. - In starter_game on the terrain: screenshots at 07:00, 12:00, 17:36, 18:36 and 01:00 show a morning, a midday, a golden, a dusk and a moonlit scene. Running at an hour a second for 8 s, Hour and Dusk fired on time (dusk at 18:04 game time, one frame after 18:00). The average frame went from 104 to 116 ms in the software-rendered headless check, with the sky refreshed at most twice a second. At a real game speed it refreshes far less often. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_019rkS6wq7fV9fWdiLwxyyc7
Day/night clock: the sun, sky and light follow the hour (ENG-F82)
Some checks failed
CI / dependencies (pull_request) Successful in 9s
CI / std-includes (pull_request) Successful in 17s
review / pr-agent PR-Agent reviewed this commit
Benchmarks / benchmarks (pull_request) Successful in 5m1s
CI / release-build (pull_request) Successful in 7m48s
CI / shipping-build (pull_request) Successful in 15m46s
CI / android (pull_request) Successful in 5m33s
Docs site / site (pull_request) Successful in 39s
CI / build-test-and-coverage (pull_request) Failing after 25m8s
kk / all-checks Failed: CI / build-test-and-coverage (pull_request)
lint / megalinter (pull_request) Successful in 2m56s
Windows (native) / test (pull_request) Has been cancelled
Windows (native) / publish (pull_request) Has been cancelled
Windows (native) / build (pull_request) Has been cancelled
7bd1cdc95e
kke::DayNight moves the sun from east through south to west, and gives
the gradient sky, key light colour and strength, and exposure for any
hour, through golden hour and dusk to blue moonlight. advance() moves
the clock and reports the hours, dawns and dusks it passed; apply()
lights a Lighting, the sky only when asked (a new sky is re-analysed for
ambient and reflections, so ScriptClock refreshes it at most twice a
second and only when it changed visibly).

Lua clock.*: start, set, pause, resume, stop, hour, time, isDay,
running, sun, dayLength; Dawn, Dusk and Hour hooks. Choosing a mood
stops the clock. docs/MOODS.md "Time of day".

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

Title

Day/night clock: the sun, sky and light follow the hour (ENG-F82)


User description

Requested by Kees · project thread

Rebased onto main after #127 (Lua pathfinding), which touches the same ScriptModule lines. All 1316 tests pass locally.

What (ENG-F82: day/night cycle)

  • kke::DayNight (kke/DayNight.h), pure maths: the sun rises in the east at sunrise, crosses the south at noonElevation and sets in the west at sunset. For any hour it gives a gradient sky (zenith, horizon, sun colour and glow), the key light's direction, colour and strength, and the exposure, keyed on the sun's elevation from noon through golden hour, sunset and dusk to night. By night the key light becomes a pale blue moon, opposite the sun and at least 25° up. advance(dt) moves the clock and returns the hours, dawns and dusks it passed, in order and across midnight. apply() lights a Lighting, and changes the sky only when asked.
  • Sky refreshes are throttled. A new sky makes SkyResolver prefilter reflections and ambient SH again (plus a device wait for the upload), so ScriptClock moves the sun every frame but refreshes the sky at most every 0.5 s, and only when a colour changed by more than 3%.
  • Lua clock.* (kke::ScriptClock): start{hour, dayLength, noon, sunrise, sunset}, set(hour), pause, resume, stop, hour, time → "07:30", isDay, running, sun → direction, elevation, dayLength. Hooks Dawn, Dusk and Hour (e.hour). Choosing a mood stops the clock, as does unloading the script that started it.
  • Docs: MOODS.md "Time of day" (with costs and a "Not yet"), the SCRIPTING.md table, GAPS F82.

Tests

  • New test_day_night.cpp (5 tests): sunrise is east at 0°, noon is south at noonElevation, sunset is west, hours wrap, and a short low winter day works. Light: a white sun and blue sky at noon, a warm weaker sun at golden hour, blue moonlight with more exposure at night, and no jump in key strength minute by minute over 24 h. advance order around dawn, dusk and midnight, with a huge step capped at one day. apply turns a mood's fill off and leaves the sky alone unless asked. skyChanged says no for a midday minute and yes for an hour at dusk. The Lua API end to end: hooks fire in order, plus pause, set, time text, sun direction, release on unload, and NaN refused.
  • Full kke_tests: 1316 passed after the rebase, no warnings; mkdocs build --strict clean; the CI include and dependency checks pass.
  • In starter_game on the terrain: screenshots at 07:00, 12:00, 17:36, 18:36 and 01:00 show a morning, a midday, a golden, a dusk and a moonlit scene. Running at an hour a second for 8 s, Hour and Dusk fired on time (dusk at 18:04 game time, one frame after 18:00). The average frame went from 104 to 116 ms in the software-rendered headless check, with the sky refreshed at most twice a second. At a real game speed it refreshes far less often.

🤖 Generated with Claude Code

https://claude.ai/code/session_019rkS6wq7fV9fWdiLwxyyc7


PR Type

Enhancement


Description

  • Adds day/night cycle with sun, sky and light following the hour

  • Lua clock.* API controls time, triggers Dawn/Dusk/Hour hooks

  • Sky refreshes only when visibly changed to save CPU


Diagram Walkthrough

flowchart LR
  A["DayNight"] -->|"lookAt"| B["Sun position"]
  B -->|"sky"| C["Gradient sky"]
  B -->|"light"| D["Key light"]
  E["ScriptClock"] -->|"update"| A
  E -->|"hooks"| F["Lua scripts"]
  G["SkyResolver"] -.->|"prefilter"| C

File Walkthrough

Relevant files
Enhancement
6 files
DayNight.cpp
Implements day/night cycle math for sun, sky and light     
+152/-0 
ScriptClock.cpp
Lua clock API with hooks and sky throttling                           
+147/-0 
ScriptModule.cpp
Registers ScriptClock with Lua VM and update loop               
+5/-0     
DayNight.h
Defines DayNight class with lookAt and advance                     
+70/-0   
ScriptClock.h
Defines ScriptClock wrapper for Lua integration                   
+42/-0   
ScriptModule.h
Declares ScriptClock pointer in ScriptModule                         
+6/-0     
Tests
1 files
test_day_night.cpp
Tests sun path, lighting transitions and Lua hooks             
+150/-0 
Documentation
3 files
GAPS.md
Updates ENG-F82 status to completed clock features             
+1/-1     
MOODS.md
Documents time of day usage and Lua examples                         
+44/-0   
SCRIPTING.md
Adds clock module to Lua API reference table                         
+1/-0     
Configuration changes
2 files
CMakeLists.txt
Adds DayNight and ScriptClock source files                             
+2/-0     
CMakeLists.txt
Includes test_day_night.cpp in test executable                     
+1/-0     

## Title Day/night clock: the sun, sky and light follow the hour (ENG-F82) ___ ### **User description** <!-- claude-projects-attribution: {"github_login":"Khyretos"} --> _Requested by **Kees** · [project thread](https://claude.ai/code/project/chan_01Ds4PKvSLCNCCKr5TJxrV2A?thread=cmsg_01Ds4PKvSLCNCCKr5TJxrV2ABb2WaYyLkc7eUMNFVK1p2Z)_ Rebased onto main after #127 (Lua pathfinding), which touches the same ScriptModule lines. All 1316 tests pass locally. ## What (ENG-F82: day/night cycle) - `kke::DayNight` (`kke/DayNight.h`), pure maths: the sun rises in the east at `sunrise`, crosses the south at `noonElevation` and sets in the west at `sunset`. For any hour it gives a gradient sky (zenith, horizon, sun colour and glow), the key light's direction, colour and strength, and the exposure, keyed on the sun's elevation from noon through golden hour, sunset and dusk to night. By night the key light becomes a pale blue moon, opposite the sun and at least 25° up. `advance(dt)` moves the clock and returns the hours, dawns and dusks it passed, in order and across midnight. `apply()` lights a `Lighting`, and changes the sky only when asked. - Sky refreshes are throttled. A new sky makes `SkyResolver` prefilter reflections and ambient SH again (plus a device wait for the upload), so `ScriptClock` moves the sun every frame but refreshes the sky at most every 0.5 s, and only when a colour changed by more than 3%. - Lua `clock.*` (`kke::ScriptClock`): `start{hour, dayLength, noon, sunrise, sunset}`, `set(hour)`, `pause`, `resume`, `stop`, `hour`, `time` → "07:30", `isDay`, `running`, `sun` → direction, elevation, `dayLength`. Hooks `Dawn`, `Dusk` and `Hour` (e.hour). Choosing a mood stops the clock, as does unloading the script that started it. - Docs: MOODS.md "Time of day" (with costs and a "Not yet"), the SCRIPTING.md table, GAPS F82. ## Tests - New `test_day_night.cpp` (5 tests): sunrise is east at 0°, noon is south at `noonElevation`, sunset is west, hours wrap, and a short low winter day works. Light: a white sun and blue sky at noon, a warm weaker sun at golden hour, blue moonlight with more exposure at night, and no jump in key strength minute by minute over 24 h. `advance` order around dawn, dusk and midnight, with a huge step capped at one day. `apply` turns a mood's fill off and leaves the sky alone unless asked. `skyChanged` says no for a midday minute and yes for an hour at dusk. The Lua API end to end: hooks fire in order, plus pause, set, time text, sun direction, release on unload, and NaN refused. - Full `kke_tests`: 1316 passed after the rebase, no warnings; `mkdocs build --strict` clean; the CI include and dependency checks pass. - In starter_game on the terrain: screenshots at 07:00, 12:00, 17:36, 18:36 and 01:00 show a morning, a midday, a golden, a dusk and a moonlit scene. Running at an hour a second for 8 s, Hour and Dusk fired on time (dusk at 18:04 game time, one frame after 18:00). The average frame went from 104 to 116 ms in the software-rendered headless check, with the sky refreshed at most twice a second. At a real game speed it refreshes far less often. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_019rkS6wq7fV9fWdiLwxyyc7 ___ ### **PR Type** Enhancement ___ ### **Description** - Adds day/night cycle with sun, sky and light following the hour - Lua clock.* API controls time, triggers Dawn/Dusk/Hour hooks - Sky refreshes only when visibly changed to save CPU ___ ### Diagram Walkthrough ```mermaid flowchart LR A["DayNight"] -->|"lookAt"| B["Sun position"] B -->|"sky"| C["Gradient sky"] B -->|"light"| D["Key light"] E["ScriptClock"] -->|"update"| A E -->|"hooks"| F["Lua scripts"] G["SkyResolver"] -.->|"prefilter"| C ``` <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>6 files</summary><table> <tr> <td><strong>DayNight.cpp</strong><dd><code>Implements day/night cycle math for sun, sky and light</code>&nbsp; &nbsp; &nbsp; </dd></td> <td><a href="https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/130/head/engine/src/DayNight.cpp">+152/-0</a>&nbsp; </td> </tr> <tr> <td><strong>ScriptClock.cpp</strong><dd><code>Lua clock API with hooks and sky throttling</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/130/head/engine/src/ScriptClock.cpp">+147/-0</a>&nbsp; </td> </tr> <tr> <td><strong>ScriptModule.cpp</strong><dd><code>Registers ScriptClock with Lua VM and update loop</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/130/head/engine/src/modules/ScriptModule.cpp">+5/-0</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>DayNight.h</strong><dd><code>Defines DayNight class with lookAt and advance</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td> <td><a href="https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/130/head/engine/include/kke/DayNight.h">+70/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>ScriptClock.h</strong><dd><code>Defines ScriptClock wrapper for Lua integration</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/130/head/engine/include/kke/ScriptClock.h">+42/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>ScriptModule.h</strong><dd><code>Declares ScriptClock pointer in ScriptModule</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/130/head/engine/include/kke/modules/ScriptModule.h">+6/-0</a>&nbsp; &nbsp; &nbsp; </td> </tr> </table></details></td></tr><tr><td><strong>Tests</strong></td><td><details><summary>1 files</summary><table> <tr> <td><strong>test_day_night.cpp</strong><dd><code>Tests sun path, lighting transitions and Lua hooks</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/130/head/tests/test_day_night.cpp">+150/-0</a>&nbsp; </td> </tr> </table></details></td></tr><tr><td><strong>Documentation</strong></td><td><details><summary>3 files</summary><table> <tr> <td><strong>GAPS.md</strong><dd><code>Updates ENG-F82 status to completed clock features</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/130/head/docs/GAPS.md">+1/-1</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>MOODS.md</strong><dd><code>Documents time of day usage and Lua examples</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/130/head/docs/MOODS.md">+44/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>SCRIPTING.md</strong><dd><code>Adds clock module to Lua API reference table</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/130/head/docs/SCRIPTING.md">+1/-0</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>Adds DayNight and ScriptClock source files</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/130/head/engine/CMakeLists.txt">+2/-0</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>CMakeLists.txt</strong><dd><code>Includes test_day_night.cpp in test executable</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td> <td><a href="https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/130/head/tests/CMakeLists.txt">+1/-0</a>&nbsp; &nbsp; &nbsp; </td> </tr> </table></details></td></tr></tr></tbody></table> </details> ___
Collaborator

PR Reviewer Guide 🔍

(Review updated until commit 6723e07f1e)

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

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

Possible Bug in Advance Logic

In the advance function, when calculating events that occurred during a time step, the code checks if m.at > from && m.at <= to. However, from is the current hour (e.g., 5.5) and to is the new hour (e.g., 6.5). If an event happens exactly at the start time (which shouldn't happen due to floating point precision but theoretically), it would be missed. More critically, the logic uses std::floor(wrap24(m.at) + 1e-4f) to get the hour for Hour events. This epsilon addition can cause issues near midnight boundaries or when m.at is very close to an integer hour from below, potentially rounding up incorrectly. For example, if m.at is 6.0000001, adding 1e-4f makes it 6.0001, floor gives 6. But if m.at is meant to represent the exact moment of crossing, the epsilon might push it into the next hour bucket incorrectly in edge cases. The condition m.at > from should probably be m.at >= from to handle the case where the clock starts exactly on an hour boundary and advances.

if (m.at > from && m.at <= to) out.push_back({m.event, int(std::floor(wrap24(m.at) + 1e-4f)) % 24});
Missing Error Handling for Lua Hooks

In the update function, when calling hooks like vm.callHookWith, there is no check to see if the hook actually exists or if the call succeeded. If a script registers a hook with a name that doesn't match "Dawn", "Dusk", or "Hour", or if the hook function itself throws an error, the engine might crash or behave unpredictably. The code assumes the hook will always be found and executed successfully. It would be safer to wrap these calls in error handling or check if the hook exists before attempting to call it.

    vm.callHookWith(name, [hour](lua_State* L) {
        lua_newtable(L);
        lua_pushinteger(L, hour);
        lua_setfield(L, -2, "hour");
        return 1;
    });
}
Unused Member Variable

The member variable m_hairRoots is declared in the header file but its usage is not fully clear from the diff. While it is used in CharacterCreatorModule.cpp to store the number of roots requested by a hairstyle, the initialization and update logic for this variable is only visible in the cpp file. If this variable is intended to be exposed or used elsewhere, it should be documented. If it's purely internal, consider making it private or removing it if it's not necessary.

size_t m_hairRoots = 0; // guides buildHair asked for (cornrows have only 6-14)
✅ Resolved findings

engine/src/ScriptClock.cpp:50-55

Potential Resource Leak

In ScriptClock::release, the function sets m_active = false and m_clock.running = false only if m_owner == source. If a script unloads but does not match the owner (which shouldn't happen in normal flow but could occur if m_owner is stale or logic diverges), the clock remains active and continues to update lighting every frame. More critically, if m_app becomes null while the clock is active (e.g., application shutdown sequence), subsequent calls to update will dereference a null pointer in light() at line 17 (if (!m_app || !m_active) return;). While there is a check for !m_app, the m_lastSky and other member variables are not cleared, potentially leading to undefined behavior if the clock is reused or if the application state is inconsistent. It is recommended to explicitly clear m_lastSky and reset other state in release to ensure a clean shutdown.

engine/src/DayNight.cpp:119

Possible Bug in Advance Logic

In the advance function, when calculating events that occur between from and to, the code uses std::floor(wrap24(m.at) + 1e-4f) to determine the hour. The epsilon 1e-4f is added before flooring. This can cause incorrect hour values when the wrapped time is extremely close to an integer from below (e.g., 6.00009). For instance, if m.at wraps to 6.00009, adding 1e-4f makes it 6.00019, which floors to 6. However, if the logic intended was to capture the exact integer hour reached, this epsilon might shift the boundary slightly or behave unexpectedly near midnight transitions where floating point precision is critical. A more robust approach would be to use a small epsilon for comparison rather than shifting the value before flooring, or to ensure the wrapping logic aligns perfectly with integer boundaries without arbitrary offsets.

## PR Reviewer Guide 🔍 <!-- pr-agent:review:full --> #### (Review updated until commit https://git.kreative-kompas.com/khyretos/kk-engine/commit/6723e07f1ecfb63566b40999f4e976ac8563c7ae) 📋 **Focus areas:** 🔴 3 open · ✅ 2 handled. Every push re-checks them; a handled area moves to *Resolved findings* at the bottom. <table> <tr><td>⏱️&nbsp;<strong>Estimated effort to review</strong>: 3 🔵🔵🔵⚪⚪</td></tr> <tr><td>🧪&nbsp;<strong>PR contains tests</strong></td></tr> <tr><td>🔒&nbsp;<strong>No security concerns identified</strong></td></tr> <tr><td>⚡&nbsp;<strong>Recommended focus areas for review</strong><br><br> <details><summary><a href='https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/130/head/engine/src/DayNight.cpp#L119-L119'><strong>Possible Bug in Advance Logic</strong></a> In the `advance` function, when calculating events that occurred during a time step, the code checks if `m.at > from && m.at <= to`. However, `from` is the current hour (e.g., 5.5) and `to` is the new hour (e.g., 6.5). If an event happens exactly at the start time (which shouldn't happen due to floating point precision but theoretically), it would be missed. More critically, the logic uses `std::floor(wrap24(m.at) + 1e-4f)` to get the hour for Hour events. This epsilon addition can cause issues near midnight boundaries or when `m.at` is very close to an integer hour from below, potentially rounding up incorrectly. For example, if `m.at` is 6.0000001, adding 1e-4f makes it 6.0001, floor gives 6. But if `m.at` is meant to represent the exact moment of crossing, the epsilon might push it into the next hour bucket incorrectly in edge cases. The condition `m.at > from` should probably be `m.at >= from` to handle the case where the clock starts exactly on an hour boundary and advances. </summary> ```c++ if (m.at > from && m.at <= to) out.push_back({m.event, int(std::floor(wrap24(m.at) + 1e-4f)) % 24}); ``` </details> <details><summary><a href='https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/130/head/engine/src/ScriptClock.cpp#L40-L46'><strong>Missing Error Handling for Lua Hooks</strong></a> In the `update` function, when calling hooks like `vm.callHookWith`, there is no check to see if the hook actually exists or if the call succeeded. If a script registers a hook with a name that doesn't match "Dawn", "Dusk", or "Hour", or if the hook function itself throws an error, the engine might crash or behave unpredictably. The code assumes the hook will always be found and executed successfully. It would be safer to wrap these calls in error handling or check if the hook exists before attempting to call it. </summary> ```c++ vm.callHookWith(name, [hour](lua_State* L) { lua_newtable(L); lua_pushinteger(L, hour); lua_setfield(L, -2, "hour"); return 1; }); } ``` </details> <details><summary><a href='https://git.kreative-kompas.com/khyretos/kk-engine/src/branch/refs/pull/130/head/engine/include/kke/modules/CharacterCreatorModule.h#L158-L158'><strong>Unused Member Variable</strong></a> The member variable `m_hairRoots` is declared in the header file but its usage is not fully clear from the diff. While it is used in `CharacterCreatorModule.cpp` to store the number of roots requested by a hairstyle, the initialization and update logic for this variable is only visible in the cpp file. If this variable is intended to be exposed or used elsewhere, it should be documented. If it's purely internal, consider making it private or removing it if it's not necessary. </summary> ```c size_t m_hairRoots = 0; // guides buildHair asked for (cornrows have only 6-14) ``` </details> </td></tr> </table> <details> <summary>✅ Resolved findings</summary> ### engine/src/ScriptClock.cpp:50-55 **Potential Resource Leak** In `ScriptClock::release`, the function sets `m_active = false` and `m_clock.running = false` only if `m_owner == source`. If a script unloads but does not match the owner (which shouldn't happen in normal flow but could occur if `m_owner` is stale or logic diverges), the clock remains active and continues to update lighting every frame. More critically, if `m_app` becomes null while the clock is active (e.g., application shutdown sequence), subsequent calls to `update` will dereference a null pointer in `light()` at line 17 (`if (!m_app || !m_active) return;`). While there is a check for `!m_app`, the `m_lastSky` and other member variables are not cleared, potentially leading to undefined behavior if the clock is reused or if the application state is inconsistent. It is recommended to explicitly clear `m_lastSky` and reset other state in `release` to ensure a clean shutdown. ### engine/src/DayNight.cpp:119 **Possible Bug in Advance Logic** In the `advance` function, when calculating events that occur between `from` and `to`, the code uses `std::floor(wrap24(m.at) + 1e-4f)` to determine the hour. The epsilon `1e-4f` is added before flooring. This can cause incorrect hour values when the wrapped time is extremely close to an integer from below (e.g., 6.00009). For instance, if `m.at` wraps to 6.00009, adding 1e-4f makes it 6.00019, which floors to 6. However, if the logic intended was to capture the exact integer hour reached, this epsilon might shift the boundary slightly or behave unexpectedly near midnight transitions where floating point precision is critical. A more robust approach would be to use a small epsilon for comparison rather than shifting the value before flooring, or to ensure the wrapping logic aligns perfectly with integer boundaries without arbitrary offsets. </details> <!-- pr-agent-review-state:v1 {"findings":[{"body":"**Possible Bug in Advance Logic**\n\nIn the `advance` function, when calculating events that occur between `from` and `to`, the code uses `std::floor(wrap24(m.at) + 1e-4f)` to determine the hour. The epsilon `1e-4f` is added before flooring. This can cause incorrect hour values when the wrapped time is extremely close to an integer from below (e.g., 6.00009). For instance, if `m.at` wraps to 6.00009, adding 1e-4f makes it 6.00019, which floors to 6. However, if the logic intended was to capture the exact integer hour reached, this epsilon might shift the boundary slightly or behave unexpectedly near midnight transitions where floating point precision is critical. A more robust approach would be to use a small epsilon for comparison rather than shifting the value before flooring, or to ensure the wrapping logic aligns perfectly with integer boundaries without arbitrary offsets.","finding_id":"3600afa04c51","first_seen":"2026-10-26T10:29:20.713267Z","last_seen":"2026-10-26T10:29:20.713267Z","last_seen_head_sha":"7bd1cdc95e57a9950672e88d5d36f0563439c687","line_end":119,"line_start":119,"path":"engine/src/DayNight.cpp","resolution_run_id":"https://git.kreative-kompas.com/khyretos/kk-engine/commit/6723e07f1ecfb63566b40999f4e976ac8563c7ae","resolved_at":"2026-10-26T11:26:20.855310Z","resolved_head_sha":"6723e07f1ecfb63566b40999f4e976ac8563c7ae","state":"RESOLVED"},{"body":"**Missing Error Handling for Lua Hooks**\n\nIn the `update` function, when calling hooks like `vm.callHookWith`, there is no check to see if the hook actually exists or if the call succeeded. If a script registers a hook with a name that doesn't match \"Dawn\", \"Dusk\", or \"Hour\", or if the hook function itself throws an error, the engine might crash or behave unpredictably. The code assumes the hook will always be found and executed successfully. It would be safer to wrap these calls in error handling or check if the hook exists before attempting to call it.","finding_id":"3b41abf54ff5","first_seen":"2026-10-26T11:26:20.855310Z","last_seen":"2026-10-26T11:26:20.855310Z","last_seen_head_sha":"6723e07f1ecfb63566b40999f4e976ac8563c7ae","line_end":46,"line_start":40,"path":"engine/src/ScriptClock.cpp","state":"ACTIVE"},{"body":"**Potential Resource Leak**\n\nIn `ScriptClock::release`, the function sets `m_active = false` and `m_clock.running = false` only if `m_owner == source`. If a script unloads but does not match the owner (which shouldn't happen in normal flow but could occur if `m_owner` is stale or logic diverges), the clock remains active and continues to update lighting every frame. More critically, if `m_app` becomes null while the clock is active (e.g., application shutdown sequence), subsequent calls to `update` will dereference a null pointer in `light()` at line 17 (`if (!m_app || !m_active) return;`). While there is a check for `!m_app`, the `m_lastSky` and other member variables are not cleared, potentially leading to undefined behavior if the clock is reused or if the application state is inconsistent. It is recommended to explicitly clear `m_lastSky` and reset other state in `release` to ensure a clean shutdown.","finding_id":"ae0a72d906d9","first_seen":"2026-10-26T10:29:20.713267Z","last_seen":"2026-10-26T10:29:20.713267Z","last_seen_head_sha":"7bd1cdc95e57a9950672e88d5d36f0563439c687","line_end":55,"line_start":50,"path":"engine/src/ScriptClock.cpp","resolution_run_id":"https://git.kreative-kompas.com/khyretos/kk-engine/commit/6723e07f1ecfb63566b40999f4e976ac8563c7ae","resolved_at":"2026-10-26T11:26:20.855310Z","resolved_head_sha":"6723e07f1ecfb63566b40999f4e976ac8563c7ae","state":"RESOLVED"},{"body":"**Unused Member Variable**\n\nThe member variable `m_hairRoots` is declared in the header file but its usage is not fully clear from the diff. While it is used in `CharacterCreatorModule.cpp` to store the number of roots requested by a hairstyle, the initialization and update logic for this variable is only visible in the cpp file. If this variable is intended to be exposed or used elsewhere, it should be documented. If it's purely internal, consider making it private or removing it if it's not necessary.","finding_id":"d60822972850","first_seen":"2026-10-26T11:26:20.855310Z","last_seen":"2026-10-26T11:26:20.855310Z","last_seen_head_sha":"6723e07f1ecfb63566b40999f4e976ac8563c7ae","line_end":158,"line_start":158,"path":"engine/include/kke/modules/CharacterCreatorModule.h","state":"ACTIVE"},{"body":"**Possible Bug in Advance Logic**\n\nIn the `advance` function, when calculating events that occurred during a time step, the code checks if `m.at > from && m.at <= to`. However, `from` is the current hour (e.g., 5.5) and `to` is the new hour (e.g., 6.5). If an event happens exactly at the start time (which shouldn't happen due to floating point precision but theoretically), it would be missed. More critically, the logic uses `std::floor(wrap24(m.at) + 1e-4f)` to get the hour for Hour events. This epsilon addition can cause issues near midnight boundaries or when `m.at` is very close to an integer hour from below, potentially rounding up incorrectly. For example, if `m.at` is 6.0000001, adding 1e-4f makes it 6.0001, floor gives 6. But if `m.at` is meant to represent the exact moment of crossing, the epsilon might push it into the next hour bucket incorrectly in edge cases. The condition `m.at > from` should probably be `m.at >= from` to handle the case where the clock starts exactly on an hour boundary and advances.","finding_id":"d7df0614cbda","first_seen":"2026-10-26T11:26:20.855310Z","last_seen":"2026-10-26T11:26:20.855310Z","last_seen_head_sha":"6723e07f1ecfb63566b40999f4e976ac8563c7ae","line_end":119,"line_start":119,"path":"engine/src/DayNight.cpp","state":"ACTIVE"}],"last_run":{"complete":true,"excluded_files":[],"head_sha":"6723e07f1ecfb63566b40999f4e976ac8563c7ae","kind":"full","run_id":"https://git.kreative-kompas.com/khyretos/kk-engine/commit/6723e07f1ecfb63566b40999f4e976ac8563c7ae"},"schema_version":1} -->
Collaborator

PR Code Suggestions ✨

No code suggestions found for the PR.

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

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

Nothing here blocks the merge (report only).

Check Errors Warnings Files
❌ MARKDOWN · markdownlint 24 0 253
❌ CPP · cppcheck 9 0 935
❌ 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-26T11:39:35Z	INFO	[vulndb] Need to update DB
2026-10-26T11:39:35Z	INFO	[vulndb] Downloading vulnerability DB...
2026-10-26T11:39:35Z	INFO	[vulndb] Downloading artifact...	repo="mirror.gcr.io/aquasec/trivy-db:2"
2.08 MiB / 129.73 MiB [>_____________________________________________________________] 1.60% ? p/s ?8.78 MiB / 129.73 MiB [---->_________________________________________________________] 6.77% ? p/s ?16.88 MiB / 129.73 MiB [------->____________________________________________________] 13.01% ? p/s ?25.77 MiB / 129.73 MiB [--------->_____________________________________] 19.87% 39.79 MiB p/s ETA 2s32.40 MiB / 129.73 MiB [----------->___________________________________] 24.97% 39.79 MiB p/s ETA 2s40.27 MiB / 129.73 MiB [-------------->________________________________] 31.04% 39.79 MiB p/s ETA 2s50.16 MiB / 129.73 MiB [------------------>____________________________] 38.66% 39.82 MiB p/s ETA 1s59.57 MiB / 129.73 MiB [--------------------->_________________________] 45.92% 39.82 MiB p/s ETA 1s68.24 MiB / 129.73 MiB [------------------------>______________________] 52.60% 39.82 MiB p/s ETA 1s75.58 MiB / 129.73 MiB [--------------------------->___________________] 58.26% 39.99 MiB p/s ETA 1s84.26 MiB / 129.73 MiB [------------------------------>________________] 64.95% 39.99 MiB p/s ETA 1s93.96 MiB / 129.73 MiB [---------------------------------->____________] 72.43% 39.99 MiB p/s ETA 0s102.11 MiB / 129.73 MiB [------------------------------------>_________] 78.71% 40.26 MiB p/s ETA 0s108.99 MiB / 129.73 MiB [-------------------------------------->_______] 84.01% 40.26 MiB p/s ETA 0s119.05 MiB / 129.73 MiB [------------------------------------------>___] 91.77% 40.26 MiB p/s ETA 0s128.08 MiB / 129.73 MiB [--------------------------------------------->] 98.73% 40.48 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 40.48 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 40.48 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 38.05 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 38.05 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 38.05 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 35.59 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 35.59 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 35.59 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 33.30 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 33.30 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 33.30 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 31.15 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 31.15 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 31.15 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 29.14 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 29.14 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 29.14 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 27.26 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 27.26 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 27.26 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 25.50 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 25.50 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 25.50 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 23.86 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 23.86 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 23.86 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 22.32 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 22.32 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 22.32 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 20.88 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 20.88 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 20.88 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 19.53 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 19.53 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 19.53 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 18.27 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 18.27 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 18.27 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 17.09 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 17.09 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 17.09 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 15.99 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 15.99 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 15.99 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 14.96 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 14.96 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 14.96 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 13.99 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 13.99 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 13.99 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 13.09 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 13.09 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 13.09 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 12.24 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 12.24 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 12.24 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 11.45 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 11.45 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 11.45 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 10.72 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 10.72 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 10.72 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 10.02 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 10.02 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 10.02 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [--------------------------------------------->] 100.00% 9.38 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [--------------------------------------------->] 100.00% 9.38 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [--------------------------------------------->] 100.00% 9.38 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [--------------------------------------------->] 100.00% 8.77 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [--------------------------------------------->] 100.00% 8.77 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [--------------------------------------------->] 100.00% 8.77 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [--------------------------------------------->] 100.00% 8.21 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [--------------------------------------------->] 100.00% 8.21 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [--------------------------------------------->] 100.00% 8.21 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [--------------------------------------------->] 100.00% 7.68 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [--------------------------------------------->] 100.00% 7.68 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [--------------------------------------------->] 100.00% 7.68 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [--------------------------------------------->] 100.00% 7.18 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [--------------------------------------------->] 100.00% 7.18 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [--------------------------------------------->] 100.00% 7.18 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [--------------------------------------------->] 100.00% 6.72 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [--------------------------------------------->] 100.00% 6.72 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------------] 100.00% 6.62 MiB p/s 20s2026-10-26T11:39:56Z	INFO	[vulndb] Artifact successfully downloaded	repo="mirror.gcr.io/aquasec/trivy-db:2"
2026-10-26T11:39:56Z	INFO	[vuln] Vulnerability scanning is enabled
2026-10-26T11:39:56Z	INFO	[misconfig] Misconfiguration scanning is enabled
2026-10-26T11:39:56Z	INFO	[checks-client] Need to update the checks bundle
2026-10-26T11:39:56Z	INFO	[checks-client] Downloading the checks bundle...
234.57 KiB / 234.57 KiB [------------------------------------------------------] 100.00% ? p/s 100ms2026-10-26T11:39:58Z	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-26T11:39:59Z	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-26T11:39:59Z	INFO	Number of language-specific files	num=0
2026-10-26T11:39:59Z	INFO	Detected config files	num=9
Report Summary
┌──────────────────────────────┬────────────┬─────────────────┬───────────────────┐
│            Target            │    Type    │ Vulnerabilities │ Misconfigurations │
├──────────────────────────────┼────────────┼─────────────────┼───────────────────┤
│ benchmarks/docker/Dockerfile │ dockerfile │        -        │         2         │
├──────────────────────────────┼────────────┼─────────────────┼───────────────────┤
│ docker/android.Dockerfile    │ dockerfile │        -        │         2         │
├──────────────────────────────┼────────────┼─────────────────┼───────────────────┤
│ docker/appimage.Dockerfile   │ dockerfile │        -        │         5         │
…

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

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

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

<!-- kk-lint --> ## 🧹 Lint report · ❌ 38 problems in 6 checks · 🧹 3 formatting checks to tidy Nothing here blocks the merge (report only). | | Check | Errors | Warnings | Files | |---|---|---:|---:|---:| | ❌ | MARKDOWN · markdownlint | 24 | 0 | 253 | | ❌ | CPP · cppcheck | 9 | 0 | 935 | | ❌ | 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-26T11:39:35Z INFO [vulndb] Need to update DB 2026-10-26T11:39:35Z INFO [vulndb] Downloading vulnerability DB... 2026-10-26T11:39:35Z INFO [vulndb] Downloading artifact... repo="mirror.gcr.io/aquasec/trivy-db:2" 2.08 MiB / 129.73 MiB [>_____________________________________________________________] 1.60% ? p/s ?8.78 MiB / 129.73 MiB [---->_________________________________________________________] 6.77% ? p/s ?16.88 MiB / 129.73 MiB [------->____________________________________________________] 13.01% ? p/s ?25.77 MiB / 129.73 MiB [--------->_____________________________________] 19.87% 39.79 MiB p/s ETA 2s32.40 MiB / 129.73 MiB [----------->___________________________________] 24.97% 39.79 MiB p/s ETA 2s40.27 MiB / 129.73 MiB [-------------->________________________________] 31.04% 39.79 MiB p/s ETA 2s50.16 MiB / 129.73 MiB [------------------>____________________________] 38.66% 39.82 MiB p/s ETA 1s59.57 MiB / 129.73 MiB [--------------------->_________________________] 45.92% 39.82 MiB p/s ETA 1s68.24 MiB / 129.73 MiB [------------------------>______________________] 52.60% 39.82 MiB p/s ETA 1s75.58 MiB / 129.73 MiB [--------------------------->___________________] 58.26% 39.99 MiB p/s ETA 1s84.26 MiB / 129.73 MiB [------------------------------>________________] 64.95% 39.99 MiB p/s ETA 1s93.96 MiB / 129.73 MiB [---------------------------------->____________] 72.43% 39.99 MiB p/s ETA 0s102.11 MiB / 129.73 MiB [------------------------------------>_________] 78.71% 40.26 MiB p/s ETA 0s108.99 MiB / 129.73 MiB [-------------------------------------->_______] 84.01% 40.26 MiB p/s ETA 0s119.05 MiB / 129.73 MiB [------------------------------------------>___] 91.77% 40.26 MiB p/s ETA 0s128.08 MiB / 129.73 MiB [--------------------------------------------->] 98.73% 40.48 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 40.48 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 40.48 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 38.05 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 38.05 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 38.05 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 35.59 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 35.59 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 35.59 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 33.30 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 33.30 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 33.30 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 31.15 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 31.15 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 31.15 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 29.14 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 29.14 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 29.14 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 27.26 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 27.26 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 27.26 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 25.50 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 25.50 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 25.50 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 23.86 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 23.86 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 23.86 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 22.32 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 22.32 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 22.32 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 20.88 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 20.88 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 20.88 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 19.53 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 19.53 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 19.53 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 18.27 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 18.27 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 18.27 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 17.09 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 17.09 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 17.09 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 15.99 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 15.99 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 15.99 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 14.96 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 14.96 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 14.96 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 13.99 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 13.99 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 13.99 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 13.09 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 13.09 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 13.09 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 12.24 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 12.24 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 12.24 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 11.45 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 11.45 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 11.45 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 10.72 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 10.72 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 10.72 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 10.02 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 10.02 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------->] 100.00% 10.02 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [--------------------------------------------->] 100.00% 9.38 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [--------------------------------------------->] 100.00% 9.38 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [--------------------------------------------->] 100.00% 9.38 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [--------------------------------------------->] 100.00% 8.77 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [--------------------------------------------->] 100.00% 8.77 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [--------------------------------------------->] 100.00% 8.77 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [--------------------------------------------->] 100.00% 8.21 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [--------------------------------------------->] 100.00% 8.21 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [--------------------------------------------->] 100.00% 8.21 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [--------------------------------------------->] 100.00% 7.68 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [--------------------------------------------->] 100.00% 7.68 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [--------------------------------------------->] 100.00% 7.68 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [--------------------------------------------->] 100.00% 7.18 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [--------------------------------------------->] 100.00% 7.18 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [--------------------------------------------->] 100.00% 7.18 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [--------------------------------------------->] 100.00% 6.72 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [--------------------------------------------->] 100.00% 6.72 MiB p/s ETA 0s129.73 MiB / 129.73 MiB [-------------------------------------------------] 100.00% 6.62 MiB p/s 20s2026-10-26T11:39:56Z INFO [vulndb] Artifact successfully downloaded repo="mirror.gcr.io/aquasec/trivy-db:2" 2026-10-26T11:39:56Z INFO [vuln] Vulnerability scanning is enabled 2026-10-26T11:39:56Z INFO [misconfig] Misconfiguration scanning is enabled 2026-10-26T11:39:56Z INFO [checks-client] Need to update the checks bundle 2026-10-26T11:39:56Z INFO [checks-client] Downloading the checks bundle... 234.57 KiB / 234.57 KiB [------------------------------------------------------] 100.00% ? p/s 100ms2026-10-26T11:39:58Z 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-26T11:39:59Z 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-26T11:39:59Z INFO Number of language-specific files num=0 2026-10-26T11:39:59Z 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>
Creator self-test: count the hairstyle's own guides
All checks were successful
CI / dependencies (pull_request) Successful in 12s
CI / std-includes (pull_request) Successful in 16s
review / pr-agent PR-Agent reviewed this commit
Benchmarks / benchmarks (pull_request) Successful in 4m40s
CI / release-build (pull_request) Successful in 7m33s
Docs site / site (pull_request) Successful in 19s
lint / megalinter (pull_request) Successful in 2m56s
CI / android (pull_request) Successful in 5m16s
CI / shipping-build (pull_request) Successful in 29m50s
Windows (native) / build (pull_request) Successful in 24m9s
Windows (native) / test (pull_request) Successful in 6m42s
Windows (native) / publish (pull_request) Has been skipped
CI / build-test-and-coverage (pull_request) Successful in 1h42m24s
kk / all-checks All 13 checks passed
6723e07f1e
The self-test wanted more than 40 hair guides, but a plaited style has
one per row or braid: cornrows grow 6 to 14. The hairstyle is random,
so the test failed whenever cornrows came up (PR #130's CI). It now
checks that every guide buildHair asked for is in the world.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019rkS6wq7fV9fWdiLwxyyc7
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!130
No description provided.