feat(core): increase RGB LED effect collor resolution.
What changed, and why it matters
This commit is a routine feature improvement for the RGB LED on Trezor hardware wallets. It increases the smoothness of LED color transitions (for example the breathing blue bootloader light and the yellow charging light) by switching from 8-bit integer color values to higher-resolution timer values and adding gamma correction. There is no indication this change fixes or introduces a security vulnerability.
No security action required. Treat as a normal feature/quality commit.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The patch refactors the STM32U5 RGB LED driver to use a new rgb_led_color_fs_t structure holding timer-tick values (up to TIMER_PERIOD) instead of packed 24-bit RGB integers. It adds gamma-corrected linear interpolation in rgb_led_effects.c, changes the LED switching frequency define from 20 kHz to 1 kHz (with TIMER_PERIOD moved to a shared header), and updates rgb_led_apply_color to accept the new structure. It also tightens a couple of guard checks (e.g., rgb_led_effect_start now also returns early if the LED is disabled). The changes are purely cosmetic/UX-related and do not touch cryptography, memory safety, authentication, or privileged interfaces.
Changed components
core/embed/io/rgb_led/stm32u5/rgb_led_effects.ccore/embed/io/rgb_led/stm32u5/rgb_led_internal.hcore/embed/io/rgb_led/stm32u5/rgb_led_lp.ccore/embed/io/rgb_led/inc/io/rgb_led.hInspect captured patch +187 / −114
diff --git a/core/embed/io/rgb_led/inc/io/rgb_led.h b/core/embed/io/rgb_led/inc/io/rgb_led.h
index c0f16ccd6..90b2ea436 100644
--- a/core/embed/io/rgb_led/inc/io/rgb_led.h
+++ b/core/embed/io/rgb_led/inc/io/rgb_led.h
@@ -28,33 +28,6 @@
#define RGB_COMPOSE_COLOR(red, green, blue) \
(((red) & 0xFF) << 16 | ((green) & 0xFF) << 8 | ((blue) & 0xFF))
-typedef enum {
- RGB_LED_STATUS_OK = 0,
- RGB_LED_NOT_INITIALIZED,
- RGB_LED_INVALID_ARGUMENT,
-} rgb_led_status_t;
-
-typedef enum {
- RGB_LED_EFFECT_BOOTLOADER_BREATHE = 0,
- RGB_LED_EFFECT_CHARGING,
- RGB_LED_NUM_OF_EFFECTS,
-} rgb_led_effect_type_t;
-
-// Initialize RGB LED driver
-void rgb_led_init(void);
-
-// Deinitialize RGB LED driver
-void rgb_led_deinit(void);
-
-#endif // KERNEL_MODE
-
-// Set RGB LED enabled state
-// enabled: true to enable, false to disable
-void rgb_led_set_enabled(bool enabled);
-
-// Get RGB LED enabled state
-bool rgb_led_get_enabled(void);
-
#define RGBLED_WHITE RGB_COMPOSE_COLOR(35, 35, 32)
#define RGBLED_GREEN RGB_COMPOSE_COLOR(0, 255, 0)
#define RGBLED_GREEN_LIGHT RGB_COMPOSE_COLOR(4, 13, 4)
@@ -88,6 +61,20 @@ void rgb_led_deinit(void);
#endif // KERNEL_MODE
+/**
+ * @brief Set RGB LED enabled state
+ *
+ * @param enabled: true to enable, false to disable
+ */
+void rgb_led_set_enabled(bool enabled);
+
+/**
+ * @brief Get RGB LED enabled state
+ *
+ * @return true if enabled, false otherwise
+ */
+bool rgb_led_get_enabled(void);
+
/**
* @brief Set the RGB led color.
*
diff --git a/core/embed/io/rgb_led/stm32u5/rgb_led_effects.c b/core/embed/io/rgb_led/stm32u5/rgb_led_effects.c
index 12d5714f2..83b750905 100644
--- a/core/embed/io/rgb_led/stm32u5/rgb_led_effects.c
+++ b/core/embed/io/rgb_led/stm32u5/rgb_led_effects.c
@@ -21,59 +21,82 @@
#include <trezor_rtl.h>
+#include "math.h"
#include "rgb_led_internal.h"
-// Effects constants
-#define EFFECT_BOOTLOADER_BREATHE_UP_MS 2000
-#define EFFECT_BOOTLOADER_BREATHE_DOWN_MS 800
-#define EFFECT_BOOTLOADER_BREATHE_CYCLE_MS \
- (EFFECT_BOOTLOADER_BREATHE_UP_MS + EFFECT_BOOTLOADER_BREATHE_DOWN_MS)
+// RGB_LED_EFFECT_BOOTLOADER_BREATHE constants
+#define EF_BB_PHASE1_MS 2000 // Breathe up
+#define EF_BB_PHASE2_MS 400 // LED ON pause
+#define EF_BB_PHASE3_MS 800 // Breathe down
+#define EF_BB_PHASE4_MS 100 // LED OFF pause
+#define EF_BB_CYCLE_MS \
+ (EF_BB_PHASE1_MS + EF_BB_PHASE2_MS + EF_BB_PHASE3_MS + EF_BB_PHASE4_MS)
-#define EFFECT_CHARGING_UP_MS 200
-#define EFFECT_CHARGING_DOWN_MS 500
-#define EFFECT_CHARGING_CYCLE_MS \
- (EFFECT_CHARGING_UP_MS + EFFECT_CHARGING_DOWN_MS)
+// RGB_LED_EFFECT_CHARGING constants
+#define EF_CHG_PHASE1_MS 300
+#define EF_CHG_PHASE2_MS 800
+#define EF_CHG_PHASE3_MS 300
+#define EF_CHG_PHASE4_MS 800
+#define EF_CHG_CYCLE_MS \
+ (EF_CHG_PHASE1_MS + EF_CHG_PHASE2_MS + EF_CHG_PHASE3_MS + EF_CHG_PHASE4_MS)
+
+#define GAMMA_CORRECTION_FACTOR 3.0f
// Effect callback function prototypes
-static uint32_t rgb_led_effect_bootloader_breathe(uint32_t elapsed_ms,
- rgb_led_effect_data_t *data);
-static uint32_t rgb_led_effect_charging(uint32_t elapsed_ms,
- rgb_led_effect_data_t *data);
+static void rgb_led_effect_bootloader_breathe(uint32_t elapsed_ms,
+ rgb_led_effect_data_t *data,
+ rgb_led_color_fs_t *color);
+static void rgb_led_effect_charging_gamma(uint32_t elapsed_ms,
+ rgb_led_effect_data_t *data,
+ rgb_led_color_fs_t *color);
// Effect callback functions lookup table
-static uint32_t (*rgb_led_effects_callbacks[])(uint32_t elapsed_ms,
- rgb_led_effect_data_t *data) = {
+static void (*rgb_led_effects_callbacks[])(uint32_t elapsed_ms,
+ rgb_led_effect_data_t *data,
+ rgb_led_color_fs_t *color) = {
[RGB_LED_EFFECT_BOOTLOADER_BREATHE] = rgb_led_effect_bootloader_breathe,
- [RGB_LED_EFFECT_CHARGING] = rgb_led_effect_charging,
+ [RGB_LED_EFFECT_CHARGING] = rgb_led_effect_charging_gamma,
};
-// Single color linear interpolation auxiliary function
-static inline uint32_t linear_interpolate(uint32_t y0, uint32_t y1, uint32_t x,
- uint32_t x1) {
- int32_t diff = (int32_t)y1 - (int32_t)y0;
- return (uint32_t)(y0 + (diff * (int32_t)x / (int32_t)x1));
+// Single color linear interpolation auxiliary function for floats
+static inline float linear_interpolate_f(float y0, float y1, float x,
+ float x1) {
+ return (y0 + ((y1 - y0) * x / x1));
}
-// Linear interpolation between two colors based on elapsed time
-static uint32_t rgb_led_linear_effect(uint32_t c_start, uint32_t c_end,
- uint32_t elapsed_ms, uint32_t total_ms) {
+// Linear interpolation between two colors based on elapsed time with gamma
+// correction
+static void rgb_led_linear_gc_effect(uint32_t c0, uint32_t c1,
+ uint32_t elapsed_ms, uint32_t total_ms,
+ rgb_led_color_fs_t *interp_color) {
if (elapsed_ms >= total_ms) {
- return c_end;
+ interp_color->red = 0;
+ interp_color->green = 0;
+ interp_color->blue = 0;
+ return;
}
- uint32_t start_r = RGB_EXTRACT_RED(c_start);
- uint32_t start_g = RGB_EXTRACT_GREEN(c_start);
- uint32_t start_b = RGB_EXTRACT_BLUE(c_start);
+ float inv_gamma = 1.0f / GAMMA_CORRECTION_FACTOR;
+
+ float r0 = powf(RGB_EXTRACT_RED(c0) / 255.0f, inv_gamma);
+ float g0 = powf(RGB_EXTRACT_GREEN(c0) / 255.0f, inv_gamma);
+ float b0 = powf(RGB_EXTRACT_BLUE(c0) / 255.0f, inv_gamma);
+
+ float r1 = powf(RGB_EXTRACT_RED(c1) / 255.0f, inv_gamma);
+ float g1 = powf(RGB_EXTRACT_GREEN(c1) / 255.0f, inv_gamma);
+ float b1 = powf(RGB_EXTRACT_BLUE(c1) / 255.0f, inv_gamma);
- uint32_t end_r = RGB_EXTRACT_RED(c_end);
- uint32_t end_g = RGB_EXTRACT_GREEN(c_end);
- uint32_t end_b = RGB_EXTRACT_BLUE(c_end);
+ float r = linear_interpolate_f(r0, r1, (float)elapsed_ms, (float)total_ms);
+ float g = linear_interpolate_f(g0, g1, (float)elapsed_ms, (float)total_ms);
+ float b = linear_interpolate_f(b0, b1, (float)elapsed_ms, (float)total_ms);
- uint32_t r = linear_interpolate(start_r, end_r, elapsed_ms, total_ms);
- uint32_t g = linear_interpolate(start_g, end_g, elapsed_ms, total_ms);
- uint32_t b = linear_interpolate(start_b, end_b, elapsed_ms, total_ms);
+ r = powf(r, GAMMA_CORRECTION_FACTOR);
+ g = powf(g, GAMMA_CORRECTION_FACTOR);
+ b = powf(b, GAMMA_CORRECTION_FACTOR);
- return RGB_COMPOSE_COLOR(r, g, b);
+ interp_color->red = (uint32_t)(r * TIMER_PERIOD);
+ interp_color->green = (uint32_t)(g * TIMER_PERIOD);
+ interp_color->blue = (uint32_t)(b * TIMER_PERIOD);
}
// Assign effect callback from the lookup table
@@ -97,21 +120,44 @@ bool rgb_led_assign_effect(rgb_led_effect_t *effect,
* Slow Linear transition effect from RGBLED_OFF to RGBLED_BLUE and back to
* RGBLED_OFF
*/
-static uint32_t rgb_led_effect_bootloader_breathe(uint32_t elapsed_ms,
- rgb_led_effect_data_t *data) {
- data->cycles = elapsed_ms / EFFECT_BOOTLOADER_BREATHE_CYCLE_MS;
- uint32_t effect_time = elapsed_ms % EFFECT_BOOTLOADER_BREATHE_CYCLE_MS;
-
- if (effect_time < EFFECT_BOOTLOADER_BREATHE_UP_MS) {
- return rgb_led_linear_effect(RGBLED_OFF, RGBLED_BLUE, effect_time,
- EFFECT_BOOTLOADER_BREATHE_UP_MS);
- } else if (effect_time < EFFECT_BOOTLOADER_BREATHE_CYCLE_MS) {
- return rgb_led_linear_effect(RGBLED_BLUE, RGBLED_OFF,
- effect_time - EFFECT_BOOTLOADER_BREATHE_UP_MS,
- EFFECT_BOOTLOADER_BREATHE_DOWN_MS);
+static void rgb_led_effect_bootloader_breathe(uint32_t elapsed_ms,
+ rgb_led_effect_data_t *data,
+ rgb_led_color_fs_t *ef_color) {
+ data->cycles = elapsed_ms / EF_BB_CYCLE_MS;
+ uint32_t ef_time = elapsed_ms % EF_BB_CYCLE_MS;
+
+ if (ef_time < EF_BB_PHASE1_MS) {
+ // PHASE 1: linear transition to RGBLED_BLUE
+ rgb_led_linear_gc_effect(RGBLED_OFF, RGBLED_BLUE, ef_time, EF_BB_PHASE1_MS,
+ ef_color);
+ return;
+ } else if (ef_time < EF_BB_PHASE1_MS + EF_BB_PHASE2_MS) {
+ // PHASE 2: hold RGBLED_BLUE color
+ ef_color->red = (RGB_EXTRACT_RED(RGBLED_BLUE) * TIMER_PERIOD) / 255;
+ ef_color->green = (RGB_EXTRACT_GREEN(RGBLED_BLUE) * TIMER_PERIOD) / 255;
+ ef_color->blue = (RGB_EXTRACT_BLUE(RGBLED_BLUE) * TIMER_PERIOD) / 255;
+ return;
+
+ } else if (ef_time < EF_BB_PHASE1_MS + EF_BB_PHASE2_MS + EF_BB_PHASE3_MS) {
+ // PHASE 3: linear transition to RGBLED_OFF
+ rgb_led_linear_gc_effect(RGBLED_BLUE, RGBLED_OFF,
+ ef_time - EF_BB_PHASE1_MS - EF_BB_PHASE2_MS,
+ EF_BB_PHASE3_MS, ef_color);
+ return;
+
+ } else if (ef_time < EF_BB_CYCLE_MS) {
+ // PHASE 4: hold the off state
+ ef_color->red = 0;
+ ef_color->green = 0;
+ ef_color->blue = 0;
+ return;
+
} else {
// Should not happen
- return RGBLED_OFF;
+ ef_color->red = 0;
+ ef_color->green = 0;
+ ef_color->blue = 0;
+ return;
}
}
@@ -120,21 +166,42 @@ static uint32_t rgb_led_effect_bootloader_breathe(uint32_t elapsed_ms,
* Faster linear transition effect from RGBLED_OFF to RGBLED_YELLOW and back to
* RGBLED_OFF
*/
-static uint32_t rgb_led_effect_charging(uint32_t elapsed_ms,
- rgb_led_effect_data_t *data) {
- data->cycles = elapsed_ms / EFFECT_CHARGING_CYCLE_MS;
- uint32_t effect_time = elapsed_ms % EFFECT_CHARGING_CYCLE_MS;
-
- if (effect_time < EFFECT_CHARGING_UP_MS) {
- return rgb_led_linear_effect(RGBLED_OFF, RGBLED_YELLOW, effect_time,
- EFFECT_CHARGING_UP_MS);
- } else if (effect_time < EFFECT_CHARGING_CYCLE_MS) {
- return rgb_led_linear_effect(RGBLED_YELLOW, RGBLED_OFF,
- effect_time - EFFECT_CHARGING_UP_MS,
- EFFECT_CHARGING_DOWN_MS);
+static void rgb_led_effect_charging_gamma(uint32_t elapsed_ms,
+ rgb_led_effect_data_t *data,
+ rgb_led_color_fs_t *ef_color) {
+ data->cycles = elapsed_ms / EF_CHG_CYCLE_MS;
+ uint32_t ef_time = elapsed_ms % EF_CHG_CYCLE_MS;
+
+ if (ef_time < EF_CHG_PHASE1_MS) {
+ // PHASE 1: linear transition to RGBLED_YELLOW
+ rgb_led_linear_gc_effect(RGBLED_OFF, RGBLED_YELLOW, ef_time,
+ EF_CHG_PHASE1_MS, ef_color);
+ return;
+ } else if (ef_time < EF_CHG_PHASE1_MS + EF_CHG_PHASE2_MS) {
+ // PHASE 2: hold RGBLED_YELLOW color
+ ef_color->red = (RGB_EXTRACT_RED(RGBLED_YELLOW) * TIMER_PERIOD) / 255;
+ ef_color->green = (RGB_EXTRACT_GREEN(RGBLED_YELLOW) * TIMER_PERIOD) / 255;
+ ef_color->blue = (RGB_EXTRACT_BLUE(RGBLED_YELLOW) * TIMER_PERIOD) / 255;
+ return;
+ } else if (ef_time < EF_CHG_PHASE1_MS + EF_CHG_PHASE2_MS + EF_CHG_PHASE3_MS) {
+ // PHASE 3: linear transition to RGBLED_OFF
+ rgb_led_linear_gc_effect(RGBLED_YELLOW, RGBLED_OFF,
+ ef_time - EF_CHG_PHASE1_MS - EF_CHG_PHASE2_MS,
+ EF_CHG_PHASE3_MS, ef_color);
+ return;
+ } else if (ef_time < EF_CHG_CYCLE_MS) {
+ // PHASE 4: hold the off state
+ ef_color->red = 0;
+ ef_color->green = 0;
+ ef_color->blue = 0;
+ return;
+
} else {
// Should not happen
- return RGBLED_OFF;
+ ef_color->red = 0;
+ ef_color->green = 0;
+ ef_color->blue = 0;
+ return;
}
}
diff --git a/core/embed/io/rgb_led/stm32u5/rgb_led_internal.h b/core/embed/io/rgb_led/stm32u5/rgb_led_internal.h
index 37b40f0bb..4bb0406ed 100644
--- a/core/embed/io/rgb_led/stm32u5/rgb_led_internal.h
+++ b/core/embed/io/rgb_led/stm32u5/rgb_led_internal.h
@@ -25,6 +25,15 @@
#include <io/rgb_led.h>
#include <sys/systimer.h>
+#define LED_SWITCHING_FREQUENCY_HZ 1000
+#define TIMER_PERIOD (16000000 / LED_SWITCHING_FREQUENCY_HZ)
+
+typedef struct {
+ uint32_t red;
+ uint32_t green;
+ uint32_t blue;
+} rgb_led_color_fs_t;
+
typedef struct {
uint32_t cycles;
uint32_t requested_cycles;
@@ -34,7 +43,8 @@ typedef struct {
rgb_led_effect_type_t type;
uint32_t start_time_ms;
rgb_led_effect_data_t data;
- uint32_t (*callback)(uint32_t elapsed_ms, rgb_led_effect_data_t *data);
+ void (*callback)(uint32_t elapsed_ms, rgb_led_effect_data_t *data,
+ rgb_led_color_fs_t *ef_color);
} rgb_led_effect_t;
typedef struct {
diff --git a/core/embed/io/rgb_led/stm32u5/rgb_led_lp.c b/core/embed/io/rgb_led/stm32u5/rgb_led_lp.c
index a82188408..106ecf329 100644
--- a/core/embed/io/rgb_led/stm32u5/rgb_led_lp.c
+++ b/core/embed/io/rgb_led/stm32u5/rgb_led_lp.c
@@ -29,9 +29,6 @@
#include "rgb_led_internal.h"
#include "sys/systick.h"
-#define LED_SWITCHING_FREQUENCY_HZ 20000
-#define TIMER_PERIOD (16000000 / LED_SWITCHING_FREQUENCY_HZ)
-
#define RGB_LED_RED_PIN GPIO_PIN_2
#define RGB_LED_RED_PORT GPIOB
#define RGB_LED_RED_CLK_ENA __HAL_RCC_GPIOB_CLK_ENABLE
@@ -48,7 +45,7 @@
static rgb_led_t g_rgb_led = {0};
-static void rgb_led_apply_color(rgb_led_t* drv, uint32_t color);
+static void rgb_led_apply_color(rgb_led_t* drv, rgb_led_color_fs_t* color_fs);
static void rgb_led_systimer_callback(void* context);
static void rgb_led_set_default_pin_state(void) {
@@ -224,11 +221,8 @@ bool rgb_led_get_enabled(void) {
void rgb_led_set_color(uint32_t color) {
rgb_led_t* drv = &g_rgb_led;
- if (!drv->initialized) {
- return;
- }
- if (!drv->enabled) {
+ if (!drv->initialized || !drv->enabled) {
return;
}
@@ -237,14 +231,19 @@ void rgb_led_set_color(uint32_t color) {
rgb_led_effect_stop();
}
- rgb_led_apply_color(drv, color);
+ rgb_led_color_fs_t color_fs;
+ color_fs.red = (RGB_EXTRACT_RED(color) * TIMER_PERIOD) / 255;
+ color_fs.green = (RGB_EXTRACT_GREEN(color) * TIMER_PERIOD) / 255;
+ color_fs.blue = (RGB_EXTRACT_BLUE(color) * TIMER_PERIOD) / 255;
+
+ rgb_led_apply_color(drv, &color_fs);
}
void rgb_led_effect_start(rgb_led_effect_type_t effect_type,
uint32_t requested_cycles) {
rgb_led_t* drv = &g_rgb_led;
- if (!drv->initialized) {
+ if (!drv->initialized || !drv->enabled) {
return;
}
@@ -253,7 +252,7 @@ void rgb_led_effect_start(rgb_led_effect_type_t effect_type,
return;
}
- systimer_unset();
+ systimer_unset(drv->effect_timer);
if (!rgb_led_assign_effect(&drv->effect, effect_type)) {
return;
@@ -277,50 +276,60 @@ void rgb_led_effect_stop(void) {
drv->ongoing_effect = false;
// Reset the LED to default state
- rgb_led_apply_color(drv, RGBLED_OFF); // Turn off the LED
+ rgb_led_color_fs_t color_fs;
+ color_fs.red = 0;
+ color_fs.green = 0;
+ color_fs.blue = 0;
+ rgb_led_apply_color(drv, &color_fs);
}
-static void rgb_led_apply_color(rgb_led_t* drv, uint32_t color) {
- uint32_t red = RGB_EXTRACT_RED(color);
- uint32_t green = RGB_EXTRACT_GREEN(color);
- uint32_t blue = RGB_EXTRACT_BLUE(color);
+static void rgb_led_apply_color(rgb_led_t* drv, rgb_led_color_fs_t* color_fs) {
+ // Check color settings is in range
+ if (color_fs->red > TIMER_PERIOD || color_fs->green > TIMER_PERIOD ||
+ color_fs->blue > TIMER_PERIOD) {
+ return;
+ }
- if (red != 0) {
+ if (color_fs->red != 0) {
__HAL_LPTIM_CAPTURE_COMPARE_ENABLE(&drv->tim_1, LPTIM_CHANNEL_1);
} else {
__HAL_LPTIM_CAPTURE_COMPARE_DISABLE(&drv->tim_1, LPTIM_CHANNEL_1);
}
- if (green != 0) {
+ if (color_fs->green != 0) {
__HAL_LPTIM_CAPTURE_COMPARE_ENABLE(&drv->tim_3, LPTIM_CHANNEL_2);
} else {
__HAL_LPTIM_CAPTURE_COMPARE_DISABLE(&drv->tim_3, LPTIM_CHANNEL_2);
}
- if (blue != 0) {
+ if (color_fs->blue != 0) {
__HAL_LPTIM_CAPTURE_COMPARE_ENABLE(&drv->tim_3, LPTIM_CHANNEL_1);
} else {
__HAL_LPTIM_CAPTURE_COMPARE_DISABLE(&drv->tim_3, LPTIM_CHANNEL_1);
}
__HAL_LPTIM_COMPARE_SET(&drv->tim_1, LPTIM_CHANNEL_1,
- TIMER_PERIOD - (red * (TIMER_PERIOD) / 255));
+ TIMER_PERIOD - (color_fs->red));
__HAL_LPTIM_COMPARE_SET(&drv->tim_3, LPTIM_CHANNEL_2,
- TIMER_PERIOD - (green * (TIMER_PERIOD) / 255));
+ TIMER_PERIOD - (color_fs->green));
__HAL_LPTIM_COMPARE_SET(&drv->tim_3, LPTIM_CHANNEL_1,
- TIMER_PERIOD - (blue * (TIMER_PERIOD) / 255));
+ TIMER_PERIOD - (color_fs->blue));
}
static void rgb_led_systimer_callback(void* context) {
rgb_led_t* drv = &g_rgb_led;
- if (!drv->initialized || !drv->ongoing_effect) {
+ if (!drv->initialized || !drv->ongoing_effect || !drv->enabled) {
return;
}
uint32_t elapsed_ms = systick_ms() - drv->effect.start_time_ms;
- uint32_t color = drv->effect.callback(elapsed_ms, &drv->effect.data);
- rgb_led_apply_color(drv, color);
+
+ rgb_led_color_fs_t color_fs;
+
+ // Call LED effect callback which retrieves current effect color.
+ drv->effect.callback(elapsed_ms, &drv->effect.data, &color_fs);
+ rgb_led_apply_color(drv, &color_fs);
// Stop the effect if the requested cycles have been reached
if (drv->effect.data.requested_cycles &&
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.