Brush walker fx - a matrix-inspired colorful 2D effect#5452
Brush walker fx - a matrix-inspired colorful 2D effect#5452
Conversation
WalkthroughAdds a new 2D "Brush Walker" effect plus an audio‑reactive variant, introducing per‑segment persistent Changes
Sequence Diagram(s)mermaid Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
usermods/user_fx/user_fx.cpp (1)
1403-1491: Extract the shared walker loop before these modes drift further.Both modes duplicate allocation, init, timing, fade, and render/update logic. The only real difference is the spawn predicate, and the bug above is already an example of the copies diverging.
Also applies to: 1504-1607
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@usermods/user_fx/user_fx.cpp` around lines 1403 - 1491, The brush walker logic in mode_brushwalker duplicates allocation, initialization, timing, fade, spawn and update/render loops (see BrushWalker, SEGENV, SEGMENT, BrushWalkerCountActive, BrushWalkerTrySpawn), causing drift/bugs between modes; extract shared behavior into a small helper API: create AllocateOrInitWalkers(SEGENV, BrushWalker*, count) to handle SEGENV.allocateData and zero/init walkers, a ShouldStep(SEGENV, interval) timing helper to manage SEGENV.step, a CommonFade(SEGMENT, fadeRate) call, and a WalkersStepRender(BrushWalker*, count, cols, rows, palStep, SEGMENT, SEGCOLOR) that does palette/primary color selection, setPixelColorXY, movement, colorIndex increment and deactivation; modify mode_brushwalker and the other duplicated mode to call these helpers and only supply the spawn predicate (e.g., use BrushWalkerTrySpawn or alternate spawn logic) and per-mode parameters like spawnChance, maxWalkers, fadeRate, palStep and interval.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@usermods/user_fx/user_fx.cpp`:
- Around line 1569-1570: The code reads the peak flag from the wrong u_data
index; change the access in user_fx.cpp where isPeak is set (uint8_t isPeak =
*(uint8_t *)um_data->u_data[2];) to read from u_data[3] instead so the AR
variant uses the actual samplePeak flag (i.e., use um_data->u_data[3] when
dereferencing the peak byte).
---
Nitpick comments:
In `@usermods/user_fx/user_fx.cpp`:
- Around line 1403-1491: The brush walker logic in mode_brushwalker duplicates
allocation, initialization, timing, fade, spawn and update/render loops (see
BrushWalker, SEGENV, SEGMENT, BrushWalkerCountActive, BrushWalkerTrySpawn),
causing drift/bugs between modes; extract shared behavior into a small helper
API: create AllocateOrInitWalkers(SEGENV, BrushWalker*, count) to handle
SEGENV.allocateData and zero/init walkers, a ShouldStep(SEGENV, interval) timing
helper to manage SEGENV.step, a CommonFade(SEGMENT, fadeRate) call, and a
WalkersStepRender(BrushWalker*, count, cols, rows, palStep, SEGMENT, SEGCOLOR)
that does palette/primary color selection, setPixelColorXY, movement, colorIndex
increment and deactivation; modify mode_brushwalker and the other duplicated
mode to call these helpers and only supply the spawn predicate (e.g., use
BrushWalkerTrySpawn or alternate spawn logic) and per-mode parameters like
spawnChance, maxWalkers, fadeRate, palStep and interval.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: a5def1e0-168b-4cc8-b4d1-cf91716aa8e0
📒 Files selected for processing (2)
platformio.iniusermods/user_fx/user_fx.cpp
| strip.addEffect(255, &mode_ants, _data_FX_MODE_ANTS); | ||
| strip.addEffect(255, &mode_morsecode, _data_FX_MODE_MORSECODE); | ||
|
|
||
There was a problem hiding this comment.
this whitespace change should be reverted.
usermods/user_fx/user_fx.cpp
Outdated
|
For code readability, please adjust your coding style for opening/closing brackets. for example, use if (SEGMENT.palette > 0) {
c = SEGMENT.color_from_palette(w.colorIndex, false, PALETTE_SOLID_WRAP, 0);
} else {
c = SEGCOLOR(0);
}or even simpler if (SEGMENT.palette > 0) c = SEGMENT.color_from_palette(w.colorIndex, false, PALETTE_SOLID_WRAP, 0);
else c = SEGCOLOR(0);https://github.com/wled/WLED/tree/main?tab=contributing-ov-file#code-style |
|
Thanks for the quick replies! I've already made some fixes, and can hopefully convince VSC to not mess up the styles again while I work my way down the todo list. |
|
I ran you FX quickly yesterday, looks very much like the "matrix" FX to me but I only did a quick test. |
…hibitor to increase rhythm response
|
@coderabbitai re-review please. In addition to your review
|
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@usermods/user_fx/user_fx.cpp`:
- Around line 1488-1490: The effect descriptors for Brush Walker hide slider 1
by starting with "@!", which prevents mode_brushwalker_core() from receiving
SEGMENT.speed changes; update both _data_FX_MODE_BRUSHWALKER metadata strings
(the two occurrences around the shown diff) to expose the speed field by
replacing the leading "@!" with an explicit speed token (e.g., "@speed!") so the
first slider becomes visible and SEGMENT.speed is driven from the UI/presets.
- Around line 1260-1276: The file's BrushWalkerFX section is missing the
repo-standard AI block marker; wrap or prepend the large AI-influenced block
(namespace BrushWalkerFX and its header comment) with the single-line marker
comment "// AI: below section was generated by an AI" so the block is clearly
attributed per guideline; locate the namespace BrushWalkerFX declaration and its
preceding comment header and add the marker immediately above (or directly
inside) that header to cover the entire AI-influenced section.
- Around line 1464-1468: The current compare uses a future gate
(data->triggerGate) with plain unsigned `>` and can fail on SEGENV.step
wraparound; change to a wrap-safe elapsed-time check: store the last spawn time
(e.g. add/repurpose data->lastSpawn or change data->triggerGate to hold the last
spawn timestamp) and when deciding to spawn, test the interval as (SEGENV.step -
data->lastSpawn) >= 32 (or equivalent signed-diff-safe test) instead of
`SEGENV.step > data->triggerGate`; on a spawn set data->lastSpawn = SEGENV.step
and keep calling trySpawn(data->walkers, maxWalkers, cols, rows) only when the
elapsed condition is met.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 87f68767-ac7b-4df6-9ee7-4b6e4e941385
📒 Files selected for processing (1)
usermods/user_fx/user_fx.cpp
| /********************************************************************************************** | ||
| * BrushWalker | ||
| * Uses palette for the trails and background color as fade target. | ||
| * Walkers spawn randomly from the edges and move in straight lines across the | ||
| * matrix, changing color as they go, leaving (fading) trails of "painted" color | ||
| * behind them. Tries to avoid spawning new walkers too close to existing ones | ||
| * to prevent overcrowding and create a more visually appealing distribution. | ||
| * Inspired by the concept of "Matrix", but with a more vivid, undirected and | ||
| * colorful twist. First implementation 2019 with FastLED, but without WLED | ||
| * framework. Redesigned and adapted for WLED in 2026, with parts from | ||
| * claude.ai, Gemini, chatGPT, Grok. Controls: Speed, Spawn Chance, Fade Rate, | ||
| * Palette Step, Max Walkers (up to 32) | ||
| * | ||
| * @author suromark 2019,2026 | ||
| * | ||
| */ | ||
| namespace BrushWalkerFX { |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Add the repo-standard AI block marker.
This header already credits the AI tools used, but the repo rule for large AI-authored sections is the standard // AI: marker around the whole block, not just prose inside the comment.
♻️ Suggested change
+// AI: below section was generated by an AI
/**********************************************************************************************
* BrushWalker
* Uses palette for the trails and background color as fade target.
* Walkers spawn randomly from the edges and move in straight lines across the
@@
} // end namespace BrushWalkerFX
+// AI: above section was generated by an AIAs per coding guidelines, "Document attribution of inspiration, knowledge, and sources for AI-generated code. Mark larger blocks of AI-generated code with a comment: // AI: below section was generated by an AI."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@usermods/user_fx/user_fx.cpp` around lines 1260 - 1276, The file's
BrushWalkerFX section is missing the repo-standard AI block marker; wrap or
prepend the large AI-influenced block (namespace BrushWalkerFX and its header
comment) with the single-line marker comment "// AI: below section was generated
by an AI" so the block is clearly attributed per guideline; locate the namespace
BrushWalkerFX declaration and its preceding comment header and add the marker
immediately above (or directly inside) that header to cover the entire
AI-influenced section.
usermods/user_fx/user_fx.cpp
Outdated
| static const char _data_FX_MODE_BRUSHWALKER[] PROGMEM = | ||
| "Brush Walker@!,Spawn,Fade,Palette Step,Max " | ||
| "Walkers;,!;!;2;pal=11,sx=204,ix=64,c1=48,c2=24,c3=4"; |
There was a problem hiding this comment.
Expose speed in both metadata strings.
mode_brushwalker_core() still uses SEGMENT.speed at Line 1441, but both descriptors start with @!, which hides slider 1. That leaves the effect speed stuck at the default unless users edit JSON or presets manually.
🎛️ Suggested change
static const char _data_FX_MODE_BRUSHWALKER[] PROGMEM =
- "Brush Walker@!,Spawn,Fade,Palette Step,Max "
+ "Brush Walker@Speed,Spawn,Fade,Palette Step,Max "
"Walkers;,!;!;2;pal=11,sx=204,ix=64,c1=48,c2=24,c3=4";
@@
static const char _data_FX_MODE_BRUSHWALKER_AR[] PROGMEM =
- "Brush Walker AR@!,Sensitivity,Fade,Palette Step,Max "
+ "Brush Walker AR@Speed,Sensitivity,Fade,Palette Step,Max "
"Walkers;,!;!;2v;pal=11,sx=204,ix=64,c1=48,c2=24,c3=4";Also applies to: 1501-1503
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@usermods/user_fx/user_fx.cpp` around lines 1488 - 1490, The effect
descriptors for Brush Walker hide slider 1 by starting with "@!", which prevents
mode_brushwalker_core() from receiving SEGMENT.speed changes; update both
_data_FX_MODE_BRUSHWALKER metadata strings (the two occurrences around the shown
diff) to expose the speed field by replacing the leading "@!" with an explicit
speed token (e.g., "@speed!") so the first slider becomes visible and
SEGMENT.speed is driven from the UI/presets.
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 59428
Re-Review: Brush Walker FX✅ Good Practices to HighlightThe BrushWalker implementation demonstrates several patterns worth carrying forward into other user-FX contributions:
🔴 P1 — Safety / Potential CrashUnguarded if ( ( um_data && *(uint8_t*)um_data->u_data[3] ) || hw_random8() < sensitivity )
if (um_data && um_data->u_size > 3 && *(uint8_t*)um_data->u_data[3])🟠 P2 — Correctness / Logic1. Hardcoded const uint8_t minGap = 8;On an 8×8 grid, a gap of 8 means no two walkers can share a row or column at all — every spawn attempt will likely fail both retries. The gap should scale with the matrix, e.g.: const uint8_t minGap = max((uint8_t)2, (uint8_t)(min(cols, rows) / 4));2. Only 2 retries in When the matrix is moderately populated, both retry attempts may fail even though valid positions exist. Consider increasing retries to 3–4, or iterating over all 4 sides before giving up. 3. if( SEGENV.step > data->triggerGate ) {
if (strip.now - data->triggerGate < 32) return; // still within gate🟡 P3 — Style (not yet fully addressed from previous review)Brace placement and spacing The init block still has the brace on a new line and unusual indentation: if (SEGENV.call == 0) { // init values on first call
for (uint8_t i = 0; i < absoluteMaxWalkers; i++) data->walkers[i].reset();And there is inconsistent spacing around the condition: if( SEGENV.step > data->triggerGate ) { // ← missing space after `if`, extra before `)`WLED style: 🟡 P4 — Code Quality / Dead Code1. if (shouldSpawn) {
if( SEGENV.step > data->triggerGate ) { ... }
shouldSpawn = false; // ← variable not read after this
}Remove this assignment. 2. Trailing The 3. Unnecessary cast in uint8_t maxWalkers = min((int)(1 + SEGMENT.custom3), (int)absoluteMaxWalkers);
uint8_t maxWalkers = (uint8_t)min(1u + SEGMENT.custom3, (uint8_t)absoluteMaxWalkers);🔵 P5 — Documentation / UX1. When 2.
🔵 P6 — Architectural Consideration (non-blocking)As noted by 🧠 Learnings used |
…ents for clarification.
There was a problem hiding this comment.
♻️ Duplicate comments (2)
usermods/user_fx/user_fx.cpp (2)
1497-1512:⚠️ Potential issue | 🟠 MajorExpose
speedin both metadata strings.Line 1449 still uses
SEGMENT.speedto drive the frame interval, but both descriptors start with@!, so slider 1 is hidden. That leaves the advertised speed control stuck at the default unless users edit JSON or presets manually.🔧 Minimal fix
static const char _data_FX_MODE_BRUSHWALKER[] PROGMEM = - "Brush Walker@!,Spawn,Fade (0=off),Palette Step (0=fixed),Max " + "Brush Walker@Speed,Spawn,Fade (0=off),Palette Step (0=fixed),Max " "Walkers;,!;!;2;pal=11,sx=204,ix=64,c1=48,c2=24,c3=4"; @@ static const char _data_FX_MODE_BRUSHWALKER_AR[] PROGMEM = - "Brush Walker AR@!,Sensitivity,Fade,Palette Step,Max " + "Brush Walker AR@Speed,Sensitivity,Fade,Palette Step,Max " "Walkers;,!;!;2v;pal=11,sx=204,ix=64,c1=48,c2=24,c3=4";🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@usermods/user_fx/user_fx.cpp` around lines 1497 - 1512, The metadata strings _data_FX_MODE_BRUSHWALKER and _data_FX_MODE_BRUSHWALKER_AR currently hide slider 1 (they begin "@!"), so SEGMENT.speed remains inaccessible; update both strings to expose a Speed parameter as the first slider (remove the '!' hide marker and/or add "Speed" as the first field in the comma-separated parameter list) so the UI shows slider 1 and maps to SEGMENT.speed used by BrushWalkerFX::mode_brushwalker_core.
1531-1531:⚠️ Potential issue | 🟡 MinorPlease revert the standalone whitespace edit on Line 1531.
Keep pure-whitespace churn out of the feature diff; it adds review noise and makes cherry-picks harder for no functional benefit.
Based on learnings, "In the WLED repository (wled/WLED), trailing-whitespace removals or other pure-whitespace "optimizations" must NOT be intermixed with real code/logic changes in the same PR."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@usermods/user_fx/user_fx.cpp` at line 1531, Revert the standalone whitespace-only change introduced in usermods/user_fx/user_fx.cpp (the stray whitespace edit inside the usermods::user_fx implementation) so the commit contains no pure-whitespace churn; restore the original spacing at that location (undo the whitespace-only edit), amend the commit or drop that hunk from the PR, and ensure only functional changes remain in the diff.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@usermods/user_fx/user_fx.cpp`:
- Around line 1497-1512: The metadata strings _data_FX_MODE_BRUSHWALKER and
_data_FX_MODE_BRUSHWALKER_AR currently hide slider 1 (they begin "@!"), so
SEGMENT.speed remains inaccessible; update both strings to expose a Speed
parameter as the first slider (remove the '!' hide marker and/or add "Speed" as
the first field in the comma-separated parameter list) so the UI shows slider 1
and maps to SEGMENT.speed used by BrushWalkerFX::mode_brushwalker_core.
- Line 1531: Revert the standalone whitespace-only change introduced in
usermods/user_fx/user_fx.cpp (the stray whitespace edit inside the
usermods::user_fx implementation) so the commit contains no pure-whitespace
churn; restore the original spacing at that location (undo the whitespace-only
edit), amend the commit or drop that hunk from the PR, and ensure only
functional changes remain in the diff.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: e1314258-81e7-4561-86c0-fde8478dd844
📒 Files selected for processing (1)
usermods/user_fx/user_fx.cpp
…ion of erroneous complaint about @!
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
usermods/user_fx/user_fx.cpp (1)
1534-1534:⚠️ Potential issue | 🟡 MinorPlease drop the whitespace-only change on Line 1534.
It makes the functional diff noisier and was already called out upstream. Keep whitespace cleanup in a dedicated follow-up PR instead.
Based on learnings, "trailing-whitespace removals or other pure-whitespace 'optimizations' must NOT be intermixed with real code/logic changes in the same PR."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@usermods/user_fx/user_fx.cpp` at line 1534, There's a whitespace-only edit in user_fx.cpp that must be reverted; remove the trailing-whitespace/no-op change introduced in that commit so the functional diff remains clean, amend the commit (or create a new one) to drop the blank/whitespace-only modification in user_fx.cpp, and keep any whitespace cleanup for a separate follow-up PR.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@usermods/user_fx/user_fx.cpp`:
- Around line 1456-1484: Compute maxWalkers using a wider integer to avoid
unsigned wrap (e.g. uint16_t tmp = 1u + (uint16_t)SEGMENT.custom3; then clamp:
uint8_t maxWalkers = (uint8_t)min(tmp, (uint16_t)absoluteMaxWalkers)), and
ensure it is at least 1 if that’s required for the effect; after computing the
new max, deactivate any higher-index persistent walkers so reducing the slider
can’t leave stale walkers alive (iterate i from maxWalkers to
absoluteMaxWalkers-1 and clear/mark inactive those entries in data->walkers,
e.g. call an existing reset/kill method or set an alive flag), keeping
references to maxWalkers, SEGMENT.custom3, absoluteMaxWalkers, data->walkers and
trySpawn to locate the relevant code.
---
Duplicate comments:
In `@usermods/user_fx/user_fx.cpp`:
- Line 1534: There's a whitespace-only edit in user_fx.cpp that must be
reverted; remove the trailing-whitespace/no-op change introduced in that commit
so the functional diff remains clean, amend the commit (or create a new one) to
drop the blank/whitespace-only modification in user_fx.cpp, and keep any
whitespace cleanup for a separate follow-up PR.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 9397ab4c-77b2-4d8b-aa11-dd0ce3be8927
📒 Files selected for processing (1)
usermods/user_fx/user_fx.cpp
| uint8_t maxWalkers = (uint8_t) min( (uint8_t) (1u + SEGMENT.custom3), (uint8_t)absoluteMaxWalkers); | ||
|
|
||
| SEGMENT.fadeToSecondaryBy(fadeRate); | ||
|
|
||
| // Trigger Logic | ||
| bool shouldSpawn = false; | ||
| if (triggerMode == 1) { | ||
| um_data_t* um_data; | ||
| if (!UsermodManager::getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) | ||
| um_data = simulateSound(SEGMENT.soundSim); | ||
| if ( ( um_data && um_data->u_size > 3 && *(uint8_t*)um_data->u_data[3] ) || hw_random8() < sensitivity ) | ||
| shouldSpawn = true; | ||
| } else { | ||
| if (hw_random8() < sensitivity) shouldSpawn = true; | ||
| } | ||
|
|
||
| if (shouldSpawn) { | ||
| if (strip.now - data->triggerGate < 32) { // uint32 overflow safe | ||
| // nope, we're still within deadtime zone | ||
| } else { | ||
| data->triggerGate = SEGENV.step; // de-clogging, avoid immediate respawn | ||
| trySpawn(data->walkers, maxWalkers, cols, rows); | ||
| } | ||
| } | ||
|
|
||
| // Object-Oriented Update Loop | ||
| for (uint8_t i = 0; i < maxWalkers; i++) { | ||
| data->walkers[i].update(cols, rows, palStep); | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
python - <<'PY'
import ctypes
ABSOLUTE_MAX_WALKERS = 32
for custom3 in (0, 31, 32, 254, 255):
wrapped = ctypes.c_uint8(1 + custom3).value
max_walkers = min(wrapped, ABSOLUTE_MAX_WALKERS)
print(f"custom3={custom3:3d} -> wrapped={wrapped:3d}, maxWalkers={max_walkers:2d}")
PYRepository: wled/WLED
Length of output: 262
🏁 Script executed:
sed -n '1456,1484p' usermods/user_fx/user_fx.cpp | cat -nRepository: wled/WLED
Length of output: 1252
🏁 Script executed:
# Find the walker struct definition
rg -A 10 'struct.*[Ww]alker' usermods/user_fx/user_fx.cpp | head -50Repository: wled/WLED
Length of output: 519
🏁 Script executed:
# Check how SEGENV is used and if walkers persist
rg -B 5 -A 5 'data->walkers\[' usermods/user_fx/user_fx.cpp | head -80Repository: wled/WLED
Length of output: 569
🏁 Script executed:
# Check the Walker::update() implementation
rg -A 15 'void.*update\(' usermods/user_fx/user_fx.cpp | grep -A 15 'class Walker' | head -40Repository: wled/WLED
Length of output: 35
🏁 Script executed:
# Get the full update method for Walker
rg -B 2 -A 15 'update\(uint8_t cols' usermods/user_fx/user_fx.cppRepository: wled/WLED
Length of output: 35
🏁 Script executed:
# Search for Walker class and its update method
rg -A 25 'Walker {' usermods/user_fx/user_fx.cpp | head -100Repository: wled/WLED
Length of output: 132
🏁 Script executed:
# Try finding update method differently
ast-grep --pattern 'void update($_) { $$$ }'Repository: wled/WLED
Length of output: 591
🏁 Script executed:
# Search specifically in user_fx.cpp for the update method body
rg -A 20 'Walker.*update' usermods/user_fx/user_fx.cpp | head -60Repository: wled/WLED
Length of output: 35
🏁 Script executed:
# Look for the struct Walker definition and its methods more carefully
rg -B 5 -A 40 'struct Walker' usermods/user_fx/user_fx.cpp | head -80Repository: wled/WLED
Length of output: 914
🏁 Script executed:
# Get more of the Walker struct to find update method
rg -A 80 'struct Walker {' usermods/user_fx/user_fx.cpp | head -120Repository: wled/WLED
Length of output: 146
🏁 Script executed:
# Get more of the Walker struct - escape the brace
rg -A 80 'struct Walker' usermods/user_fx/user_fx.cpp | head -150Repository: wled/WLED
Length of output: 1864
🏁 Script executed:
# Find the update method in Walker - search for "void update"
rg -B 2 -A 20 'void update\(' usermods/user_fx/user_fx.cpp | grep -A 20 'Moves the walker'Repository: wled/WLED
Length of output: 35
🏁 Script executed:
# Alternative: get line count and search more broadly
wc -l usermods/user_fx/user_fx.cppRepository: wled/WLED
Length of output: 86
🏁 Script executed:
# Search for "update" method after the comments about "Moves the walker and paints"
rg -A 25 'Moves the walker and paints the pixel' usermods/user_fx/user_fx.cppRepository: wled/WLED
Length of output: 632
Fix the walker-limit edge cases around maxWalkers.
Line 1456 narrows 1u + SEGMENT.custom3 before clamping, so custom3 == 255 wraps to 0 and maxWalkers becomes 0, disabling the effect entirely. Additionally, lowering the slider leaves higher-index walkers active in persistent SEGENV data, so they can reappear from stale positions if the cap is raised again.
Proposed fix
- uint8_t maxWalkers = (uint8_t) min( (uint8_t) (1u + SEGMENT.custom3), (uint8_t)absoluteMaxWalkers);
+ uint16_t requestedWalkers = 1u + static_cast<uint16_t>(SEGMENT.custom3);
+ if (requestedWalkers > absoluteMaxWalkers) requestedWalkers = absoluteMaxWalkers;
+ uint8_t maxWalkers = static_cast<uint8_t>(requestedWalkers);
+ for (uint8_t i = maxWalkers; i < absoluteMaxWalkers; ++i) {
+ data->walkers[i].active = false;
+ }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| uint8_t maxWalkers = (uint8_t) min( (uint8_t) (1u + SEGMENT.custom3), (uint8_t)absoluteMaxWalkers); | |
| SEGMENT.fadeToSecondaryBy(fadeRate); | |
| // Trigger Logic | |
| bool shouldSpawn = false; | |
| if (triggerMode == 1) { | |
| um_data_t* um_data; | |
| if (!UsermodManager::getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) | |
| um_data = simulateSound(SEGMENT.soundSim); | |
| if ( ( um_data && um_data->u_size > 3 && *(uint8_t*)um_data->u_data[3] ) || hw_random8() < sensitivity ) | |
| shouldSpawn = true; | |
| } else { | |
| if (hw_random8() < sensitivity) shouldSpawn = true; | |
| } | |
| if (shouldSpawn) { | |
| if (strip.now - data->triggerGate < 32) { // uint32 overflow safe | |
| // nope, we're still within deadtime zone | |
| } else { | |
| data->triggerGate = SEGENV.step; // de-clogging, avoid immediate respawn | |
| trySpawn(data->walkers, maxWalkers, cols, rows); | |
| } | |
| } | |
| // Object-Oriented Update Loop | |
| for (uint8_t i = 0; i < maxWalkers; i++) { | |
| data->walkers[i].update(cols, rows, palStep); | |
| } | |
| uint16_t requestedWalkers = 1u + static_cast<uint16_t>(SEGMENT.custom3); | |
| if (requestedWalkers > absoluteMaxWalkers) requestedWalkers = absoluteMaxWalkers; | |
| uint8_t maxWalkers = static_cast<uint8_t>(requestedWalkers); | |
| for (uint8_t i = maxWalkers; i < absoluteMaxWalkers; ++i) { | |
| data->walkers[i].active = false; | |
| } | |
| SEGMENT.fadeToSecondaryBy(fadeRate); | |
| // Trigger Logic | |
| bool shouldSpawn = false; | |
| if (triggerMode == 1) { | |
| um_data_t* um_data; | |
| if (!UsermodManager::getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) | |
| um_data = simulateSound(SEGMENT.soundSim); | |
| if ( ( um_data && um_data->u_size > 3 && *(uint8_t*)um_data->u_data[3] ) || hw_random8() < sensitivity ) | |
| shouldSpawn = true; | |
| } else { | |
| if (hw_random8() < sensitivity) shouldSpawn = true; | |
| } | |
| if (shouldSpawn) { | |
| if (strip.now - data->triggerGate < 32) { // uint32 overflow safe | |
| // nope, we're still within deadtime zone | |
| } else { | |
| data->triggerGate = SEGENV.step; // de-clogging, avoid immediate respawn | |
| trySpawn(data->walkers, maxWalkers, cols, rows); | |
| } | |
| } | |
| // Object-Oriented Update Loop | |
| for (uint8_t i = 0; i < maxWalkers; i++) { | |
| data->walkers[i].update(cols, rows, palStep); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@usermods/user_fx/user_fx.cpp` around lines 1456 - 1484, Compute maxWalkers
using a wider integer to avoid unsigned wrap (e.g. uint16_t tmp = 1u +
(uint16_t)SEGMENT.custom3; then clamp: uint8_t maxWalkers = (uint8_t)min(tmp,
(uint16_t)absoluteMaxWalkers)), and ensure it is at least 1 if that’s required
for the effect; after computing the new max, deactivate any higher-index
persistent walkers so reducing the slider can’t leave stale walkers alive
(iterate i from maxWalkers to absoluteMaxWalkers-1 and clear/mark inactive those
entries in data->walkers, e.g. call an existing reset/kill method or set an
alive flag), keeping references to maxWalkers, SEGMENT.custom3,
absoluteMaxWalkers, data->walkers and trySpawn to locate the relevant code.
There was a problem hiding this comment.
♻️ Duplicate comments (2)
usermods/user_fx/user_fx.cpp (2)
1481-1484:⚠️ Potential issue | 🟡 MinorHigher-index walkers remain active when
maxWalkersis reduced.The loop only updates walkers up to
maxWalkers, but walkers at higher indices stayactive = true. If the slider is later increased, they'll reappear at stale positions.🛠️ Suggested fix: deactivate excess walkers
uint8_t maxWalkers = /* fixed calculation */; + // Deactivate walkers beyond current max to prevent stale reappearance + for (uint8_t i = maxWalkers; i < absoluteMaxWalkers; ++i) { + data->walkers[i].active = false; + } SEGMENT.fadeToSecondaryBy(fadeRate);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@usermods/user_fx/user_fx.cpp` around lines 1481 - 1484, After updating the first maxWalkers entries, explicitly deactivate any higher-index walkers so they don't remain active with stale state: add a loop after the existing for-loop that iterates for (uint8_t i = maxWalkers; i < <total array length>; ++i) and set data->walkers[i].active = false (and optionally reset position/state) so any excess walkers are turned off when maxWalkers is reduced; reference data->walkers, maxWalkers and the walker.active field when making the change.
1456-1456:⚠️ Potential issue | 🟠 Major
maxWalkerswraps to 0 whencustom3 == 255.The expression
(uint8_t)(1u + SEGMENT.custom3)truncates 256 → 0, somin(0, 32) = 0. This disables the effect entirely at slider max.🐛 Proposed fix
- uint8_t maxWalkers = (uint8_t) min( (uint8_t) (1u + SEGMENT.custom3), (uint8_t)absoluteMaxWalkers); + uint16_t requestedWalkers = 1u + static_cast<uint16_t>(SEGMENT.custom3); + uint8_t maxWalkers = static_cast<uint8_t>(min(requestedWalkers, (uint16_t)absoluteMaxWalkers));🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@usermods/user_fx/user_fx.cpp` at line 1456, The bug is that (uint8_t)(1u + SEGMENT.custom3) wraps to 0 when SEGMENT.custom3 == 255; fix by doing the addition and min-comparison in a wider integer type before casting to uint8_t: compute a temporary using uint16_t or int (e.g., temp = 1u + SEGMENT.custom3), apply std::min(temp, (int)absoluteMaxWalkers), then cast the result to uint8_t and assign to maxWalkers; update the expression around maxWalkers, SEGMENT.custom3, and absoluteMaxWalkers accordingly so the slider max yields 256→capped value instead of 0.
🧹 Nitpick comments (2)
usermods/user_fx/user_fx.cpp (2)
1279-1279: Preferconstexprfor compile-time constants.Per coding guidelines,
constexpris preferred overconstfor compile-time constants.-const uint8_t absoluteMaxWalkers = 32; +constexpr uint8_t absoluteMaxWalkers = 32;Also applies to
minGapat line 1404.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@usermods/user_fx/user_fx.cpp` at line 1279, Replace the runtime consts with compile-time constexprs: change the declaration of absoluteMaxWalkers to use constexpr uint8_t (symbol: absoluteMaxWalkers) and likewise change minGap (symbol: minGap) to constexpr uint8_t so both are evaluated at compile time per coding guidelines; update their declarations only—no other logic changes.
1439-1446: Inconsistent indentation in initialization block.The content inside the
if (SEGENV.call == 0)block has inconsistent indentation levels (extra spaces on some lines).♻️ Suggested cleanup
if (SEGENV.call == 0) { // init values on first call - for (uint8_t i = 0; i < absoluteMaxWalkers; i++) { - data->walkers[i].reset(); - } - data->triggerGate = strip.now; - SEGMENT.fill(SEGCOLOR(1)); - SEGENV.step = strip.now; - } + for (uint8_t i = 0; i < absoluteMaxWalkers; i++) { + data->walkers[i].reset(); + } + data->triggerGate = strip.now; + SEGMENT.fill(SEGCOLOR(1)); + SEGENV.step = strip.now; + }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@usermods/user_fx/user_fx.cpp` around lines 1439 - 1446, Normalize the inconsistent indentation inside the initialization block guarded by SEGENV.call == 0: align all statements under that if to the same indentation level (the for loop, data->walkers[i].reset(), data->triggerGate = strip.now, SEGMENT.fill(SEGCOLOR(1)), and SEGENV.step = strip.now) so the body of the if is consistently indented and visually grouped with the opening brace; keep the existing logic (looping over absoluteMaxWalkers and calling data->walkers[i].reset()) unchanged — only adjust whitespace/indentation for readability.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@usermods/user_fx/user_fx.cpp`:
- Around line 1481-1484: After updating the first maxWalkers entries, explicitly
deactivate any higher-index walkers so they don't remain active with stale
state: add a loop after the existing for-loop that iterates for (uint8_t i =
maxWalkers; i < <total array length>; ++i) and set data->walkers[i].active =
false (and optionally reset position/state) so any excess walkers are turned off
when maxWalkers is reduced; reference data->walkers, maxWalkers and the
walker.active field when making the change.
- Line 1456: The bug is that (uint8_t)(1u + SEGMENT.custom3) wraps to 0 when
SEGMENT.custom3 == 255; fix by doing the addition and min-comparison in a wider
integer type before casting to uint8_t: compute a temporary using uint16_t or
int (e.g., temp = 1u + SEGMENT.custom3), apply std::min(temp,
(int)absoluteMaxWalkers), then cast the result to uint8_t and assign to
maxWalkers; update the expression around maxWalkers, SEGMENT.custom3, and
absoluteMaxWalkers accordingly so the slider max yields 256→capped value instead
of 0.
---
Nitpick comments:
In `@usermods/user_fx/user_fx.cpp`:
- Line 1279: Replace the runtime consts with compile-time constexprs: change the
declaration of absoluteMaxWalkers to use constexpr uint8_t (symbol:
absoluteMaxWalkers) and likewise change minGap (symbol: minGap) to constexpr
uint8_t so both are evaluated at compile time per coding guidelines; update
their declarations only—no other logic changes.
- Around line 1439-1446: Normalize the inconsistent indentation inside the
initialization block guarded by SEGENV.call == 0: align all statements under
that if to the same indentation level (the for loop, data->walkers[i].reset(),
data->triggerGate = strip.now, SEGMENT.fill(SEGCOLOR(1)), and SEGENV.step =
strip.now) so the body of the if is consistently indented and visually grouped
with the opening brace; keep the existing logic (looping over absoluteMaxWalkers
and calling data->walkers[i].reset()) unchanged — only adjust
whitespace/indentation for readability.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 865c4010-ebcb-441a-8596-c48e40fc7214
📒 Files selected for processing (1)
usermods/user_fx/user_fx.cpp
The "Brush Walker" is a 2D effect that moves dots across the grid leaving a (fading, optional) trail. It is similar to the Matrix movie title / the matrix effect in WLED, but it picks directions randomly from four sides and uses the current palette to pick a random color, slowly changing the palette position of the next color as the dot moves. The rate/stepping of palette index change is configurable. The function requests storage for up to 32 dots from SEGENV, each dot having X/Y, dX/dY, active flag and a palette index.
The "Brush Walker AR" triggers new walkers by checking the data supplied by audioreactive peak detection, plus an adjustable "base noise" of walkers to keep the display from going completely blank.
I mainly ported this over from an old project I did in Arduino and ESP8266 in 2019, because I found no other effect with this look yet. I used VSC AI assistance and did some cleanup and added comments afterwards because AI isn't quite there yet :-)
Tested on ESP32 dev board and ESP32C3 Supermini board, with I2S microphone directly and audio over UDP, driving several small WS2812B 8x8 grids and a 16x36 SK6812 furniture installation.
Summary by CodeRabbit