Kaleidoscope

Source: src/kaleidoscope.c · 282 lines · 11 functions.

Screen captures

Views of this part running on the Mini Arcade.

Kaleidoscope screen on the Mini Arcade
Kaleidoscope showing its colourful generated pattern.

Screen / module overview

  1. Start the effect

    Randomise 18 particles. Select the first pattern and theme, set initial speed, and request a clear frame.

  2. Handle controls

    A changes pattern, D resets particles, C changes colour theme and E changes speed.

  3. Update when 30 ms has elapsed

    Cap elapsed motion at 50 ms and apply the speed multiplier. Advance the pulse and particle angles; reflect angles at wedge edges and reset particles beyond the radius.

  4. Clear or fade

    Clear black after a reset; otherwise fade the previous image to retain trails.

  5. Render and repeat

    Draw ribbons, stars or squares in reflected sectors and three scaled layers. Draw the centre and temporary control status.

Module entry points

These functions are exposed to the rest of the project. Open one to follow its decisions and the helpers it calls.

How to read these diagrams

Each function has its own expandable diagram using the actual expressions and statements from this file. Decisions split into True and False; loop bodies are indented. Returns end the function immediately; break and continue follow their C meanings. Arrows between statements show their order when execution reaches them.

These are structural source diagrams before preprocessing, not runtime traces. Conditional compilation and macros remain source expressions. Ternary choices remain inside their statement. Calls are linked separately; callback execution and task scheduling are not inferred.

Functions in this file

particleColour() · source line 78
static uint16_t particleColour(const kaleidoscope_particle_t *particle)
  1. Exit this functionreturn theme_colours[theme_index][particle->colour];
Read original function source
static uint16_t particleColour(const kaleidoscope_particle_t *particle)
{
    return theme_colours[theme_index][particle->colour];
}
fadeFrame() · source line 83
static void fadeFrame(uint16_t *frame)
  1. FOR loopsize_t index = 0U; index < (size_t)LCD_WIDTH * LCD_HEIGHT; ++index

    Repeat body while the loop condition permits; then continue below.

      1. Executeconst uint16_t pixel = frame[index];
      2. Executeconst uint16_t red = (uint16_t)(((pixel >> 11U) & 0x1FU) * 15U / 16U);
      3. Executeconst uint16_t green = (uint16_t)(((pixel >> 5U) & 0x3FU) * 15U / 16U);
      4. Executeconst uint16_t blue = (uint16_t)((pixel & 0x1FU) * 15U / 16U);
      5. Executeframe[index] = (uint16_t)((red << 11U) | (green << 5U) | blue);
Read original function source
static void fadeFrame(uint16_t *frame)
{
    for (size_t index = 0U; index < (size_t)LCD_WIDTH * LCD_HEIGHT; ++index) {
        const uint16_t pixel = frame[index];
        const uint16_t red = (uint16_t)(((pixel >> 11U) & 0x1FU) * 15U / 16U);
        const uint16_t green = (uint16_t)(((pixel >> 5U) & 0x3FU) * 15U / 16U);
        const uint16_t blue = (uint16_t)((pixel & 0x1FU) * 15U / 16U);
        frame[index] = (uint16_t)((red << 11U) | (green << 5U) | blue);
    }
}
drawRibbon() · source line 94
static void drawRibbon(uint16_t *frame, const kaleidoscope_particle_t *particle, float angle,
                       float pulse_scale)

Calls into this documentation:

  1. Executeconst float radius = particle->radius * pulse_scale;
  2. Executeconst float ribbon_end = (particle->radius - particle->length) * pulse_scale;
  3. Executeconst float side_angle = angle + KALEIDOSCOPE_PI / 2.0f;
  4. Executeconst float side_x = cosf(side_angle) * particle->width * pulse_scale;
  5. Executeconst float side_y = sinf(side_angle) * particle->width * pulse_scale;
  6. Executeconst int x0 = KALEIDOSCOPE_CENTRE_X + (int)lroundf(cosf(angle) * radius);
  7. Executeconst int y0 = KALEIDOSCOPE_CENTRE_Y + (int)lroundf(sinf(angle) * radius);
  8. Executeconst int x1 = KALEIDOSCOPE_CENTRE_X + (int)lroundf(cosf(angle) * ribbon_end);
  9. Executeconst int y1 = KALEIDOSCOPE_CENTRE_Y + (int)lroundf(sinf(angle) * ribbon_end);
  10. FOR loopint edge = -1; edge <= 1; ++edge

    Repeat body while the loop condition permits; then continue below.

      1. Executedraw_line(frame, (int)lroundf((float)x0 + side_x * edge), (int)lroundf((float)y0 + side_y * edge), (int)lroundf((float)x1 + side_x * edge), (int)lroundf((float)y1 + side_y * edge), particleColour(particle));
Read original function source
static void drawRibbon(uint16_t *frame, const kaleidoscope_particle_t *particle, float angle,
                       float pulse_scale)
{
    const float radius = particle->radius * pulse_scale;
    const float ribbon_end = (particle->radius - particle->length) * pulse_scale;
    const float side_angle = angle + KALEIDOSCOPE_PI / 2.0f;
    const float side_x = cosf(side_angle) * particle->width * pulse_scale;
    const float side_y = sinf(side_angle) * particle->width * pulse_scale;
    const int x0 = KALEIDOSCOPE_CENTRE_X +
        (int)lroundf(cosf(angle) * radius);
    const int y0 = KALEIDOSCOPE_CENTRE_Y +
        (int)lroundf(sinf(angle) * radius);
    const int x1 = KALEIDOSCOPE_CENTRE_X +
        (int)lroundf(cosf(angle) * ribbon_end);
    const int y1 = KALEIDOSCOPE_CENTRE_Y +
        (int)lroundf(sinf(angle) * ribbon_end);
    for (int edge = -1; edge <= 1; ++edge) {
        draw_line(frame, (int)lroundf((float)x0 + side_x * edge),
                  (int)lroundf((float)y0 + side_y * edge),
                  (int)lroundf((float)x1 + side_x * edge),
                  (int)lroundf((float)y1 + side_y * edge),
                  particleColour(particle));
    }
}
drawStar() · source line 119
static void drawStar(uint16_t *frame, const kaleidoscope_particle_t *particle, float angle,
                     float pulse_scale)

Calls into this documentation:

  1. Executeconst int centre_x = KALEIDOSCOPE_CENTRE_X + (int)lroundf(cosf(angle) * particle->radius * pulse_scale);
  2. Executeconst int centre_y = KALEIDOSCOPE_CENTRE_Y + (int)lroundf(sinf(angle) * particle->radius * pulse_scale);
  3. Executeconst float arm = (5.0f + particle->width * 2.0f) * pulse_scale;
  4. FOR loopunsigned arm_index = 0U; arm_index < 4U; ++arm_index

    Repeat body while the loop condition permits; then continue below.

      1. Executeconst float arm_angle = angle + (float)arm_index * KALEIDOSCOPE_PI / 2.0f;
      2. Executeconst int end_x = centre_x + (int)lroundf(cosf(arm_angle) * arm);
      3. Executeconst int end_y = centre_y + (int)lroundf(sinf(arm_angle) * arm);
      4. Executedraw_line(frame, centre_x, centre_y, end_x, end_y, particleColour(particle));
  5. Executefill_circle(frame, centre_x, centre_y, 2, particleColour(particle));
Read original function source
static void drawStar(uint16_t *frame, const kaleidoscope_particle_t *particle, float angle,
                     float pulse_scale)
{
    const int centre_x = KALEIDOSCOPE_CENTRE_X +
        (int)lroundf(cosf(angle) * particle->radius * pulse_scale);
    const int centre_y = KALEIDOSCOPE_CENTRE_Y +
        (int)lroundf(sinf(angle) * particle->radius * pulse_scale);
    const float arm = (5.0f + particle->width * 2.0f) * pulse_scale;
    for (unsigned arm_index = 0U; arm_index < 4U; ++arm_index) {
        const float arm_angle = angle + (float)arm_index * KALEIDOSCOPE_PI / 2.0f;
        const int end_x = centre_x + (int)lroundf(cosf(arm_angle) * arm);
        const int end_y = centre_y + (int)lroundf(sinf(arm_angle) * arm);
        draw_line(frame, centre_x, centre_y, end_x, end_y, particleColour(particle));
    }
    fill_circle(frame, centre_x, centre_y, 2, particleColour(particle));
}
drawSquare() · source line 136
static void drawSquare(uint16_t *frame, const kaleidoscope_particle_t *particle, float angle,
                       float pulse_scale)

Calls into this documentation:

  1. Executeconst int centre_x = KALEIDOSCOPE_CENTRE_X + (int)lroundf(cosf(angle) * particle->radius * pulse_scale);
  2. Executeconst int centre_y = KALEIDOSCOPE_CENTRE_Y + (int)lroundf(sinf(angle) * particle->radius * pulse_scale);
  3. Executeconst float half_side = (5.0f + particle->width * 2.0f) * pulse_scale;
  4. Executeint previous_x = 0;
  5. Executeint previous_y = 0;
  6. FOR loopunsigned corner = 0U; corner <= 4U; ++corner

    Repeat body while the loop condition permits; then continue below.

      1. Executeconst float corner_angle = angle + KALEIDOSCOPE_PI / 4.0f + (float)(corner % 4U) * KALEIDOSCOPE_PI / 2.0f;
      2. Executeconst int x = centre_x + (int)lroundf(cosf(corner_angle) * half_side);
      3. Executeconst int y = centre_y + (int)lroundf(sinf(corner_angle) * half_side);
      4. Decisioncorner > 0U
        True
        1. Executedraw_line(frame, previous_x, previous_y, x, y, particleColour(particle));
        False

        Continue after this decision.

      5. Executeprevious_x = x;
      6. Executeprevious_y = y;
Read original function source
static void drawSquare(uint16_t *frame, const kaleidoscope_particle_t *particle, float angle,
                       float pulse_scale)
{
    const int centre_x = KALEIDOSCOPE_CENTRE_X +
        (int)lroundf(cosf(angle) * particle->radius * pulse_scale);
    const int centre_y = KALEIDOSCOPE_CENTRE_Y +
        (int)lroundf(sinf(angle) * particle->radius * pulse_scale);
    const float half_side = (5.0f + particle->width * 2.0f) * pulse_scale;
    int previous_x = 0;
    int previous_y = 0;
    for (unsigned corner = 0U; corner <= 4U; ++corner) {
        const float corner_angle = angle + KALEIDOSCOPE_PI / 4.0f +
            (float)(corner % 4U) * KALEIDOSCOPE_PI / 2.0f;
        const int x = centre_x + (int)lroundf(cosf(corner_angle) * half_side);
        const int y = centre_y + (int)lroundf(sinf(corner_angle) * half_side);
        if (corner > 0U) draw_line(frame, previous_x, previous_y, x, y,
                                   particleColour(particle));
        previous_x = x;
        previous_y = y;
    }
}
resetParticle() · source line 158
static void resetParticle(kaleidoscope_particle_t *particle)
  1. Executeparticle->angle = (float)(esp_random() % 1000U) * KALEIDOSCOPE_WEDGE_ANGLE / 1000.0f;
  2. Executeparticle->radius = 6.0f + (float)(esp_random() % 6800U) / 100.0f;
  3. Executeparticle->angle_speed = 0.10f + (float)(esp_random() % 120U) / 500.0f;
  4. Executeparticle->radius_speed = 8.0f + (float)(esp_random() % 220U) / 10.0f;
  5. Executeparticle->length = 18.0f + (float)(esp_random() % 4400U) / 100.0f;
  6. Executeparticle->width = 2.0f + (float)(esp_random() % 3U);
  7. Executeparticle->colour = (uint8_t)(esp_random() % KALEIDOSCOPE_THEME_COLOURS);
Read original function source
static void resetParticle(kaleidoscope_particle_t *particle)
{
    particle->angle = (float)(esp_random() % 1000U) * KALEIDOSCOPE_WEDGE_ANGLE / 1000.0f;
    particle->radius = 6.0f + (float)(esp_random() % 6800U) / 100.0f;
    particle->angle_speed = 0.10f + (float)(esp_random() % 120U) / 500.0f;
    particle->radius_speed = 8.0f + (float)(esp_random() % 220U) / 10.0f;
    particle->length = 18.0f + (float)(esp_random() % 4400U) / 100.0f;
    particle->width = 2.0f + (float)(esp_random() % 3U);
    particle->colour = (uint8_t)(esp_random() % KALEIDOSCOPE_THEME_COLOURS);
}
startKaleidoscope() · source line 169
void startKaleidoscope(uint32_t now_ms)

Calls into this documentation:

  1. FOR loopunsigned index = 0U; index < KALEIDOSCOPE_PARTICLES; ++index

    Repeat body while the loop condition permits; then continue below.

      1. ExecuteresetParticle(&particles[index]);
  2. Executelast_update_ms = now_ms;
  3. Executecontrols_changed_at = now_ms;
  4. Executespeed_index = 1U;
  5. Executepattern_index = 0U;
  6. Executetheme_index = 0U;
  7. Executepulse_phase = 0.0f;
  8. Executepattern_changed = false;
  9. Executereset_requested = false;
  10. Executetheme_changed = false;
  11. Executeclear_requested = true;
  12. Executerunning = true;
Read original function source
void startKaleidoscope(uint32_t now_ms)
{
    for (unsigned index = 0U; index < KALEIDOSCOPE_PARTICLES; ++index) {
        resetParticle(&particles[index]);
    }
    last_update_ms = now_ms;
    controls_changed_at = now_ms;
    speed_index = 1U;
    pattern_index = 0U;
    theme_index = 0U;
    pulse_phase = 0.0f;
    pattern_changed = false;
    reset_requested = false;
    theme_changed = false;
    clear_requested = true;
    running = true;
}
stopKaleidoscope() · source line 187
void stopKaleidoscope(void)
  1. Executerunning = false;
Read original function source
void stopKaleidoscope(void)
{
    running = false;
}
kaleidoscopeAction() · source line 192
void kaleidoscopeAction(bool new_pattern, bool reset, bool theme, bool speed, uint32_t now_ms)

Calls into this documentation:

  1. Decision!running
    True
    1. Exit this functionreturn;
    False

    Continue after this decision.

  2. Decisionnew_pattern
    True
      1. Executepattern_index = (uint8_t)((pattern_index + 1U) % KALEIDOSCOPE_PATTERN_COUNT);
    False

    Continue after this decision.

  3. Decisionnew_pattern || reset
    True
      1. FOR loopunsigned index = 0U; index < KALEIDOSCOPE_PARTICLES; ++index

        Repeat body while the loop condition permits; then continue below.

          1. ExecuteresetParticle(&particles[index]);
      2. Executeclear_requested = true;
    False

    Continue after this decision.

  4. Decisiontheme
    True
    1. Executetheme_index = (uint8_t)((theme_index + 1U) % KALEIDOSCOPE_THEME_COUNT);
    False

    Continue after this decision.

  5. Decisionspeed
    True
    1. Executespeed_index = (uint8_t)((speed_index + 1U) % KALEIDOSCOPE_SPEED_COUNT);
    False

    Continue after this decision.

  6. Decision!new_pattern && !reset && !theme && !speed
    True
    1. Exit this functionreturn;
    False

    Continue after this decision.

  7. Executepattern_changed = new_pattern;
  8. Executereset_requested = reset && !new_pattern;
  9. Executetheme_changed = theme && !new_pattern && !reset;
  10. Executecontrols_changed_at = now_ms;
Read original function source
void kaleidoscopeAction(bool new_pattern, bool reset, bool theme, bool speed, uint32_t now_ms)
{
    if (!running) return;
    if (new_pattern) {
        pattern_index = (uint8_t)((pattern_index + 1U) % KALEIDOSCOPE_PATTERN_COUNT);
    }
    if (new_pattern || reset) {
        for (unsigned index = 0U; index < KALEIDOSCOPE_PARTICLES; ++index) {
            resetParticle(&particles[index]);
        }
        clear_requested = true;
    }
    if (theme) theme_index = (uint8_t)((theme_index + 1U) % KALEIDOSCOPE_THEME_COUNT);
    if (speed) speed_index = (uint8_t)((speed_index + 1U) % KALEIDOSCOPE_SPEED_COUNT);
    if (!new_pattern && !reset && !theme && !speed) return;
    pattern_changed = new_pattern;
    reset_requested = reset && !new_pattern;
    theme_changed = theme && !new_pattern && !reset;
    controls_changed_at = now_ms;
}
updateKaleidoscope() · source line 213
void updateKaleidoscope(uint32_t now_ms)

Calls into this documentation:

  1. Decision!running || now_ms - last_update_ms < KALEIDOSCOPE_FRAME_MS
    True
    1. Exit this functionreturn;
    False

    Continue after this decision.

  2. Executeconst uint32_t elapsed_ms = now_ms - last_update_ms;
  3. Executelast_update_ms = now_ms;
  4. Executeconst float elapsed = (float)(elapsed_ms > 50U ? 50U : elapsed_ms) / 1000.0f * speed_multipliers[speed_index];
  5. Executepulse_phase += elapsed * KALEIDOSCOPE_PULSE_RATE;
  6. Decisionpulse_phase >= 2.0f * KALEIDOSCOPE_PI
    True
    1. Executepulse_phase -= 2.0f * KALEIDOSCOPE_PI;
    False

    Continue after this decision.

  7. FOR loopunsigned index = 0U; index < KALEIDOSCOPE_PARTICLES; ++index

    Repeat body while the loop condition permits; then continue below.

      1. Executekaleidoscope_particle_t *particle = &particles[index];
      2. Executeparticle->angle += particle->angle_speed * elapsed;
      3. Decisionparticle->angle > KALEIDOSCOPE_WEDGE_ANGLE
        True
          1. Executeparticle->angle = KALEIDOSCOPE_WEDGE_ANGLE - (particle->angle - KALEIDOSCOPE_WEDGE_ANGLE);
          2. Executeparticle->angle_speed = -particle->angle_speed;
        False
        1. Decisionparticle->angle < 0.0f
          True
            1. Executeparticle->angle = -particle->angle;
            2. Executeparticle->angle_speed = -particle->angle_speed;
          False

          Continue after this decision.

      4. Executeparticle->radius += particle->radius_speed * elapsed;
      5. Decisionparticle->radius > KALEIDOSCOPE_RADIUS
        True
        1. ExecuteresetParticle(particle);
        False

        Continue after this decision.

Read original function source
void updateKaleidoscope(uint32_t now_ms)
{
    if (!running || now_ms - last_update_ms < KALEIDOSCOPE_FRAME_MS) return;
    const uint32_t elapsed_ms = now_ms - last_update_ms;
    last_update_ms = now_ms;
    const float elapsed = (float)(elapsed_ms > 50U ? 50U : elapsed_ms) / 1000.0f *
        speed_multipliers[speed_index];
    pulse_phase += elapsed * KALEIDOSCOPE_PULSE_RATE;
    if (pulse_phase >= 2.0f * KALEIDOSCOPE_PI) pulse_phase -= 2.0f * KALEIDOSCOPE_PI;
    for (unsigned index = 0U; index < KALEIDOSCOPE_PARTICLES; ++index) {
        kaleidoscope_particle_t *particle = &particles[index];
        particle->angle += particle->angle_speed * elapsed;
        if (particle->angle > KALEIDOSCOPE_WEDGE_ANGLE) {
            particle->angle = KALEIDOSCOPE_WEDGE_ANGLE -
                (particle->angle - KALEIDOSCOPE_WEDGE_ANGLE);
            particle->angle_speed = -particle->angle_speed;
        } else if (particle->angle < 0.0f) {
            particle->angle = -particle->angle;
            particle->angle_speed = -particle->angle_speed;
        }
        particle->radius += particle->radius_speed * elapsed;
        if (particle->radius > KALEIDOSCOPE_RADIUS) resetParticle(particle);
    }
}
drawKaleidoscope() · source line 238
void drawKaleidoscope(uint16_t *frame)

Calls into this documentation:

  1. Decision!running || frame == NULL
    True
    1. Exit this functionreturn;
    False

    Continue after this decision.

  2. Decisionclear_requested
    True
      1. Executeclear(frame, COLOR_BLACK);
      2. Executeclear_requested = false;
    False
      1. ExecutefadeFrame(frame);
  3. Executeconst unsigned particle_count = pattern_index == 0U ? KALEIDOSCOPE_PARTICLES : 9U;
  4. Executeconst float pulse_scale = 1.0f + sinf(pulse_phase) * KALEIDOSCOPE_PULSE_AMOUNT;
  5. FOR loopunsigned index = 0U; index < particle_count; ++index

    Repeat body while the loop condition permits; then continue below.

      1. Executeconst kaleidoscope_particle_t *particle = &particles[index];
      2. FOR loopunsigned sector = 0U; sector < KALEIDOSCOPE_SECTORS; ++sector

        Repeat body while the loop condition permits; then continue below.

          1. Executeconst float base_angle = (float)sector * 2.0f * KALEIDOSCOPE_WEDGE_ANGLE;
          2. FOR loopunsigned reflection = 0U; reflection < 2U; ++reflection

            Repeat body while the loop condition permits; then continue below.

              1. Executeconst float angle = base_angle + (reflection == 0U ? particle->angle : -particle->angle);
              2. FOR loopunsigned layer = 0U; layer < KALEIDOSCOPE_TUNNEL_LAYERS; ++layer

                Repeat body while the loop condition permits; then continue below.

                  1. Executeconst float layer_scale = pulse_scale * tunnel_scales[layer];
                  2. Decisionpattern_index == 0U
                    True
                    1. ExecutedrawRibbon(frame, particle, angle, layer_scale);
                    False
                    1. Decisionpattern_index == 1U
                      True
                      1. ExecutedrawStar(frame, particle, angle, layer_scale);
                      False
                      1. ExecutedrawSquare(frame, particle, angle, layer_scale);
  6. Executefill_circle(frame, KALEIDOSCOPE_CENTRE_X, KALEIDOSCOPE_CENTRE_Y, (int)lroundf(4.0f * pulse_scale), COLOR_WHITE);
  7. Decisionlast_update_ms - controls_changed_at < KALEIDOSCOPE_STATUS_MS
    True
      1. Executechar text[18];
      2. Decisionpattern_changed
        True
          1. Executesnprintf(text, sizeof(text), "A %s", pattern_names[pattern_index]);
        False
        1. Decisionreset_requested
          True
            1. Executesnprintf(text, sizeof(text), "D RESET");
          False
          1. Decisiontheme_changed
            True
              1. Executesnprintf(text, sizeof(text), "C %s", theme_names[theme_index]);
            False
              1. Executesnprintf(text, sizeof(text), "E SPEED %u", (unsigned)(speed_index + 1U));
      3. Executedraw_text_centred(frame, KALEIDOSCOPE_CENTRE_X, 155, text, 1, COLOR_LIGHTGREY);
    False

    Continue after this decision.

Read original function source
void drawKaleidoscope(uint16_t *frame)
{
    if (!running || frame == NULL) return;
    if (clear_requested) {
        clear(frame, COLOR_BLACK);
        clear_requested = false;
    } else {
        fadeFrame(frame);
    }
    const unsigned particle_count = pattern_index == 0U ? KALEIDOSCOPE_PARTICLES : 9U;
    const float pulse_scale = 1.0f + sinf(pulse_phase) * KALEIDOSCOPE_PULSE_AMOUNT;
    for (unsigned index = 0U; index < particle_count; ++index) {
        const kaleidoscope_particle_t *particle = &particles[index];
        for (unsigned sector = 0U; sector < KALEIDOSCOPE_SECTORS; ++sector) {
            const float base_angle = (float)sector * 2.0f * KALEIDOSCOPE_WEDGE_ANGLE;
            for (unsigned reflection = 0U; reflection < 2U; ++reflection) {
                const float angle = base_angle + (reflection == 0U ? particle->angle :
                                                   -particle->angle);
                for (unsigned layer = 0U; layer < KALEIDOSCOPE_TUNNEL_LAYERS; ++layer) {
                    const float layer_scale = pulse_scale * tunnel_scales[layer];
                    if (pattern_index == 0U) drawRibbon(frame, particle, angle, layer_scale);
                    else if (pattern_index == 1U) drawStar(frame, particle, angle, layer_scale);
                    else drawSquare(frame, particle, angle, layer_scale);
                }
            }
        }
    }
    fill_circle(frame, KALEIDOSCOPE_CENTRE_X, KALEIDOSCOPE_CENTRE_Y,
                (int)lroundf(4.0f * pulse_scale), COLOR_WHITE);
    if (last_update_ms - controls_changed_at < KALEIDOSCOPE_STATUS_MS) {
        char text[18];
        if (pattern_changed) {
            snprintf(text, sizeof(text), "A %s", pattern_names[pattern_index]);
        } else if (reset_requested) {
            snprintf(text, sizeof(text), "D RESET");
        } else if (theme_changed) {
            snprintf(text, sizeof(text), "C %s", theme_names[theme_index]);
        } else {
            snprintf(text, sizeof(text), "E SPEED %u", (unsigned)(speed_index + 1U));
        }
        draw_text_centred(frame, KALEIDOSCOPE_CENTRE_X, 155, text, 1, COLOR_LIGHTGREY);
    }
}
Complete original file, including constants and data
/* Coded By JD(2026) */
#error "Reference listing only — not provided as build-ready source."
#include "kaleidoscope.h"

#include "game_graphics.h"
#include "PET2001_font.h"

#include "esp_random.h"

#include <math.h>
#include <stdbool.h>
#include <stdio.h>

#define KALEIDOSCOPE_PARTICLES 18U
#define KALEIDOSCOPE_SECTORS 8U
#define KALEIDOSCOPE_FRAME_MS 30U
#define KALEIDOSCOPE_SPEED_COUNT 4U
#define KALEIDOSCOPE_PATTERN_COUNT 3U
#define KALEIDOSCOPE_THEME_COUNT 6U
#define KALEIDOSCOPE_THEME_COLOURS 8U
#define KALEIDOSCOPE_STATUS_MS 1500U
#define KALEIDOSCOPE_PI 3.14159265f
#define KALEIDOSCOPE_PULSE_AMOUNT 0.12f
#define KALEIDOSCOPE_PULSE_RATE 0.80f
#define KALEIDOSCOPE_TUNNEL_LAYERS 3U
#define KALEIDOSCOPE_WEDGE_ANGLE (KALEIDOSCOPE_PI / (float)KALEIDOSCOPE_SECTORS)
#define KALEIDOSCOPE_CENTRE_X (LCD_WIDTH / 2)
#define KALEIDOSCOPE_CENTRE_Y (LCD_HEIGHT / 2)
#define KALEIDOSCOPE_RADIUS ((float)LCD_WIDTH / 2.0f)

typedef struct {
    float angle;
    float radius;
    float angle_speed;
    float radius_speed;
    float length;
    float width;
    uint8_t colour;
} kaleidoscope_particle_t;

static const uint16_t theme_colours[KALEIDOSCOPE_THEME_COUNT][KALEIDOSCOPE_THEME_COLOURS] = {
    {COLOR_RED, COLOR_ORANGE, COLOR_YELLOW, COLOR_LIME,
     COLOR_CYAN, COLOR_MEDIUMPURPLE, COLOR_MAGENTA, COLOR_WHITE},
    {COLOR_DARKRED, COLOR_RED, COLOR_ORANGERED, COLOR_ORANGE,
     COLOR_GOLD, COLOR_YELLOW, COLOR_LIGHTYELLOW, COLOR_WHITE},
    {COLOR_DARKBLUE, COLOR_BLUE, COLOR_DODGERBLUE, COLOR_DEEPSKYBLUE,
     COLOR_CYAN, COLOR_TURQUOISE, COLOR_LIGHTBLUE, COLOR_WHITE},
    {COLOR_DARKMAGENTA, COLOR_PURPLE, COLOR_MEDIUMPURPLE, COLOR_MAGENTA,
     COLOR_DEEPPINK, COLOR_CYAN, COLOR_LIME, COLOR_WHITE},
    {COLOR_DARKGREEN, COLOR_FORESTGREEN, COLOR_GREEN, COLOR_LIMEGREEN,
     COLOR_LIME, COLOR_GREENYELLOW, COLOR_LIGHTGREEN, COLOR_WHITE},
    {COLOR_DARKRED, COLOR_FIREBRICK, COLOR_CRIMSON, COLOR_RED,
     COLOR_ORANGERED, COLOR_ORANGE, COLOR_LIGHTSALMON, COLOR_WHITE},
};
static const float speed_multipliers[KALEIDOSCOPE_SPEED_COUNT] = {
    0.50f, 0.80f, 1.20f, 1.70f,
};
static const float tunnel_scales[KALEIDOSCOPE_TUNNEL_LAYERS] = {
    0.38f, 0.66f, 1.00f,
};
static const char *const pattern_names[KALEIDOSCOPE_PATTERN_COUNT] = {
    "RIBBONS", "STARS", "SQUARES",
};
static const char *const theme_names[KALEIDOSCOPE_THEME_COUNT] = {
    "RAINBOW", "FIRE", "OCEAN", "NEON", "MONO", "RED",
};

static kaleidoscope_particle_t particles[KALEIDOSCOPE_PARTICLES];
static uint32_t last_update_ms;
static uint32_t controls_changed_at;
static uint8_t speed_index;
static uint8_t pattern_index;
static uint8_t theme_index;
static float pulse_phase;
static bool pattern_changed;
static bool reset_requested;
static bool theme_changed;
static bool clear_requested;
static bool running;

static uint16_t particleColour(const kaleidoscope_particle_t *particle)
{
    return theme_colours[theme_index][particle->colour];
}

static void fadeFrame(uint16_t *frame)
{
    for (size_t index = 0U; index < (size_t)LCD_WIDTH * LCD_HEIGHT; ++index) {
        const uint16_t pixel = frame[index];
        const uint16_t red = (uint16_t)(((pixel >> 11U) & 0x1FU) * 15U / 16U);
        const uint16_t green = (uint16_t)(((pixel >> 5U) & 0x3FU) * 15U / 16U);
        const uint16_t blue = (uint16_t)((pixel & 0x1FU) * 15U / 16U);
        frame[index] = (uint16_t)((red << 11U) | (green << 5U) | blue);
    }
}

static void drawRibbon(uint16_t *frame, const kaleidoscope_particle_t *particle, float angle,
                       float pulse_scale)
{
    const float radius = particle->radius * pulse_scale;
    const float ribbon_end = (particle->radius - particle->length) * pulse_scale;
    const float side_angle = angle + KALEIDOSCOPE_PI / 2.0f;
    const float side_x = cosf(side_angle) * particle->width * pulse_scale;
    const float side_y = sinf(side_angle) * particle->width * pulse_scale;
    const int x0 = KALEIDOSCOPE_CENTRE_X +
        (int)lroundf(cosf(angle) * radius);
    const int y0 = KALEIDOSCOPE_CENTRE_Y +
        (int)lroundf(sinf(angle) * radius);
    const int x1 = KALEIDOSCOPE_CENTRE_X +
        (int)lroundf(cosf(angle) * ribbon_end);
    const int y1 = KALEIDOSCOPE_CENTRE_Y +
        (int)lroundf(sinf(angle) * ribbon_end);
    for (int edge = -1; edge <= 1; ++edge) {
        draw_line(frame, (int)lroundf((float)x0 + side_x * edge),
                  (int)lroundf((float)y0 + side_y * edge),
                  (int)lroundf((float)x1 + side_x * edge),
                  (int)lroundf((float)y1 + side_y * edge),
                  particleColour(particle));
    }
}

static void drawStar(uint16_t *frame, const kaleidoscope_particle_t *particle, float angle,
                     float pulse_scale)
{
    const int centre_x = KALEIDOSCOPE_CENTRE_X +
        (int)lroundf(cosf(angle) * particle->radius * pulse_scale);
    const int centre_y = KALEIDOSCOPE_CENTRE_Y +
        (int)lroundf(sinf(angle) * particle->radius * pulse_scale);
    const float arm = (5.0f + particle->width * 2.0f) * pulse_scale;
    for (unsigned arm_index = 0U; arm_index < 4U; ++arm_index) {
        const float arm_angle = angle + (float)arm_index * KALEIDOSCOPE_PI / 2.0f;
        const int end_x = centre_x + (int)lroundf(cosf(arm_angle) * arm);
        const int end_y = centre_y + (int)lroundf(sinf(arm_angle) * arm);
        draw_line(frame, centre_x, centre_y, end_x, end_y, particleColour(particle));
    }
    fill_circle(frame, centre_x, centre_y, 2, particleColour(particle));
}

static void drawSquare(uint16_t *frame, const kaleidoscope_particle_t *particle, float angle,
                       float pulse_scale)
{
    const int centre_x = KALEIDOSCOPE_CENTRE_X +
        (int)lroundf(cosf(angle) * particle->radius * pulse_scale);
    const int centre_y = KALEIDOSCOPE_CENTRE_Y +
        (int)lroundf(sinf(angle) * particle->radius * pulse_scale);
    const float half_side = (5.0f + particle->width * 2.0f) * pulse_scale;
    int previous_x = 0;
    int previous_y = 0;
    for (unsigned corner = 0U; corner <= 4U; ++corner) {
        const float corner_angle = angle + KALEIDOSCOPE_PI / 4.0f +
            (float)(corner % 4U) * KALEIDOSCOPE_PI / 2.0f;
        const int x = centre_x + (int)lroundf(cosf(corner_angle) * half_side);
        const int y = centre_y + (int)lroundf(sinf(corner_angle) * half_side);
        if (corner > 0U) draw_line(frame, previous_x, previous_y, x, y,
                                   particleColour(particle));
        previous_x = x;
        previous_y = y;
    }
}

static void resetParticle(kaleidoscope_particle_t *particle)
{
    particle->angle = (float)(esp_random() % 1000U) * KALEIDOSCOPE_WEDGE_ANGLE / 1000.0f;
    particle->radius = 6.0f + (float)(esp_random() % 6800U) / 100.0f;
    particle->angle_speed = 0.10f + (float)(esp_random() % 120U) / 500.0f;
    particle->radius_speed = 8.0f + (float)(esp_random() % 220U) / 10.0f;
    particle->length = 18.0f + (float)(esp_random() % 4400U) / 100.0f;
    particle->width = 2.0f + (float)(esp_random() % 3U);
    particle->colour = (uint8_t)(esp_random() % KALEIDOSCOPE_THEME_COLOURS);
}

void startKaleidoscope(uint32_t now_ms)
{
    for (unsigned index = 0U; index < KALEIDOSCOPE_PARTICLES; ++index) {
        resetParticle(&particles[index]);
    }
    last_update_ms = now_ms;
    controls_changed_at = now_ms;
    speed_index = 1U;
    pattern_index = 0U;
    theme_index = 0U;
    pulse_phase = 0.0f;
    pattern_changed = false;
    reset_requested = false;
    theme_changed = false;
    clear_requested = true;
    running = true;
}

void stopKaleidoscope(void)
{
    running = false;
}

void kaleidoscopeAction(bool new_pattern, bool reset, bool theme, bool speed, uint32_t now_ms)
{
    if (!running) return;
    if (new_pattern) {
        pattern_index = (uint8_t)((pattern_index + 1U) % KALEIDOSCOPE_PATTERN_COUNT);
    }
    if (new_pattern || reset) {
        for (unsigned index = 0U; index < KALEIDOSCOPE_PARTICLES; ++index) {
            resetParticle(&particles[index]);
        }
        clear_requested = true;
    }
    if (theme) theme_index = (uint8_t)((theme_index + 1U) % KALEIDOSCOPE_THEME_COUNT);
    if (speed) speed_index = (uint8_t)((speed_index + 1U) % KALEIDOSCOPE_SPEED_COUNT);
    if (!new_pattern && !reset && !theme && !speed) return;
    pattern_changed = new_pattern;
    reset_requested = reset && !new_pattern;
    theme_changed = theme && !new_pattern && !reset;
    controls_changed_at = now_ms;
}

void updateKaleidoscope(uint32_t now_ms)
{
    if (!running || now_ms - last_update_ms < KALEIDOSCOPE_FRAME_MS) return;
    const uint32_t elapsed_ms = now_ms - last_update_ms;
    last_update_ms = now_ms;
    const float elapsed = (float)(elapsed_ms > 50U ? 50U : elapsed_ms) / 1000.0f *
        speed_multipliers[speed_index];
    pulse_phase += elapsed * KALEIDOSCOPE_PULSE_RATE;
    if (pulse_phase >= 2.0f * KALEIDOSCOPE_PI) pulse_phase -= 2.0f * KALEIDOSCOPE_PI;
    for (unsigned index = 0U; index < KALEIDOSCOPE_PARTICLES; ++index) {
        kaleidoscope_particle_t *particle = &particles[index];
        particle->angle += particle->angle_speed * elapsed;
        if (particle->angle > KALEIDOSCOPE_WEDGE_ANGLE) {
            particle->angle = KALEIDOSCOPE_WEDGE_ANGLE -
                (particle->angle - KALEIDOSCOPE_WEDGE_ANGLE);
            particle->angle_speed = -particle->angle_speed;
        } else if (particle->angle < 0.0f) {
            particle->angle = -particle->angle;
            particle->angle_speed = -particle->angle_speed;
        }
        particle->radius += particle->radius_speed * elapsed;
        if (particle->radius > KALEIDOSCOPE_RADIUS) resetParticle(particle);
    }
}

void drawKaleidoscope(uint16_t *frame)
{
    if (!running || frame == NULL) return;
    if (clear_requested) {
        clear(frame, COLOR_BLACK);
        clear_requested = false;
    } else {
        fadeFrame(frame);
    }
    const unsigned particle_count = pattern_index == 0U ? KALEIDOSCOPE_PARTICLES : 9U;
    const float pulse_scale = 1.0f + sinf(pulse_phase) * KALEIDOSCOPE_PULSE_AMOUNT;
    for (unsigned index = 0U; index < particle_count; ++index) {
        const kaleidoscope_particle_t *particle = &particles[index];
        for (unsigned sector = 0U; sector < KALEIDOSCOPE_SECTORS; ++sector) {
            const float base_angle = (float)sector * 2.0f * KALEIDOSCOPE_WEDGE_ANGLE;
            for (unsigned reflection = 0U; reflection < 2U; ++reflection) {
                const float angle = base_angle + (reflection == 0U ? particle->angle :
                                                   -particle->angle);
                for (unsigned layer = 0U; layer < KALEIDOSCOPE_TUNNEL_LAYERS; ++layer) {
                    const float layer_scale = pulse_scale * tunnel_scales[layer];
                    if (pattern_index == 0U) drawRibbon(frame, particle, angle, layer_scale);
                    else if (pattern_index == 1U) drawStar(frame, particle, angle, layer_scale);
                    else drawSquare(frame, particle, angle, layer_scale);
                }
            }
        }
    }
    fill_circle(frame, KALEIDOSCOPE_CENTRE_X, KALEIDOSCOPE_CENTRE_Y,
                (int)lroundf(4.0f * pulse_scale), COLOR_WHITE);
    if (last_update_ms - controls_changed_at < KALEIDOSCOPE_STATUS_MS) {
        char text[18];
        if (pattern_changed) {
            snprintf(text, sizeof(text), "A %s", pattern_names[pattern_index]);
        } else if (reset_requested) {
            snprintf(text, sizeof(text), "D RESET");
        } else if (theme_changed) {
            snprintf(text, sizeof(text), "C %s", theme_names[theme_index]);
        } else {
            snprintf(text, sizeof(text), "E SPEED %u", (unsigned)(speed_index + 1U));
        }
        drawPet2001TextCentredTransparent(frame, KALEIDOSCOPE_CENTRE_X, 155, text,
                                           COLOR_LIGHTGREY, 1);
    }
}