Skip to content

Add flashing effect on line clear in TetrisAI_v2.#5320

Open
gustebeast wants to merge 3 commits intowled:mainfrom
gustebeast:wled-gtb-line-clear
Open

Add flashing effect on line clear in TetrisAI_v2.#5320
gustebeast wants to merge 3 commits intowled:mainfrom
gustebeast:wled-gtb-line-clear

Conversation

@gustebeast
Copy link
Contributor

@gustebeast gustebeast commented Jan 27, 2026

See https://imgur.com/1dsNCVd for a demo.

Summary by CodeRabbit

  • New Features

    • Optional setting to disable row-flashing during line clears (persisted).
    • Per-row flashing animation with a timed clearing window before removal and continued drawing during clears.
  • Bug Fixes / Behavior Changes

    • Line removal delayed until flashing completes to avoid premature deletion.
    • AI scoring and move evaluation now consider pending-cleared lines, which may change move choices.
  • Documentation

    • Adds photosensitivity (epilepsy) warning and instructions to disable flashing.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 27, 2026

Walkthrough

Implements staged line-clearing: full rows are marked as clearing and flashed (flash can be disabled), a clearing timer is tracked per game, after ~750ms lines are marked ready and removed; AI scoring and cleanup use the "ready for removal" signal; usermod exposes and persists the noFlashOnClear setting.

Changes

Cohort / File(s) Summary
Clearing state & lifecycle
usermods/TetrisAI_v2/gridbw.h
Adds clearingRows (std::vector<bool>) and clearedLinesReadyForRemoval (bool); adds hasClearingRows() and isLineReadyForRemoval(uint8_t); cleanupFullLines() now marks lines for flashing and defers removal until ready; state reset in constructor/reset.
Rendering, timing & config
usermods/TetrisAI_v2/TetrisAI_v2.cpp
Adds global noFlashOnClear, TetrisAI_data::clearingStartTime; renderer uses per-row isRowClearing and local gridPixel for color logic; flashes clearing rows (or shows gray when disabled); when hasClearingRows() waits ~750ms, sets lines ready, calls cleanup, and resets timer; adds usermod _name, addToConfig, readFromConfig.
Color cleanup & AI rating
usermods/TetrisAI_v2/gridcolor.h, usermods/TetrisAI_v2/tetrisai.h
GridColor::cleanupFullLines() and TetrisAI::updateRating() now use gridBW.isLineReadyForRemoval(y) instead of isLineFull(y), aligning removal and scoring with staged-clearing state.
Documentation
usermods/TetrisAI_v2/readme.md
Adds photosensitive epilepsy warning, documents disabling flash via usermod settings, and updates activation guidance to tetrisai_v2 identifier.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 5.88% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Add flashing effect on line clear in TetrisAI_v2' accurately and concisely summarizes the main change, matching the core objective of adding a configurable flashing animation when lines are cleared.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
usermods/TetrisAI_v2/gridbw.h (1)

142-151: reset() should clear the new clearing state variables.

The reset() method doesn't reset clearingRows or clearedLinesReadyForRemoval, potentially leaving stale state when the grid is reset (e.g., on game restart or segment reconfiguration).

Proposed fix
     void reset()
     {
         if (width > 32)
         {
             width = 32;
         }

         pixels.clear();
         pixels.resize(height);
+        clearingRows = 0;
+        clearedLinesReadyForRemoval = false;
     }
🤖 Fix all issues with AI agents
In `@usermods/TetrisAI_v2/gridbw.h`:
- Around line 109-117: The shift (1 << row) in the clearing logic can overflow
when row >= 32; update the clearingRows handling to use a wider integer and an
unsigned literal: change the type of clearingRows to uint64_t and replace (1 <<
row) with (1ULL << row), and update any code that reads/writes clearingRows
accordingly (e.g., where clearingRows is declared/checked) so rows ≥ 32 are
represented safely; alternatively, if you must keep uint32_t, add a guard if
(row < 32) before performing the shift to avoid UB.
- Around line 29-34: clearingRows is a uint32_t bitmask limited to 32 rows so
rows >=32 won't flash; replace it with a dynamic bit container (e.g.,
std::vector<bool> clearingRows or std::bitset<255>/boost::dynamic_bitset sized
to the grid height) and update all uses that set/test bits to index into the
vector (same semantics as current bit ops), and ensure
clearedLinesReadyForRemoval behavior remains the same; alternatively, if you
prefer a simpler change, document the 32-row limitation in the comment near
clearingRows and enforce/validate grid height ≤32 where the flash effect is
used.

In `@usermods/TetrisAI_v2/TetrisAI_v2.cpp`:
- Around line 196-210: The cleanup branch that runs when strip.now -
tetrisai_data->clearingStartTime > 750 sets clearedLinesReadyForRemoval and
calls cleanupFullLines() but does not redraw the grid, producing a one-frame
flicker; modify the block handling that timeout (the if branch using
tetrisai_data->tetris.grid.gridBW.clearingRows and clearingStartTime) to call
drawGrid(&tetrisai_data->tetris, tetrisai_data) immediately after
cleanupFullLines() (before resetting clearingStartTime) so the post-cleanup grid
is rendered right away.
- Around line 35-37: The current bit-shift (1 << index_y) on
tetris->grid.gridBW.clearingRows (uint32_t) is UB when index_y >= 32; fix by
preventing out-of-range shifts or widening the bitfield: either (A) add a bounds
check where isRowClearing is computed (e.g., replace with (index_y < 32) &&
((tetris->grid.gridBW.clearingRows & (1u << index_y)) != 0)), or (B) change the
bitmask type across the code (gridBW.clearingRows and all uses in
TetrisAI_v2.cpp and gridbw.h) to a wider/better structure (e.g., uint64_t with
1ULL << index_y or std::bitset<MAX_HEIGHT>) and update the corresponding shifts
to use the matching literal; apply the same coordinated change where
clearingRows is referenced in gridbw.h (and any other occurrences).
🧹 Nitpick comments (1)
usermods/TetrisAI_v2/TetrisAI_v2.cpp (1)

35-46: Flash timing creates asymmetric pattern (150ms on / 50ms off).

The condition (strip.now % 200) < 150 produces a 150ms Gray, 50ms Black pattern. If a symmetric 100ms/100ms flash was intended, consider adjusting to (strip.now % 200) < 100. The current asymmetry is subtle but visible.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@usermods/TetrisAI_v2/gridbw.h`:
- Around line 131-134: The isLineFull(uint8_t y) implementation uses (1 <<
width) which is UB when width==32; change the mask computation in isLineFull to
use unsigned 32-bit operations and handle the full-width case explicitly (e.g.,
compute a uint32_t mask using 1U << width with a ternary or branch so when
width==32 you use 0xFFFFFFFFu, otherwise (1U << width) - 1) and then compare
pixels[y] against that mask; update the isLineFull function to build the mask
with unsigned literals and a safe width==32 path.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@usermods/TetrisAI_v2/gridbw.h`:
- Around line 28-33: GridBW::reset currently doesn't clear the new clearing
state, so clearingRows and clearedLinesReadyForRemoval can persist and keep
hasClearingRows() true after a reset; update GridBW::reset() to clear
clearingRows (e.g., clear() or assign empty vector) and set
clearedLinesReadyForRemoval = false so any in-progress flashing is cancelled on
reset, touching the reset() implementation and ensuring any code that relies on
hasClearingRows() observes the cleared state.

In `@usermods/TetrisAI_v2/TetrisAI_v2.cpp`:
- Around line 195-205: Reset the clearingStartTime when reinitializing the game
state so a new TetrisAIGame doesn't inherit an in-progress flash timer: locate
the re-init/reconstruction code that creates or resets tetrisai_data /
TetrisAIGame and explicitly set tetrisai_data->clearingStartTime = 0 (or
initialize it to 0 in the TetrisAIGame constructor) so the 750ms flash window is
always respected before calling gridBW.clearedLinesReadyForRemoval /
cleanupFullLines().

@softhack007
Copy link
Member

softhack007 commented Jan 28, 2026

Caution

gustebeast force-pushed the wled-gtb-line-clear branch from 963b45a to e182841 Compare12 hours ago
gustebeast force-pushed the wled-gtb-line-clear branch from 8761132 to eee4fa0 Compare8 hours ago

@gustebeast please stop using force-push !

See https://github.com/wled/WLED?tab=contributing-ov-file#updating-your-code

Edit: we don't require a single-commit "squashed" PR - its ok if you just keep adding commits to your PR branch.

Copy link
Member

@softhack007 softhack007 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would like to see two changes:

  1. make the flashing effect user-configurable, i.e. add a UM setting for "flashing effect" on/off.
    maybe a "photosensitive epilepsy warning" should be added to the usermod readme, too.
  2. make sure that the code works reliably with fixtures that are more than 31 pixels wide or high. As the rabbit pointed out, your current implementation, that uses the bits of a uint32_t, will not work with a 48x56 matrix. Especially HUB75 panels can be 64x64 or even bigger.

For point 2: if you want to keep the bits in an uint32_t, imho its OK if you only support flashing in the lowest 32 rows.

@gustebeast
Copy link
Contributor Author

Flashing effect is now configurable. When disabled, the line will turn white (technically gray) on clear but won't flash on and off. Readme has been updated to highlight the option for those with photosensitivity.

uint32_t issue has been addressed by replacing with a vec, matching existing implementation's maximum allowed height. For the width the game already enforces a 32 width maximum with checks in place to ensure it this isn't exceeded.

@softhack007
Copy link
Member

@gustebeast there is still a warning from the rabbit about bit-shift exceeding 32bit in isLineFull().
Should this be fixed, or at least protected by if (row < 32) ?

@softhack007
Copy link
Member

@coderabbitai please re-review this PR, and summarize your remaining findings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 4, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Examine the gridbw.h file to find the isLineFull() implementation
echo "=== Checking gridbw.h for isLineFull() implementation ==="
cat usermods/TetrisAI_v2/gridbw.h

echo -e "\n=== Checking TetrisAI_v2.cpp for issues ==="
cat usermods/TetrisAI_v2/TetrisAI_v2.cpp

echo -e "\n=== Checking gridcolor.h ==="
cat usermods/TetrisAI_v2/gridcolor.h

echo -e "\n=== Checking tetrisai.h ==="
cat usermods/TetrisAI_v2/tetrisai.h

Length of output: 23562


@softhack007, I've completed the re-review. Here's my summary of findings:

✅ Successfully Addressed

  • uint32_t issue: Now using std::vector for tracking, matching the existing implementation's approach
  • Configurable flashing: The noFlashOnClear setting is properly implemented with persistence via addToConfig/readFromConfig
  • Documentation: Readme updated with photosensitivity warning
  • Timing/wraparound handling: Uses proper elapsed-time checks with strip.now

⚠️ Remaining Issue: Bit-shift in isLineFull()

Location: usermods/TetrisAI_v2/gridbw.h (Line 143-146)

bool isLineFull(uint8_t y)
{
    return pixels[y] == (uint32_t)((1 << width) - 1);
}

Problem: When width equals 32, the expression (1 << width) shifts a 32-bit int by 32 bits, which is undefined behavior in C/C++. Even though the constructor caps width at 32, this edge case triggers the warning.

Suggested fix:

bool isLineFull(uint8_t y)
{
    if (width >= 32) {
        return pixels[y] == 0xFFFFFFFF;
    }
    return pixels[y] == (uint32_t)((1 << width) - 1);
}

Or more concisely:

bool isLineFull(uint8_t y)
{
    uint32_t fullMask = (width >= 32) ? 0xFFFFFFFF : ((1U << width) - 1);
    return pixels[y] == fullMask;
}

Overall Assessment

The PR successfully implements the line-clearing flash effect with proper configuration support. The only remaining concern is the bit-shift safety issue in isLineFull(), which should be addressed to avoid undefined behavior when the grid width is 32.


🧠 Learnings used
Learnt from: DedeHai
Repo: wled/WLED PR: 4798
File: wled00/FX.cpp:7531-7533
Timestamp: 2025-08-26T11:51:21.817Z
Learning: In WLED PR `#4798`, DedeHai confirmed that certain gamma-related calls in FX.cpp/FX_fcn.cpp/particle systems are intentional for effect-level shaping (e.g., brightness curves, TV sim, Pride 2015 pre-mix), distinct from final output gamma. Do not flag or remove these in future reviews; add comments when feasible to clarify intent.

Learnt from: softhack007
Repo: wled/WLED PR: 0
File: :0-0
Timestamp: 2025-11-14T13:37:11.994Z
Learning: In WLED code reviews, when code is modified or added, look for "magic numbers" (hardcoded numeric literals) and suggest replacing them with appropriate defined constants when those constants are meaningful in the context of the PR. For example, the hardcoded value 32 should be replaced with WLED_MAX_SEGNAME_LEN when it represents a segment name length limit. This improves code maintainability and reduces the risk of inconsistencies.

Learnt from: softhack007
Repo: wled/WLED PR: 0
File: :0-0
Timestamp: 2025-12-28T14:06:48.772Z
Learning: In WLED post-commit ee9ac947, the rendering pipeline uses per-segment buffers and per-pixel bus updates. Unmapped (0xFFFF) mapping entries are now skipped in WS2812FX::show() (no “clear to black”), which can leave physical gap LEDs with stale/random colors unless they are explicitly cleared. This is a behavior change from pre-0.16 where a full physical buffer was effectively refreshed each frame.

Learnt from: softhack007
Repo: wled/WLED PR: 0
File: :0-0
Timestamp: 2025-11-14T13:37:30.955Z
Learning: In WLED code reviews, when code is modified or added, look for "magic numbers" (hardcoded numeric literals) and suggest replacing them with defined constants when meaningful constants exist in the codebase. For example, suggest replacing hardcoded "32" with WLED_MAX_SEGNAME_LEN if the context relates to segment name length limits.

Learnt from: BobLoeffler68
Repo: wled/WLED PR: 5109
File: wled00/FX.cpp:3174-3343
Timestamp: 2025-11-27T06:33:11.436Z
Learning: WLED Ants effect (wled00/FX.cpp): The author prefers the current velocity initialization using hw_random16(1000, 5000)/5000.0f, resulting in an effective range of ~3.6–10.0 (with VELOCITY_MIN=2.0, VELOCITY_MAX=10.0), and wants the code kept as-is with comments updated to document this behavior. Avoid suggesting changes to span the full 2.0–10.0 range in future reviews.

Learnt from: softhack007
Repo: wled/WLED PR: 0
File: :0-0
Timestamp: 2025-11-14T13:37:30.955Z
Learning: In WLED code reviews, verify that file operations (especially file.open()) respect LittleFS filename limitations. Assume default WLED configuration with LittleFS default filename limit of 255 bytes. Do not assume extreme configuration values like WLED_MAX_SEGNAME_LEN = 512 which would not be standard configurations.

Learnt from: BobLoeffler68
Repo: wled/WLED PR: 4891
File: wled00/FX.cpp:3333-3349
Timestamp: 2025-08-31T03:38:14.114Z
Learning: WLED PacMan effect (wled00/FX.cpp): Keep pacmancharacters_t position fields as signed int (not int16_t). Maintainer preference (blazoncek) prioritizes avoiding potential overhead/regressions over minor RAM savings. Avoid type shrinking here unless memory pressure is demonstrated.

Learnt from: mval-sg
Repo: wled/WLED PR: 4876
File: wled00/xml.cpp:0-0
Timestamp: 2025-08-28T08:09:20.630Z
Learning: The WLED codebase has opportunities for refactoring hardcoded array bounds (like the "15" used for DMX channels) to use sizeof(array)/sizeof(array[0]) for more maintainable code, but such changes should be done consistently across the entire codebase in a dedicated refactoring effort.

Learnt from: softhack007
Repo: wled/WLED PR: 0
File: :0-0
Timestamp: 2025-11-14T13:37:11.994Z
Learning: In WLED code reviews, file operations (especially file.open()) should be checked to ensure they respect LittleFS filename limitations. The default LittleFS filename limit is 255 bytes (LFS_NAME_MAX). Reviews should assume default WLED configuration defines and not extreme edge-case values (e.g., WLED_MAX_SEGNAME_LEN = 512 would not be standard). File paths should be validated to stay within the 255-byte limit.

Learnt from: BobLoeffler68
Repo: wled/WLED PR: 5293
File: usermods/user_fx/user_fx.cpp:167-171
Timestamp: 2026-01-13T03:07:20.560Z
Learning: In WLED effects (usermods/user_fx/user_fx.cpp and similar), when storing future timestamps, assignments like `stored_time = now + delay` are correct and handle uint32_t wraparound properly. Only comparison statements need to use subtraction-based elapsed-time checks (e.g., `(now - stored_time) >= delay`). Do not flag assignment statements as needing fixes for wraparound handling.

@gustebeast
Copy link
Contributor Author

Apologies for the force push, I did make sure to keep the change on a new commit though. I hadn't included the fix before since it's an existing issue with the code, but I'm happy to include the fix in this PR.

@softhack007
Copy link
Member

softhack007 commented Feb 5, 2026

Apologies for the force push, I did make sure to keep the change on a new commit though.

Please never do that again. I'm not sure what happened, but we lost at least one comment in a completely unrelated issue ticket.

Are you saying that force-push was absolutely necessary just for a single line change?
image

There are other non-destructive methods to update your branch. I can recommend that you look into "git cherry-pick" (copy commit from one branch to another) or "update from main"/ "merge into current branch" / "rebase current branch" features of github desktop.

Unless the PR page (bottom part) explicitly says "There are merge conflicts that must be resolved", its not even necessary to keep your PR branch in sync with the target branch.

Edit: just to be clear - on next force-push, i'll simply close this PR to prevent further damage, and ask for a new PR.

@softhack007
Copy link
Member

⚠️ Outside diff range comments (1)
usermods/TetrisAI_v2/gridbw.h (1)
142-151: reset() should clear the new clearing state variables.

The reset() method doesn't reset clearingRows or clearedLinesReadyForRemoval, potentially leaving stale state when the grid is reset (e.g., on game restart or segment reconfiguration).

@gustebeast I found this in the "minor" section of coderabbit suggestions. Not sure if this is relavant and correct, I'll leave it to your judgement if you want to address this proposal.

@gustebeast
Copy link
Contributor Author

Understood on the force push. I was using it for commits that I hadn't made PRs for yet and accidentally carried the habit back over to this PR. Won't do it again, and will look into avoiding it on my personal commits as well. Just need a way to squash my changes into a single commit for local development so my initial PR is a single commit. Having additional commits for changes post PR makes sense.

As for the reset comment, the fix for that was squashed into the first commit (another reason not to force push)
249b50f

Copy link
Member

@softhack007 softhack007 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, looks good for me. Thanks 👍

About the force-push: I'm glad you understood our reasons, what more could I ask for 😃 thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants