Demo

Source: src/demo.c · 4405 lines · 85 functions.

Screen captures

Views of this part running on the Mini Arcade.

Solid 3D demo segment on the Mini Arcade
The Solid 3D segment from the wider Demo module.

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

bitmapPixelSet() · source line 2068
static bool bitmapPixelSet(const uint8_t *bitmap, unsigned x, unsigned y)
  1. Executeconst size_t byte_index = (size_t)y * DEMO_LOGO_ROW_BYTES + x / 8U;
  2. Executeconst uint8_t bit = (uint8_t)(7U - x % 8U);
  3. Exit this functionreturn (bitmap[byte_index] & (uint8_t)(1U << bit)) != 0U;
Read original function source
static bool bitmapPixelSet(const uint8_t *bitmap, unsigned x, unsigned y)
{
    const size_t byte_index = (size_t)y * DEMO_LOGO_ROW_BYTES + x / 8U;
    const uint8_t bit = (uint8_t)(7U - x % 8U);
    return (bitmap[byte_index] & (uint8_t)(1U << bit)) != 0U;
}
advanceIconSlot() · source line 2075
static void advanceIconSlot(void)
  1. Executeactive_icon_slot = (active_icon_slot + 1U) % DEMO_ICON_SLOT_COUNT;
  2. Executeactive_logo_bitmap = demo_icon_slots[active_icon_slot];
Read original function source
static void advanceIconSlot(void)
{
    active_icon_slot = (active_icon_slot + 1U) % DEMO_ICON_SLOT_COUNT;
    active_logo_bitmap = demo_icon_slots[active_icon_slot];
}
selectDemoIconColourScheme() · source line 2081
static void selectDemoIconColourScheme(void)
  1. Executedemo_icon_colour_scheme_index = (size_t)esp_random() % DEMO_ICON_COLOUR_SCHEME_COUNT;
Read original function source
static void selectDemoIconColourScheme(void)
{
    demo_icon_colour_scheme_index =
        (size_t)esp_random() % DEMO_ICON_COLOUR_SCHEME_COUNT;
}
demoStageInactivityTimeout() · source line 2087
static uint32_t demoStageInactivityTimeout(void)

Calls into this documentation:

  1. Exit this functionreturn demoSelectorStageInactivityTimeout(selected_stage);
Read original function source
static uint32_t demoStageInactivityTimeout(void)
{
    return demoSelectorStageInactivityTimeout(selected_stage);
}
buildStageThreeBallMap() · source line 2092
static void buildStageThreeBallMap(void)
  1. Decisionstage_three_ball_map_ready
    True
    1. Exit this functionreturn;
    False

    Continue after this decision.

  2. FOR loopint y = 0; y < DEMO_STAGE_THREE_BALL_DIAMETER; ++y

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

      1. FOR loopint x = 0; x < DEMO_STAGE_THREE_BALL_DIAMETER; ++x

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

          1. Executeconst int index = y * DEMO_STAGE_THREE_BALL_DIAMETER + x;
          2. Executeconst float normal_x = (float)(x - DEMO_STAGE_THREE_BALL_RADIUS) / (float)DEMO_STAGE_THREE_BALL_RADIUS;
          3. Executeconst float normal_y = (float)(y - DEMO_STAGE_THREE_BALL_RADIUS) / (float)DEMO_STAGE_THREE_BALL_RADIUS;
          4. Executeconst float radius_squared = normal_x * normal_x + normal_y * normal_y;
          5. Decisionradius_squared > 1.0f
            True
              1. Executestage_three_ball_shade_map[index] = 0U;
              2. Next loop iterationcontinue;
            False

            Continue after this decision.

          6. Executeconst float depth = sqrtf(1.0f - radius_squared);
          7. Executeconst float longitude = (atan2f(normal_x, depth) + DEMO_PI) / (2.0f * DEMO_PI);
          8. Executeconst float latitude = (asinf(normal_y) + DEMO_PI * 0.5f) / DEMO_PI;
          9. Executestage_three_ball_longitude_map[index] = (uint8_t)(longitude * 255.0f);
          10. Executestage_three_ball_latitude_map[index] = (uint8_t)(latitude * 255.0f);
          11. Executestage_three_ball_shade_map[index] = depth > 0.78f ? 3U : depth > 0.42f ? 2U : 1U;
  3. Executestage_three_ball_map_ready = true;
Read original function source
static void buildStageThreeBallMap(void)
{
    if (stage_three_ball_map_ready) return;
    for (int y = 0; y < DEMO_STAGE_THREE_BALL_DIAMETER; ++y) {
        for (int x = 0; x < DEMO_STAGE_THREE_BALL_DIAMETER; ++x) {
            const int index = y * DEMO_STAGE_THREE_BALL_DIAMETER + x;
            const float normal_x = (float)(x - DEMO_STAGE_THREE_BALL_RADIUS) /
                                   (float)DEMO_STAGE_THREE_BALL_RADIUS;
            const float normal_y = (float)(y - DEMO_STAGE_THREE_BALL_RADIUS) /
                                   (float)DEMO_STAGE_THREE_BALL_RADIUS;
            const float radius_squared = normal_x * normal_x + normal_y * normal_y;
            if (radius_squared > 1.0f) {
                stage_three_ball_shade_map[index] = 0U;
                continue;
            }
            const float depth = sqrtf(1.0f - radius_squared);
            const float longitude = (atan2f(normal_x, depth) + DEMO_PI) / (2.0f * DEMO_PI);
            const float latitude = (asinf(normal_y) + DEMO_PI * 0.5f) / DEMO_PI;
            stage_three_ball_longitude_map[index] = (uint8_t)(longitude * 255.0f);
            stage_three_ball_latitude_map[index] = (uint8_t)(latitude * 255.0f);
            stage_three_ball_shade_map[index] = depth > 0.78f ? 3U : depth > 0.42f ? 2U : 1U;
        }
    }
    stage_three_ball_map_ready = true;
}
startStageThreeBall() · source line 2119
static void startStageThreeBall(uint32_t now_ms)

Calls into this documentation:

  1. ExecutebuildStageThreeBallMap();
  2. Executestage_three_next_icon_change_at = now_ms + DEMO_STAGE_THREE_ICON_CHANGE_MS;
  3. Executestage_three_icon_slot = 0U;
  4. Executestage_three_icon_bitmap = demo_icon_slots[stage_three_icon_slot];
  5. Executestage_three_ball_x = 82.0f;
  6. Executestage_three_ball_y = 52.0f;
  7. Executestage_three_ball_velocity_x = DEMO_STAGE_THREE_BALL_HORIZONTAL_VELOCITY;
  8. Executestage_three_ball_velocity_y = 0.0f;
  9. Executestage_three_ball_rotation_phase = 0.0f;
Read original function source
static void startStageThreeBall(uint32_t now_ms)
{
    buildStageThreeBallMap();
    stage_three_next_icon_change_at = now_ms + DEMO_STAGE_THREE_ICON_CHANGE_MS;
    stage_three_icon_slot = 0U;
    stage_three_icon_bitmap = demo_icon_slots[stage_three_icon_slot];
    stage_three_ball_x = 82.0f;
    stage_three_ball_y = 52.0f;
    stage_three_ball_velocity_x = DEMO_STAGE_THREE_BALL_HORIZONTAL_VELOCITY;
    stage_three_ball_velocity_y = 0.0f;
    stage_three_ball_rotation_phase = 0.0f;
}
selectStageSixMesh() · source line 2132
static void selectStageSixMesh(void)
  1. Executeconst demo_mesh_t *next_mesh;
  2. DO / WHILE loopnext_mesh == stage_six_current_mesh

    Run body, then test condition; repeat while true.

      1. Executenext_mesh = demo_stage_six_mesh_catalog[esp_random() % DEMO_STAGE_SIX_MESH_CATALOG_COUNT];
  3. Executestage_six_current_mesh = next_mesh;
  4. Executesize_t next_colour_scheme = (size_t)esp_random() % DEMO_SOLID_COLOUR_SCHEME_COUNT;
  5. Decisionstage_six_colour_scheme_index < DEMO_SOLID_COLOUR_SCHEME_COUNT && DEMO_SOLID_COLOUR_SCHEME_COUNT > 1U
    True
      1. Executenext_colour_scheme = (size_t)esp_random() % (DEMO_SOLID_COLOUR_SCHEME_COUNT - 1U);
      2. Decisionnext_colour_scheme >= stage_six_colour_scheme_index
        True
        1. Execute++next_colour_scheme;
        False

        Continue after this decision.

    False

    Continue after this decision.

  6. Executestage_six_colour_scheme_index = next_colour_scheme;
Read original function source
static void selectStageSixMesh(void)
{
    const demo_mesh_t *next_mesh;
    do {
        next_mesh = demo_stage_six_mesh_catalog[esp_random() % DEMO_STAGE_SIX_MESH_CATALOG_COUNT];
    } while (next_mesh == stage_six_current_mesh);
    stage_six_current_mesh = next_mesh;

    size_t next_colour_scheme =
        (size_t)esp_random() % DEMO_SOLID_COLOUR_SCHEME_COUNT;
    if (stage_six_colour_scheme_index < DEMO_SOLID_COLOUR_SCHEME_COUNT &&
        DEMO_SOLID_COLOUR_SCHEME_COUNT > 1U) {
        next_colour_scheme = (size_t)esp_random() % (DEMO_SOLID_COLOUR_SCHEME_COUNT - 1U);
        if (next_colour_scheme >= stage_six_colour_scheme_index) ++next_colour_scheme;
    }
    stage_six_colour_scheme_index = next_colour_scheme;
}
startStageSixSolid() · source line 2151
static void startStageSixSolid(uint32_t now_ms)

Calls into this documentation:

  1. Executestage_six_next_change_at = now_ms + DEMO_STAGE_SIX_CHANGE_MS;
  2. Executestage_six_current_mesh = NULL;
  3. ExecuteselectStageSixMesh();
Read original function source
static void startStageSixSolid(uint32_t now_ms)
{
    stage_six_next_change_at = now_ms + DEMO_STAGE_SIX_CHANGE_MS;
    stage_six_current_mesh = NULL;
    selectStageSixMesh();
}
randomTextCornerIconTravelDuration() · source line 2158
static uint32_t randomTextCornerIconTravelDuration(void)
  1. Executeconst uint32_t range = DEMO_TEXT_CORNER_ICON_TRAVEL_MAX_MS - DEMO_TEXT_CORNER_ICON_TRAVEL_MIN_MS + 1U;
  2. Exit this functionreturn DEMO_TEXT_CORNER_ICON_TRAVEL_MIN_MS + esp_random() % range;
Read original function source
static uint32_t randomTextCornerIconTravelDuration(void)
{
    // Keep the range inclusive so setting MIN and MAX equal later creates a
    // fixed, repeatable period without changing the animation code.
    const uint32_t range = DEMO_TEXT_CORNER_ICON_TRAVEL_MAX_MS -
                           DEMO_TEXT_CORNER_ICON_TRAVEL_MIN_MS + 1U;
    return DEMO_TEXT_CORNER_ICON_TRAVEL_MIN_MS + esp_random() % range;
}
randomTextCornerIconRotationSpeed() · source line 2167
static int16_t randomTextCornerIconRotationSpeed(void)
  1. Executeconst uint32_t range = (uint32_t)(DEMO_TEXT_CORNER_ICON_ROTATION_MAX_DEGREES_PER_SECOND - DEMO_TEXT_CORNER_ICON_ROTATION_MIN_DEGREES_PER_SECOND + 1);
  2. Executeconst int16_t speed = (int16_t)(DEMO_TEXT_CORNER_ICON_ROTATION_MIN_DEGREES_PER_SECOND + (int)(esp_random() % range));
  3. Exit this functionreturn (esp_random() & 1U) != 0U ? speed : (int16_t)-speed;
Read original function source
static int16_t randomTextCornerIconRotationSpeed(void)
{
    const uint32_t range = (uint32_t)(DEMO_TEXT_CORNER_ICON_ROTATION_MAX_DEGREES_PER_SECOND -
                                      DEMO_TEXT_CORNER_ICON_ROTATION_MIN_DEGREES_PER_SECOND + 1);
    const int16_t speed = (int16_t)(DEMO_TEXT_CORNER_ICON_ROTATION_MIN_DEGREES_PER_SECOND +
                                    (int)(esp_random() % range));
    // Give neighbouring icons visibly different movement by independently
    // choosing their clockwise or anti-clockwise direction as well as speed.
    return (esp_random() & 1U) != 0U ? speed : (int16_t)-speed;
}
startTextCornerIconCycles() · source line 2178
static void startTextCornerIconCycles(uint32_t now_ms)

Calls into this documentation:

  1. FOR loopsize_t corner = 0U; corner < DEMO_TEXT_CORNER_ICON_COUNT; ++corner

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

      1. Executedemo_text_corner_icon_cycles[corner].cycle_started_at = now_ms;
      2. Executedemo_text_corner_icon_cycles[corner].cycle_duration_ms = randomTextCornerIconTravelDuration();
      3. Executedemo_text_corner_icon_cycles[corner].rotation_started_at = now_ms;
      4. Executedemo_text_corner_icon_cycles[corner].rotation_start_degrees = (float)(esp_random() % 360U);
      5. Executedemo_text_corner_icon_cycles[corner].rotation_degrees_per_second = randomTextCornerIconRotationSpeed();
Read original function source
static void startTextCornerIconCycles(uint32_t now_ms)
{
    // A text segment always starts with every icon at its own outer corner.
    // The first random duration then separates the icons naturally as they
    // travel toward the centre X line at different rates.
    for (size_t corner = 0U; corner < DEMO_TEXT_CORNER_ICON_COUNT; ++corner) {
        demo_text_corner_icon_cycles[corner].cycle_started_at = now_ms;
        demo_text_corner_icon_cycles[corner].cycle_duration_ms =
            randomTextCornerIconTravelDuration();
        // Rotation does not restart with every travel cycle: it stays smooth
        // while movement chooses a new random duration at each outer edge.
        demo_text_corner_icon_cycles[corner].rotation_started_at = now_ms;
        demo_text_corner_icon_cycles[corner].rotation_start_degrees =
            (float)(esp_random() % 360U);
        demo_text_corner_icon_cycles[corner].rotation_degrees_per_second =
            randomTextCornerIconRotationSpeed();
    }
}
updateTextCornerIconCycles() · source line 2197
static void updateTextCornerIconCycles(uint32_t now_ms)

Calls into this documentation:

  1. FOR loopsize_t corner = 0U; corner < DEMO_TEXT_CORNER_ICON_COUNT; ++corner

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

      1. Executedemo_text_corner_icon_cycle_t *const cycle = &demo_text_corner_icon_cycles[corner];
      2. WHILE loop(uint32_t)(now_ms - cycle->cycle_started_at) >= cycle->cycle_duration_ms

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

          1. Executecycle->cycle_started_at += cycle->cycle_duration_ms;
          2. Executecycle->cycle_duration_ms = randomTextCornerIconTravelDuration();
Read original function source
static void updateTextCornerIconCycles(uint32_t now_ms)
{
    for (size_t corner = 0U; corner < DEMO_TEXT_CORNER_ICON_COUNT; ++corner) {
        demo_text_corner_icon_cycle_t *const cycle = &demo_text_corner_icon_cycles[corner];
        // A late frame can cross more than one complete cycle. Preserve the
        // remaining time by advancing from the old start, rather than snapping
        // the icon back to its corner at the current frame time.
        while ((uint32_t)(now_ms - cycle->cycle_started_at) >= cycle->cycle_duration_ms) {
            cycle->cycle_started_at += cycle->cycle_duration_ms;
            cycle->cycle_duration_ms = randomTextCornerIconTravelDuration();
        }
    }
}
startStageSevenWalloons() · source line 2211
static void startStageSevenWalloons(uint32_t now_ms)
  1. Executestage_seven_phase = WALLOONS_TITLE_CARD;
  2. Executestage_seven_phase_started_at = now_ms;
  3. Executestage_seven_completed_bounces = 0U;
Read original function source
static void startStageSevenWalloons(uint32_t now_ms)
{
    // Begin with the boxed PET title. After the short title interval, the box
    // disappears and the original action begins with the left Walloon walking
    // from PET cell (2, 15) to (14, 15).
    stage_seven_phase = WALLOONS_TITLE_CARD;
    stage_seven_phase_started_at = now_ms;
    stage_seven_completed_bounces = 0U;
}
releaseStageTwelveImage() · source line 2222
static void releaseStageTwelveImage(void)
  1. Executeheap_caps_free(stage_twelve_image);
  2. Executestage_twelve_image = NULL;
  3. Executestage_twelve_image_ready = false;
  4. Executestage_twelve_image_path[0] = '\0';
  5. Executestage_twelve_next_image_change_at = 0U;
Read original function source
static void releaseStageTwelveImage(void)
{
    heap_caps_free(stage_twelve_image);
    stage_twelve_image = NULL;
    stage_twelve_image_ready = false;
    stage_twelve_image_path[0] = '\0';
    stage_twelve_next_image_change_at = 0U;
}
releaseStageFifteenGroundSource() · source line 2232
static void releaseStageFifteenGroundSource(void)
  1. Executeheap_caps_free(stage_fifteen_ground_source);
  2. Executestage_fifteen_ground_source = NULL;
Read original function source
static void releaseStageFifteenGroundSource(void)
{
    heap_caps_free(stage_fifteen_ground_source);
    stage_fifteen_ground_source = NULL;
}
startStageFifteenBattleZoneRaster() · source line 2238
static void startStageFifteenBattleZoneRaster(void)

Calls into this documentation:

  1. ExecutereleaseStageFifteenGroundSource();
  2. Executeconst size_t ground_pixels = (size_t)LCD_WIDTH * (LCD_HEIGHT - BATTLE_ZONE_HORIZON_Y);
  3. Executestage_fifteen_ground_source = heap_caps_malloc(ground_pixels * sizeof(stage_fifteen_ground_source[0]), MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
Read original function source
static void startStageFifteenBattleZoneRaster(void)
{
    releaseStageFifteenGroundSource();
    const size_t ground_pixels = (size_t)LCD_WIDTH *
        (LCD_HEIGHT - BATTLE_ZONE_HORIZON_Y);
    stage_fifteen_ground_source = heap_caps_malloc(ground_pixels *
                                                   sizeof(stage_fifteen_ground_source[0]),
                                                   MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
}
chooseStageTwelveLensEdgePoint() · source line 2249
static void chooseStageTwelveLensEdgePoint(stage_twelve_lens_edge_t edge,
                                           float *x, float *y)
  1. Select a caseedge

    Enter matching case; follow source fall-through until a break, return or end.

      1. Case / labelcase STAGE_TWELVE_LENS_EDGE_TOP:
      2. Execute*x = (float)(esp_random() % LCD_WIDTH);
      3. Execute*y = 0.0f;
      4. Exit enclosing loop or switchbreak;
      5. Case / labelcase STAGE_TWELVE_LENS_EDGE_RIGHT:
      6. Execute*x = (float)(LCD_WIDTH - 1);
      7. Execute*y = (float)(esp_random() % LCD_HEIGHT);
      8. Exit enclosing loop or switchbreak;
      9. Case / labelcase STAGE_TWELVE_LENS_EDGE_BOTTOM:
      10. Execute*x = (float)(esp_random() % LCD_WIDTH);
      11. Execute*y = (float)(LCD_HEIGHT - 1);
      12. Exit enclosing loop or switchbreak;
      13. Case / labelcase STAGE_TWELVE_LENS_EDGE_LEFT:
      14. Case / labeldefault:
      15. Execute*x = 0.0f;
      16. Execute*y = (float)(esp_random() % LCD_HEIGHT);
      17. Exit enclosing loop or switchbreak;
Read original function source
static void chooseStageTwelveLensEdgePoint(stage_twelve_lens_edge_t edge,
                                           float *x, float *y)
{
    // The centre is always on one screen edge. The lens radius can therefore
    // extend outside the display, while every sampled source pixel stays valid.
    switch (edge) {
        case STAGE_TWELVE_LENS_EDGE_TOP:
            *x = (float)(esp_random() % LCD_WIDTH);
            *y = 0.0f;
            break;
        case STAGE_TWELVE_LENS_EDGE_RIGHT:
            *x = (float)(LCD_WIDTH - 1);
            *y = (float)(esp_random() % LCD_HEIGHT);
            break;
        case STAGE_TWELVE_LENS_EDGE_BOTTOM:
            *x = (float)(esp_random() % LCD_WIDTH);
            *y = (float)(LCD_HEIGHT - 1);
            break;
        case STAGE_TWELVE_LENS_EDGE_LEFT:
        default:
            *x = 0.0f;
            *y = (float)(esp_random() % LCD_HEIGHT);
            break;
    }
}
chooseStageTwelveLensTarget() · source line 2275
static void chooseStageTwelveLensTarget(void)

Calls into this documentation:

  1. FOR loopunsigned attempt = 0U; attempt < DEMO_STAGE_TWELVE_LENS_TARGET_ATTEMPTS; ++attempt

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

      1. DO / WHILE loopstage_twelve_lens_target_edge == stage_twelve_lens_current_edge

        Run body, then test condition; repeat while true.

          1. Executestage_twelve_lens_target_edge = (stage_twelve_lens_edge_t)(esp_random() % STAGE_TWELVE_LENS_EDGE_COUNT);
      2. ExecutechooseStageTwelveLensEdgePoint(stage_twelve_lens_target_edge, &stage_twelve_lens_target_x, &stage_twelve_lens_target_y);
      3. Executeconst float dx = stage_twelve_lens_target_x - stage_twelve_lens_x;
      4. Executeconst float dy = stage_twelve_lens_target_y - stage_twelve_lens_y;
      5. Executeconst float distance_squared = dx * dx + dy * dy;
      6. Executeconst float minimum_squared = DEMO_STAGE_TWELVE_LENS_MIN_TARGET_DISTANCE * DEMO_STAGE_TWELVE_LENS_MIN_TARGET_DISTANCE;
      7. Decisiondistance_squared >= minimum_squared
        True
        1. Exit this functionreturn;
        False

        Continue after this decision.

Read original function source
static void chooseStageTwelveLensTarget(void)
{
    // Pick a different edge and a visibly long route. The bounded retry count
    // guarantees progress even if future tuning makes the distance too strict.
    for (unsigned attempt = 0U; attempt < DEMO_STAGE_TWELVE_LENS_TARGET_ATTEMPTS; ++attempt) {
        do {
            stage_twelve_lens_target_edge =
                (stage_twelve_lens_edge_t)(esp_random() % STAGE_TWELVE_LENS_EDGE_COUNT);
        } while (stage_twelve_lens_target_edge == stage_twelve_lens_current_edge);
        chooseStageTwelveLensEdgePoint(stage_twelve_lens_target_edge,
                                       &stage_twelve_lens_target_x,
                                       &stage_twelve_lens_target_y);
        const float dx = stage_twelve_lens_target_x - stage_twelve_lens_x;
        const float dy = stage_twelve_lens_target_y - stage_twelve_lens_y;
        const float distance_squared = dx * dx + dy * dy;
        const float minimum_squared = DEMO_STAGE_TWELVE_LENS_MIN_TARGET_DISTANCE *
                                      DEMO_STAGE_TWELVE_LENS_MIN_TARGET_DISTANCE;
        if (distance_squared >= minimum_squared) return;
    }
}
selectStageTwelveSlideshowImage() · source line 2296
static bool selectStageTwelveSlideshowImage(char *path, size_t path_size,
                                            const char *excluded_path)

Calls into this documentation:

  1. ExecuteDIR *const directory = opendir(DEMO_STAGE_TWELVE_SLIDESHOW_DIRECTORY);
  2. Decisiondirectory == NULL
    True
    1. Exit this functionreturn false;
    False

    Continue after this decision.

  3. Executechar selected_name[DEMO_STAGE_TWELVE_IMAGE_PATH_MAX] = {0};
  4. Executesize_t supported_count = 0U;
  5. Executestruct dirent *entry;
  6. WHILE loop(entry = readdir(directory)) != NULL

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

      1. Decision!imageIsSupportedName(entry->d_name)
        True
        1. Next loop iterationcontinue;
        False

        Continue after this decision.

      2. Executechar candidate_path[DEMO_STAGE_TWELVE_IMAGE_PATH_MAX];
      3. Executeconst int candidate_written = snprintf(candidate_path, sizeof(candidate_path), "%s/%s", DEMO_STAGE_TWELVE_SLIDESHOW_DIRECTORY, entry->d_name);
      4. Decisioncandidate_written < 0 || (size_t)candidate_written >= sizeof(candidate_path)
        True
        1. Next loop iterationcontinue;
        False

        Continue after this decision.

      5. Decisionexcluded_path != NULL && strcmp(candidate_path, excluded_path) == 0
        True
        1. Next loop iterationcontinue;
        False

        Continue after this decision.

      6. Execute++supported_count;
      7. Decisionesp_random() % supported_count != 0U
        True
        1. Next loop iterationcontinue;
        False

        Continue after this decision.

      8. Executeconst int written = snprintf(selected_name, sizeof(selected_name), "%s", entry->d_name);
      9. Decisionwritten < 0 || (size_t)written >= sizeof(selected_name)
        True
          1. Executeclosedir(directory);
          2. Exit this functionreturn false;
        False

        Continue after this decision.

  7. Executeclosedir(directory);
  8. Decisionsupported_count == 0U
    True
    1. Exit this functionreturn false;
    False

    Continue after this decision.

  9. Executeconst int written = snprintf(path, path_size, "%s/%s", DEMO_STAGE_TWELVE_SLIDESHOW_DIRECTORY, selected_name);
  10. Exit this functionreturn written >= 0 && (size_t)written < path_size;
Read original function source
static bool selectStageTwelveSlideshowImage(char *path, size_t path_size,
                                            const char *excluded_path)
{
    DIR *const directory = opendir(DEMO_STAGE_TWELVE_SLIDESHOW_DIRECTORY);
    if (directory == NULL) return false;

    // Reservoir sampling gives every supported file an equal chance without
    // storing the slideshow directory's full filename list in RAM.
    char selected_name[DEMO_STAGE_TWELVE_IMAGE_PATH_MAX] = {0};
    size_t supported_count = 0U;
    struct dirent *entry;
    while ((entry = readdir(directory)) != NULL) {
        if (!imageIsSupportedName(entry->d_name)) continue;
        char candidate_path[DEMO_STAGE_TWELVE_IMAGE_PATH_MAX];
        const int candidate_written = snprintf(candidate_path, sizeof(candidate_path), "%s/%s",
                                               DEMO_STAGE_TWELVE_SLIDESHOW_DIRECTORY, entry->d_name);
        if (candidate_written < 0 || (size_t)candidate_written >= sizeof(candidate_path)) continue;
        // Do not immediately repeat the current picture when the directory
        // contains another supported file to choose from.
        if (excluded_path != NULL && strcmp(candidate_path, excluded_path) == 0) continue;
        ++supported_count;
        if (esp_random() % supported_count != 0U) continue;
        const int written = snprintf(selected_name, sizeof(selected_name), "%s", entry->d_name);
        if (written < 0 || (size_t)written >= sizeof(selected_name)) {
            closedir(directory);
            return false;
        }
    }
    closedir(directory);
    if (supported_count == 0U) return false;

    const int written = snprintf(path, path_size, "%s/%s",
                                 DEMO_STAGE_TWELVE_SLIDESHOW_DIRECTORY, selected_name);
    return written >= 0 && (size_t)written < path_size;
}
beginStageTwelveLensGlide() · source line 2332
static void beginStageTwelveLensGlide(uint32_t now_ms)

Calls into this documentation:

  1. Executestage_twelve_lens_start_x = stage_twelve_lens_x;
  2. Executestage_twelve_lens_start_y = stage_twelve_lens_y;
  3. ExecutechooseStageTwelveLensTarget();
  4. Executestage_twelve_lens_glide_started_at = now_ms;
  5. Executestage_twelve_lens_glide_duration_ms = DEMO_STAGE_TWELVE_LENS_GLIDE_MIN_MS + (esp_random() % DEMO_STAGE_TWELVE_LENS_GLIDE_RANGE_MS);
Read original function source
static void beginStageTwelveLensGlide(uint32_t now_ms)
{
    stage_twelve_lens_start_x = stage_twelve_lens_x;
    stage_twelve_lens_start_y = stage_twelve_lens_y;
    chooseStageTwelveLensTarget();
    stage_twelve_lens_glide_started_at = now_ms;
    stage_twelve_lens_glide_duration_ms = DEMO_STAGE_TWELVE_LENS_GLIDE_MIN_MS +
        (esp_random() % DEMO_STAGE_TWELVE_LENS_GLIDE_RANGE_MS);
}
startStageTwelveSlideshowLens() · source line 2342
static void startStageTwelveSlideshowLens(uint32_t now_ms)

Calls into this documentation:

  1. ExecutereleaseStageTwelveImage();
  2. Executestage_twelve_image = heap_caps_malloc((size_t)LCD_WIDTH * LCD_HEIGHT * sizeof(stage_twelve_image[0]), MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
  3. Decisionstage_twelve_image == NULL
    True
    1. Exit this functionreturn;
    False

    Continue after this decision.

  4. Executechar image_path[DEMO_STAGE_TWELVE_IMAGE_PATH_MAX];
  5. Decision!selectStageTwelveSlideshowImage(image_path, sizeof(image_path), NULL) || !imageDecodeFile(image_path, stage_twelve_image)
    True
      1. ExecutereleaseStageTwelveImage();
      2. Exit this functionreturn;
    False

    Continue after this decision.

  6. Executestage_twelve_image_ready = true;
  7. Executesnprintf(stage_twelve_image_path, sizeof(stage_twelve_image_path), "%s", image_path);
  8. Executestage_twelve_next_image_change_at = now_ms + DEMO_STAGE_TWELVE_IMAGE_CHANGE_MS;
  9. Executestage_twelve_lens_current_edge = (stage_twelve_lens_edge_t)(esp_random() % STAGE_TWELVE_LENS_EDGE_COUNT);
  10. ExecutechooseStageTwelveLensEdgePoint(stage_twelve_lens_current_edge, &stage_twelve_lens_x, &stage_twelve_lens_y);
  11. ExecutebeginStageTwelveLensGlide(now_ms);
Read original function source
static void startStageTwelveSlideshowLens(uint32_t now_ms)
{
    releaseStageTwelveImage();
    stage_twelve_image = heap_caps_malloc((size_t)LCD_WIDTH * LCD_HEIGHT *
                                           sizeof(stage_twelve_image[0]),
                                           MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
    if (stage_twelve_image == NULL) return;

    char image_path[DEMO_STAGE_TWELVE_IMAGE_PATH_MAX];
    if (!selectStageTwelveSlideshowImage(image_path, sizeof(image_path), NULL) ||
        !imageDecodeFile(image_path, stage_twelve_image)) {
        releaseStageTwelveImage();
        return;
    }

    stage_twelve_image_ready = true;
    snprintf(stage_twelve_image_path, sizeof(stage_twelve_image_path), "%s", image_path);
    stage_twelve_next_image_change_at = now_ms + DEMO_STAGE_TWELVE_IMAGE_CHANGE_MS;
    stage_twelve_lens_current_edge =
        (stage_twelve_lens_edge_t)(esp_random() % STAGE_TWELVE_LENS_EDGE_COUNT);
    chooseStageTwelveLensEdgePoint(stage_twelve_lens_current_edge,
                                   &stage_twelve_lens_x, &stage_twelve_lens_y);
    beginStageTwelveLensGlide(now_ms);
}
updateStageTwelveSlideshowLens() · source line 2367
static void updateStageTwelveSlideshowLens(uint32_t now_ms)

Calls into this documentation:

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

    Continue after this decision.

  2. Decision(int32_t)(now_ms - stage_twelve_next_image_change_at) >= 0
    True
      1. Executechar image_path[DEMO_STAGE_TWELVE_IMAGE_PATH_MAX];
      2. DecisionselectStageTwelveSlideshowImage(image_path, sizeof(image_path), stage_twelve_image_path) && imageDecodeFile(image_path, stage_twelve_image)
        True
          1. Executesnprintf(stage_twelve_image_path, sizeof(stage_twelve_image_path), "%s", image_path);
        False

        Continue after this decision.

      3. Executestage_twelve_next_image_change_at = now_ms + DEMO_STAGE_TWELVE_IMAGE_CHANGE_MS;
    False

    Continue after this decision.

  3. Executeconst uint32_t elapsed_ms = now_ms - stage_twelve_lens_glide_started_at;
  4. Decisionelapsed_ms >= stage_twelve_lens_glide_duration_ms
    True
      1. Executestage_twelve_lens_x = stage_twelve_lens_target_x;
      2. Executestage_twelve_lens_y = stage_twelve_lens_target_y;
      3. Executestage_twelve_lens_current_edge = stage_twelve_lens_target_edge;
      4. ExecutebeginStageTwelveLensGlide(now_ms);
      5. Exit this functionreturn;
    False

    Continue after this decision.

  5. Executeconst float progress = (float)elapsed_ms / (float)stage_twelve_lens_glide_duration_ms;
  6. Executestage_twelve_lens_x = stage_twelve_lens_start_x + (stage_twelve_lens_target_x - stage_twelve_lens_start_x) * progress;
  7. Executestage_twelve_lens_y = stage_twelve_lens_start_y + (stage_twelve_lens_target_y - stage_twelve_lens_start_y) * progress;
Read original function source
static void updateStageTwelveSlideshowLens(uint32_t now_ms)
{
    if (!stage_twelve_image_ready) return;
    if ((int32_t)(now_ms - stage_twelve_next_image_change_at) >= 0) {
        char image_path[DEMO_STAGE_TWELVE_IMAGE_PATH_MAX];
        // Reuse the existing PSRAM image buffer. If a new file cannot be
        // selected or decoded, leave the already visible picture in place.
        if (selectStageTwelveSlideshowImage(image_path, sizeof(image_path),
                                             stage_twelve_image_path) &&
            imageDecodeFile(image_path, stage_twelve_image)) {
            snprintf(stage_twelve_image_path, sizeof(stage_twelve_image_path), "%s", image_path);
        }
        stage_twelve_next_image_change_at = now_ms + DEMO_STAGE_TWELVE_IMAGE_CHANGE_MS;
    }
    const uint32_t elapsed_ms = now_ms - stage_twelve_lens_glide_started_at;
    if (elapsed_ms >= stage_twelve_lens_glide_duration_ms) {
        stage_twelve_lens_x = stage_twelve_lens_target_x;
        stage_twelve_lens_y = stage_twelve_lens_target_y;
        stage_twelve_lens_current_edge = stage_twelve_lens_target_edge;
        beginStageTwelveLensGlide(now_ms);
        return;
    }

    const float progress = (float)elapsed_ms / (float)stage_twelve_lens_glide_duration_ms;
    // Use constant-speed interpolation rather than smoothstep. Smoothstep
    // reaches zero speed at every target, which made the lens visibly pause.
    stage_twelve_lens_x = stage_twelve_lens_start_x +
        (stage_twelve_lens_target_x - stage_twelve_lens_start_x) * progress;
    stage_twelve_lens_y = stage_twelve_lens_start_y +
        (stage_twelve_lens_target_y - stage_twelve_lens_start_y) * progress;
}
walloonsPhaseSteps() · source line 2400
static unsigned walloonsPhaseSteps(uint32_t now_ms, unsigned maximum_steps)
  1. Executeconst unsigned elapsed_steps = (now_ms - stage_seven_phase_started_at) / DEMO_STAGE_SEVEN_STEP_MS;
  2. Exit this functionreturn elapsed_steps < maximum_steps ? elapsed_steps : maximum_steps;
Read original function source
static unsigned walloonsPhaseSteps(uint32_t now_ms, unsigned maximum_steps)
{
    const unsigned elapsed_steps = (now_ms - stage_seven_phase_started_at) /
                                   DEMO_STAGE_SEVEN_STEP_MS;
    return elapsed_steps < maximum_steps ? elapsed_steps : maximum_steps;
}
setWalloonsPhase() · source line 2407
static void setWalloonsPhase(walloons_phase_t phase, uint32_t now_ms)
  1. Executestage_seven_phase = phase;
  2. Executestage_seven_phase_started_at = now_ms;
Read original function source
static void setWalloonsPhase(walloons_phase_t phase, uint32_t now_ms)
{
    stage_seven_phase = phase;
    stage_seven_phase_started_at = now_ms;
}
updateStageSevenWalloons() · source line 2413
static void updateStageSevenWalloons(uint32_t now_ms)

Calls into this documentation:

  1. Executeconst uint32_t elapsed = now_ms - stage_seven_phase_started_at;
  2. Executeconst uint32_t walk_duration = 12U * DEMO_STAGE_SEVEN_STEP_MS;
  3. Executeconst uint32_t platform_duration = 14U * DEMO_STAGE_SEVEN_STEP_MS;
  4. Executeconst uint32_t vertical_duration = 13U * DEMO_STAGE_SEVEN_STEP_MS;
  5. Select a casestage_seven_phase

    Enter matching case; follow source fall-through until a break, return or end.

      1. Case / labelcase WALLOONS_TITLE_CARD:
      2. Decisionelapsed >= DEMO_STAGE_SEVEN_TITLE_MS
        True
          1. ExecutesetWalloonsPhase(WALLOONS_LEFT_WALK_IN, now_ms);
        False

        Continue after this decision.

      3. Exit enclosing loop or switchbreak;
      4. Case / labelcase WALLOONS_LEFT_WALK_IN:
      5. Decisionelapsed >= walk_duration
        True
          1. ExecutesetWalloonsPhase(WALLOONS_PAUSE_ON_SEESAW, now_ms);
        False

        Continue after this decision.

      6. Exit enclosing loop or switchbreak;
      7. Case / labelcase WALLOONS_PAUSE_ON_SEESAW:
      8. Decisionelapsed >= DEMO_STAGE_SEVEN_PAUSE_MS
        True
          1. ExecutesetWalloonsPhase(WALLOONS_RIGHT_WALK_ON_PLATFORM, now_ms);
        False

        Continue after this decision.

      9. Exit enclosing loop or switchbreak;
      10. Case / labelcase WALLOONS_RIGHT_WALK_ON_PLATFORM:
      11. Decisionelapsed >= platform_duration
        True
          1. ExecutesetWalloonsPhase(WALLOONS_RIGHT_DROP, now_ms);
        False

        Continue after this decision.

      12. Exit enclosing loop or switchbreak;
      13. Case / labelcase WALLOONS_RIGHT_DROP:
      14. Decisionelapsed >= vertical_duration
        True
          1. ExecutesetWalloonsPhase(WALLOONS_BOUNCE_RIGHT_UP, now_ms);
        False

        Continue after this decision.

      15. Exit enclosing loop or switchbreak;
      16. Case / labelcase WALLOONS_BOUNCE_RIGHT_UP:
      17. Decisionelapsed >= vertical_duration
        True
          1. ExecutesetWalloonsPhase(WALLOONS_BOUNCE_RIGHT_DOWN, now_ms);
        False

        Continue after this decision.

      18. Exit enclosing loop or switchbreak;
      19. Case / labelcase WALLOONS_BOUNCE_RIGHT_DOWN:
      20. Decisionelapsed >= vertical_duration
        True
          1. ExecutesetWalloonsPhase(WALLOONS_BOUNCE_LEFT_UP, now_ms);
        False

        Continue after this decision.

      21. Exit enclosing loop or switchbreak;
      22. Case / labelcase WALLOONS_BOUNCE_LEFT_UP:
      23. Decisionelapsed >= vertical_duration
        True
          1. ExecutesetWalloonsPhase(WALLOONS_BOUNCE_LEFT_DOWN, now_ms);
        False

        Continue after this decision.

      24. Exit enclosing loop or switchbreak;
      25. Case / labelcase WALLOONS_BOUNCE_LEFT_DOWN:
      26. Decisionelapsed >= vertical_duration
        True
          1. ExecutesetWalloonsPhase(WALLOONS_LEFT_LANDED, now_ms);
        False

        Continue after this decision.

      27. Exit enclosing loop or switchbreak;
      28. Case / labelcase WALLOONS_LEFT_LANDED:
      29. Decisionelapsed >= DEMO_STAGE_SEVEN_LANDING_MS
        True
          1. Execute++stage_seven_completed_bounces;
          2. Decisionstage_seven_completed_bounces >= DEMO_STAGE_SEVEN_BOUNCE_COUNT
            True
              1. ExecutesetWalloonsPhase(WALLOONS_FINISHED, now_ms);
            False
              1. ExecutesetWalloonsPhase(WALLOONS_BOUNCE_RIGHT_UP, now_ms);
        False

        Continue after this decision.

      30. Exit enclosing loop or switchbreak;
      31. Case / labelcase WALLOONS_FINISHED:
      32. Exit enclosing loop or switchbreak;
Read original function source
static void updateStageSevenWalloons(uint32_t now_ms)
{
    const uint32_t elapsed = now_ms - stage_seven_phase_started_at;
    const uint32_t walk_duration = 12U * DEMO_STAGE_SEVEN_STEP_MS;
    const uint32_t platform_duration = 14U * DEMO_STAGE_SEVEN_STEP_MS;
    // There are twelve row moves. Hold the twelfth (landing) cell for one full
    // step interval instead of changing phase before drawDemo can show it.
    const uint32_t vertical_duration = 13U * DEMO_STAGE_SEVEN_STEP_MS;

    switch (stage_seven_phase) {
        case WALLOONS_TITLE_CARD:
            if (elapsed >= DEMO_STAGE_SEVEN_TITLE_MS) {
                setWalloonsPhase(WALLOONS_LEFT_WALK_IN, now_ms);
            }
            break;
        case WALLOONS_LEFT_WALK_IN:
            if (elapsed >= walk_duration) {
                setWalloonsPhase(WALLOONS_PAUSE_ON_SEESAW, now_ms);
            }
            break;
        case WALLOONS_PAUSE_ON_SEESAW:
            if (elapsed >= DEMO_STAGE_SEVEN_PAUSE_MS) {
                setWalloonsPhase(WALLOONS_RIGHT_WALK_ON_PLATFORM, now_ms);
            }
            break;
        case WALLOONS_RIGHT_WALK_ON_PLATFORM:
            if (elapsed >= platform_duration) {
                setWalloonsPhase(WALLOONS_RIGHT_DROP, now_ms);
            }
            break;
        case WALLOONS_RIGHT_DROP:
            if (elapsed >= vertical_duration) {
                setWalloonsPhase(WALLOONS_BOUNCE_RIGHT_UP, now_ms);
            }
            break;
        case WALLOONS_BOUNCE_RIGHT_UP:
            if (elapsed >= vertical_duration) {
                setWalloonsPhase(WALLOONS_BOUNCE_RIGHT_DOWN, now_ms);
            }
            break;
        case WALLOONS_BOUNCE_RIGHT_DOWN:
            if (elapsed >= vertical_duration) {
                setWalloonsPhase(WALLOONS_BOUNCE_LEFT_UP, now_ms);
            }
            break;
        case WALLOONS_BOUNCE_LEFT_UP:
            if (elapsed >= vertical_duration) {
                setWalloonsPhase(WALLOONS_BOUNCE_LEFT_DOWN, now_ms);
            }
            break;
        case WALLOONS_BOUNCE_LEFT_DOWN:
            if (elapsed >= vertical_duration) {
                setWalloonsPhase(WALLOONS_LEFT_LANDED, now_ms);
            }
            break;
        case WALLOONS_LEFT_LANDED:
            // Keep the left Walloon visibly on the new low end before launching
            // the right Walloon. This is the original stunt's missing landing beat.
            if (elapsed >= DEMO_STAGE_SEVEN_LANDING_MS) {
                ++stage_seven_completed_bounces;
                if (stage_seven_completed_bounces >= DEMO_STAGE_SEVEN_BOUNCE_COUNT) {
                    setWalloonsPhase(WALLOONS_FINISHED, now_ms);
                } else {
                    setWalloonsPhase(WALLOONS_BOUNCE_RIGHT_UP, now_ms);
                }
            }
            break;
        case WALLOONS_FINISHED:
            // Hold the completed stunt until C or the shared inactivity timer advances Demo.
            break;
    }
}
demoTimeNow() · source line 2486
static uint32_t demoTimeNow(uint32_t now_ms)
  1. Exit this functionreturn now_ms - demo_paused_total_ms;
Read original function source
static uint32_t demoTimeNow(uint32_t now_ms)
{
    return now_ms - demo_paused_total_ms;
}
enterDemoStage() · source line 2491
static void enterDemoStage(demo_stage_t stage, uint32_t now_ms)

Calls into this documentation:

  1. Decisionselected_stage == DEMO_STAGE_SLIDESHOW_LENS
    True
    1. ExecutedemoStageSlideshowLensStop();
    False

    Continue after this decision.

  2. Decisionselected_stage == DEMO_STAGE_GAME_OF_LIFE
    True
    1. ExecutedemoStageGameOfLifeStop();
    False

    Continue after this decision.

  3. Decisionselected_stage == DEMO_STAGE_BATTLE_ZONE_RASTER
    True
    1. ExecutereleaseStageFifteenGroundSource();
    False

    Continue after this decision.

  4. Executeselected_stage = stage;
  5. Executedemo_last_interaction_at = now_ms;
  6. Decisionselected_stage == DEMO_STAGE_ICON
    True
      1. Executestage_one_next_effect_change_at = now_ms + DEMO_STAGE_ONE_EFFECT_CHANGE_MS;
    False

    Continue after this decision.

  7. Decisionselected_stage == DEMO_STAGE_AMIGA_BALL
    True
    1. ExecutestartStageThreeBall(now_ms);
    False

    Continue after this decision.

  8. Decisionselected_stage == DEMO_STAGE_GEOMETRIC_SOLID
    True
    1. ExecutestartStageSixSolid(now_ms);
    False

    Continue after this decision.

  9. Decisionselected_stage == DEMO_STAGE_SCROLLER || selected_stage == DEMO_STAGE_REVERSE_SCROLLER
    True
    1. ExecutestartTextCornerIconCycles(now_ms);
    False

    Continue after this decision.

  10. Decisionselected_stage == DEMO_STAGE_WALLOONS
    True
    1. ExecutedemoStageWalloonsStart(now_ms);
    False

    Continue after this decision.

  11. Decisionselected_stage == DEMO_STAGE_KALEIDO
    True
    1. ExecutedemoStageKaleidoStart(now_ms);
    False

    Continue after this decision.

  12. Decisionselected_stage == DEMO_STAGE_SPARKLE
    True
    1. ExecutedemoStageSparkleStart(now_ms);
    False

    Continue after this decision.

  13. Decisionselected_stage == DEMO_STAGE_SQUARES
    True
    1. ExecutedemoStageSquaresStart(now_ms);
    False

    Continue after this decision.

  14. Decisionselected_stage == DEMO_STAGE_BATTLE_ZONE_RASTER
    True
    1. ExecutestartStageFifteenBattleZoneRaster();
    False

    Continue after this decision.

  15. Decisionselected_stage == DEMO_STAGE_BEVEL_GEARS
    True
      1. Executestage_seventeen_rig_pitch_degrees = -12.0f;
      2. Executestage_seventeen_rig_yaw_degrees = 18.0f;
      3. Executestage_seventeen_rig_roll_degrees = 0.0f;
      4. Executestage_seventeen_gear_degrees[0] = 0.0f;
      5. Executestage_seventeen_gear_degrees[1] = 286.0f;
      6. Executestage_seventeen_gear_degrees[2] = 226.0f;
      7. Executestage_seventeen_style = STAGE_SEVENTEEN_STYLE_FLAT;
    False

    Continue after this decision.

  16. Decisionselected_stage == DEMO_STAGE_MOUSE_MAZE
    True
    1. ExecutedemoStageMouseMazeStart(now_ms);
    False

    Continue after this decision.

  17. Decisionselected_stage == DEMO_STAGE_RASTER_BANDS || selected_stage == DEMO_STAGE_LOWER_RASTER_SCROLLER || selected_stage == DEMO_STAGE_BATTLE_ZONE_RASTER
    True
      1. Executeraster_band_effect_elapsed_ms = 0U;
      2. Decisionselected_stage == DEMO_STAGE_LOWER_RASTER_SCROLLER
        True
          1. Executeraster_band_palette_index = DEMO_RASTER_FIRE_PALETTE_INDEX;
        False
        1. Decisionselected_stage == DEMO_STAGE_BATTLE_ZONE_RASTER
          True
            1. Executeraster_band_palette_index = DEMO_RASTER_GREEN_PALETTE_INDEX;
          False
            1. Executeraster_band_palette_index = DEMO_RASTER_METALLIC_PALETTE_INDEX;
    False

    Continue after this decision.

  18. Decisionselected_stage == DEMO_STAGE_LOWER_RASTER_SCROLLER && scroll_text != NULL
    True
      1. Executescroll_text_width = strlen(scroll_text) * PET2001_FONT_GLYPH_WIDTH * (size_t)DEMO_STAGE_FOURTEEN_SCROLL_SCALE;
      2. Executescroll_text_x = scroll_speed_pixels_per_second >= 0.0f ? (float)LCD_WIDTH : -(float)scroll_text_width;
    False

    Continue after this decision.

  19. Decisionselected_stage == DEMO_STAGE_SLIDESHOW_LENS
    True
    1. ExecutedemoStageSlideshowLensStart(now_ms);
    False

    Continue after this decision.

  20. Decisionselected_stage == DEMO_STAGE_PLASMA
    True
    1. ExecutedemoStagePlasmaStart();
    False

    Continue after this decision.

  21. Decisionselected_stage == DEMO_STAGE_GAME_OF_LIFE
    True
    1. ExecutedemoStageGameOfLifeStart(now_ms);
    False

    Continue after this decision.

Read original function source
static void enterDemoStage(demo_stage_t stage, uint32_t now_ms)
{
    // Stage 12 owns a full-screen PSRAM source only while it is visible.
    if (selected_stage == DEMO_STAGE_SLIDESHOW_LENS) demoStageSlideshowLensStop();
    if (selected_stage == DEMO_STAGE_GAME_OF_LIFE) demoStageGameOfLifeStop();
    if (selected_stage == DEMO_STAGE_BATTLE_ZONE_RASTER) releaseStageFifteenGroundSource();
    selected_stage = stage;
    // Entering any segment begins a fresh shared inactivity interval.
    demo_last_interaction_at = now_ms;
    if (selected_stage == DEMO_STAGE_ICON) {
        // A full Demo loop begins a fresh automatic Stage-1 effect interval.
        stage_one_next_effect_change_at = now_ms + DEMO_STAGE_ONE_EFFECT_CHANGE_MS;
    }
    if (selected_stage == DEMO_STAGE_AMIGA_BALL) startStageThreeBall(now_ms);
    if (selected_stage == DEMO_STAGE_GEOMETRIC_SOLID) startStageSixSolid(now_ms);
    if (selected_stage == DEMO_STAGE_SCROLLER ||
        selected_stage == DEMO_STAGE_REVERSE_SCROLLER) startTextCornerIconCycles(now_ms);
    if (selected_stage == DEMO_STAGE_WALLOONS) demoStageWalloonsStart(now_ms);
    if (selected_stage == DEMO_STAGE_KALEIDO) demoStageKaleidoStart(now_ms);
    if (selected_stage == DEMO_STAGE_SPARKLE) demoStageSparkleStart(now_ms);
    if (selected_stage == DEMO_STAGE_SQUARES) demoStageSquaresStart(now_ms);
    if (selected_stage == DEMO_STAGE_BATTLE_ZONE_RASTER) startStageFifteenBattleZoneRaster();
    if (selected_stage == DEMO_STAGE_BEVEL_GEARS) {
        stage_seventeen_rig_pitch_degrees = -12.0f;
        stage_seventeen_rig_yaw_degrees = 18.0f;
        stage_seventeen_rig_roll_degrees = 0.0f;
        stage_seventeen_gear_degrees[0] = 0.0f;
        // Each driven gear begins half a tooth out of phase with the lead gear
        // at its fixed mesh contact. Their later angles are derived by motion.
        stage_seventeen_gear_degrees[1] = 286.0f;
        stage_seventeen_gear_degrees[2] = 226.0f;
        // Start with the clearest filled treatment; B cycles outline, flat-retro,
        // and shaded-metal versions without restarting the rig.
        stage_seventeen_style = STAGE_SEVENTEEN_STYLE_FLAT;
    }
    if (selected_stage == DEMO_STAGE_MOUSE_MAZE) demoStageMouseMazeStart(now_ms);
    if (selected_stage == DEMO_STAGE_RASTER_BANDS ||
        selected_stage == DEMO_STAGE_LOWER_RASTER_SCROLLER ||
        selected_stage == DEMO_STAGE_BATTLE_ZONE_RASTER) {
        raster_band_effect_elapsed_ms = 0U;
        // Each raster segment starts on its own fitting palette. B can then
        // cycle every palette from the shared list without interrupting motion.
        if (selected_stage == DEMO_STAGE_LOWER_RASTER_SCROLLER) {
            raster_band_palette_index = DEMO_RASTER_FIRE_PALETTE_INDEX;
        } else if (selected_stage == DEMO_STAGE_BATTLE_ZONE_RASTER) {
            raster_band_palette_index = DEMO_RASTER_GREEN_PALETTE_INDEX;
        } else {
            raster_band_palette_index = DEMO_RASTER_METALLIC_PALETTE_INDEX;
        }
    }
    if (selected_stage == DEMO_STAGE_LOWER_RASTER_SCROLLER && scroll_text != NULL) {
        // This segment is wider than the shared scale-2 ticker. Restart the
        // current message just outside its travel edge using its scale-3 width.
        scroll_text_width = strlen(scroll_text) * PET2001_FONT_GLYPH_WIDTH *
                            (size_t)DEMO_STAGE_FOURTEEN_SCROLL_SCALE;
        scroll_text_x = scroll_speed_pixels_per_second >= 0.0f
            ? (float)LCD_WIDTH : -(float)scroll_text_width;
    }
    if (selected_stage == DEMO_STAGE_SLIDESHOW_LENS) demoStageSlideshowLensStart(now_ms);
    if (selected_stage == DEMO_STAGE_PLASMA) demoStagePlasmaStart();
    if (selected_stage == DEMO_STAGE_GAME_OF_LIFE) demoStageGameOfLifeStart(now_ms);
}
advanceDemoStage() · source line 2554
static void advanceDemoStage(uint32_t now_ms)

Calls into this documentation:

  1. ExecuteenterDemoStage(demoSelectorNextAutomaticStage(selected_stage), now_ms);
Read original function source
static void advanceDemoStage(uint32_t now_ms)
{
    enterDemoStage(demoSelectorNextAutomaticStage(selected_stage), now_ms);
}
updateStageThreeBall() · source line 2559
static void updateStageThreeBall(float elapsed_seconds)
  1. Executestage_three_ball_x += stage_three_ball_velocity_x * elapsed_seconds;
  2. Executestage_three_ball_y += stage_three_ball_velocity_y * elapsed_seconds;
  3. Executestage_three_ball_velocity_y += DEMO_STAGE_THREE_BALL_GRAVITY * elapsed_seconds;
  4. Executestage_three_ball_rotation_phase += DEMO_STAGE_THREE_BALL_ROTATION_BANDS_PER_SECOND * elapsed_seconds;
  5. Decisionstage_three_ball_rotation_phase >= (float)DEMO_STAGE_THREE_BALL_LONGITUDE_BANDS
    True
      1. Executestage_three_ball_rotation_phase -= (float)DEMO_STAGE_THREE_BALL_LONGITUDE_BANDS;
    False

    Continue after this decision.

  6. Decisionstage_three_ball_x < (float)DEMO_STAGE_THREE_BALL_RADIUS
    True
      1. Executestage_three_ball_x = (float)DEMO_STAGE_THREE_BALL_RADIUS;
      2. Executestage_three_ball_velocity_x = DEMO_STAGE_THREE_BALL_HORIZONTAL_VELOCITY;
    False
    1. Decisionstage_three_ball_x > (float)(LCD_WIDTH - 1 - DEMO_STAGE_THREE_BALL_RADIUS)
      True
        1. Executestage_three_ball_x = (float)(LCD_WIDTH - 1 - DEMO_STAGE_THREE_BALL_RADIUS);
        2. Executestage_three_ball_velocity_x = -DEMO_STAGE_THREE_BALL_HORIZONTAL_VELOCITY;
      False

      Continue after this decision.

  7. Decisionstage_three_ball_y > DEMO_STAGE_THREE_BALL_FLOOR_Y - (float)DEMO_STAGE_THREE_BALL_RADIUS
    True
      1. Executestage_three_ball_y = DEMO_STAGE_THREE_BALL_FLOOR_Y - (float)DEMO_STAGE_THREE_BALL_RADIUS;
      2. Executestage_three_ball_velocity_y = DEMO_STAGE_THREE_BALL_BOUNCE_VELOCITY;
    False

    Continue after this decision.

Read original function source
static void updateStageThreeBall(float elapsed_seconds)
{
    stage_three_ball_x += stage_three_ball_velocity_x * elapsed_seconds;
    stage_three_ball_y += stage_three_ball_velocity_y * elapsed_seconds;
    stage_three_ball_velocity_y += DEMO_STAGE_THREE_BALL_GRAVITY * elapsed_seconds;
    stage_three_ball_rotation_phase +=
        DEMO_STAGE_THREE_BALL_ROTATION_BANDS_PER_SECOND * elapsed_seconds;
    if (stage_three_ball_rotation_phase >= (float)DEMO_STAGE_THREE_BALL_LONGITUDE_BANDS) {
        stage_three_ball_rotation_phase -= (float)DEMO_STAGE_THREE_BALL_LONGITUDE_BANDS;
    }
    if (stage_three_ball_x < (float)DEMO_STAGE_THREE_BALL_RADIUS) {
        stage_three_ball_x = (float)DEMO_STAGE_THREE_BALL_RADIUS;
        stage_three_ball_velocity_x = DEMO_STAGE_THREE_BALL_HORIZONTAL_VELOCITY;
    } else if (stage_three_ball_x >
               (float)(LCD_WIDTH - 1 - DEMO_STAGE_THREE_BALL_RADIUS)) {
        stage_three_ball_x = (float)(LCD_WIDTH - 1 - DEMO_STAGE_THREE_BALL_RADIUS);
        stage_three_ball_velocity_x = -DEMO_STAGE_THREE_BALL_HORIZONTAL_VELOCITY;
    }
    if (stage_three_ball_y > DEMO_STAGE_THREE_BALL_FLOOR_Y -
                             (float)DEMO_STAGE_THREE_BALL_RADIUS) {
        stage_three_ball_y = DEMO_STAGE_THREE_BALL_FLOOR_Y -
                             (float)DEMO_STAGE_THREE_BALL_RADIUS;
        stage_three_ball_velocity_y = DEMO_STAGE_THREE_BALL_BOUNCE_VELOCITY;
    }
}
selectRandomStageTransition() · source line 2585
static void selectRandomStageTransition(void)
  1. Executeselected_stage_transition = (demo_stage_transition_effect_t) (esp_random() % DEMO_STAGE_TRANSITION_EFFECT_COUNT);
  2. Decisionselected_stage_transition != DEMO_STAGE_TRANSITION_SCROLL
    True
    1. Exit this functionreturn;
    False

    Continue after this decision.

  3. Select a caseesp_random() % 4U

    Enter matching case; follow source fall-through until a break, return or end.

      1. Case / labelcase 0U:
      2. Executestage_scroll_out_direction = SCREEN_TRANSITION_SCROLL_LEFT;
      3. Executestage_scroll_in_direction = SCREEN_TRANSITION_SCROLL_RIGHT;
      4. Exit enclosing loop or switchbreak;
      5. Case / labelcase 1U:
      6. Executestage_scroll_out_direction = SCREEN_TRANSITION_SCROLL_RIGHT;
      7. Executestage_scroll_in_direction = SCREEN_TRANSITION_SCROLL_LEFT;
      8. Exit enclosing loop or switchbreak;
      9. Case / labelcase 2U:
      10. Executestage_scroll_out_direction = SCREEN_TRANSITION_SCROLL_UP;
      11. Executestage_scroll_in_direction = SCREEN_TRANSITION_SCROLL_DOWN;
      12. Exit enclosing loop or switchbreak;
      13. Case / labeldefault:
      14. Executestage_scroll_out_direction = SCREEN_TRANSITION_SCROLL_DOWN;
      15. Executestage_scroll_in_direction = SCREEN_TRANSITION_SCROLL_UP;
      16. Exit enclosing loop or switchbreak;
Read original function source
static void selectRandomStageTransition(void)
{
    selected_stage_transition = (demo_stage_transition_effect_t)
        (esp_random() % DEMO_STAGE_TRANSITION_EFFECT_COUNT);
    if (selected_stage_transition != DEMO_STAGE_TRANSITION_SCROLL) return;

    // Each pair keeps both frames on the same axis, travelling in opposite directions.
    // The index is random so each scroll transition can go left, right, up, or down.
    switch (esp_random() % 4U) {
        case 0U:
            stage_scroll_out_direction = SCREEN_TRANSITION_SCROLL_LEFT;
            stage_scroll_in_direction = SCREEN_TRANSITION_SCROLL_RIGHT;
            break;
        case 1U:
            stage_scroll_out_direction = SCREEN_TRANSITION_SCROLL_RIGHT;
            stage_scroll_in_direction = SCREEN_TRANSITION_SCROLL_LEFT;
            break;
        case 2U:
            stage_scroll_out_direction = SCREEN_TRANSITION_SCROLL_UP;
            stage_scroll_in_direction = SCREEN_TRANSITION_SCROLL_DOWN;
            break;
        default:
            stage_scroll_out_direction = SCREEN_TRANSITION_SCROLL_DOWN;
            stage_scroll_in_direction = SCREEN_TRANSITION_SCROLL_UP;
            break;
    }
}
selectScrollMessage() · source line 2613
static void selectScrollMessage(bool switch_logo)

Calls into this documentation:

  1. Executesize_t next = (size_t)esp_random() % DEMO_SCROLL_MESSAGE_COUNT;
  2. Decisionscroll_message_index < DEMO_SCROLL_MESSAGE_COUNT
    True
      1. Executenext = (size_t)esp_random() % (DEMO_SCROLL_MESSAGE_COUNT - 1U);
      2. Decisionnext >= scroll_message_index
        True
        1. Execute++next;
        False

        Continue after this decision.

    False

    Continue after this decision.

  3. Executescroll_message_index = next;
  4. Executescroll_text = demo_scroll_messages[scroll_message_index];
  5. Executeconst size_t scroll_scale = selected_stage == DEMO_STAGE_LOWER_RASTER_SCROLLER ? (size_t)DEMO_STAGE_FOURTEEN_SCROLL_SCALE : DEMO_SCROLL_SCALE;
  6. Executescroll_text_width = strlen(scroll_text) * PET2001_FONT_GLYPH_WIDTH * scroll_scale;
  7. Executescroll_text_x = scroll_speed_pixels_per_second >= 0.0f ? (float)LCD_WIDTH : -(float)scroll_text_width;
  8. Decisionswitch_logo && !DEMO_LOCK_ICON_TO_PRIMARY
    True
      1. ExecuteadvanceIconSlot();
    False

    Continue after this decision.

  9. Executescroll_text_effect_elapsed_ms = 0U;
Read original function source
static void selectScrollMessage(bool switch_logo)
{
    size_t next = (size_t)esp_random() % DEMO_SCROLL_MESSAGE_COUNT;
    if (scroll_message_index < DEMO_SCROLL_MESSAGE_COUNT) {
        next = (size_t)esp_random() % (DEMO_SCROLL_MESSAGE_COUNT - 1U);
        if (next >= scroll_message_index) ++next;
    }
    scroll_message_index = next;
    scroll_text = demo_scroll_messages[scroll_message_index];
    const size_t scroll_scale = selected_stage == DEMO_STAGE_LOWER_RASTER_SCROLLER
        ? (size_t)DEMO_STAGE_FOURTEEN_SCROLL_SCALE
        : DEMO_SCROLL_SCALE;
    scroll_text_width = strlen(scroll_text) * PET2001_FONT_GLYPH_WIDTH * scroll_scale;
    scroll_text_x = scroll_speed_pixels_per_second >= 0.0f
        ? (float)LCD_WIDTH : -(float)scroll_text_width;
    if (switch_logo && !DEMO_LOCK_ICON_TO_PRIMARY) {
        advanceIconSlot();
    }
    scroll_text_effect_elapsed_ms = 0U;
}
dimFlamesBehindTickerGlyph() · source line 2634
static bool dimFlamesBehindTickerGlyph(uint16_t *frame, int centre_x, int centre_y,
                                       float glyph_scale)
  1. Decisionselected_effect != DEMO_EFFECT_RADIAL_FLAME || glyph_scale <= 0.0f
    True
    1. Exit this functionreturn false;
    False

    Continue after this decision.

  2. Executeconst int half_width = (int)ceilf((float)PET2001_FONT_GLYPH_WIDTH * glyph_scale * 0.5f);
  3. Executeconst int half_height = (int)ceilf((float)PET2001_FONT_GLYPH_HEIGHT * glyph_scale * 0.5f);
  4. Executebool touches_flame = false;
  5. FOR loopint y = centre_y - half_height; y <= centre_y + half_height; ++y

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

      1. Decisiony < 0 || y >= LCD_HEIGHT
        True
        1. Next loop iterationcontinue;
        False

        Continue after this decision.

      2. FOR loopint x = centre_x - half_width; x <= centre_x + half_width; ++x

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

          1. Decisionx < 0 || x >= LCD_WIDTH
            True
            1. Next loop iterationcontinue;
            False

            Continue after this decision.

          2. Executeuint16_t *pixel = &frame[(size_t)y * LCD_WIDTH + (size_t)x];
          3. FOR loopsize_t colour = 0U; colour < sizeof(demo_ring_colours) / sizeof(demo_ring_colours[0]); ++colour

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

              1. Decision*pixel != demo_ring_colours[colour]
                True
                1. Next loop iterationcontinue;
                False

                Continue after this decision.

              2. Execute*pixel = colour == 0U ? COLOR_BLACK : demo_ring_colours[colour - 1U];
              3. Executetouches_flame = true;
              4. Exit enclosing loop or switchbreak;
  6. Exit this functionreturn touches_flame;
Read original function source
static bool dimFlamesBehindTickerGlyph(uint16_t *frame, int centre_x, int centre_y,
                                       float glyph_scale)
{
    if (selected_effect != DEMO_EFFECT_RADIAL_FLAME || glyph_scale <= 0.0f) return false;
    const int half_width = (int)ceilf((float)PET2001_FONT_GLYPH_WIDTH * glyph_scale * 0.5f);
    const int half_height = (int)ceilf((float)PET2001_FONT_GLYPH_HEIGHT * glyph_scale * 0.5f);
    bool touches_flame = false;
    for (int y = centre_y - half_height; y <= centre_y + half_height; ++y) {
        if (y < 0 || y >= LCD_HEIGHT) continue;
        for (int x = centre_x - half_width; x <= centre_x + half_width; ++x) {
            if (x < 0 || x >= LCD_WIDTH) continue;
            uint16_t *pixel = &frame[(size_t)y * LCD_WIDTH + (size_t)x];
            for (size_t colour = 0U; colour < sizeof(demo_ring_colours) /
                                              sizeof(demo_ring_colours[0]); ++colour) {
                if (*pixel != demo_ring_colours[colour]) continue;
                // Retain the moving fire shape but step it down for white PET text.
                *pixel = colour == 0U ? COLOR_BLACK : demo_ring_colours[colour - 1U];
                touches_flame = true;
                break;
            }
        }
    }
    return touches_flame;
}
demoScrollRainbowColour() · source line 2659
static uint16_t demoScrollRainbowColour(unsigned glyph_index)
  1. Executeconst unsigned colour_count = sizeof(demo_scroll_rainbow_colours) / sizeof(demo_scroll_rainbow_colours[0]);
  2. Executeconst unsigned time_step = scroll_text_effect_elapsed_ms / DEMO_SCROLL_RAINBOW_MS_PER_COLOUR;
  3. Executeconst unsigned glyph_step = glyph_index / DEMO_SCROLL_RAINBOW_GLYPHS_PER_COLOUR;
  4. Exit this functionreturn demo_scroll_rainbow_colours[(glyph_step + time_step) % colour_count];
Read original function source
static uint16_t demoScrollRainbowColour(unsigned glyph_index)
{
    const unsigned colour_count = sizeof(demo_scroll_rainbow_colours) /
                                 sizeof(demo_scroll_rainbow_colours[0]);
    const unsigned time_step = scroll_text_effect_elapsed_ms /
                               DEMO_SCROLL_RAINBOW_MS_PER_COLOUR;
    const unsigned glyph_step = glyph_index / DEMO_SCROLL_RAINBOW_GLYPHS_PER_COLOUR;
    return demo_scroll_rainbow_colours[(glyph_step + time_step) % colour_count];
}
stageFourMagnifierScale() · source line 2670
static float stageFourMagnifierScale(int glyph_centre_x)
  1. Executeconst float distance = fabsf((float)glyph_centre_x - (float)DEMO_STAGE_FOUR_MAGNIFIER_CENTRE_X);
  2. Executefloat influence = 1.0f - distance / DEMO_STAGE_FOUR_MAGNIFIER_RADIUS;
  3. Decisioninfluence < 0.0f
    True
    1. Executeinfluence = 0.0f;
    False

    Continue after this decision.

  4. Executeinfluence *= influence;
  5. Exit this functionreturn (float)DEMO_SCROLL_SCALE + (DEMO_STAGE_FOUR_MAGNIFIER_MAX_SCALE - (float)DEMO_SCROLL_SCALE) * influence;
Read original function source
static float stageFourMagnifierScale(int glyph_centre_x)
{
    const float distance = fabsf((float)glyph_centre_x -
                                 (float)DEMO_STAGE_FOUR_MAGNIFIER_CENTRE_X);
    float influence = 1.0f - distance / DEMO_STAGE_FOUR_MAGNIFIER_RADIUS;
    if (influence < 0.0f) influence = 0.0f;
    // Squaring concentrates the largest letters around the exact screen centre.
    influence *= influence;
    return (float)DEMO_SCROLL_SCALE +
           (DEMO_STAGE_FOUR_MAGNIFIER_MAX_SCALE - (float)DEMO_SCROLL_SCALE) * influence;
}
stageFourMagnifierPosition() · source line 2682
static float stageFourMagnifierPosition(int glyph_centre_x)
  1. Executeconst float centre = (float)DEMO_STAGE_FOUR_MAGNIFIER_CENTRE_X;
  2. Executeconst float offset = (float)glyph_centre_x - centre;
  3. Executeconst float distance = fabsf(offset);
  4. Decisiondistance >= DEMO_STAGE_FOUR_MAGNIFIER_RADIUS
    True
    1. Exit this functionreturn (float)glyph_centre_x;
    False

    Continue after this decision.

  5. Executeconst float influence = 1.0f - distance / DEMO_STAGE_FOUR_MAGNIFIER_RADIUS;
  6. Exit this functionreturn centre + offset * (1.0f + DEMO_STAGE_FOUR_MAGNIFIER_SPACING_STRENGTH * influence * influence);
Read original function source
static float stageFourMagnifierPosition(int glyph_centre_x)
{
    const float centre = (float)DEMO_STAGE_FOUR_MAGNIFIER_CENTRE_X;
    const float offset = (float)glyph_centre_x - centre;
    const float distance = fabsf(offset);
    if (distance >= DEMO_STAGE_FOUR_MAGNIFIER_RADIUS) return (float)glyph_centre_x;
    const float influence = 1.0f - distance / DEMO_STAGE_FOUR_MAGNIFIER_RADIUS;
    // Spread the glyph centres with the lens so expanded neighbouring letters do not overlap.
    return centre + offset * (1.0f + DEMO_STAGE_FOUR_MAGNIFIER_SPACING_STRENGTH *
                              influence * influence);
}
stageFiveMagnifierScale() · source line 2696
static float stageFiveMagnifierScale(int glyph_centre_x)
  1. Executefloat influence = fabsf((float)glyph_centre_x - (float)DEMO_STAGE_FOUR_MAGNIFIER_CENTRE_X) / DEMO_STAGE_FOUR_MAGNIFIER_RADIUS;
  2. Decisioninfluence > 1.0f
    True
    1. Executeinfluence = 1.0f;
    False

    Continue after this decision.

  3. Exit this functionreturn (float)DEMO_SCROLL_SCALE + (DEMO_STAGE_FOUR_MAGNIFIER_MAX_SCALE - (float)DEMO_SCROLL_SCALE) * influence * influence;
Read original function source
static float stageFiveMagnifierScale(int glyph_centre_x)
{
    float influence = fabsf((float)glyph_centre_x -
                            (float)DEMO_STAGE_FOUR_MAGNIFIER_CENTRE_X) /
                      DEMO_STAGE_FOUR_MAGNIFIER_RADIUS;
    if (influence > 1.0f) influence = 1.0f;
    // Inverse lens: smallest at centre, largest at the screen edges and beyond.
    return (float)DEMO_SCROLL_SCALE +
           (DEMO_STAGE_FOUR_MAGNIFIER_MAX_SCALE - (float)DEMO_SCROLL_SCALE) *
           influence * influence;
}
stageFiveMagnifierPosition() · source line 2708
static float stageFiveMagnifierPosition(int glyph_centre_x)
  1. Executeconst float centre = (float)DEMO_STAGE_FOUR_MAGNIFIER_CENTRE_X;
  2. Executeconst float offset = (float)glyph_centre_x - centre;
  3. Executefloat influence = fabsf(offset) / DEMO_STAGE_FOUR_MAGNIFIER_RADIUS;
  4. Decisioninfluence > 1.0f
    True
    1. Executeinfluence = 1.0f;
    False

    Continue after this decision.

  5. Exit this functionreturn centre + offset * (1.0f + DEMO_STAGE_FOUR_MAGNIFIER_SPACING_STRENGTH * influence * influence);
Read original function source
static float stageFiveMagnifierPosition(int glyph_centre_x)
{
    const float centre = (float)DEMO_STAGE_FOUR_MAGNIFIER_CENTRE_X;
    const float offset = (float)glyph_centre_x - centre;
    float influence = fabsf(offset) / DEMO_STAGE_FOUR_MAGNIFIER_RADIUS;
    if (influence > 1.0f) influence = 1.0f;
    // Keep the maximum expansion beyond both screen edges so large letters enter
    // and leave cleanly instead of snapping back to their ordinary spacing.
    return centre + offset * (1.0f + DEMO_STAGE_FOUR_MAGNIFIER_SPACING_STRENGTH *
                              influence * influence);
}
drawScrollText() · source line 2722
static void drawScrollText(uint16_t *frame)

Calls into this documentation:

  1. Decisionscroll_text == NULL
    True
    1. Exit this functionreturn;
    False

    Continue after this decision.

  2. Executeconst unsigned text_length = (unsigned)strlen(scroll_text);
  3. Executeconst int base_x = (int)lroundf(scroll_text_x);
  4. Executeconst bool magnifier_active = selected_stage == DEMO_STAGE_SCROLLER || selected_stage == DEMO_STAGE_REVERSE_SCROLLER;
  5. Executeconst bool raster_band_scroller = selected_stage == DEMO_STAGE_RASTER_BANDS || selected_stage == DEMO_STAGE_LOWER_RASTER_SCROLLER || selected_stage == DEMO_STAGE_BATTLE_ZONE_RASTER;
  6. Executeconst bool lower_raster_band_scroller = selected_stage == DEMO_STAGE_LOWER_RASTER_SCROLLER;
  7. Executeconst bool battle_zone_raster_scroller = selected_stage == DEMO_STAGE_BATTLE_ZONE_RASTER;
  8. Executeconst int advance = PET2001_FONT_GLYPH_WIDTH * (lower_raster_band_scroller ? (int)DEMO_STAGE_FOURTEEN_SCROLL_SCALE : DEMO_SCROLL_SCALE);
  9. Executeconst uint32_t text_wave_elapsed_ms = raster_band_scroller ? raster_band_effect_elapsed_ms : scroll_text_effect_elapsed_ms;
  10. Executeconst bool reverse_magnifier = selected_stage == DEMO_STAGE_REVERSE_SCROLLER;
  11. FOR loopunsigned index = 0U; index < text_length; ++index

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

      1. Executeconst int unscaled_centre_x = base_x + (int)index * advance + advance / 2;
      2. Executeconst int centre_x = !magnifier_active ? unscaled_centre_x : (int)lroundf(reverse_magnifier ? stageFiveMagnifierPosition(unscaled_centre_x) : stageFourMagnifierPosition(unscaled_centre_x));
      3. Executeconst float glyph_scale = !magnifier_active ? (lower_raster_band_scroller ? DEMO_STAGE_FOURTEEN_SCROLL_SCALE : (float)DEMO_SCROLL_SCALE) : (reverse_magnifier ? stageFiveMagnifierScale(unscaled_centre_x) : stageFourMagnifierScale(unscaled_centre_x));
      4. Executeconst int glyph_margin = (int)ceilf((float)PET2001_FONT_GLYPH_WIDTH * glyph_scale);
      5. Executeconst int centre_y = magnifier_active ? DEMO_STAGE_FOUR_MAGNIFIER_CENTRE_Y : (lower_raster_band_scroller ? DEMO_STAGE_FOURTEEN_SCROLL_CENTRE_Y : (battle_zone_raster_scroller ? DEMO_STAGE_FIFTEEN_SCROLL_CENTRE_Y : (raster_band_scroller ? DEMO_STAGE_ELEVEN_SCROLL_CENTRE_Y : DEMO_SCROLL_Y))) + graphics_text_effect_wave_offset(text_wave_elapsed_ms, index, lower_raster_band_scroller ? DEMO_STAGE_FOURTEEN_SCROLL_WAVE_AMPLITUDE : (battle_zone_raster_scroller ? DEMO_STAGE_FIFTEEN_SCROLL_WAVE_AMPLITUDE : (raster_band_scroller ? DEMO_STAGE_ELEVEN_SCROLL_WAVE_AMPLITUDE : DEMO_SCROLL_EFFECT_WAVE_AMPLITUDE))) + (raster_band_scroller ? 0 : advance / 2);
      6. Decisioncentre_x + glyph_margin < 0 || centre_x - glyph_margin >= LCD_WIDTH || centre_y + glyph_margin < 0 || centre_y - glyph_margin >= LCD_HEIGHT
        True
          1. Next loop iterationcontinue;
        False

        Continue after this decision.

      7. Executeconst uint16_t glyph_color = dimFlamesBehindTickerGlyph(frame, centre_x, centre_y, glyph_scale) ? COLOR_WHITE : demoScrollRainbowColour(index);
      8. ExecutedrawPet2001AsciiCharTransformed(frame, centre_x, centre_y, scroll_text[index], glyph_scale, 0.0f, glyph_color);
Read original function source
static void drawScrollText(uint16_t *frame)
{
    if (scroll_text == NULL) return;

    const unsigned text_length = (unsigned)strlen(scroll_text);
    const int base_x = (int)lroundf(scroll_text_x);
    const bool magnifier_active = selected_stage == DEMO_STAGE_SCROLLER ||
                                  selected_stage == DEMO_STAGE_REVERSE_SCROLLER;
    const bool raster_band_scroller = selected_stage == DEMO_STAGE_RASTER_BANDS ||
                                      selected_stage == DEMO_STAGE_LOWER_RASTER_SCROLLER ||
                                      selected_stage == DEMO_STAGE_BATTLE_ZONE_RASTER;
    const bool lower_raster_band_scroller =
        selected_stage == DEMO_STAGE_LOWER_RASTER_SCROLLER;
    const bool battle_zone_raster_scroller =
        selected_stage == DEMO_STAGE_BATTLE_ZONE_RASTER;
    const int advance = PET2001_FONT_GLYPH_WIDTH *
        (lower_raster_band_scroller ? (int)DEMO_STAGE_FOURTEEN_SCROLL_SCALE :
                                     DEMO_SCROLL_SCALE);
    const uint32_t text_wave_elapsed_ms = raster_band_scroller ?
        raster_band_effect_elapsed_ms : scroll_text_effect_elapsed_ms;
    const bool reverse_magnifier = selected_stage == DEMO_STAGE_REVERSE_SCROLLER;
    for (unsigned index = 0U; index < text_length; ++index) {
        const int unscaled_centre_x = base_x + (int)index * advance + advance / 2;
        const int centre_x = !magnifier_active ? unscaled_centre_x :
            (int)lroundf(reverse_magnifier ? stageFiveMagnifierPosition(unscaled_centre_x) :
                                                 stageFourMagnifierPosition(unscaled_centre_x));
        const float glyph_scale = !magnifier_active ?
            (lower_raster_band_scroller ? DEMO_STAGE_FOURTEEN_SCROLL_SCALE :
                                          (float)DEMO_SCROLL_SCALE) :
            (reverse_magnifier ? stageFiveMagnifierScale(unscaled_centre_x) :
                                 stageFourMagnifierScale(unscaled_centre_x));
        const int glyph_margin = (int)ceilf((float)PET2001_FONT_GLYPH_WIDTH * glyph_scale);
        const int centre_y = magnifier_active ? DEMO_STAGE_FOUR_MAGNIFIER_CENTRE_Y :
            (lower_raster_band_scroller ? DEMO_STAGE_FOURTEEN_SCROLL_CENTRE_Y :
             (battle_zone_raster_scroller ? DEMO_STAGE_FIFTEEN_SCROLL_CENTRE_Y :
              (raster_band_scroller ? DEMO_STAGE_ELEVEN_SCROLL_CENTRE_Y : DEMO_SCROLL_Y))) +
            graphics_text_effect_wave_offset(text_wave_elapsed_ms, index,
                lower_raster_band_scroller ? DEMO_STAGE_FOURTEEN_SCROLL_WAVE_AMPLITUDE :
                (battle_zone_raster_scroller ? DEMO_STAGE_FIFTEEN_SCROLL_WAVE_AMPLITUDE :
                 (raster_band_scroller ? DEMO_STAGE_ELEVEN_SCROLL_WAVE_AMPLITUDE :
                                       DEMO_SCROLL_EFFECT_WAVE_AMPLITUDE))) +
            (raster_band_scroller ? 0 : advance / 2);
        // Long messages contain many off-screen glyphs; skip their transform work.
        if (centre_x + glyph_margin < 0 || centre_x - glyph_margin >= LCD_WIDTH ||
            centre_y + glyph_margin < 0 || centre_y - glyph_margin >= LCD_HEIGHT) {
            continue;
        }
        const uint16_t glyph_color = dimFlamesBehindTickerGlyph(frame, centre_x, centre_y,
                                                                 glyph_scale)
            ? COLOR_WHITE : demoScrollRainbowColour(index);
        drawPet2001AsciiCharTransformed(frame, centre_x, centre_y, scroll_text[index],
                                         glyph_scale, 0.0f, glyph_color);
    }
}
projectPoint() · source line 2777
static graphics_point_t projectPoint(float source_x, float source_y, float depth,
                                     const demo_rotation_t *rotation)
  1. Executeconst float scaled_x = source_x * DEMO_FACE_SCALE;
  2. Executeconst float scaled_y = source_y * DEMO_FACE_SCALE;
  3. Executeconst float pitched_y = scaled_y * rotation->pitch_cosine - depth * rotation->pitch_sine;
  4. Executeconst float pitched_z = scaled_y * rotation->pitch_sine + depth * rotation->pitch_cosine;
  5. Executeconst float yawed_x = scaled_x * rotation->yaw_cosine + pitched_z * rotation->yaw_sine;
  6. Executeconst float yawed_z = -scaled_x * rotation->yaw_sine + pitched_z * rotation->yaw_cosine;
  7. Executeconst float rotated_x = yawed_x * rotation->roll_cosine - pitched_y * rotation->roll_sine;
  8. Executeconst float rotated_y = yawed_x * rotation->roll_sine + pitched_y * rotation->roll_cosine;
  9. Executeconst float perspective = DEMO_CAMERA_DISTANCE / (DEMO_CAMERA_DISTANCE + yawed_z);
  10. Executeconst graphics_point_t result = { .x = DEMO_CENTRE_X + (int)lroundf(rotated_x * perspective), .y = DEMO_CENTRE_Y + (int)lroundf(rotated_y * perspective), };
  11. Exit this functionreturn result;
Read original function source
static graphics_point_t projectPoint(float source_x, float source_y, float depth,
                                     const demo_rotation_t *rotation)
{
    const float scaled_x = source_x * DEMO_FACE_SCALE;
    const float scaled_y = source_y * DEMO_FACE_SCALE;
    const float pitched_y = scaled_y * rotation->pitch_cosine -
                            depth * rotation->pitch_sine;
    const float pitched_z = scaled_y * rotation->pitch_sine +
                            depth * rotation->pitch_cosine;
    const float yawed_x = scaled_x * rotation->yaw_cosine +
                          pitched_z * rotation->yaw_sine;
    const float yawed_z = -scaled_x * rotation->yaw_sine +
                          pitched_z * rotation->yaw_cosine;
    const float rotated_x = yawed_x * rotation->roll_cosine -
                            pitched_y * rotation->roll_sine;
    const float rotated_y = yawed_x * rotation->roll_sine +
                            pitched_y * rotation->roll_cosine;
    const float perspective = DEMO_CAMERA_DISTANCE / (DEMO_CAMERA_DISTANCE + yawed_z);
    const graphics_point_t result = {
        .x = DEMO_CENTRE_X + (int)lroundf(rotated_x * perspective),
        .y = DEMO_CENTRE_Y + (int)lroundf(rotated_y * perspective),
    };
    return result;
}
demoRingInnerRadius() · source line 2802
static float demoRingInnerRadius(void)

Calls into this documentation:

  1. Executeconst float pitch = pitch_degrees * DEMO_PI / 180.0f;
  2. Executeconst float yaw = yaw_degrees * DEMO_PI / 180.0f;
  3. Executeconst float roll = roll_degrees * DEMO_PI / 180.0f;
  4. Executeconst demo_rotation_t rotation = { cosf(pitch), sinf(pitch), cosf(yaw), sinf(yaw), cosf(roll), sinf(roll), };
  5. Executefloat outer_radius = 0.0f;
  6. FOR loopunsigned sample = 0U; sample < 16U; ++sample

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

      1. Executeconst float angle = (float)sample * DEMO_PI * 2.0f / 16.0f;
      2. FOR loopunsigned depth_index = 0U; depth_index < 2U; ++depth_index

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

          1. Executeconst graphics_point_t point = projectPoint(cosf(angle) * 30.0f, sinf(angle) * 30.0f, depth_index == 0U ? 0.0f : DEMO_EXTRUSION_DEPTH, &rotation);
          2. Executeconst float dx = (float)point.x - DEMO_CENTRE_X;
          3. Executeconst float dy = (float)point.y - DEMO_CENTRE_Y;
          4. Executeconst float radius = sqrtf(dx * dx + dy * dy);
          5. Decisionradius > outer_radius
            True
            1. Executeouter_radius = radius;
            False

            Continue after this decision.

  7. Exit this functionreturn outer_radius + DEMO_RING_ICON_GAP;
Read original function source
static float demoRingInnerRadius(void)
{
    const float pitch = pitch_degrees * DEMO_PI / 180.0f;
    const float yaw = yaw_degrees * DEMO_PI / 180.0f;
    const float roll = roll_degrees * DEMO_PI / 180.0f;
    const demo_rotation_t rotation = {
        cosf(pitch), sinf(pitch), cosf(yaw), sinf(yaw), cosf(roll), sinf(roll),
    };
    float outer_radius = 0.0f;
    for (unsigned sample = 0U; sample < 16U; ++sample) {
        const float angle = (float)sample * DEMO_PI * 2.0f / 16.0f;
        for (unsigned depth_index = 0U; depth_index < 2U; ++depth_index) {
            const graphics_point_t point = projectPoint(cosf(angle) * 30.0f,
                sinf(angle) * 30.0f, depth_index == 0U ? 0.0f : DEMO_EXTRUSION_DEPTH,
                &rotation);
            const float dx = (float)point.x - DEMO_CENTRE_X;
            const float dy = (float)point.y - DEMO_CENTRE_Y;
            const float radius = sqrtf(dx * dx + dy * dy);
            if (radius > outer_radius) outer_radius = radius;
        }
    }
    return outer_radius + DEMO_RING_ICON_GAP;
}
drawLogoSlice() · source line 2826
static void drawLogoSlice(uint16_t *frame, const uint8_t *bitmap, float depth,
                          const demo_rotation_t *rotation, uint16_t color)

Calls into this documentation:

  1. FOR loopunsigned y = 0U; y < DEMO_LOGO_HEIGHT; ++y

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

      1. Executeunsigned run_start = 0U;
      2. WHILE looprun_start < DEMO_LOGO_WIDTH

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

          1. WHILE looprun_start < DEMO_LOGO_WIDTH && !bitmapPixelSet(bitmap, run_start, y)

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

              1. Execute++run_start;
          2. Decisionrun_start == DEMO_LOGO_WIDTH
            True
            1. Exit enclosing loop or switchbreak;
            False

            Continue after this decision.

          3. Executeunsigned run_end = run_start + 1U;
          4. WHILE looprun_end < DEMO_LOGO_WIDTH && bitmapPixelSet(bitmap, run_end, y)

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

              1. Execute++run_end;
          5. Executeconst float left = (float)run_start - (float)DEMO_LOGO_WIDTH * 0.5f;
          6. Executeconst float right = (float)run_end - (float)DEMO_LOGO_WIDTH * 0.5f;
          7. Executeconst float top = (float)y - (float)DEMO_LOGO_HEIGHT * 0.5f;
          8. Executeconst graphics_point_t run[4] = { projectPoint(left, top, depth, rotation), projectPoint(right, top, depth, rotation), projectPoint(right, top + 1.0f, depth, rotation), projectPoint(left, top + 1.0f, depth, rotation), };
          9. Executefill_convex_polygon(frame, run, 4U, color);
          10. Executerun_start = run_end;
Read original function source
static void drawLogoSlice(uint16_t *frame, const uint8_t *bitmap, float depth,
                          const demo_rotation_t *rotation, uint16_t color)
{
    for (unsigned y = 0U; y < DEMO_LOGO_HEIGHT; ++y) {
        unsigned run_start = 0U;
        while (run_start < DEMO_LOGO_WIDTH) {
            while (run_start < DEMO_LOGO_WIDTH && !bitmapPixelSet(bitmap, run_start, y)) {
                ++run_start;
            }
            if (run_start == DEMO_LOGO_WIDTH) break;
            unsigned run_end = run_start + 1U;
            while (run_end < DEMO_LOGO_WIDTH && bitmapPixelSet(bitmap, run_end, y)) {
                ++run_end;
            }

            const float left = (float)run_start - (float)DEMO_LOGO_WIDTH * 0.5f;
            const float right = (float)run_end - (float)DEMO_LOGO_WIDTH * 0.5f;
            const float top = (float)y - (float)DEMO_LOGO_HEIGHT * 0.5f;
            const graphics_point_t run[4] = {
                projectPoint(left, top, depth, rotation),
                projectPoint(right, top, depth, rotation),
                projectPoint(right, top + 1.0f, depth, rotation),
                projectPoint(left, top + 1.0f, depth, rotation),
            };
            fill_convex_polygon(frame, run, 4U, color);
            run_start = run_end;
        }
    }
}
projectApproachPoint() · source line 2856
static graphics_point_t projectApproachPoint(float source_x, float source_y, float depth,
                                             const demo_rotation_t *rotation, float scale,
                                             int centre_x, int centre_y)
  1. Executeconst float scaled_x = source_x * scale;
  2. Executeconst float scaled_y = source_y * scale;
  3. Executeconst float scaled_depth = depth * scale;
  4. Executeconst float pitched_y = scaled_y * rotation->pitch_cosine - scaled_depth * rotation->pitch_sine;
  5. Executeconst float pitched_z = scaled_y * rotation->pitch_sine + scaled_depth * rotation->pitch_cosine;
  6. Executeconst float yawed_x = scaled_x * rotation->yaw_cosine + pitched_z * rotation->yaw_sine;
  7. Executeconst float yawed_z = -scaled_x * rotation->yaw_sine + pitched_z * rotation->yaw_cosine;
  8. Executeconst float rotated_x = yawed_x * rotation->roll_cosine - pitched_y * rotation->roll_sine;
  9. Executeconst float rotated_y = yawed_x * rotation->roll_sine + pitched_y * rotation->roll_cosine;
  10. Executeconst float perspective = DEMO_CAMERA_DISTANCE / (DEMO_CAMERA_DISTANCE + yawed_z);
  11. Executeconst graphics_point_t result = { .x = centre_x + (int)lroundf(rotated_x * perspective), .y = centre_y + (int)lroundf(rotated_y * perspective), };
  12. Exit this functionreturn result;
Read original function source
static graphics_point_t projectApproachPoint(float source_x, float source_y, float depth,
                                             const demo_rotation_t *rotation, float scale,
                                             int centre_x, int centre_y)
{
    const float scaled_x = source_x * scale;
    const float scaled_y = source_y * scale;
    const float scaled_depth = depth * scale;
    const float pitched_y = scaled_y * rotation->pitch_cosine -
                            scaled_depth * rotation->pitch_sine;
    const float pitched_z = scaled_y * rotation->pitch_sine +
                            scaled_depth * rotation->pitch_cosine;
    const float yawed_x = scaled_x * rotation->yaw_cosine +
                          pitched_z * rotation->yaw_sine;
    const float yawed_z = -scaled_x * rotation->yaw_sine +
                          pitched_z * rotation->yaw_cosine;
    const float rotated_x = yawed_x * rotation->roll_cosine -
                            pitched_y * rotation->roll_sine;
    const float rotated_y = yawed_x * rotation->roll_sine +
                            pitched_y * rotation->roll_cosine;
    const float perspective = DEMO_CAMERA_DISTANCE / (DEMO_CAMERA_DISTANCE + yawed_z);
    const graphics_point_t result = {
        .x = centre_x + (int)lroundf(rotated_x * perspective),
        .y = centre_y + (int)lroundf(rotated_y * perspective),
    };
    return result;
}
drawApproachLogoSlice() · source line 2883
static void drawApproachLogoSlice(uint16_t *frame, const uint8_t *bitmap, float depth,
                                  const demo_rotation_t *rotation, float scale,
                                  int centre_x, int centre_y, uint16_t color)

Calls into this documentation:

  1. FOR loopunsigned y = 0U; y < DEMO_LOGO_HEIGHT; ++y

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

      1. Executeunsigned run_start = 0U;
      2. WHILE looprun_start < DEMO_LOGO_WIDTH

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

          1. WHILE looprun_start < DEMO_LOGO_WIDTH && !bitmapPixelSet(bitmap, run_start, y)

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

              1. Execute++run_start;
          2. Decisionrun_start == DEMO_LOGO_WIDTH
            True
            1. Exit enclosing loop or switchbreak;
            False

            Continue after this decision.

          3. Executeunsigned run_end = run_start + 1U;
          4. WHILE looprun_end < DEMO_LOGO_WIDTH && bitmapPixelSet(bitmap, run_end, y)

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

              1. Execute++run_end;
          5. Executeconst float left = (float)run_start - (float)DEMO_LOGO_WIDTH * 0.5f;
          6. Executeconst float right = (float)run_end - (float)DEMO_LOGO_WIDTH * 0.5f;
          7. Executeconst float top = (float)y - (float)DEMO_LOGO_HEIGHT * 0.5f;
          8. Executeconst graphics_point_t run[4] = { projectApproachPoint(left, top, depth, rotation, scale, centre_x, centre_y), projectApproachPoint(right, top, depth, rotation, scale, centre_x, centre_y), projectApproachPoint(right, top + 1.0f, depth, rotation, scale, centre_x, centre_y), projectApproachPoint(left, top + 1.0f, depth, rotation, scale, centre_x, centre_y), };
          9. Executefill_convex_polygon(frame, run, 4U, color);
          10. Executerun_start = run_end;
Read original function source
static void drawApproachLogoSlice(uint16_t *frame, const uint8_t *bitmap, float depth,
                                  const demo_rotation_t *rotation, float scale,
                                  int centre_x, int centre_y, uint16_t color)
{
    for (unsigned y = 0U; y < DEMO_LOGO_HEIGHT; ++y) {
        unsigned run_start = 0U;
        while (run_start < DEMO_LOGO_WIDTH) {
            while (run_start < DEMO_LOGO_WIDTH && !bitmapPixelSet(bitmap, run_start, y)) {
                ++run_start;
            }
            if (run_start == DEMO_LOGO_WIDTH) break;
            unsigned run_end = run_start + 1U;
            while (run_end < DEMO_LOGO_WIDTH && bitmapPixelSet(bitmap, run_end, y)) {
                ++run_end;
            }
            const float left = (float)run_start - (float)DEMO_LOGO_WIDTH * 0.5f;
            const float right = (float)run_end - (float)DEMO_LOGO_WIDTH * 0.5f;
            const float top = (float)y - (float)DEMO_LOGO_HEIGHT * 0.5f;
            const graphics_point_t run[4] = {
                projectApproachPoint(left, top, depth, rotation, scale, centre_x, centre_y),
                projectApproachPoint(right, top, depth, rotation, scale, centre_x, centre_y),
                projectApproachPoint(right, top + 1.0f, depth, rotation, scale, centre_x, centre_y),
                projectApproachPoint(left, top + 1.0f, depth, rotation, scale, centre_x, centre_y),
            };
            fill_convex_polygon(frame, run, 4U, color);
            run_start = run_end;
        }
    }
}
beginDemoSegments() · source line 2916
static void beginDemoSegments(uint32_t now_ms)
  1. Executedemo_instructions_active = false;
  2. Executedemo_last_interaction_at = now_ms;
  3. Executestage_one_next_effect_change_at = now_ms + DEMO_STAGE_ONE_EFFECT_CHANGE_MS;
Read original function source
static void beginDemoSegments(uint32_t now_ms)
{
    demo_instructions_active = false;
    demo_last_interaction_at = now_ms;
    stage_one_next_effect_change_at = now_ms + DEMO_STAGE_ONE_EFFECT_CHANGE_MS;
}
startDemo() · source line 2923
void startDemo(uint32_t now_ms)

Calls into this documentation:

  1. ExecutedemoStageSlideshowLensStop();
  2. ExecutedemoStageGameOfLifeStop();
  3. ExecutereleaseStageFifteenGroundSource();
  4. ExecutedemoStagePlasmaInitialize();
  5. Executepitch_degrees = 0.0f;
  6. Executeyaw_degrees = 0.0f;
  7. Executeroll_degrees = 0.0f;
  8. Executeactive_icon_slot = 0U;
  9. Executeactive_logo_bitmap = demo_icon_slots[active_icon_slot];
  10. ExecuteselectDemoIconColourScheme();
  11. Executescroll_speed_pixels_per_second = DEMO_SCROLL_INITIAL_PIXELS_PER_SECOND;
  12. Executeselected_stage = DEMO_STAGE_ICON;
  13. Executestage_two_distance = DEMO_STAGE_TWO_START_DISTANCE;
  14. Executestage_two_auto_approaching = true;
  15. Executestage_three_next_icon_change_at = 0U;
  16. Executestage_three_icon_slot = 0U;
  17. Executestage_three_icon_bitmap = NULL;
  18. Executestage_six_next_change_at = 0U;
  19. Executestage_six_current_mesh = NULL;
  20. Executestage_six_colour_scheme_index = DEMO_SOLID_COLOUR_SCHEME_COUNT;
  21. Executeraster_band_palette_index = DEMO_RASTER_METALLIC_PALETTE_INDEX;
  22. Executedemo_paused_total_ms = 0U;
  23. Executedemo_selector_active = false;
  24. Executedemo_selector_clear_new_stage = false;
  25. Executedemo_selector_item = 0U;
  26. Executedemo_selector_joystick_direction = 0;
  27. Executedemo_selector_repeating = false;
  28. Executedemo_instructions_active = true;
  29. Executedemo_instructions_started_at = now_ms;
  30. Executestarfield_initialize(&demo_instruction_starfield, 60U, 0, 0, LCD_WIDTH, LCD_HEIGHT, STARFIELD_COLOUR);
  31. Executestage_one_next_effect_change_at = 0U;
  32. Executedemo_last_interaction_at = now_ms;
  33. ExecutescreenTransitionReset(&stage_transition);
  34. Executestage_change_requested = false;
  35. Executestage_zoom_in_requested = false;
  36. Executestage_zooming_in = false;
  37. Executestage_scroll_in_requested = false;
  38. Executestage_reveal_requested = false;
  39. Executeselected_stage_transition = DEMO_STAGE_TRANSITION_SHRINK_ZOOM;
  40. Executestage_scroll_out_direction = SCREEN_TRANSITION_SCROLL_LEFT;
  41. Executestage_scroll_in_direction = SCREEN_TRANSITION_SCROLL_RIGHT;
  42. ExecuteselectScrollMessage(false);
  43. Executelast_update_ms = now_ms;
  44. Executesparkle_phase = now_ms / DEMO_DOT_SPARKLE_INTERVAL_MS;
  45. Executeradial_flame_initialize(&demo_ring_flame, demo_ring_heat, DEMO_RING_SEGMENTS, DEMO_RING_LAYERS, DEMO_RING_LAYER_DEPTH, esp_random());
  46. Executeselected_effect = DEMO_EFFECT_RADIAL_FLAME;
  47. Executerunning = true;
Read original function source
void startDemo(uint32_t now_ms)
{
    demoStageSlideshowLensStop();
    demoStageGameOfLifeStop();
    releaseStageFifteenGroundSource();
    demoStagePlasmaInitialize();
    pitch_degrees = 0.0f;
    yaw_degrees = 0.0f;
    roll_degrees = 0.0f;
    active_icon_slot = 0U;
    active_logo_bitmap = demo_icon_slots[active_icon_slot];
    selectDemoIconColourScheme();
    scroll_speed_pixels_per_second = DEMO_SCROLL_INITIAL_PIXELS_PER_SECOND;
    selected_stage = DEMO_STAGE_ICON;
    stage_two_distance = DEMO_STAGE_TWO_START_DISTANCE;
    stage_two_auto_approaching = true;
    stage_three_next_icon_change_at = 0U;
    stage_three_icon_slot = 0U;
    stage_three_icon_bitmap = NULL;
    stage_six_next_change_at = 0U;
    stage_six_current_mesh = NULL;
    stage_six_colour_scheme_index = DEMO_SOLID_COLOUR_SCHEME_COUNT;
    raster_band_palette_index = DEMO_RASTER_METALLIC_PALETTE_INDEX;
    demo_paused_total_ms = 0U;
    demo_selector_active = false;
    demo_selector_clear_new_stage = false;
    demo_selector_item = 0U;
    demo_selector_joystick_direction = 0;
    demo_selector_repeating = false;
    demo_instructions_active = true;
    demo_instructions_started_at = now_ms;
    starfield_initialize(&demo_instruction_starfield, 60U, 0, 0, LCD_WIDTH, LCD_HEIGHT,
                          STARFIELD_COLOUR);
    stage_one_next_effect_change_at = 0U;
    demo_last_interaction_at = now_ms;
    screenTransitionReset(&stage_transition);
    stage_change_requested = false;
    stage_zoom_in_requested = false;
    stage_zooming_in = false;
    stage_scroll_in_requested = false;
    stage_reveal_requested = false;
    selected_stage_transition = DEMO_STAGE_TRANSITION_SHRINK_ZOOM;
    stage_scroll_out_direction = SCREEN_TRANSITION_SCROLL_LEFT;
    stage_scroll_in_direction = SCREEN_TRANSITION_SCROLL_RIGHT;
    selectScrollMessage(false);
    last_update_ms = now_ms;
    sparkle_phase = now_ms / DEMO_DOT_SPARKLE_INTERVAL_MS;
    radial_flame_initialize(&demo_ring_flame, demo_ring_heat, DEMO_RING_SEGMENTS,
                            DEMO_RING_LAYERS, DEMO_RING_LAYER_DEPTH, esp_random());
    // Start with the outward fire ring; A/K then selects the remaining effects.
    selected_effect = DEMO_EFFECT_RADIAL_FLAME;
    running = true;
}
stopDemo() · source line 2977
void stopDemo(void)

Calls into this documentation:

  1. ExecutescreenTransitionDeinitialize(&stage_transition);
  2. ExecutedemoStageSlideshowLensStop();
  3. ExecutedemoStageGameOfLifeStop();
  4. ExecutereleaseStageFifteenGroundSource();
  5. Executerunning = false;
Read original function source
void stopDemo(void)
{
    screenTransitionDeinitialize(&stage_transition);
    demoStageSlideshowLensStop();
    demoStageGameOfLifeStop();
    releaseStageFifteenGroundSource();
    running = false;
}
moveDemoSelector() · source line 2986
static void moveDemoSelector(int direction, uint32_t now_ms)
  1. Executeconst size_t item_count = DEMO_STAGE_COUNT + 1U;
  2. Decisiondirection < 0
    True
      1. Executedemo_selector_item = demo_selector_item == 0U ? item_count - 1U : demo_selector_item - 1U;
    False
    1. Decisiondirection > 0
      True
        1. Executedemo_selector_item = (demo_selector_item + 1U) % item_count;
      False

      Continue after this decision.

  3. Executedemo_selector_last_move_at = now_ms;
Read original function source
static void moveDemoSelector(int direction, uint32_t now_ms)
{
    const size_t item_count = DEMO_STAGE_COUNT + 1U;
    if (direction < 0) {
        demo_selector_item = demo_selector_item == 0U ? item_count - 1U :
            demo_selector_item - 1U;
    } else if (direction > 0) {
        demo_selector_item = (demo_selector_item + 1U) % item_count;
    }
    demo_selector_last_move_at = now_ms;
}
closeDemoSelector() · source line 2998
static void closeDemoSelector(uint32_t now_ms)

Calls into this documentation:

  1. Executedemo_paused_total_ms += now_ms - demo_selector_started_at;
  2. Executedemo_selector_active = false;
  3. Executedemo_selector_joystick_direction = 0;
  4. Executedemo_selector_repeating = false;
  5. Executelast_update_ms = demoTimeNow(now_ms);
Read original function source
static void closeDemoSelector(uint32_t now_ms)
{
    demo_paused_total_ms += now_ms - demo_selector_started_at;
    demo_selector_active = false;
    demo_selector_joystick_direction = 0;
    demo_selector_repeating = false;
    last_update_ms = demoTimeNow(now_ms);
}
openDemoSelector() · source line 3007
static void openDemoSelector(uint32_t now_ms)
  1. Executedemo_selector_active = true;
  2. Executedemo_selector_started_at = now_ms;
  3. Executedemo_selector_item = 0U;
  4. Executedemo_selector_joystick_direction = 0;
  5. Executedemo_selector_repeating = false;
Read original function source
static void openDemoSelector(uint32_t now_ms)
{
    demo_selector_active = true;
    demo_selector_started_at = now_ms;
    demo_selector_item = 0U;
    demo_selector_joystick_direction = 0;
    demo_selector_repeating = false;
}
demoAction() · source line 3016
void demoAction(bool next_effect, bool toggle_icon, bool next_stage, bool user_long_pressed,
                bool selector_previous, bool selector_next, uint32_t now_ms)

Calls into this documentation:

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

    Continue after this decision.

  2. Decisiondemo_selector_active
    True
      1. Decisionselector_previous
        True
          1. ExecutemoveDemoSelector(-1, now_ms);
        False
        1. Decisionselector_next
          True
            1. ExecutemoveDemoSelector(1, now_ms);
          False
          1. Decisionnext_effect
            True
              1. Executeconst size_t selected_item = demo_selector_item;
              2. ExecutecloseDemoSelector(now_ms);
              3. Decisionselected_item != 0U
                True
                  1. ExecutescreenTransitionReset(&stage_transition);
                  2. Executestage_change_requested = false;
                  3. Executestage_zoom_in_requested = false;
                  4. Executestage_zooming_in = false;
                  5. Executestage_scroll_in_requested = false;
                  6. Executestage_reveal_requested = false;
                  7. Executedemo_instructions_active = false;
                  8. ExecuteenterDemoStage((demo_stage_t)(selected_item - 1U), demoTimeNow(now_ms));
                  9. Executedemo_selector_clear_new_stage = true;
                False

                Continue after this decision.

            False

            Continue after this decision.

      2. Exit this functionreturn;
    False

    Continue after this decision.

  3. Decisionuser_long_pressed
    True
      1. ExecuteopenDemoSelector(now_ms);
      2. Exit this functionreturn;
    False

    Continue after this decision.

  4. Executenow_ms = demoTimeNow(now_ms);
  5. Decisiondemo_instructions_active
    True
      1. Decisionnext_stage
        True
        1. ExecutebeginDemoSegments(now_ms);
        False

        Continue after this decision.

      2. Exit this functionreturn;
    False

    Continue after this decision.

  6. Decisiontoggle_icon
    True
      1. Decisionselected_stage == DEMO_STAGE_GEOMETRIC_SOLID
        True
          1. ExecuteselectStageSixMesh();
          2. Executedemo_last_interaction_at = now_ms;
        False
        1. Decisionselected_stage == DEMO_STAGE_RASTER_BANDS || selected_stage == DEMO_STAGE_LOWER_RASTER_SCROLLER || selected_stage == DEMO_STAGE_BATTLE_ZONE_RASTER
          True
            1. Executeraster_band_palette_index = (raster_band_palette_index + 1U) % DEMO_RASTER_BAND_PALETTE_COUNT;
            2. Executedemo_last_interaction_at = now_ms;
          False
          1. Decisionselected_stage == DEMO_STAGE_PLASMA
            True
              1. ExecutedemoStagePlasmaNextPalette();
              2. Executedemo_last_interaction_at = now_ms;
            False
            1. Decisionselected_stage == DEMO_STAGE_BEVEL_GEARS
              True
                1. Executestage_seventeen_style = (stage_seventeen_style_t) (((unsigned)stage_seventeen_style + 1U) % STAGE_SEVENTEEN_STYLE_COUNT);
                2. Executedemo_last_interaction_at = now_ms;
              False
              1. Decisionselected_stage != DEMO_STAGE_AMIGA_BALL && selected_stage != DEMO_STAGE_SCROLLER && selected_stage != DEMO_STAGE_REVERSE_SCROLLER && selected_stage != DEMO_STAGE_WALLOONS && selected_stage != DEMO_STAGE_KALEIDO && selected_stage != DEMO_STAGE_SPARKLE && selected_stage != DEMO_STAGE_SQUARES && selected_stage != DEMO_STAGE_SLIDESHOW_LENS && !DEMO_LOCK_ICON_TO_PRIMARY
                True
                  1. ExecuteadvanceIconSlot();
                  2. Executedemo_last_interaction_at = now_ms;
                False

                Continue after this decision.

    False

    Continue after this decision.

  7. Decisionnext_stage && !stage_change_requested && !stage_zoom_in_requested && !stage_scroll_in_requested && !stage_reveal_requested && !screenTransitionActive(&stage_transition) && !screenRevealActive(&stage_transition) && !screenScrollActive(&stage_transition) && !screenSliceActive(&stage_transition)
    True
      1. ExecuteselectRandomStageTransition();
      2. Executestage_change_requested = true;
    False
    1. Decisionnext_effect && selected_stage == DEMO_STAGE_ICON
      True
        1. Executeselected_effect = (demo_effect_t)(((unsigned)selected_effect + 1U) % DEMO_EFFECT_COUNT);
        2. Executedemo_last_interaction_at = now_ms;
      False

      Continue after this decision.

Read original function source
void demoAction(bool next_effect, bool toggle_icon, bool next_stage, bool user_long_pressed,
                bool selector_previous, bool selector_next, uint32_t now_ms)
{
    if (!running) return;
    if (demo_selector_active) {
        if (selector_previous) {
            moveDemoSelector(-1, now_ms);
        } else if (selector_next) {
            moveDemoSelector(1, now_ms);
        } else if (next_effect) {
            const size_t selected_item = demo_selector_item;
            closeDemoSelector(now_ms);
            if (selected_item != 0U) {
                screenTransitionReset(&stage_transition);
                stage_change_requested = false;
                stage_zoom_in_requested = false;
                stage_zooming_in = false;
                stage_scroll_in_requested = false;
                stage_reveal_requested = false;
                demo_instructions_active = false;
                enterDemoStage((demo_stage_t)(selected_item - 1U), demoTimeNow(now_ms));
                // Present one clean black frame before the newly initialized segment draws.
                demo_selector_clear_new_stage = true;
            }
        }
        return;
    }
    if (user_long_pressed) {
        openDemoSelector(now_ms);
        return;
    }
    now_ms = demoTimeNow(now_ms);
    if (demo_instructions_active) {
        // C skips the control page and starts Segment 1. A, B, and the stick
        // remain inactive here so the Demo always begins from its known state.
        if (next_stage) beginDemoSegments(now_ms);
        return;
    }
    if (toggle_icon) {
        if (selected_stage == DEMO_STAGE_GEOMETRIC_SOLID) {
            // B previews another catalogue solid and restarts the shared inactivity timer.
            // Deliberately leave the independent five-second automatic change deadline alone.
            selectStageSixMesh();
            demo_last_interaction_at = now_ms;
        } else if (selected_stage == DEMO_STAGE_RASTER_BANDS ||
                   selected_stage == DEMO_STAGE_LOWER_RASTER_SCROLLER ||
                   selected_stage == DEMO_STAGE_BATTLE_ZONE_RASTER) {
            // B cycles the shared palette list without disturbing the bar's
            // travel, colour-roll phase, or the scroller's wave.
            raster_band_palette_index =
                (raster_band_palette_index + 1U) % DEMO_RASTER_BAND_PALETTE_COUNT;
            demo_last_interaction_at = now_ms;
        } else if (selected_stage == DEMO_STAGE_PLASMA) {
            // B changes only the plasma ramp; wave phases and ticker movement continue.
            demoStagePlasmaNextPalette();
            demo_last_interaction_at = now_ms;
        } else if (selected_stage == DEMO_STAGE_BEVEL_GEARS) {
            // B changes only the presentation treatment; gear and rig phases stay continuous.
            stage_seventeen_style = (stage_seventeen_style_t)
                (((unsigned)stage_seventeen_style + 1U) % STAGE_SEVENTEEN_STYLE_COUNT);
            demo_last_interaction_at = now_ms;
        } else if (selected_stage != DEMO_STAGE_AMIGA_BALL &&
                   selected_stage != DEMO_STAGE_SCROLLER &&
                   selected_stage != DEMO_STAGE_REVERSE_SCROLLER &&
                   selected_stage != DEMO_STAGE_WALLOONS &&
                   selected_stage != DEMO_STAGE_KALEIDO &&
                   selected_stage != DEMO_STAGE_SPARKLE &&
                   selected_stage != DEMO_STAGE_SQUARES &&
                   selected_stage != DEMO_STAGE_SLIDESHOW_LENS &&
                   !DEMO_LOCK_ICON_TO_PRIMARY) {
            advanceIconSlot();
            demo_last_interaction_at = now_ms;
        }
    }
    if (next_stage && !stage_change_requested && !stage_zoom_in_requested &&
        !stage_scroll_in_requested && !stage_reveal_requested &&
        !screenTransitionActive(&stage_transition) && !screenRevealActive(&stage_transition) &&
        !screenScrollActive(&stage_transition) && !screenSliceActive(&stage_transition)) {
        // Capture happens after this update, once drawDemo has completed the outgoing frame.
        selectRandomStageTransition();
        stage_change_requested = true;
    } else if (next_effect && selected_stage == DEMO_STAGE_ICON) {
        selected_effect = (demo_effect_t)(((unsigned)selected_effect + 1U) %
                                          DEMO_EFFECT_COUNT);
        demo_last_interaction_at = now_ms;
    }
}
updateDemo() · source line 3104
void updateDemo(int joystick_x, int joystick_y, bool joystick_valid, uint32_t now_ms)

Calls into this documentation:

  1. Decisiondemo_selector_active
    True
      1. Executeint direction = 0;
      2. Decisionjoystick_valid
        True
          1. Decisionjoystick_y > DEMO_SELECTOR_JOYSTICK_ENTER
            True
            1. Executedirection = -1;
            False
            1. Decisionjoystick_y < -DEMO_SELECTOR_JOYSTICK_ENTER
              True
              1. Executedirection = 1;
              False

              Continue after this decision.

        False

        Continue after this decision.

      3. Decisiondirection == 0
        True
          1. Decisionjoystick_y >= -DEMO_SELECTOR_JOYSTICK_RELEASE && joystick_y <= DEMO_SELECTOR_JOYSTICK_RELEASE
            True
              1. Executedemo_selector_joystick_direction = 0;
              2. Executedemo_selector_repeating = false;
            False

            Continue after this decision.

          2. Exit this functionreturn;
        False

        Continue after this decision.

      4. Decisiondirection != demo_selector_joystick_direction
        True
          1. Executedemo_selector_joystick_direction = direction;
          2. Executedemo_selector_repeating = false;
          3. ExecutemoveDemoSelector(direction, now_ms);
        False
        1. Decisionnow_ms - demo_selector_last_move_at >= (demo_selector_repeating ? DEMO_SELECTOR_JOYSTICK_REPEAT_MS : DEMO_SELECTOR_JOYSTICK_REPEAT_START_MS)
          True
            1. Executedemo_selector_repeating = true;
            2. ExecutemoveDemoSelector(direction, now_ms);
          False

          Continue after this decision.

      5. Exit this functionreturn;
    False

    Continue after this decision.

  2. Executenow_ms = demoTimeNow(now_ms);
  3. Decision!running || now_ms - last_update_ms < DEMO_FRAME_MS
    True
    1. Exit this functionreturn;
    False

    Continue after this decision.

  4. Executeuint32_t elapsed_ms = now_ms - last_update_ms;
  5. Decisionelapsed_ms > 50U
    True
    1. Executeelapsed_ms = 50U;
    False

    Continue after this decision.

  6. Executelast_update_ms = now_ms;
  7. Decisiondemo_instructions_active
    True
      1. Executestarfield_update(&demo_instruction_starfield, elapsed_ms);
      2. Decision(uint32_t)(now_ms - demo_instructions_started_at) >= DEMO_INSTRUCTIONS_DURATION_MS
        True
          1. ExecutebeginDemoSegments(now_ms);
        False

        Continue after this decision.

      3. Exit this functionreturn;
    False

    Continue after this decision.

  8. DecisionscreenTransitionActive(&stage_transition)
    True
      1. DecisionscreenTransitionComplete(&stage_transition)
        True
          1. ExecutescreenTransitionReset(&stage_transition);
          2. Decisionstage_zooming_in
            True
              1. Executestage_zooming_in = false;
            False
              1. ExecuteadvanceDemoStage(now_ms);
              2. Executestage_zoom_in_requested = true;
        False

        Continue after this decision.

      2. Exit this functionreturn;
    False

    Continue after this decision.

  9. DecisionscreenRevealActive(&stage_transition)
    True
      1. DecisionscreenRevealComplete(&stage_transition)
        True
          1. ExecutescreenRevealReset(&stage_transition);
        False

        Continue after this decision.

      2. Exit this functionreturn;
    False

    Continue after this decision.

  10. DecisionscreenScrollActive(&stage_transition)
    True
      1. DecisionscreenScrollComplete(&stage_transition)
        True
          1. ExecutescreenScrollReset(&stage_transition);
        False

        Continue after this decision.

      2. Exit this functionreturn;
    False

    Continue after this decision.

  11. DecisionscreenSliceActive(&stage_transition)
    True
      1. DecisionscreenSliceComplete(&stage_transition)
        True
          1. ExecutescreenSliceReset(&stage_transition);
          2. ExecuteadvanceDemoStage(now_ms);
          3. Executestage_zoom_in_requested = true;
        False

        Continue after this decision.

      2. Exit this functionreturn;
    False

    Continue after this decision.

  12. Executepitch_degrees += DEMO_PITCH_DEGREES_PER_SECOND * (float)elapsed_ms / 1000.0f;
  13. Executeyaw_degrees += DEMO_YAW_DEGREES_PER_SECOND * (float)elapsed_ms / 1000.0f;
  14. Executeroll_degrees += DEMO_ROLL_DEGREES_PER_SECOND * (float)elapsed_ms / 1000.0f;
  15. Executeconst float elapsed_seconds = (float)elapsed_ms / 1000.0f;
  16. Decisionselected_stage == DEMO_STAGE_AMIGA_BALL
    True
      1. ExecuteupdateStageThreeBall(elapsed_seconds);
      2. Decision(int32_t)(now_ms - stage_three_next_icon_change_at) >= 0
        True
          1. Executestage_three_icon_slot = (stage_three_icon_slot + 1U) % DEMO_ICON_SLOT_COUNT;
          2. Executestage_three_icon_bitmap = demo_icon_slots[stage_three_icon_slot];
          3. Executestage_three_next_icon_change_at += DEMO_STAGE_THREE_ICON_CHANGE_MS;
        False

        Continue after this decision.

    False

    Continue after this decision.

  17. Decisionselected_stage == DEMO_STAGE_GEOMETRIC_SOLID
    True
      1. Decision(int32_t)(now_ms - stage_six_next_change_at) >= 0
        True
          1. ExecuteselectStageSixMesh();
          2. Executestage_six_next_change_at += DEMO_STAGE_SIX_CHANGE_MS;
        False

        Continue after this decision.

    False

    Continue after this decision.

  18. Decisionselected_stage == DEMO_STAGE_WALLOONS
    True
      1. ExecutedemoStageWalloonsUpdate(now_ms);
    False

    Continue after this decision.

  19. Decisionselected_stage == DEMO_STAGE_KALEIDO
    True
      1. ExecutedemoStageKaleidoUpdate(now_ms);
    False

    Continue after this decision.

  20. Decisionselected_stage == DEMO_STAGE_SPARKLE
    True
      1. ExecutedemoStageSparkleUpdate(now_ms);
    False

    Continue after this decision.

  21. Decisionselected_stage == DEMO_STAGE_SQUARES
    True
      1. ExecutedemoStageSquaresUpdate(now_ms);
    False

    Continue after this decision.

  22. Decisionselected_stage == DEMO_STAGE_RASTER_BANDS || selected_stage == DEMO_STAGE_LOWER_RASTER_SCROLLER || selected_stage == DEMO_STAGE_BATTLE_ZONE_RASTER
    True
      1. Executeraster_band_effect_elapsed_ms += elapsed_ms;
    False

    Continue after this decision.

  23. Decisionselected_stage == DEMO_STAGE_SLIDESHOW_LENS
    True
      1. ExecutedemoStageSlideshowLensUpdate(now_ms);
    False

    Continue after this decision.

  24. Decisionselected_stage == DEMO_STAGE_PLASMA
    True
      1. ExecutedemoStagePlasmaUpdate(elapsed_ms);
    False

    Continue after this decision.

  25. Decisionselected_stage == DEMO_STAGE_GAME_OF_LIFE
    True
      1. ExecutedemoStageGameOfLifeUpdate(elapsed_ms);
    False

    Continue after this decision.

  26. Decisionselected_stage == DEMO_STAGE_BEVEL_GEARS
    True
      1. Executeconst float angle_step = DEMO_STAGE_SEVENTEEN_GEAR_DEGREES_PER_SECOND * elapsed_seconds;
      2. Executestage_seventeen_rig_pitch_degrees += DEMO_STAGE_SEVENTEEN_RIG_PITCH_DEGREES_PER_SECOND * elapsed_seconds;
      3. Executestage_seventeen_rig_yaw_degrees += DEMO_STAGE_SEVENTEEN_RIG_YAW_DEGREES_PER_SECOND * elapsed_seconds;
      4. Executestage_seventeen_rig_roll_degrees += DEMO_STAGE_SEVENTEEN_RIG_ROLL_DEGREES_PER_SECOND * elapsed_seconds;
      5. Executestage_seventeen_gear_degrees[0] += angle_step;
      6. Executestage_seventeen_gear_degrees[1] -= angle_step;
      7. Executestage_seventeen_gear_degrees[2] -= angle_step;
    False

    Continue after this decision.

  27. Decisionselected_stage == DEMO_STAGE_MOUSE_MAZE
    True
      1. ExecutedemoStageMouseMazeUpdate(now_ms);
    False

    Continue after this decision.

  28. Decisionselected_stage == DEMO_STAGE_SCROLLER || selected_stage == DEMO_STAGE_REVERSE_SCROLLER
    True
      1. ExecuteupdateTextCornerIconCycles(now_ms);
    False

    Continue after this decision.

  29. Decisionselected_stage == DEMO_STAGE_ICON && (int32_t)(now_ms - stage_one_next_effect_change_at) >= 0
    True
      1. Executeselected_effect = (demo_effect_t)(((unsigned)selected_effect + 1U) % DEMO_EFFECT_COUNT);
      2. Executestage_one_next_effect_change_at = now_ms + DEMO_STAGE_ONE_EFFECT_CHANGE_MS;
    False

    Continue after this decision.

  30. Decisionjoystick_valid && (joystick_x < -DEMO_INACTIVITY_JOYSTICK_DEAD_ZONE || joystick_x > DEMO_INACTIVITY_JOYSTICK_DEAD_ZONE || (selected_stage == DEMO_STAGE_APPROACH && (joystick_y < -DEMO_INACTIVITY_JOYSTICK_DEAD_ZONE || joystick_y > DEMO_INACTIVITY_JOYSTICK_DEAD_ZONE)))
    True
      1. Executedemo_last_interaction_at = now_ms;
    False

    Continue after this decision.

  31. Decisionjoystick_valid && (joystick_x < -DEMO_SCROLL_JOYSTICK_DEAD_ZONE || joystick_x > DEMO_SCROLL_JOYSTICK_DEAD_ZONE)
    True
      1. Executefloat deflection = (float)(joystick_x < 0 ? -joystick_x : joystick_x) - (float)DEMO_SCROLL_JOYSTICK_DEAD_ZONE;
      2. Executedeflection /= DEMO_SCROLL_JOYSTICK_FULL_TRAVEL - (float)DEMO_SCROLL_JOYSTICK_DEAD_ZONE;
      3. Decisiondeflection > 1.0f
        True
        1. Executedeflection = 1.0f;
        False

        Continue after this decision.

      4. Executeconst float direction = joystick_x < 0 ? 1.0f : -1.0f;
      5. Executescroll_speed_pixels_per_second += direction * DEMO_SCROLL_ACCELERATION_PIXELS_PER_SECOND_SQUARED * deflection * elapsed_seconds;
      6. Decisionscroll_speed_pixels_per_second > DEMO_SCROLL_MAX_PIXELS_PER_SECOND
        True
          1. Executescroll_speed_pixels_per_second = DEMO_SCROLL_MAX_PIXELS_PER_SECOND;
        False
        1. Decisionscroll_speed_pixels_per_second < -DEMO_SCROLL_MAX_PIXELS_PER_SECOND
          True
            1. Executescroll_speed_pixels_per_second = -DEMO_SCROLL_MAX_PIXELS_PER_SECOND;
          False

          Continue after this decision.

    False

    Continue after this decision.

  32. Decisionselected_stage == DEMO_STAGE_APPROACH && joystick_valid && (joystick_y < -DEMO_STAGE_TWO_JOYSTICK_DEAD_ZONE || joystick_y > DEMO_STAGE_TWO_JOYSTICK_DEAD_ZONE)
    True
      1. Executefloat deflection = (float)(joystick_y < 0 ? -joystick_y : joystick_y) - (float)DEMO_STAGE_TWO_JOYSTICK_DEAD_ZONE;
      2. Executedeflection /= DEMO_SCROLL_JOYSTICK_FULL_TRAVEL - (float)DEMO_STAGE_TWO_JOYSTICK_DEAD_ZONE;
      3. Decisiondeflection > 1.0f
        True
        1. Executedeflection = 1.0f;
        False

        Continue after this decision.

      4. Executeconst float direction = joystick_y > 0 ? 1.0f : -1.0f;
      5. Executestage_two_distance -= direction * DEMO_STAGE_TWO_APPROACH_UNITS_PER_SECOND * deflection * elapsed_seconds;
      6. Decisionstage_two_distance < DEMO_STAGE_TWO_MIN_DISTANCE
        True
          1. Executestage_two_distance = DEMO_STAGE_TWO_MIN_DISTANCE;
        False
        1. Decisionstage_two_distance > DEMO_STAGE_TWO_MAX_DISTANCE
          True
            1. Executestage_two_distance = DEMO_STAGE_TWO_MAX_DISTANCE;
          False

          Continue after this decision.

    False

    Continue after this decision.

  33. Decisionselected_stage == DEMO_STAGE_APPROACH && (!joystick_valid || (joystick_y >= -DEMO_STAGE_TWO_JOYSTICK_DEAD_ZONE && joystick_y <= DEMO_STAGE_TWO_JOYSTICK_DEAD_ZONE))
    True
      1. Executeconst float auto_distance_step = DEMO_STAGE_TWO_AUTO_APPROACH_UNITS_PER_SECOND * elapsed_seconds;
      2. Decisionstage_two_auto_approaching
        True
          1. Executestage_two_distance -= auto_distance_step;
          2. Decisionstage_two_distance <= DEMO_STAGE_TWO_MIN_DISTANCE
            True
              1. Executestage_two_distance = DEMO_STAGE_TWO_MIN_DISTANCE;
              2. Executestage_two_auto_approaching = false;
            False

            Continue after this decision.

        False
          1. Executestage_two_distance += auto_distance_step;
          2. Decisionstage_two_distance >= DEMO_STAGE_TWO_MAX_DISTANCE
            True
              1. Executestage_two_distance = DEMO_STAGE_TWO_MAX_DISTANCE;
              2. Executestage_two_auto_approaching = true;
            False

            Continue after this decision.

    False

    Continue after this decision.

  34. Decisionnow_ms - demo_last_interaction_at >= demoStageInactivityTimeout() && !stage_change_requested
    True
      1. ExecuteselectRandomStageTransition();
      2. Executestage_change_requested = true;
    False

    Continue after this decision.

  35. Executescroll_text_x -= scroll_speed_pixels_per_second * elapsed_seconds;
  36. Executescroll_text_effect_elapsed_ms += elapsed_ms;
  37. Decisionpitch_degrees >= 360.0f
    True
    1. Executepitch_degrees -= 360.0f;
    False

    Continue after this decision.

  38. Decisionyaw_degrees >= 360.0f
    True
    1. Executeyaw_degrees -= 360.0f;
    False

    Continue after this decision.

  39. Decisionroll_degrees >= 360.0f
    True
    1. Executeroll_degrees -= 360.0f;
    False

    Continue after this decision.

  40. Decisionstage_seventeen_rig_pitch_degrees >= 360.0f
    True
    1. Executestage_seventeen_rig_pitch_degrees -= 360.0f;
    False

    Continue after this decision.

  41. Decisionstage_seventeen_rig_yaw_degrees >= 360.0f
    True
    1. Executestage_seventeen_rig_yaw_degrees -= 360.0f;
    False

    Continue after this decision.

  42. Decisionstage_seventeen_rig_roll_degrees >= 360.0f
    True
    1. Executestage_seventeen_rig_roll_degrees -= 360.0f;
    False

    Continue after this decision.

  43. FOR loopsize_t gear = 0U; gear < 3U; ++gear

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

      1. WHILE loopstage_seventeen_gear_degrees[gear] >= 360.0f

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

          1. Executestage_seventeen_gear_degrees[gear] -= 360.0f;
      2. WHILE loopstage_seventeen_gear_degrees[gear] < 0.0f

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

          1. Executestage_seventeen_gear_degrees[gear] += 360.0f;
  44. Decision(scroll_speed_pixels_per_second > 0.0f && scroll_text_x <= -(float)scroll_text_width) || (scroll_speed_pixels_per_second < 0.0f && scroll_text_x >= (float)LCD_WIDTH)
    True
      1. ExecuteselectScrollMessage(selected_stage != DEMO_STAGE_SCROLLER && selected_stage != DEMO_STAGE_REVERSE_SCROLLER && selected_stage != DEMO_STAGE_RASTER_BANDS && selected_stage != DEMO_STAGE_BATTLE_ZONE_RASTER);
    False

    Continue after this decision.

  45. Executesparkle_phase = now_ms / DEMO_DOT_SPARKLE_INTERVAL_MS;
  46. Executeradial_flame_update_wavy(&demo_ring_flame, DEMO_RING_WAVE_AMPLITUDE, DEMO_RING_WAVE_COUNT, (float)now_ms * DEMO_RING_WAVE_DEGREES_PER_SECOND / 1000.0f, DEMO_RING_EXTRA_COOLING);
Read original function source
void updateDemo(int joystick_x, int joystick_y, bool joystick_valid, uint32_t now_ms)
{
    if (demo_selector_active) {
        int direction = 0;
        if (joystick_valid) {
            if (joystick_y > DEMO_SELECTOR_JOYSTICK_ENTER) direction = -1;
            else if (joystick_y < -DEMO_SELECTOR_JOYSTICK_ENTER) direction = 1;
        }
        if (direction == 0) {
            if (joystick_y >= -DEMO_SELECTOR_JOYSTICK_RELEASE &&
                joystick_y <= DEMO_SELECTOR_JOYSTICK_RELEASE) {
                demo_selector_joystick_direction = 0;
                demo_selector_repeating = false;
            }
            return;
        }
        if (direction != demo_selector_joystick_direction) {
            demo_selector_joystick_direction = direction;
            demo_selector_repeating = false;
            moveDemoSelector(direction, now_ms);
        } else if (now_ms - demo_selector_last_move_at >=
                   (demo_selector_repeating ? DEMO_SELECTOR_JOYSTICK_REPEAT_MS :
                    DEMO_SELECTOR_JOYSTICK_REPEAT_START_MS)) {
            demo_selector_repeating = true;
            moveDemoSelector(direction, now_ms);
        }
        return;
    }
    now_ms = demoTimeNow(now_ms);
    if (!running || now_ms - last_update_ms < DEMO_FRAME_MS) return;
    uint32_t elapsed_ms = now_ms - last_update_ms;
    if (elapsed_ms > 50U) elapsed_ms = 50U;
    last_update_ms = now_ms;
    if (demo_instructions_active) {
        // The control page has its own fixed duration and does not consume any
        // of Segment 1's normal inactivity timeout.
        starfield_update(&demo_instruction_starfield, elapsed_ms);
        if ((uint32_t)(now_ms - demo_instructions_started_at) >=
            DEMO_INSTRUCTIONS_DURATION_MS) {
            beginDemoSegments(now_ms);
        }
        return;
    }
    if (screenTransitionActive(&stage_transition)) {
        if (screenTransitionComplete(&stage_transition)) {
            // The transition's finished frame was shown. Either resume the new stage or
            // select it and request its off-screen capture for the incoming zoom.
            screenTransitionReset(&stage_transition);
            if (stage_zooming_in) {
                stage_zooming_in = false;
            } else {
                advanceDemoStage(now_ms);
                stage_zoom_in_requested = true;
            }
        }
        return;
    }
    if (screenRevealActive(&stage_transition)) {
        if (screenRevealComplete(&stage_transition)) {
            screenRevealReset(&stage_transition);
        }
        return;
    }
    if (screenScrollActive(&stage_transition)) {
        if (screenScrollComplete(&stage_transition)) {
            screenScrollReset(&stage_transition);
        }
        return;
    }
    if (screenSliceActive(&stage_transition)) {
        if (screenSliceComplete(&stage_transition)) {
            screenSliceReset(&stage_transition);
            advanceDemoStage(now_ms);
            stage_zoom_in_requested = true;
        }
        return;
    }
    pitch_degrees += DEMO_PITCH_DEGREES_PER_SECOND * (float)elapsed_ms / 1000.0f;
    yaw_degrees += DEMO_YAW_DEGREES_PER_SECOND * (float)elapsed_ms / 1000.0f;
    roll_degrees += DEMO_ROLL_DEGREES_PER_SECOND * (float)elapsed_ms / 1000.0f;
    const float elapsed_seconds = (float)elapsed_ms / 1000.0f;
    if (selected_stage == DEMO_STAGE_AMIGA_BALL) {
        updateStageThreeBall(elapsed_seconds);
        // This signed comparison continues to work if the millisecond counter wraps.
        if ((int32_t)(now_ms - stage_three_next_icon_change_at) >= 0) {
            stage_three_icon_slot = (stage_three_icon_slot + 1U) % DEMO_ICON_SLOT_COUNT;
            stage_three_icon_bitmap = demo_icon_slots[stage_three_icon_slot];
            stage_three_next_icon_change_at += DEMO_STAGE_THREE_ICON_CHANGE_MS;
        }
    }
    if (selected_stage == DEMO_STAGE_GEOMETRIC_SOLID) {
        if ((int32_t)(now_ms - stage_six_next_change_at) >= 0) {
            selectStageSixMesh();
            stage_six_next_change_at += DEMO_STAGE_SIX_CHANGE_MS;
        }
    }
    if (selected_stage == DEMO_STAGE_WALLOONS) {
        demoStageWalloonsUpdate(now_ms);
    }
    if (selected_stage == DEMO_STAGE_KALEIDO) {
        demoStageKaleidoUpdate(now_ms);
    }
    if (selected_stage == DEMO_STAGE_SPARKLE) {
        demoStageSparkleUpdate(now_ms);
    }
    if (selected_stage == DEMO_STAGE_SQUARES) {
        demoStageSquaresUpdate(now_ms);
    }
    if (selected_stage == DEMO_STAGE_RASTER_BANDS ||
        selected_stage == DEMO_STAGE_LOWER_RASTER_SCROLLER ||
        selected_stage == DEMO_STAGE_BATTLE_ZONE_RASTER) {
        raster_band_effect_elapsed_ms += elapsed_ms;
    }
    if (selected_stage == DEMO_STAGE_SLIDESHOW_LENS) {
        demoStageSlideshowLensUpdate(now_ms);
    }
    if (selected_stage == DEMO_STAGE_PLASMA) {
        demoStagePlasmaUpdate(elapsed_ms);
    }
    if (selected_stage == DEMO_STAGE_GAME_OF_LIFE) {
        demoStageGameOfLifeUpdate(elapsed_ms);
    }
    if (selected_stage == DEMO_STAGE_BEVEL_GEARS) {
        const float angle_step = DEMO_STAGE_SEVENTEEN_GEAR_DEGREES_PER_SECOND *
                                 elapsed_seconds;
        stage_seventeen_rig_pitch_degrees +=
            DEMO_STAGE_SEVENTEEN_RIG_PITCH_DEGREES_PER_SECOND * elapsed_seconds;
        stage_seventeen_rig_yaw_degrees +=
            DEMO_STAGE_SEVENTEEN_RIG_YAW_DEGREES_PER_SECOND * elapsed_seconds;
        stage_seventeen_rig_roll_degrees +=
            DEMO_STAGE_SEVENTEEN_RIG_ROLL_DEGREES_PER_SECOND * elapsed_seconds;
        // The two driven gears reverse from the common lead phase, preserving
        // their fixed tooth-to-gap relationship at both mesh contacts.
        stage_seventeen_gear_degrees[0] += angle_step;
        stage_seventeen_gear_degrees[1] -= angle_step;
        stage_seventeen_gear_degrees[2] -= angle_step;
    }
    if (selected_stage == DEMO_STAGE_MOUSE_MAZE) {
        demoStageMouseMazeUpdate(now_ms);
    }
    if (selected_stage == DEMO_STAGE_SCROLLER ||
        selected_stage == DEMO_STAGE_REVERSE_SCROLLER) {
        updateTextCornerIconCycles(now_ms);
    }
    if (selected_stage == DEMO_STAGE_ICON &&
        (int32_t)(now_ms - stage_one_next_effect_change_at) >= 0) {
        // Keep the automatic sequence in the same enum order as a manual A press.
        selected_effect = (demo_effect_t)(((unsigned)selected_effect + 1U) % DEMO_EFFECT_COUNT);
        stage_one_next_effect_change_at = now_ms + DEMO_STAGE_ONE_EFFECT_CHANGE_MS;
    }
    if (joystick_valid &&
        (joystick_x < -DEMO_INACTIVITY_JOYSTICK_DEAD_ZONE ||
         joystick_x > DEMO_INACTIVITY_JOYSTICK_DEAD_ZONE ||
         (selected_stage == DEMO_STAGE_APPROACH &&
          (joystick_y < -DEMO_INACTIVITY_JOYSTICK_DEAD_ZONE ||
           joystick_y > DEMO_INACTIVITY_JOYSTICK_DEAD_ZONE)))) {
        demo_last_interaction_at = now_ms;
    }
    if (joystick_valid && (joystick_x < -DEMO_SCROLL_JOYSTICK_DEAD_ZONE ||
                           joystick_x > DEMO_SCROLL_JOYSTICK_DEAD_ZONE)) {
        float deflection = (float)(joystick_x < 0 ? -joystick_x : joystick_x) -
                           (float)DEMO_SCROLL_JOYSTICK_DEAD_ZONE;
        deflection /= DEMO_SCROLL_JOYSTICK_FULL_TRAVEL -
                      (float)DEMO_SCROLL_JOYSTICK_DEAD_ZONE;
        if (deflection > 1.0f) deflection = 1.0f;
        const float direction = joystick_x < 0 ? 1.0f : -1.0f;
        scroll_speed_pixels_per_second += direction *
                                          DEMO_SCROLL_ACCELERATION_PIXELS_PER_SECOND_SQUARED *
                                          deflection * elapsed_seconds;
        if (scroll_speed_pixels_per_second > DEMO_SCROLL_MAX_PIXELS_PER_SECOND) {
            scroll_speed_pixels_per_second = DEMO_SCROLL_MAX_PIXELS_PER_SECOND;
        } else if (scroll_speed_pixels_per_second < -DEMO_SCROLL_MAX_PIXELS_PER_SECOND) {
            scroll_speed_pixels_per_second = -DEMO_SCROLL_MAX_PIXELS_PER_SECOND;
        }
    }
    if (selected_stage == DEMO_STAGE_APPROACH && joystick_valid &&
        (joystick_y < -DEMO_STAGE_TWO_JOYSTICK_DEAD_ZONE ||
         joystick_y > DEMO_STAGE_TWO_JOYSTICK_DEAD_ZONE)) {
        float deflection = (float)(joystick_y < 0 ? -joystick_y : joystick_y) -
                           (float)DEMO_STAGE_TWO_JOYSTICK_DEAD_ZONE;
        deflection /= DEMO_SCROLL_JOYSTICK_FULL_TRAVEL -
                      (float)DEMO_STAGE_TWO_JOYSTICK_DEAD_ZONE;
        if (deflection > 1.0f) deflection = 1.0f;
        const float direction = joystick_y > 0 ? 1.0f : -1.0f;
        stage_two_distance -= direction * DEMO_STAGE_TWO_APPROACH_UNITS_PER_SECOND *
                              deflection * elapsed_seconds;
        if (stage_two_distance < DEMO_STAGE_TWO_MIN_DISTANCE) {
            stage_two_distance = DEMO_STAGE_TWO_MIN_DISTANCE;
        } else if (stage_two_distance > DEMO_STAGE_TWO_MAX_DISTANCE) {
            stage_two_distance = DEMO_STAGE_TWO_MAX_DISTANCE;
        }
    }
    if (selected_stage == DEMO_STAGE_APPROACH &&
        (!joystick_valid ||
         (joystick_y >= -DEMO_STAGE_TWO_JOYSTICK_DEAD_ZONE &&
          joystick_y <= DEMO_STAGE_TWO_JOYSTICK_DEAD_ZONE))) {
        // With no vertical-stick input, travel toward the current end and reverse there.
        const float auto_distance_step = DEMO_STAGE_TWO_AUTO_APPROACH_UNITS_PER_SECOND *
                                         elapsed_seconds;
        if (stage_two_auto_approaching) {
            stage_two_distance -= auto_distance_step;
            if (stage_two_distance <= DEMO_STAGE_TWO_MIN_DISTANCE) {
                stage_two_distance = DEMO_STAGE_TWO_MIN_DISTANCE;
                stage_two_auto_approaching = false;
            }
        } else {
            stage_two_distance += auto_distance_step;
            if (stage_two_distance >= DEMO_STAGE_TWO_MAX_DISTANCE) {
                stage_two_distance = DEMO_STAGE_TWO_MAX_DISTANCE;
                stage_two_auto_approaching = true;
            }
        }
    }
    if (now_ms - demo_last_interaction_at >= demoStageInactivityTimeout() &&
        !stage_change_requested) {
        // Match C's normal route so every automatic advance uses a regular transition.
        selectRandomStageTransition();
        stage_change_requested = true;
    }
    scroll_text_x -= scroll_speed_pixels_per_second * elapsed_seconds;
    scroll_text_effect_elapsed_ms += elapsed_ms;
    if (pitch_degrees >= 360.0f) pitch_degrees -= 360.0f;
    if (yaw_degrees >= 360.0f) yaw_degrees -= 360.0f;
    if (roll_degrees >= 360.0f) roll_degrees -= 360.0f;
    if (stage_seventeen_rig_pitch_degrees >= 360.0f) stage_seventeen_rig_pitch_degrees -= 360.0f;
    if (stage_seventeen_rig_yaw_degrees >= 360.0f) stage_seventeen_rig_yaw_degrees -= 360.0f;
    if (stage_seventeen_rig_roll_degrees >= 360.0f) stage_seventeen_rig_roll_degrees -= 360.0f;
    for (size_t gear = 0U; gear < 3U; ++gear) {
        while (stage_seventeen_gear_degrees[gear] >= 360.0f) {
            stage_seventeen_gear_degrees[gear] -= 360.0f;
        }
        while (stage_seventeen_gear_degrees[gear] < 0.0f) {
            stage_seventeen_gear_degrees[gear] += 360.0f;
        }
    }
    if ((scroll_speed_pixels_per_second > 0.0f &&
         scroll_text_x <= -(float)scroll_text_width) ||
        (scroll_speed_pixels_per_second < 0.0f && scroll_text_x >= (float)LCD_WIDTH)) {
        selectScrollMessage(selected_stage != DEMO_STAGE_SCROLLER &&
                            selected_stage != DEMO_STAGE_REVERSE_SCROLLER &&
                            selected_stage != DEMO_STAGE_RASTER_BANDS &&
                            selected_stage != DEMO_STAGE_BATTLE_ZONE_RASTER);
    }
    sparkle_phase = now_ms / DEMO_DOT_SPARKLE_INTERVAL_MS;
    radial_flame_update_wavy(&demo_ring_flame, DEMO_RING_WAVE_AMPLITUDE,
                             DEMO_RING_WAVE_COUNT,
                             (float)now_ms * DEMO_RING_WAVE_DEGREES_PER_SECOND / 1000.0f,
                             DEMO_RING_EXTRA_COOLING);
}
drawApproachStage() · source line 3355
static void drawApproachStage(uint16_t *frame)

Calls into this documentation:

  1. Executestatic const int terrain_bands[] = {58, 76, 101, 134};
  2. Executeconst int horizon = 42;
  3. FOR loopsize_t index = 0U; index < sizeof(terrain_bands) / sizeof(terrain_bands[0]); ++index

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

      1. Executedraw_line(frame, 0, terrain_bands[index], LCD_WIDTH - 1, terrain_bands[index], COLOR_DARKGREEN);
  4. FOR loopint lane = -3; lane <= 3; ++lane

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

      1. Executedraw_line(frame, DEMO_CENTRE_X + lane * 10, horizon, DEMO_CENTRE_X + lane * 50, LCD_HEIGHT - 1, COLOR_DARKGREEN);
  5. Executedraw_line(frame, 0, horizon, LCD_WIDTH - 1, horizon, COLOR_LIME);
  6. Executeint icon_size = (int)lroundf(2400.0f / stage_two_distance);
  7. Decisionicon_size < 4
    True
    1. Executeicon_size = 4;
    False

    Continue after this decision.

  8. Decisionicon_size > DEMO_STAGE_TWO_MAX_ICON_PIXELS
    True
    1. Executeicon_size = DEMO_STAGE_TWO_MAX_ICON_PIXELS;
    False

    Continue after this decision.

  9. Executeconst int icon_base_y = horizon + (int)lroundf(2000.0f / stage_two_distance);
  10. Executeconst float pitch_radians = pitch_degrees * DEMO_PI / 180.0f;
  11. Executeconst float yaw_radians = yaw_degrees * DEMO_PI / 180.0f;
  12. Executeconst float roll_radians = roll_degrees * DEMO_PI / 180.0f;
  13. Executeconst demo_rotation_t rotation = { .pitch_cosine = cosf(pitch_radians), .pitch_sine = sinf(pitch_radians), .yaw_cosine = cosf(yaw_radians), .yaw_sine = sinf(yaw_radians), .roll_cosine = cosf(roll_radians), .roll_sine = sinf(roll_radians), };
  14. Executeconst float scale = (float)icon_size / (float)DEMO_LOGO_WIDTH;
  15. Executeconst int centre_y = icon_base_y - icon_size / 2;
  16. Decisionrotation.pitch_cosine * rotation.yaw_cosine >= 0.0f
    True
      1. FOR loopunsigned depth = DEMO_EXTRUSION_DEPTH; depth > 0U; depth -= DEMO_EXTRUSION_STEP

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

          1. ExecutedrawApproachLogoSlice(frame, active_logo_bitmap, (float)depth, &rotation, scale, DEMO_CENTRE_X, centre_y, demo_icon_colour_schemes[demo_icon_colour_scheme_index].depth);
      2. ExecutedrawApproachLogoSlice(frame, active_logo_bitmap, 0.0f, &rotation, scale, DEMO_CENTRE_X, centre_y, demo_icon_colour_schemes[demo_icon_colour_scheme_index].front);
    False
      1. ExecutedrawApproachLogoSlice(frame, active_logo_bitmap, 0.0f, &rotation, scale, DEMO_CENTRE_X, centre_y, demo_icon_colour_schemes[demo_icon_colour_scheme_index].front);
      2. FOR loopunsigned depth = DEMO_EXTRUSION_STEP; depth <= DEMO_EXTRUSION_DEPTH; depth += DEMO_EXTRUSION_STEP

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

          1. ExecutedrawApproachLogoSlice(frame, active_logo_bitmap, (float)depth, &rotation, scale, DEMO_CENTRE_X, centre_y, demo_icon_colour_schemes[demo_icon_colour_scheme_index].depth);
Read original function source
static void drawApproachStage(uint16_t *frame)
{
    static const int terrain_bands[] = {58, 76, 101, 134};
    const int horizon = 42;
    for (size_t index = 0U; index < sizeof(terrain_bands) / sizeof(terrain_bands[0]); ++index) {
        draw_line(frame, 0, terrain_bands[index], LCD_WIDTH - 1, terrain_bands[index],
                  COLOR_DARKGREEN);
    }
    for (int lane = -3; lane <= 3; ++lane) {
        draw_line(frame, DEMO_CENTRE_X + lane * 10, horizon,
                  DEMO_CENTRE_X + lane * 50, LCD_HEIGHT - 1, COLOR_DARKGREEN);
    }
    draw_line(frame, 0, horizon, LCD_WIDTH - 1, horizon, COLOR_LIME);

    int icon_size = (int)lroundf(2400.0f / stage_two_distance);
    if (icon_size < 4) icon_size = 4;
    if (icon_size > DEMO_STAGE_TWO_MAX_ICON_PIXELS) icon_size = DEMO_STAGE_TWO_MAX_ICON_PIXELS;
    const int icon_base_y = horizon + (int)lroundf(2000.0f / stage_two_distance);
    const float pitch_radians = pitch_degrees * DEMO_PI / 180.0f;
    const float yaw_radians = yaw_degrees * DEMO_PI / 180.0f;
    const float roll_radians = roll_degrees * DEMO_PI / 180.0f;
    const demo_rotation_t rotation = {
        .pitch_cosine = cosf(pitch_radians),
        .pitch_sine = sinf(pitch_radians),
        .yaw_cosine = cosf(yaw_radians),
        .yaw_sine = sinf(yaw_radians),
        .roll_cosine = cosf(roll_radians),
        .roll_sine = sinf(roll_radians),
    };
    const float scale = (float)icon_size / (float)DEMO_LOGO_WIDTH;
    const int centre_y = icon_base_y - icon_size / 2;
    if (rotation.pitch_cosine * rotation.yaw_cosine >= 0.0f) {
        for (unsigned depth = DEMO_EXTRUSION_DEPTH; depth > 0U;
             depth -= DEMO_EXTRUSION_STEP) {
            drawApproachLogoSlice(frame, active_logo_bitmap, (float)depth, &rotation, scale,
                                  DEMO_CENTRE_X, centre_y,
                                  demo_icon_colour_schemes[demo_icon_colour_scheme_index].depth);
        }
        drawApproachLogoSlice(frame, active_logo_bitmap, 0.0f, &rotation, scale,
                              DEMO_CENTRE_X, centre_y,
                              demo_icon_colour_schemes[demo_icon_colour_scheme_index].front);
    } else {
        drawApproachLogoSlice(frame, active_logo_bitmap, 0.0f, &rotation, scale,
                              DEMO_CENTRE_X, centre_y,
                              demo_icon_colour_schemes[demo_icon_colour_scheme_index].front);
        for (unsigned depth = DEMO_EXTRUSION_STEP; depth <= DEMO_EXTRUSION_DEPTH;
             depth += DEMO_EXTRUSION_STEP) {
            drawApproachLogoSlice(frame, active_logo_bitmap, (float)depth, &rotation, scale,
                                  DEMO_CENTRE_X, centre_y,
                                  demo_icon_colour_schemes[demo_icon_colour_scheme_index].depth);
        }
    }
}
drawStageThreeBall() · source line 3409
static void drawStageThreeBall(uint16_t *frame)

Calls into this documentation:

  1. Executeclear(frame, RGB565(0, 32, 175));
  2. Executeconst int centre_x = (int)lroundf(stage_three_ball_x);
  3. Executeconst int centre_y = (int)lroundf(stage_three_ball_y);
  4. Executeconst uint8_t rotation = (uint8_t)stage_three_ball_rotation_phase;
  5. Executeconst uint16_t shadow = RGB565(0, 0, 80);
  6. Executefill_rect(frame, centre_x - 13, (int)DEMO_STAGE_THREE_BALL_FLOOR_Y, 27, 1, shadow);
  7. Executefill_rect(frame, centre_x - 19, (int)DEMO_STAGE_THREE_BALL_FLOOR_Y + 1, 39, 2, shadow);
  8. Executefill_rect(frame, centre_x - 15, (int)DEMO_STAGE_THREE_BALL_FLOOR_Y + 3, 31, 1, shadow);
  9. Executeconst uint8_t *bitmap = stage_three_icon_bitmap != NULL ? stage_three_icon_bitmap : demo_jd_bitmap;
  10. FOR loopint y = 0; y < DEMO_STAGE_THREE_BALL_DIAMETER; ++y

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

      1. Executeconst int screen_y = centre_y - DEMO_STAGE_THREE_BALL_RADIUS + y;
      2. Decisionscreen_y < 0 || screen_y >= LCD_HEIGHT
        True
        1. Next loop iterationcontinue;
        False

        Continue after this decision.

      3. FOR loopint x = 0; x < DEMO_STAGE_THREE_BALL_DIAMETER; ++x

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

          1. Executeconst int index = y * DEMO_STAGE_THREE_BALL_DIAMETER + x;
          2. Executeconst uint8_t shade = stage_three_ball_shade_map[index];
          3. Decisionshade == 0U
            True
            1. Next loop iterationcontinue;
            False

            Continue after this decision.

          4. Executeconst int screen_x = centre_x - DEMO_STAGE_THREE_BALL_RADIUS + x;
          5. Decisionscreen_x < 0 || screen_x >= LCD_WIDTH
            True
            1. Next loop iterationcontinue;
            False

            Continue after this decision.

          6. Executeconst unsigned texture_x = ((((unsigned)stage_three_ball_longitude_map[index] * DEMO_LOGO_WIDTH) >> 8U) + (unsigned)rotation * DEMO_LOGO_WIDTH / DEMO_STAGE_THREE_BALL_LONGITUDE_BANDS) % DEMO_LOGO_WIDTH;
          7. Executeconst unsigned texture_y = ((unsigned)stage_three_ball_latitude_map[index] * DEMO_LOGO_HEIGHT) >> 8U;
          8. Executeconst size_t byte_index = (size_t)texture_y * DEMO_LOGO_ROW_BYTES + texture_x / 8U;
          9. Executeconst uint8_t bit = (uint8_t)(7U - texture_x % 8U);
          10. Executeconst bool icon_pixel = (bitmap[byte_index] & (uint8_t)(1U << bit)) != 0U;
          11. Executeframe[(size_t)screen_y * LCD_WIDTH + (size_t)screen_x] = icon_pixel ? stage_three_red_shades[shade - 1U] : stage_three_white_shades[shade - 1U];
Read original function source
static void drawStageThreeBall(uint16_t *frame)
{
    // Keep the classic blue field, while the scrolling PET text is layered later by drawDemo.
    clear(frame, RGB565(0, 32, 175));
    const int centre_x = (int)lroundf(stage_three_ball_x);
    const int centre_y = (int)lroundf(stage_three_ball_y);
    const uint8_t rotation = (uint8_t)stage_three_ball_rotation_phase;
    const uint16_t shadow = RGB565(0, 0, 80);
    fill_rect(frame, centre_x - 13, (int)DEMO_STAGE_THREE_BALL_FLOOR_Y, 27, 1, shadow);
    fill_rect(frame, centre_x - 19, (int)DEMO_STAGE_THREE_BALL_FLOOR_Y + 1, 39, 2, shadow);
    fill_rect(frame, centre_x - 15, (int)DEMO_STAGE_THREE_BALL_FLOOR_Y + 3, 31, 1, shadow);

    const uint8_t *bitmap = stage_three_icon_bitmap != NULL ? stage_three_icon_bitmap :
                                                             demo_jd_bitmap;
    for (int y = 0; y < DEMO_STAGE_THREE_BALL_DIAMETER; ++y) {
        const int screen_y = centre_y - DEMO_STAGE_THREE_BALL_RADIUS + y;
        if (screen_y < 0 || screen_y >= LCD_HEIGHT) continue;
        for (int x = 0; x < DEMO_STAGE_THREE_BALL_DIAMETER; ++x) {
            const int index = y * DEMO_STAGE_THREE_BALL_DIAMETER + x;
            const uint8_t shade = stage_three_ball_shade_map[index];
            if (shade == 0U) continue;
            const int screen_x = centre_x - DEMO_STAGE_THREE_BALL_RADIUS + x;
            if (screen_x < 0 || screen_x >= LCD_WIDTH) continue;

            // The packed Demo icon is a rotating spherical texture. Set pixels replace
            // the Amiga ball's red checks; clear pixels retain the shaded white surface.
            const unsigned texture_x = ((((unsigned)stage_three_ball_longitude_map[index] *
                                          DEMO_LOGO_WIDTH) >> 8U) +
                                        (unsigned)rotation * DEMO_LOGO_WIDTH /
                                        DEMO_STAGE_THREE_BALL_LONGITUDE_BANDS) % DEMO_LOGO_WIDTH;
            const unsigned texture_y = ((unsigned)stage_three_ball_latitude_map[index] *
                                        DEMO_LOGO_HEIGHT) >> 8U;
            const size_t byte_index = (size_t)texture_y * DEMO_LOGO_ROW_BYTES + texture_x / 8U;
            const uint8_t bit = (uint8_t)(7U - texture_x % 8U);
            const bool icon_pixel = (bitmap[byte_index] & (uint8_t)(1U << bit)) != 0U;
            frame[(size_t)screen_y * LCD_WIDTH + (size_t)screen_x] = icon_pixel
                ? stage_three_red_shades[shade - 1U] : stage_three_white_shades[shade - 1U];
        }
    }
}
rotateMeshPoint() · source line 3450
static demo_point_3d_t rotateMeshPoint(demo_point_3d_t point, const demo_rotation_t *rotation)
  1. Executeconst float pitched_y = point.y * rotation->pitch_cosine - point.z * rotation->pitch_sine;
  2. Executeconst float pitched_z = point.y * rotation->pitch_sine + point.z * rotation->pitch_cosine;
  3. Executeconst float yawed_x = point.x * rotation->yaw_cosine + pitched_z * rotation->yaw_sine;
  4. Executeconst float yawed_z = -point.x * rotation->yaw_sine + pitched_z * rotation->yaw_cosine;
  5. Executeconst demo_point_3d_t result = { .x = yawed_x * rotation->roll_cosine - pitched_y * rotation->roll_sine, .y = yawed_x * rotation->roll_sine + pitched_y * rotation->roll_cosine, .z = yawed_z, };
  6. Exit this functionreturn result;
Read original function source
static demo_point_3d_t rotateMeshPoint(demo_point_3d_t point, const demo_rotation_t *rotation)
{
    const float pitched_y = point.y * rotation->pitch_cosine - point.z * rotation->pitch_sine;
    const float pitched_z = point.y * rotation->pitch_sine + point.z * rotation->pitch_cosine;
    const float yawed_x = point.x * rotation->yaw_cosine + pitched_z * rotation->yaw_sine;
    const float yawed_z = -point.x * rotation->yaw_sine + pitched_z * rotation->yaw_cosine;
    const demo_point_3d_t result = {
        .x = yawed_x * rotation->roll_cosine - pitched_y * rotation->roll_sine,
        .y = yawed_x * rotation->roll_sine + pitched_y * rotation->roll_cosine,
        .z = yawed_z,
    };
    return result;
}
projectMeshPoint() · source line 3464
static graphics_point_t projectMeshPoint(const demo_point_3d_t *point)
  1. Executeconst float perspective = DEMO_STAGE_SIX_CAMERA_DISTANCE / (DEMO_STAGE_SIX_CAMERA_DISTANCE + point->z);
  2. Executeconst graphics_point_t result = { .x = DEMO_STAGE_SIX_CENTRE_X + (int)lroundf(point->x * perspective), .y = DEMO_STAGE_SIX_CENTRE_Y + (int)lroundf(point->y * perspective), };
  3. Exit this functionreturn result;
Read original function source
static graphics_point_t projectMeshPoint(const demo_point_3d_t *point)
{
    const float perspective = DEMO_STAGE_SIX_CAMERA_DISTANCE /
                              (DEMO_STAGE_SIX_CAMERA_DISTANCE + point->z);
    const graphics_point_t result = {
        .x = DEMO_STAGE_SIX_CENTRE_X + (int)lroundf(point->x * perspective),
        .y = DEMO_STAGE_SIX_CENTRE_Y + (int)lroundf(point->y * perspective),
    };
    return result;
}
meshFaceColour() · source line 3475
static uint16_t meshFaceColour(float brightness)
  1. Executeconst demo_solid_colour_scheme_t *scheme = &demo_solid_colour_schemes[stage_six_colour_scheme_index];
  2. Decisionbrightness > 0.76f
    True
    1. Exit this functionreturn scheme->face_shades[0];
    False

    Continue after this decision.

  3. Decisionbrightness > 0.52f
    True
    1. Exit this functionreturn scheme->face_shades[1];
    False

    Continue after this decision.

  4. Decisionbrightness > 0.28f
    True
    1. Exit this functionreturn scheme->face_shades[2];
    False

    Continue after this decision.

  5. Exit this functionreturn scheme->face_shades[3];
Read original function source
static uint16_t meshFaceColour(float brightness)
{
    const demo_solid_colour_scheme_t *scheme =
        &demo_solid_colour_schemes[stage_six_colour_scheme_index];
    if (brightness > 0.76f) return scheme->face_shades[0];
    if (brightness > 0.52f) return scheme->face_shades[1];
    if (brightness > 0.28f) return scheme->face_shades[2];
    return scheme->face_shades[3];
}
meshFaceBrightness() · source line 3485
static float meshFaceBrightness(const demo_point_3d_t *first, const demo_point_3d_t *second,
                                const demo_point_3d_t *third)
  1. Executeconst demo_point_3d_t first_edge = { .x = second->x - first->x, .y = second->y - first->y, .z = second->z - first->z, };
  2. Executeconst demo_point_3d_t second_edge = { .x = third->x - first->x, .y = third->y - first->y, .z = third->z - first->z, };
  3. Executeconst demo_point_3d_t normal = { .x = first_edge.y * second_edge.z - first_edge.z * second_edge.y, .y = first_edge.z * second_edge.x - first_edge.x * second_edge.z, .z = first_edge.x * second_edge.y - first_edge.y * second_edge.x, };
  4. Executeconst float length = sqrtf(normal.x * normal.x + normal.y * normal.y + normal.z * normal.z);
  5. Decisionlength <= 0.0f
    True
    1. Exit this functionreturn 0.0f;
    False

    Continue after this decision.

  6. Executeconst float light = fabsf((normal.x * 0.35f - normal.y * 0.45f + normal.z * 0.82f) / length);
  7. Exit this functionreturn light > 1.0f ? 1.0f : light;
Read original function source
static float meshFaceBrightness(const demo_point_3d_t *first, const demo_point_3d_t *second,
                                const demo_point_3d_t *third)
{
    const demo_point_3d_t first_edge = {
        .x = second->x - first->x, .y = second->y - first->y, .z = second->z - first->z,
    };
    const demo_point_3d_t second_edge = {
        .x = third->x - first->x, .y = third->y - first->y, .z = third->z - first->z,
    };
    const demo_point_3d_t normal = {
        .x = first_edge.y * second_edge.z - first_edge.z * second_edge.y,
        .y = first_edge.z * second_edge.x - first_edge.x * second_edge.z,
        .z = first_edge.x * second_edge.y - first_edge.y * second_edge.x,
    };
    const float length = sqrtf(normal.x * normal.x + normal.y * normal.y + normal.z * normal.z);
    if (length <= 0.0f) return 0.0f;
    // Both sides are shaded because depth sorting, rather than culling, is what lets
    // one generic face table render solids with either winding direction.
    const float light = fabsf((normal.x * 0.35f - normal.y * 0.45f + normal.z * 0.82f) / length);
    return light > 1.0f ? 1.0f : light;
}
drawFilledMesh() · source line 3507
static void drawFilledMesh(uint16_t *frame, const demo_mesh_t *mesh,
                           const demo_rotation_t *rotation)

Calls into this documentation:

  1. Decisionmesh == NULL || mesh->vertices == NULL || mesh->faces == NULL || mesh->vertex_count > DEMO_MESH_MAX_VERTICES || mesh->face_count > DEMO_MESH_MAX_FACES
    True
      1. Exit this functionreturn;
    False

    Continue after this decision.

  2. Executedemo_point_3d_t rotated_vertices[DEMO_MESH_MAX_VERTICES];
  3. Executedemo_mesh_face_draw_t draw_order[DEMO_MESH_MAX_FACES];
  4. FOR loopsize_t vertex = 0U; vertex < mesh->vertex_count; ++vertex

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

      1. Executedemo_point_3d_t scaled = mesh->vertices[vertex];
      2. Executescaled.x *= DEMO_STAGE_SIX_SOLID_SCALE;
      3. Executescaled.y *= DEMO_STAGE_SIX_SOLID_SCALE;
      4. Executescaled.z *= DEMO_STAGE_SIX_SOLID_SCALE;
      5. Executerotated_vertices[vertex] = rotateMeshPoint(scaled, rotation);
  5. FOR loopsize_t face = 0U; face < mesh->face_count; ++face

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

      1. Executeconst demo_mesh_face_t *source_face = &mesh->faces[face];
      2. Decisionsource_face->vertex_indices == NULL || source_face->vertex_count < 3U || source_face->vertex_count > DEMO_MESH_MAX_FACE_VERTICES
        True
          1. Exit this functionreturn;
        False

        Continue after this decision.

      3. Executefloat total_depth = 0.0f;
      4. FOR loopsize_t corner = 0U; corner < source_face->vertex_count; ++corner

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

          1. Executeconst uint8_t vertex = source_face->vertex_indices[corner];
          2. Decisionvertex >= mesh->vertex_count
            True
            1. Exit this functionreturn;
            False

            Continue after this decision.

          3. Executetotal_depth += rotated_vertices[vertex].z;
      5. Executedraw_order[face] = (demo_mesh_face_draw_t) { .face_index = face, .depth = total_depth / (float)source_face->vertex_count, .brightness = meshFaceBrightness(&rotated_vertices[source_face->vertex_indices[0]], &rotated_vertices[source_face->vertex_indices[1]], &rotated_vertices[source_face->vertex_indices[2]]), };
  6. FOR loopsize_t pass = 0U; pass + 1U < mesh->face_count; ++pass

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

      1. FOR loopsize_t index = 0U; index + 1U < mesh->face_count - pass; ++index

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

          1. Decisiondraw_order[index].depth <= draw_order[index + 1U].depth
            True
            1. Next loop iterationcontinue;
            False

            Continue after this decision.

          2. Executeconst demo_mesh_face_draw_t temporary = draw_order[index];
          3. Executedraw_order[index] = draw_order[index + 1U];
          4. Executedraw_order[index + 1U] = temporary;
  7. FOR loopsize_t order = 0U; order < mesh->face_count; ++order

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

      1. Executeconst demo_mesh_face_t *source_face = &mesh->faces[draw_order[order].face_index];
      2. Executegraphics_point_t polygon[DEMO_MESH_MAX_FACE_VERTICES];
      3. FOR loopsize_t corner = 0U; corner < source_face->vertex_count; ++corner

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

          1. Executepolygon[corner] = projectMeshPoint(&rotated_vertices[source_face->vertex_indices[corner]]);
      4. Executefill_convex_polygon(frame, polygon, source_face->vertex_count, meshFaceColour(draw_order[order].brightness));
      5. FOR loopsize_t corner = 0U; corner < source_face->vertex_count; ++corner

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

          1. Executeconst size_t next_corner = (corner + 1U) % source_face->vertex_count;
          2. Executedraw_line(frame, polygon[corner].x, polygon[corner].y, polygon[next_corner].x, polygon[next_corner].y, demo_solid_colour_schemes[stage_six_colour_scheme_index].edge);
Read original function source
static void drawFilledMesh(uint16_t *frame, const demo_mesh_t *mesh,
                           const demo_rotation_t *rotation)
{
    if (mesh == NULL || mesh->vertices == NULL || mesh->faces == NULL ||
        mesh->vertex_count > DEMO_MESH_MAX_VERTICES || mesh->face_count > DEMO_MESH_MAX_FACES) {
        return;
    }

    demo_point_3d_t rotated_vertices[DEMO_MESH_MAX_VERTICES];
    demo_mesh_face_draw_t draw_order[DEMO_MESH_MAX_FACES];
    for (size_t vertex = 0U; vertex < mesh->vertex_count; ++vertex) {
        demo_point_3d_t scaled = mesh->vertices[vertex];
        scaled.x *= DEMO_STAGE_SIX_SOLID_SCALE;
        scaled.y *= DEMO_STAGE_SIX_SOLID_SCALE;
        scaled.z *= DEMO_STAGE_SIX_SOLID_SCALE;
        rotated_vertices[vertex] = rotateMeshPoint(scaled, rotation);
    }

    for (size_t face = 0U; face < mesh->face_count; ++face) {
        const demo_mesh_face_t *source_face = &mesh->faces[face];
        if (source_face->vertex_indices == NULL || source_face->vertex_count < 3U ||
            source_face->vertex_count > DEMO_MESH_MAX_FACE_VERTICES) {
            return;
        }
        float total_depth = 0.0f;
        for (size_t corner = 0U; corner < source_face->vertex_count; ++corner) {
            const uint8_t vertex = source_face->vertex_indices[corner];
            if (vertex >= mesh->vertex_count) return;
            total_depth += rotated_vertices[vertex].z;
        }
        draw_order[face] = (demo_mesh_face_draw_t) {
            .face_index = face,
            .depth = total_depth / (float)source_face->vertex_count,
            .brightness = meshFaceBrightness(&rotated_vertices[source_face->vertex_indices[0]],
                                             &rotated_vertices[source_face->vertex_indices[1]],
                                             &rotated_vertices[source_face->vertex_indices[2]]),
        };
    }

    // Painter's order draws the farthest polygon first, allowing nearer filled faces
    // to cover it without a full per-pixel depth buffer.
    for (size_t pass = 0U; pass + 1U < mesh->face_count; ++pass) {
        for (size_t index = 0U; index + 1U < mesh->face_count - pass; ++index) {
            if (draw_order[index].depth <= draw_order[index + 1U].depth) continue;
            const demo_mesh_face_draw_t temporary = draw_order[index];
            draw_order[index] = draw_order[index + 1U];
            draw_order[index + 1U] = temporary;
        }
    }

    for (size_t order = 0U; order < mesh->face_count; ++order) {
        const demo_mesh_face_t *source_face = &mesh->faces[draw_order[order].face_index];
        graphics_point_t polygon[DEMO_MESH_MAX_FACE_VERTICES];
        for (size_t corner = 0U; corner < source_face->vertex_count; ++corner) {
            polygon[corner] = projectMeshPoint(&rotated_vertices[source_face->vertex_indices[corner]]);
        }
        fill_convex_polygon(frame, polygon, source_face->vertex_count,
                            meshFaceColour(draw_order[order].brightness));
        for (size_t corner = 0U; corner < source_face->vertex_count; ++corner) {
            const size_t next_corner = (corner + 1U) % source_face->vertex_count;
            draw_line(frame, polygon[corner].x, polygon[corner].y,
                      polygon[next_corner].x, polygon[next_corner].y,
                      demo_solid_colour_schemes[stage_six_colour_scheme_index].edge);
        }
    }
}
drawStageSixSolid() · source line 3574
static void drawStageSixSolid(uint16_t *frame)

Calls into this documentation:

  1. Executeconst float pitch_radians = pitch_degrees * DEMO_PI / 180.0f;
  2. Executeconst float yaw_radians = yaw_degrees * DEMO_PI / 180.0f;
  3. Executeconst float roll_radians = roll_degrees * DEMO_PI / 180.0f;
  4. Executeconst demo_rotation_t rotation = { .pitch_cosine = cosf(pitch_radians), .pitch_sine = sinf(pitch_radians), .yaw_cosine = cosf(yaw_radians), .yaw_sine = sinf(yaw_radians), .roll_cosine = cosf(roll_radians), .roll_sine = sinf(roll_radians), };
  5. ExecutedrawFilledMesh(frame, stage_six_current_mesh, &rotation);
Read original function source
static void drawStageSixSolid(uint16_t *frame)
{
    const float pitch_radians = pitch_degrees * DEMO_PI / 180.0f;
    const float yaw_radians = yaw_degrees * DEMO_PI / 180.0f;
    const float roll_radians = roll_degrees * DEMO_PI / 180.0f;
    const demo_rotation_t rotation = {
        .pitch_cosine = cosf(pitch_radians), .pitch_sine = sinf(pitch_radians),
        .yaw_cosine = cosf(yaw_radians), .yaw_sine = sinf(yaw_radians),
        .roll_cosine = cosf(roll_radians), .roll_sine = sinf(roll_radians),
    };
    drawFilledMesh(frame, stage_six_current_mesh, &rotation);
}
stageSeventeenRotation() · source line 3587
static demo_rotation_t stageSeventeenRotation(float pitch_degrees, float yaw_degrees,
                                              float roll_degrees)
  1. Executeconst float pitch = pitch_degrees * DEMO_PI / 180.0f;
  2. Executeconst float yaw = yaw_degrees * DEMO_PI / 180.0f;
  3. Executeconst float roll = roll_degrees * DEMO_PI / 180.0f;
  4. Exit this functionreturn (demo_rotation_t) { .pitch_cosine = cosf(pitch), .pitch_sine = sinf(pitch), .yaw_cosine = cosf(yaw), .yaw_sine = sinf(yaw), .roll_cosine = cosf(roll), .roll_sine = sinf(roll), };
Read original function source
static demo_rotation_t stageSeventeenRotation(float pitch_degrees, float yaw_degrees,
                                              float roll_degrees)
{
    const float pitch = pitch_degrees * DEMO_PI / 180.0f;
    const float yaw = yaw_degrees * DEMO_PI / 180.0f;
    const float roll = roll_degrees * DEMO_PI / 180.0f;
    return (demo_rotation_t) {
        .pitch_cosine = cosf(pitch), .pitch_sine = sinf(pitch),
        .yaw_cosine = cosf(yaw), .yaw_sine = sinf(yaw),
        .roll_cosine = cosf(roll), .roll_sine = sinf(roll),
    };
}
stageSeventeenGearOrigin() · source line 3600
static demo_point_3d_t stageSeventeenGearOrigin(unsigned gear)
  1. Select a casegear

    Enter matching case; follow source fall-through until a break, return or end.

      1. Case / labelcase 0U:
      2. Exit this functionreturn (demo_point_3d_t) {.x = 0.0f, .y = DEMO_STAGE_SEVENTEEN_LEAD_Y, .z = 0.0f};
      3. Case / labelcase 1U:
      4. Exit this functionreturn (demo_point_3d_t) {.x = -DEMO_STAGE_SEVENTEEN_DRIVEN_X, .y = DEMO_STAGE_SEVENTEEN_DRIVEN_Y, .z = 0.0f};
      5. Case / labeldefault:
      6. Exit this functionreturn (demo_point_3d_t) {.x = DEMO_STAGE_SEVENTEEN_DRIVEN_X, .y = DEMO_STAGE_SEVENTEEN_DRIVEN_Y, .z = 0.0f};
Read original function source
static demo_point_3d_t stageSeventeenGearOrigin(unsigned gear)
{
    switch (gear) {
        case 0U: return (demo_point_3d_t) {.x = 0.0f, .y = DEMO_STAGE_SEVENTEEN_LEAD_Y,
                                             .z = 0.0f};
        case 1U: return (demo_point_3d_t) {.x = -DEMO_STAGE_SEVENTEEN_DRIVEN_X,
                                             .y = DEMO_STAGE_SEVENTEEN_DRIVEN_Y, .z = 0.0f};
        default: return (demo_point_3d_t) {.x = DEMO_STAGE_SEVENTEEN_DRIVEN_X,
                                            .y = DEMO_STAGE_SEVENTEEN_DRIVEN_Y, .z = 0.0f};
    }
}
stageSeventeenGearAxisRotation() · source line 3612
static demo_rotation_t stageSeventeenGearAxisRotation(unsigned gear)

Calls into this documentation:

  1. Select a casegear

    Enter matching case; follow source fall-through until a break, return or end.

      1. Case / labelcase 0U:
      2. Exit this functionreturn stageSeventeenRotation(-22.5f, 0.0f, 0.0f);
      3. Case / labelcase 1U:
      4. Exit this functionreturn stageSeventeenRotation(14.8f, 22.5f, 0.0f);
      5. Case / labeldefault:
      6. Exit this functionreturn stageSeventeenRotation(14.8f, -22.5f, 0.0f);
Read original function source
static demo_rotation_t stageSeventeenGearAxisRotation(unsigned gear)
{
    // Each backward axis converges toward the triangular centre. The front
    // centres therefore remain at the wide-rim mesh contacts while the narrow
    // rear centres close by the same ratio as the tapering tooth radii.
    switch (gear) {
        case 0U: return stageSeventeenRotation(-22.5f, 0.0f, 0.0f);
        case 1U: return stageSeventeenRotation(14.8f, 22.5f, 0.0f);
        default: return stageSeventeenRotation(14.8f, -22.5f, 0.0f);
    }
}
stageSeventeenTransformPoint() · source line 3624
static demo_point_3d_t stageSeventeenTransformPoint(demo_point_3d_t point,
                                                     const demo_point_3d_t *origin,
                                                     const demo_rotation_t *axis_rotation,
                                                     const demo_rotation_t *rig_rotation)

Calls into this documentation:

  1. Executedemo_point_3d_t transformed = rotateMeshPoint(point, axis_rotation);
  2. Executetransformed.x += origin->x;
  3. Executetransformed.y += origin->y;
  4. Executetransformed.z += origin->z;
  5. Exit this functionreturn rotateMeshPoint(transformed, rig_rotation);
Read original function source
static demo_point_3d_t stageSeventeenTransformPoint(demo_point_3d_t point,
                                                     const demo_point_3d_t *origin,
                                                     const demo_rotation_t *axis_rotation,
                                                     const demo_rotation_t *rig_rotation)
{
    demo_point_3d_t transformed = rotateMeshPoint(point, axis_rotation);
    transformed.x += origin->x;
    transformed.y += origin->y;
    transformed.z += origin->z;
    return rotateMeshPoint(transformed, rig_rotation);
}
stageSeventeenProjectPoint() · source line 3636
static graphics_point_t stageSeventeenProjectPoint(const demo_point_3d_t *point)
  1. Executeconst float perspective = DEMO_STAGE_SEVENTEEN_CAMERA_DISTANCE / (DEMO_STAGE_SEVENTEEN_CAMERA_DISTANCE + point->z);
  2. Exit this functionreturn (graphics_point_t) { .x = DEMO_STAGE_SEVENTEEN_CENTRE_X + (int)lroundf(point->x * perspective), .y = DEMO_STAGE_SEVENTEEN_CENTRE_Y + (int)lroundf(point->y * perspective), };
Read original function source
static graphics_point_t stageSeventeenProjectPoint(const demo_point_3d_t *point)
{
    const float perspective = DEMO_STAGE_SEVENTEEN_CAMERA_DISTANCE /
                              (DEMO_STAGE_SEVENTEEN_CAMERA_DISTANCE + point->z);
    return (graphics_point_t) {
        .x = DEMO_STAGE_SEVENTEEN_CENTRE_X + (int)lroundf(point->x * perspective),
        .y = DEMO_STAGE_SEVENTEEN_CENTRE_Y + (int)lroundf(point->y * perspective),
    };
}
stageSeventeenFaceColour() · source line 3646
static uint16_t stageSeventeenFaceColour(void)
  1. Decisionstage_seventeen_style == STAGE_SEVENTEEN_STYLE_FLAT
    True
    1. Exit this functionreturn RGB565(230, 195, 30);
    False

    Continue after this decision.

  2. Decisionstage_seventeen_style == STAGE_SEVENTEEN_STYLE_METALLIC
    True
    1. Exit this functionreturn RGB565(255, 220, 70);
    False

    Continue after this decision.

  3. Exit this functionreturn COLOR_BLACK;
Read original function source
static uint16_t stageSeventeenFaceColour(void)
{
    if (stage_seventeen_style == STAGE_SEVENTEEN_STYLE_FLAT) return RGB565(230, 195, 30);
    if (stage_seventeen_style == STAGE_SEVENTEEN_STYLE_METALLIC) return RGB565(255, 220, 70);
    return COLOR_BLACK;
}
stageSeventeenRearColour() · source line 3653
static uint16_t stageSeventeenRearColour(void)
  1. Decisionstage_seventeen_style == STAGE_SEVENTEEN_STYLE_FLAT
    True
    1. Exit this functionreturn RGB565(170, 135, 10);
    False

    Continue after this decision.

  2. Decisionstage_seventeen_style == STAGE_SEVENTEEN_STYLE_METALLIC
    True
    1. Exit this functionreturn RGB565(210, 175, 30);
    False

    Continue after this decision.

  3. Exit this functionreturn COLOR_BLACK;
Read original function source
static uint16_t stageSeventeenRearColour(void)
{
    if (stage_seventeen_style == STAGE_SEVENTEEN_STYLE_FLAT) return RGB565(170, 135, 10);
    if (stage_seventeen_style == STAGE_SEVENTEEN_STYLE_METALLIC) return RGB565(210, 175, 30);
    return COLOR_BLACK;
}
stageSeventeenSideColour() · source line 3660
static uint16_t stageSeventeenSideColour(unsigned segment)
  1. Decisionstage_seventeen_style == STAGE_SEVENTEEN_STYLE_FLAT
    True
      1. Exit this functionreturn (segment & 2U) == 0U ? RGB565(160, 120, 8) : RGB565(90, 68, 2);
    False

    Continue after this decision.

  2. Decisionstage_seventeen_style == STAGE_SEVENTEEN_STYLE_METALLIC
    True
      1. Exit this functionreturn (segment & 2U) == 0U ? RGB565(195, 155, 18) : RGB565(115, 85, 3);
    False

    Continue after this decision.

  3. Exit this functionreturn COLOR_BLACK;
Read original function source
static uint16_t stageSeventeenSideColour(unsigned segment)
{
    if (stage_seventeen_style == STAGE_SEVENTEEN_STYLE_FLAT) {
        return (segment & 2U) == 0U ? RGB565(160, 120, 8) : RGB565(90, 68, 2);
    }
    if (stage_seventeen_style == STAGE_SEVENTEEN_STYLE_METALLIC) {
        return (segment & 2U) == 0U ? RGB565(195, 155, 18) : RGB565(115, 85, 3);
    }
    return COLOR_BLACK;
}
stageSeventeenEdgeColour() · source line 3671
static uint16_t stageSeventeenEdgeColour(void)
  1. Exit this functionreturn stage_seventeen_style == STAGE_SEVENTEEN_STYLE_METALLIC ? RGB565(255, 245, 160) : RGB565(255, 220, 50);
Read original function source
static uint16_t stageSeventeenEdgeColour(void)
{
    return stage_seventeen_style == STAGE_SEVENTEEN_STYLE_METALLIC ? RGB565(255, 245, 160) :
        RGB565(255, 220, 50);
}
stageSeventeenAppendFace() · source line 3677
static void stageSeventeenAppendFace(const graphics_point_t *points, uint8_t point_count,
                                     float depth, uint16_t colour)
  1. Decisionstage_seventeen_draw_face_count >= DEMO_STAGE_SEVENTEEN_DRAW_FACE_COUNT || point_count < 3U || point_count > 4U
    True
    1. Exit this functionreturn;
    False

    Continue after this decision.

  2. Executestage_seventeen_draw_face_t *const face = &stage_seventeen_draw_faces[stage_seventeen_draw_face_count++];
  3. FOR loopuint8_t point = 0U; point < point_count; ++point

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

    1. Executeface->points[point] = points[point];
  4. Executeface->depth = depth;
  5. Executeface->colour = colour;
  6. Executeface->point_count = point_count;
Read original function source
static void stageSeventeenAppendFace(const graphics_point_t *points, uint8_t point_count,
                                     float depth, uint16_t colour)
{
    if (stage_seventeen_draw_face_count >= DEMO_STAGE_SEVENTEEN_DRAW_FACE_COUNT ||
        point_count < 3U || point_count > 4U) return;
    stage_seventeen_draw_face_t *const face =
        &stage_seventeen_draw_faces[stage_seventeen_draw_face_count++];
    for (uint8_t point = 0U; point < point_count; ++point) face->points[point] = points[point];
    face->depth = depth;
    face->colour = colour;
    face->point_count = point_count;
}
stageSeventeenBuildGear() · source line 3690
static void stageSeventeenBuildGear(unsigned gear, const demo_rotation_t *rig_rotation)

Calls into this documentation:

  1. Executeconst unsigned point_count = DEMO_STAGE_SEVENTEEN_TOOTH_COUNT * 2U;
  2. Executegraphics_point_t front[DEMO_STAGE_SEVENTEEN_TOOTH_COUNT * 2U];
  3. Executegraphics_point_t rear[DEMO_STAGE_SEVENTEEN_TOOTH_COUNT * 2U];
  4. Executedemo_point_3d_t front_points[DEMO_STAGE_SEVENTEEN_TOOTH_COUNT * 2U];
  5. Executedemo_point_3d_t rear_points[DEMO_STAGE_SEVENTEEN_TOOTH_COUNT * 2U];
  6. Executeconst demo_point_3d_t origin = stageSeventeenGearOrigin(gear);
  7. Executeconst demo_rotation_t axis_rotation = stageSeventeenGearAxisRotation(gear);
  8. Executeconst float spin = stage_seventeen_gear_degrees[gear] * DEMO_PI / 180.0f;
  9. Executeconst float front_z = 0.0f;
  10. Executeconst float rear_z = DEMO_STAGE_SEVENTEEN_CONE_DEPTH;
  11. Executedemo_point_3d_t front_centre = {.x = 0.0f, .y = 0.0f, .z = front_z};
  12. Executedemo_point_3d_t rear_centre = {.x = 0.0f, .y = 0.0f, .z = rear_z};
  13. Executefront_centre = stageSeventeenTransformPoint(front_centre, &origin, &axis_rotation, rig_rotation);
  14. Executerear_centre = stageSeventeenTransformPoint(rear_centre, &origin, &axis_rotation, rig_rotation);
  15. Executeconst graphics_point_t projected_front_centre = stageSeventeenProjectPoint(&front_centre);
  16. Executeconst graphics_point_t projected_rear_centre = stageSeventeenProjectPoint(&rear_centre);
  17. FOR loopunsigned point = 0U; point < point_count; ++point

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

      1. Executeconst float angle = spin + (float)point * DEMO_PI / (float)DEMO_STAGE_SEVENTEEN_TOOTH_COUNT;
      2. Executeconst float front_radius = (point & 1U) == 0U ? DEMO_STAGE_SEVENTEEN_FRONT_RADIUS : DEMO_STAGE_SEVENTEEN_FRONT_RADIUS * DEMO_STAGE_SEVENTEEN_TOOTH_ROOT_RATIO;
      3. Executeconst float rear_radius = (point & 1U) == 0U ? DEMO_STAGE_SEVENTEEN_REAR_RADIUS : DEMO_STAGE_SEVENTEEN_REAR_RADIUS * DEMO_STAGE_SEVENTEEN_TOOTH_ROOT_RATIO;
      4. Executedemo_point_3d_t front_point = { .x = cosf(angle) * front_radius, .y = sinf(angle) * front_radius, .z = front_z, };
      5. Executedemo_point_3d_t rear_point = { .x = cosf(angle) * rear_radius, .y = sinf(angle) * rear_radius, .z = rear_z, };
      6. Executefront_points[point] = stageSeventeenTransformPoint(front_point, &origin, &axis_rotation, rig_rotation);
      7. Executerear_points[point] = stageSeventeenTransformPoint(rear_point, &origin, &axis_rotation, rig_rotation);
      8. Executefront[point] = stageSeventeenProjectPoint(&front_points[point]);
      9. Executerear[point] = stageSeventeenProjectPoint(&rear_points[point]);
  18. FOR loopunsigned point = 0U; point < point_count; ++point

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

      1. Executeconst unsigned next = (point + 1U) % point_count;
      2. Executeconst graphics_point_t side[] = {rear[point], rear[next], front[next], front[point]};
      3. ExecutestageSeventeenAppendFace(side, 4U, (rear_points[point].z + rear_points[next].z + front_points[next].z + front_points[point].z) * 0.25f, stageSeventeenSideColour(point));
      4. Executeconst graphics_point_t wedge[] = {projected_front_centre, front[point], front[next]};
      5. ExecutestageSeventeenAppendFace(wedge, 3U, (front_centre.z + front_points[point].z + front_points[next].z) / 3.0f, stageSeventeenFaceColour());
      6. Executeconst graphics_point_t rear_wedge[] = {projected_rear_centre, rear[next], rear[point]};
      7. ExecutestageSeventeenAppendFace(rear_wedge, 3U, (rear_centre.z + rear_points[point].z + rear_points[next].z) / 3.0f, stageSeventeenRearColour());
Read original function source
static void stageSeventeenBuildGear(unsigned gear, const demo_rotation_t *rig_rotation)
{
    const unsigned point_count = DEMO_STAGE_SEVENTEEN_TOOTH_COUNT * 2U;
    graphics_point_t front[DEMO_STAGE_SEVENTEEN_TOOTH_COUNT * 2U];
    graphics_point_t rear[DEMO_STAGE_SEVENTEEN_TOOTH_COUNT * 2U];
    demo_point_3d_t front_points[DEMO_STAGE_SEVENTEEN_TOOTH_COUNT * 2U];
    demo_point_3d_t rear_points[DEMO_STAGE_SEVENTEEN_TOOTH_COUNT * 2U];
    const demo_point_3d_t origin = stageSeventeenGearOrigin(gear);
    const demo_rotation_t axis_rotation = stageSeventeenGearAxisRotation(gear);
    const float spin = stage_seventeen_gear_degrees[gear] * DEMO_PI / 180.0f;
    // Origins are the wide, front-face centres. Each gear then narrows and
    // travels inward through its complete depth toward the shared rear mesh.
    const float front_z = 0.0f;
    const float rear_z = DEMO_STAGE_SEVENTEEN_CONE_DEPTH;
    demo_point_3d_t front_centre = {.x = 0.0f, .y = 0.0f, .z = front_z};
    demo_point_3d_t rear_centre = {.x = 0.0f, .y = 0.0f, .z = rear_z};
    front_centre = stageSeventeenTransformPoint(front_centre, &origin, &axis_rotation, rig_rotation);
    rear_centre = stageSeventeenTransformPoint(rear_centre, &origin, &axis_rotation, rig_rotation);
    const graphics_point_t projected_front_centre = stageSeventeenProjectPoint(&front_centre);
    const graphics_point_t projected_rear_centre = stageSeventeenProjectPoint(&rear_centre);

    for (unsigned point = 0U; point < point_count; ++point) {
        const float angle = spin + (float)point * DEMO_PI / (float)DEMO_STAGE_SEVENTEEN_TOOTH_COUNT;
        const float front_radius = (point & 1U) == 0U ? DEMO_STAGE_SEVENTEEN_FRONT_RADIUS :
            DEMO_STAGE_SEVENTEEN_FRONT_RADIUS * DEMO_STAGE_SEVENTEEN_TOOTH_ROOT_RATIO;
        // Preserve the same tooth/gap rhythm on the narrow end. Joining these
        // matching corners makes every tooth a full tapered ridge, rather than
        // letting the front cog collapse into a smooth conical rear hub.
        const float rear_radius = (point & 1U) == 0U ? DEMO_STAGE_SEVENTEEN_REAR_RADIUS :
            DEMO_STAGE_SEVENTEEN_REAR_RADIUS * DEMO_STAGE_SEVENTEEN_TOOTH_ROOT_RATIO;
        demo_point_3d_t front_point = {
            .x = cosf(angle) * front_radius, .y = sinf(angle) * front_radius, .z = front_z,
        };
        demo_point_3d_t rear_point = {
            .x = cosf(angle) * rear_radius,
            .y = sinf(angle) * rear_radius, .z = rear_z,
        };
        front_points[point] =
            stageSeventeenTransformPoint(front_point, &origin, &axis_rotation, rig_rotation);
        rear_points[point] =
            stageSeventeenTransformPoint(rear_point, &origin, &axis_rotation, rig_rotation);
        front[point] = stageSeventeenProjectPoint(&front_points[point]);
        rear[point] = stageSeventeenProjectPoint(&rear_points[point]);
    }

    for (unsigned point = 0U; point < point_count; ++point) {
        const unsigned next = (point + 1U) % point_count;
        const graphics_point_t side[] = {rear[point], rear[next], front[next], front[point]};
        stageSeventeenAppendFace(side, 4U,
            (rear_points[point].z + rear_points[next].z + front_points[next].z +
             front_points[point].z) * 0.25f, stageSeventeenSideColour(point));
        // A cog outline is concave at the tooth roots, so its front remains a
        // set of separately sorted centre wedges rather than one polygon.
        const graphics_point_t wedge[] = {projected_front_centre, front[point], front[next]};
        stageSeventeenAppendFace(wedge, 3U,
            (front_centre.z + front_points[point].z + front_points[next].z) / 3.0f,
            stageSeventeenFaceColour());
        // The rear is also concave at its tooth roots. Its individual wedges
        // close the narrow end without crossing the adjacent tapered ridges.
        const graphics_point_t rear_wedge[] = {projected_rear_centre, rear[next], rear[point]};
        stageSeventeenAppendFace(rear_wedge, 3U,
            (rear_centre.z + rear_points[point].z + rear_points[next].z) / 3.0f,
            stageSeventeenRearColour());
    }
}
drawStageSeventeenBevelGears() · source line 3756
static void drawStageSeventeenBevelGears(uint16_t *frame)

Calls into this documentation:

  1. Executeconst demo_rotation_t rig_rotation = stageSeventeenRotation(stage_seventeen_rig_pitch_degrees, stage_seventeen_rig_yaw_degrees, stage_seventeen_rig_roll_degrees);
  2. Executestage_seventeen_draw_face_count = 0U;
  3. FOR loopunsigned gear = 0U; gear < 3U; ++gear

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

    1. ExecutestageSeventeenBuildGear(gear, &rig_rotation);
  4. FOR loopsize_t pass = 0U; pass + 1U < stage_seventeen_draw_face_count; ++pass

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

      1. FOR loopsize_t index = 0U; index + 1U < stage_seventeen_draw_face_count - pass; ++index

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

          1. Decisionstage_seventeen_draw_faces[index].depth >= stage_seventeen_draw_faces[index + 1U].depth
            True
            1. Next loop iterationcontinue;
            False

            Continue after this decision.

          2. Executeconst stage_seventeen_draw_face_t temporary = stage_seventeen_draw_faces[index];
          3. Executestage_seventeen_draw_faces[index] = stage_seventeen_draw_faces[index + 1U];
          4. Executestage_seventeen_draw_faces[index + 1U] = temporary;
  5. FOR loopsize_t face_index = 0U; face_index < stage_seventeen_draw_face_count; ++face_index

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

      1. Executeconst stage_seventeen_draw_face_t *const face = &stage_seventeen_draw_faces[face_index];
      2. Decisionstage_seventeen_style != STAGE_SEVENTEEN_STYLE_OUTLINE
        True
          1. Executefill_convex_polygon(frame, face->points, face->point_count, face->colour);
        False

        Continue after this decision.

      3. FOR loopuint8_t point = 0U; point < face->point_count; ++point

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

          1. Executeconst uint8_t next = (uint8_t)((point + 1U) % face->point_count);
          2. Executedraw_line(frame, face->points[point].x, face->points[point].y, face->points[next].x, face->points[next].y, stageSeventeenEdgeColour());
Read original function source
static void drawStageSeventeenBevelGears(uint16_t *frame)
{
    const demo_rotation_t rig_rotation = stageSeventeenRotation(stage_seventeen_rig_pitch_degrees,
        stage_seventeen_rig_yaw_degrees, stage_seventeen_rig_roll_degrees);
    stage_seventeen_draw_face_count = 0U;
    for (unsigned gear = 0U; gear < 3U; ++gear) stageSeventeenBuildGear(gear, &rig_rotation);
    // Positive Z is farther from this camera. Sorting every tooth face, rather
    // than three whole gears, preserves the interlock throughout the rig tumble.
    for (size_t pass = 0U; pass + 1U < stage_seventeen_draw_face_count; ++pass) {
        for (size_t index = 0U; index + 1U < stage_seventeen_draw_face_count - pass; ++index) {
            if (stage_seventeen_draw_faces[index].depth >=
                stage_seventeen_draw_faces[index + 1U].depth) continue;
            const stage_seventeen_draw_face_t temporary = stage_seventeen_draw_faces[index];
            stage_seventeen_draw_faces[index] = stage_seventeen_draw_faces[index + 1U];
            stage_seventeen_draw_faces[index + 1U] = temporary;
        }
    }
    for (size_t face_index = 0U; face_index < stage_seventeen_draw_face_count; ++face_index) {
        const stage_seventeen_draw_face_t *const face = &stage_seventeen_draw_faces[face_index];
        if (stage_seventeen_style != STAGE_SEVENTEEN_STYLE_OUTLINE) {
            fill_convex_polygon(frame, face->points, face->point_count, face->colour);
        }
        for (uint8_t point = 0U; point < face->point_count; ++point) {
            const uint8_t next = (uint8_t)((point + 1U) % face->point_count);
            draw_line(frame, face->points[point].x, face->points[point].y,
                      face->points[next].x, face->points[next].y, stageSeventeenEdgeColour());
        }
    }
}
drawTextCornerIcons() · source line 3786
static void drawTextCornerIcons(uint16_t *frame)

Calls into this documentation:

  1. Executeconst unsigned colour_count = sizeof(demo_scroll_rainbow_colours) / sizeof(demo_scroll_rainbow_colours[0]);
  2. FOR loopsize_t corner = 0U; corner < sizeof(demo_text_corner_icon_slots) / sizeof(demo_text_corner_icon_slots[0]); ++corner

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

      1. Executeconst size_t slot = (size_t)demo_text_corner_icon_slots[corner] % DEMO_ICON_SLOT_COUNT;
      2. Executeconst uint8_t *bitmap = demo_icon_slots[slot];
      3. Executeconst demo_text_corner_icon_cycle_t *const cycle = &demo_text_corner_icon_cycles[corner];
      4. Executeconst float rotation_degrees = cycle->rotation_start_degrees + (float)cycle->rotation_degrees_per_second * (float)(last_update_ms - cycle->rotation_started_at) / 1000.0f;
      5. Executeconst float rotation_radians = rotation_degrees * DEMO_PI / 180.0f;
      6. Executeconst demo_rotation_t rotation = { .pitch_cosine = 1.0f, .pitch_sine = 0.0f, .yaw_cosine = 1.0f, .yaw_sine = 0.0f, .roll_cosine = cosf(rotation_radians), .roll_sine = sinf(rotation_radians), };
      7. Executeconst float travel_phase = (float)(last_update_ms - cycle->cycle_started_at) / (float)cycle->cycle_duration_ms;
      8. Executeconst float centre_progress = 0.5f - 0.5f * cosf(travel_phase * 2.0f * DEMO_PI);
      9. Executeconst int centre_x = (int)lroundf((float)demo_text_corner_icon_x[corner] + ((float)(LCD_WIDTH / 2) - (float)demo_text_corner_icon_x[corner]) * centre_progress);
      10. Executeconst uint32_t pulse_offset = (uint32_t)corner * DEMO_TEXT_CORNER_ICON_PULSE_STAGGER_MS;
      11. Executeconst float pulse_phase = (float)((last_update_ms + pulse_offset) % DEMO_TEXT_CORNER_ICON_PULSE_MS) / (float)DEMO_TEXT_CORNER_ICON_PULSE_MS;
      12. Executeconst float pulse = 0.5f + 0.5f * sinf(pulse_phase * 2.0f * DEMO_PI);
      13. Executeconst float scale = DEMO_TEXT_CORNER_ICON_SCALE_MIN + (DEMO_TEXT_CORNER_ICON_SCALE_MAX - DEMO_TEXT_CORNER_ICON_SCALE_MIN) * pulse;
      14. Executeconst unsigned colour_index = (unsigned)(last_update_ms / DEMO_TEXT_CORNER_ICON_COLOUR_MS + corner * 2U) % colour_count;
      15. Executeconst uint16_t colour = demo_scroll_rainbow_colours[colour_index];
      16. FOR loopunsigned depth = DEMO_TEXT_CORNER_ICON_DEPTH; depth > 0U; depth -= DEMO_TEXT_CORNER_ICON_DEPTH_STEP

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

          1. ExecutedrawApproachLogoSlice(frame, bitmap, (float)depth, &rotation, scale, centre_x, demo_text_corner_icon_y[corner], C64_COLOR_DARK_GREY);
      17. ExecutedrawApproachLogoSlice(frame, bitmap, 0.0f, &rotation, scale, centre_x, demo_text_corner_icon_y[corner], colour);
Read original function source
static void drawTextCornerIcons(uint16_t *frame)
{
    // No value below comes from the main Demo rotation or either text effect.
    // Every icon's travel, pulse, colour, and rotation come from its own state.
    const unsigned colour_count = sizeof(demo_scroll_rainbow_colours) /
                                 sizeof(demo_scroll_rainbow_colours[0]);
    for (size_t corner = 0U; corner < sizeof(demo_text_corner_icon_slots) /
                                       sizeof(demo_text_corner_icon_slots[0]); ++corner) {
        const size_t slot = (size_t)demo_text_corner_icon_slots[corner] % DEMO_ICON_SLOT_COUNT;
        const uint8_t *bitmap = demo_icon_slots[slot];
        const demo_text_corner_icon_cycle_t *const cycle =
            &demo_text_corner_icon_cycles[corner];
        const float rotation_degrees = cycle->rotation_start_degrees +
            (float)cycle->rotation_degrees_per_second *
            (float)(last_update_ms - cycle->rotation_started_at) / 1000.0f;
        const float rotation_radians = rotation_degrees * DEMO_PI / 180.0f;
        const demo_rotation_t rotation = {
            .pitch_cosine = 1.0f, .pitch_sine = 0.0f,
            .yaw_cosine = 1.0f, .yaw_sine = 0.0f,
            .roll_cosine = cosf(rotation_radians),
            .roll_sine = sinf(rotation_radians),
        };
        // updateTextCornerIconCycles() restarts this cycle at every completed
        // trip with a newly random duration. This fraction therefore describes
        // one complete, independent edge -> centre -> edge journey.
        const float travel_phase = (float)(last_update_ms - cycle->cycle_started_at) /
                                   (float)cycle->cycle_duration_ms;
        // 0.0 at the outer corner, 1.0 at the centre X line, then back out.
        const float centre_progress = 0.5f - 0.5f * cosf(travel_phase * 2.0f * DEMO_PI);
        const int centre_x = (int)lroundf((float)demo_text_corner_icon_x[corner] +
                                          ((float)(LCD_WIDTH / 2) -
                                           (float)demo_text_corner_icon_x[corner]) *
                                          centre_progress);
        const uint32_t pulse_offset = (uint32_t)corner * DEMO_TEXT_CORNER_ICON_PULSE_STAGGER_MS;
        const float pulse_phase = (float)((last_update_ms + pulse_offset) %
                                          DEMO_TEXT_CORNER_ICON_PULSE_MS) /
                                  (float)DEMO_TEXT_CORNER_ICON_PULSE_MS;
        const float pulse = 0.5f + 0.5f * sinf(pulse_phase * 2.0f * DEMO_PI);
        const float scale = DEMO_TEXT_CORNER_ICON_SCALE_MIN +
                            (DEMO_TEXT_CORNER_ICON_SCALE_MAX -
                             DEMO_TEXT_CORNER_ICON_SCALE_MIN) * pulse;
        const unsigned colour_index = (unsigned)(last_update_ms /
                                      DEMO_TEXT_CORNER_ICON_COLOUR_MS + corner * 2U) % colour_count;
        const uint16_t colour = demo_scroll_rainbow_colours[colour_index];

        // Draw far slices first so the pulsing coloured face remains in front.
        for (unsigned depth = DEMO_TEXT_CORNER_ICON_DEPTH; depth > 0U;
             depth -= DEMO_TEXT_CORNER_ICON_DEPTH_STEP) {
            drawApproachLogoSlice(frame, bitmap, (float)depth, &rotation, scale,
                                  centre_x, demo_text_corner_icon_y[corner], C64_COLOR_DARK_GREY);
        }
        drawApproachLogoSlice(frame, bitmap, 0.0f, &rotation, scale,
                              centre_x, demo_text_corner_icon_y[corner], colour);
    }
}
drawStageFifteenBattleZoneBackground() · source line 3845
static void drawStageFifteenBattleZoneBackground(uint16_t *frame)

Calls into this documentation:

  1. Executeconst int source_top = BATTLE_ZONE_HORIZON_Y;
  2. Executeconst int source_height = LCD_HEIGHT - source_top;
  3. Executeconst int destination_top = DEMO_STAGE_FIFTEEN_HORIZON_Y;
  4. Executeconst int destination_height = LCD_HEIGHT - destination_top;
  5. Executeconst int forward_source_offset = (int)( (raster_band_effect_elapsed_ms % DEMO_STAGE_FIFTEEN_FORWARD_CYCLE_MS) * (uint32_t)source_height / DEMO_STAGE_FIFTEEN_FORWARD_CYCLE_MS);
  6. ExecutedrawBattleZoneLandscape(frame);
  7. Decisionstage_fifteen_ground_source != NULL
    True
      1. Executememcpy(stage_fifteen_ground_source, &frame[(size_t)source_top * LCD_WIDTH], (size_t)LCD_WIDTH * (size_t)source_height * sizeof(frame[0]));
      2. FOR loopint x = 0; x < LCD_WIDTH; ++x

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

          1. Executestage_fifteen_ground_source[x] = COLOR_BLACK;
    False

    Continue after this decision.

  8. Decisionstage_fifteen_ground_source == NULL
    True
      1. FOR loopint destination_y = LCD_HEIGHT - 1; destination_y >= destination_top; --destination_y

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

          1. Executeconst int source_y = source_top + (destination_y - destination_top) * (source_height - 1) / (destination_height - 1);
          2. Executememcpy(&frame[(size_t)destination_y * LCD_WIDTH], &frame[(size_t)source_y * LCD_WIDTH], LCD_WIDTH * sizeof(frame[0]));
    False
      1. FOR loopint destination_y = LCD_HEIGHT - 1; destination_y >= destination_top; --destination_y

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

          1. Executeconst int unscrolled_source_offset = (destination_y - destination_top) * (source_height - 1) / (destination_height - 1);
          2. Executeconst int source_row = (unscrolled_source_offset - forward_source_offset + source_height) % source_height;
          3. Executememcpy(&frame[(size_t)destination_y * LCD_WIDTH], &stage_fifteen_ground_source[(size_t)source_row * LCD_WIDTH], LCD_WIDTH * sizeof(frame[0]));
  9. Executefill_rect(frame, 0, 0, LCD_WIDTH, destination_top, COLOR_BLACK);
  10. Executedraw_line(frame, 0, destination_top, LCD_WIDTH - 1, destination_top, COLOR_LIME);
Read original function source
static void drawStageFifteenBattleZoneBackground(uint16_t *frame)
{
    const int source_top = BATTLE_ZONE_HORIZON_Y;
    const int source_height = LCD_HEIGHT - source_top;
    const int destination_top = DEMO_STAGE_FIFTEEN_HORIZON_Y;
    const int destination_height = LCD_HEIGHT - destination_top;
    // Moving the source lookup upward makes terrain features travel down from
    // the horizon toward the viewer. A wrapped source introduces the next
    // ground pattern at the fixed horizon.
    const int forward_source_offset = (int)(
        (raster_band_effect_elapsed_ms % DEMO_STAGE_FIFTEEN_FORWARD_CYCLE_MS) *
        (uint32_t)source_height / DEMO_STAGE_FIFTEEN_FORWARD_CYCLE_MS);

    drawBattleZoneLandscape(frame);
    if (stage_fifteen_ground_source != NULL) {
        memcpy(stage_fifteen_ground_source, &frame[(size_t)source_top * LCD_WIDTH],
               (size_t)LCD_WIDTH * (size_t)source_height * sizeof(frame[0]));
        // The captured first ground row is Battle Zone's lime horizon. Segment
        // 15 draws its own fixed horizon below, so remove this copy before it
        // can wrap through the moving texture as an unwanted bright stripe.
        for (int x = 0; x < LCD_WIDTH; ++x) {
            stage_fifteen_ground_source[x] = COLOR_BLACK;
        }
    }

    if (stage_fifteen_ground_source == NULL) {
        // A PSRAM allocation failure leaves a static, safely compressed
        // landscape instead of risking corrupt rows with wrapped in-place reads.
        for (int destination_y = LCD_HEIGHT - 1; destination_y >= destination_top;
             --destination_y) {
            const int source_y = source_top +
                (destination_y - destination_top) * (source_height - 1) /
                (destination_height - 1);
            memcpy(&frame[(size_t)destination_y * LCD_WIDTH],
                   &frame[(size_t)source_y * LCD_WIDTH], LCD_WIDTH * sizeof(frame[0]));
        }
    } else {
        for (int destination_y = LCD_HEIGHT - 1; destination_y >= destination_top;
             --destination_y) {
            const int unscrolled_source_offset =
                (destination_y - destination_top) * (source_height - 1) /
                (destination_height - 1);
            const int source_row =
                (unscrolled_source_offset - forward_source_offset + source_height) % source_height;
            memcpy(&frame[(size_t)destination_y * LCD_WIDTH],
                   &stage_fifteen_ground_source[(size_t)source_row * LCD_WIDTH],
                   LCD_WIDTH * sizeof(frame[0]));
        }
    }
    // The original unscaled sky and terrain in the top half are no longer part
    // of this segment, leaving the bar and horizon-anchored scroller clear.
    fill_rect(frame, 0, 0, LCD_WIDTH, destination_top, COLOR_BLACK);
    // The wrapped texture moves, but the bright green horizon remains a stable
    // reference line exactly beneath the scroller.
    draw_line(frame, 0, destination_top, LCD_WIDTH - 1, destination_top, COLOR_LIME);
}
drawRasterBands() · source line 3905
static void drawRasterBands(uint16_t *frame)

Calls into this documentation:

  1. Executeconst bool lower_half_stage = selected_stage == DEMO_STAGE_LOWER_RASTER_SCROLLER;
  2. Executeconst bool battle_zone_stage = selected_stage == DEMO_STAGE_BATTLE_ZONE_RASTER;
  3. Executeconst int block_height = lower_half_stage ? DEMO_STAGE_FOURTEEN_BAND_HEIGHT : (battle_zone_stage ? DEMO_STAGE_FIFTEEN_BAND_BLOCK_HEIGHT : DEMO_STAGE_ELEVEN_BAND_BLOCK_HEIGHT);
  4. Executeconst uint32_t drift_ms = battle_zone_stage ? DEMO_STAGE_FIFTEEN_BAND_DRIFT_MS : DEMO_STAGE_ELEVEN_BAND_DRIFT_MS;
  5. Executeconst int travel_height = battle_zone_stage ? DEMO_STAGE_FIFTEEN_HORIZON_Y : LCD_HEIGHT;
  6. Executeconst float drift_phase = (float)(raster_band_effect_elapsed_ms % drift_ms) / (float)drift_ms;
  7. Executeconst int band_top = lower_half_stage ? DEMO_STAGE_FOURTEEN_BAND_TOP : (int)lroundf((float)(travel_height - block_height) * (0.5f + 0.5f * sinf(drift_phase * 2.0f * DEMO_PI)));
  8. Executeconst demo_raster_band_palette_t *palette = demo_raster_band_palettes[raster_band_palette_index];
  9. Executedraw_rolling_raster_band(frame, band_top, block_height, DEMO_STAGE_ELEVEN_BAND_HEIGHT, raster_band_effect_elapsed_ms, DEMO_STAGE_ELEVEN_COLOUR_ROLL_MS, palette);
Read original function source
static void drawRasterBands(uint16_t *frame)
{
    const bool lower_half_stage = selected_stage == DEMO_STAGE_LOWER_RASTER_SCROLLER;
    const bool battle_zone_stage = selected_stage == DEMO_STAGE_BATTLE_ZONE_RASTER;
    const int block_height = lower_half_stage ? DEMO_STAGE_FOURTEEN_BAND_HEIGHT :
        (battle_zone_stage ? DEMO_STAGE_FIFTEEN_BAND_BLOCK_HEIGHT :
                             DEMO_STAGE_ELEVEN_BAND_BLOCK_HEIGHT);
    const uint32_t drift_ms = battle_zone_stage ? DEMO_STAGE_FIFTEEN_BAND_DRIFT_MS :
                                                  DEMO_STAGE_ELEVEN_BAND_DRIFT_MS;
    // Segment 16 reserves its lower half for the scaled Battle Zone ground.
    // Its bar may touch the halfway horizon but cannot enter that landscape.
    const int travel_height = battle_zone_stage ? DEMO_STAGE_FIFTEEN_HORIZON_Y :
                                                  LCD_HEIGHT;
    const float drift_phase = (float)(raster_band_effect_elapsed_ms % drift_ms) /
                              (float)drift_ms;
    const int band_top = lower_half_stage ? DEMO_STAGE_FOURTEEN_BAND_TOP :
        (int)lroundf((float)(travel_height - block_height) *
            (0.5f + 0.5f * sinf(drift_phase * 2.0f * DEMO_PI)));
    const demo_raster_band_palette_t *palette =
        demo_raster_band_palettes[raster_band_palette_index];
    draw_rolling_raster_band(frame, band_top, block_height,
                             DEMO_STAGE_ELEVEN_BAND_HEIGHT,
                             raster_band_effect_elapsed_ms,
                             DEMO_STAGE_ELEVEN_COLOUR_ROLL_MS, palette);
}
drawStageTwelveSlideshowLens() · source line 3932
static void drawStageTwelveSlideshowLens(uint16_t *frame)

Calls into this documentation:

  1. Decision!stage_twelve_image_ready || stage_twelve_image == NULL
    True
      1. Executedraw_text_centred(frame, LCD_WIDTH / 2, LCD_HEIGHT / 2, "NO SLIDESHOW IMAGE", 1, C64_COLOR_LIGHT_GREY);
      2. Exit this functionreturn;
    False

    Continue after this decision.

  2. Executeconst size_t frame_bytes = (size_t)LCD_WIDTH * LCD_HEIGHT * sizeof(frame[0]);
  3. Executememcpy(frame, stage_twelve_image, frame_bytes);
  4. Executeconst int centre_x = (int)lroundf(stage_twelve_lens_x);
  5. Executeconst int centre_y = (int)lroundf(stage_twelve_lens_y);
  6. Executeconst int radius = DEMO_STAGE_TWELVE_LENS_RADIUS;
  7. Executeconst int outer_radius_squared = radius * radius;
  8. Executeconst int inner_radius = radius - DEMO_STAGE_TWELVE_LENS_RIM_WIDTH;
  9. Executeconst int inner_radius_squared = inner_radius * inner_radius;
  10. Executeconst int first_y = centre_y - radius < 0 ? 0 : centre_y - radius;
  11. Executeconst int last_y = centre_y + radius >= LCD_HEIGHT ? LCD_HEIGHT - 1 : centre_y + radius;
  12. Executeconst int first_x = centre_x - radius < 0 ? 0 : centre_x - radius;
  13. Executeconst int last_x = centre_x + radius >= LCD_WIDTH ? LCD_WIDTH - 1 : centre_x + radius;
  14. FOR loopint y = first_y; y <= last_y; ++y

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

      1. FOR loopint x = first_x; x <= last_x; ++x

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

          1. Executeconst int dx = x - centre_x;
          2. Executeconst int dy = y - centre_y;
          3. Executeconst int distance_squared = dx * dx + dy * dy;
          4. Decisiondistance_squared > outer_radius_squared
            True
            1. Next loop iterationcontinue;
            False

            Continue after this decision.

          5. Decisiondistance_squared > inner_radius_squared
            True
              1. Executeframe[(size_t)y * LCD_WIDTH + (size_t)x] = DEMO_STAGE_TWELVE_LENS_RIM_COLOUR;
              2. Next loop iterationcontinue;
            False

            Continue after this decision.

          6. Executeconst int source_x = (int)lroundf((float)centre_x + (float)dx / DEMO_STAGE_TWELVE_LENS_MAGNIFICATION);
          7. Executeconst int source_y = (int)lroundf((float)centre_y + (float)dy / DEMO_STAGE_TWELVE_LENS_MAGNIFICATION);
          8. Executeframe[(size_t)y * LCD_WIDTH + (size_t)x] = stage_twelve_image[(size_t)source_y * LCD_WIDTH + (size_t)source_x];
Read original function source
static void drawStageTwelveSlideshowLens(uint16_t *frame)
{
    if (!stage_twelve_image_ready || stage_twelve_image == NULL) {
        draw_text_centred(frame, LCD_WIDTH / 2, LCD_HEIGHT / 2,
                          "NO SLIDESHOW IMAGE", 1, C64_COLOR_LIGHT_GREY);
        return;
    }

    const size_t frame_bytes = (size_t)LCD_WIDTH * LCD_HEIGHT * sizeof(frame[0]);
    memcpy(frame, stage_twelve_image, frame_bytes);

    const int centre_x = (int)lroundf(stage_twelve_lens_x);
    const int centre_y = (int)lroundf(stage_twelve_lens_y);
    const int radius = DEMO_STAGE_TWELVE_LENS_RADIUS;
    const int outer_radius_squared = radius * radius;
    const int inner_radius = radius - DEMO_STAGE_TWELVE_LENS_RIM_WIDTH;
    const int inner_radius_squared = inner_radius * inner_radius;

    // Sample nearer to the lens centre inside the circle. Clamp the visible
    // part to the framebuffer because edge destinations deliberately place
    // part of the lens beyond the screen.
    const int first_y = centre_y - radius < 0 ? 0 : centre_y - radius;
    const int last_y = centre_y + radius >= LCD_HEIGHT ? LCD_HEIGHT - 1 :
                                                        centre_y + radius;
    const int first_x = centre_x - radius < 0 ? 0 : centre_x - radius;
    const int last_x = centre_x + radius >= LCD_WIDTH ? LCD_WIDTH - 1 :
                                                       centre_x + radius;
    for (int y = first_y; y <= last_y; ++y) {
        for (int x = first_x; x <= last_x; ++x) {
            const int dx = x - centre_x;
            const int dy = y - centre_y;
            const int distance_squared = dx * dx + dy * dy;
            if (distance_squared > outer_radius_squared) continue;

            if (distance_squared > inner_radius_squared) {
                frame[(size_t)y * LCD_WIDTH + (size_t)x] = DEMO_STAGE_TWELVE_LENS_RIM_COLOUR;
                continue;
            }

            const int source_x = (int)lroundf((float)centre_x +
                (float)dx / DEMO_STAGE_TWELVE_LENS_MAGNIFICATION);
            const int source_y = (int)lroundf((float)centre_y +
                (float)dy / DEMO_STAGE_TWELVE_LENS_MAGNIFICATION);
            frame[(size_t)y * LCD_WIDTH + (size_t)x] =
                stage_twelve_image[(size_t)source_y * LCD_WIDTH + (size_t)source_x];
        }
    }
}
drawStageSevenGlyph() · source line 3982
static void drawStageSevenGlyph(uint16_t *frame, int column, int row, uint8_t glyph)

Calls into this documentation:

  1. Executeconst int display_row = row + DEMO_STAGE_SEVEN_ROW_OFFSET;
  2. Decisioncolumn < 0 || column >= 40 || display_row < 0 || display_row >= 21
    True
    1. Exit this functionreturn;
    False

    Continue after this decision.

  3. ExecutedrawPet2001Glyph(frame, column * (int)PET2001_FONT_GLYPH_WIDTH, display_row * (int)PET2001_FONT_GLYPH_HEIGHT, glyph, false, DEMO_STAGE_SEVEN_PET_COLOUR, COLOR_BLACK, 1);
Read original function source
static void drawStageSevenGlyph(uint16_t *frame, int column, int row, uint8_t glyph)
{
    const int display_row = row + DEMO_STAGE_SEVEN_ROW_OFFSET;
    if (column < 0 || column >= 40 || display_row < 0 || display_row >= 21) return;
    drawPet2001Glyph(frame, column * (int)PET2001_FONT_GLYPH_WIDTH,
                     display_row * (int)PET2001_FONT_GLYPH_HEIGHT, glyph, false,
                     DEMO_STAGE_SEVEN_PET_COLOUR, COLOR_BLACK, 1);
}
drawStageSevenWalloon() · source line 3991
static void drawStageSevenWalloon(uint16_t *frame, int column, int row, bool walking)

Calls into this documentation:

  1. ExecutedrawStageSevenGlyph(frame, column, row, walking ? 81U : 87U);
  2. ExecutedrawStageSevenGlyph(frame, column - 1, row + 1, 64U);
  3. ExecutedrawStageSevenGlyph(frame, column, row + 1, 71U);
  4. ExecutedrawStageSevenGlyph(frame, column + 1, row + 1, 64U);
  5. ExecutedrawStageSevenGlyph(frame, column, row + 2, 90U);
  6. ExecutedrawStageSevenGlyph(frame, column - 1, row + 3, 78U);
  7. ExecutedrawStageSevenGlyph(frame, column + 1, row + 3, 77U);
Read original function source
static void drawStageSevenWalloon(uint16_t *frame, int column, int row, bool walking)
{
    // This is the original seven-cell Walloon from BASIC lines 1800-1820.
    // The walking top cell alternates the original 87 and 81 glyphs, while the
    // rest of the PET-character sprite remains exactly as the listing defines it.
    drawStageSevenGlyph(frame, column, row, walking ? 81U : 87U);
    drawStageSevenGlyph(frame, column - 1, row + 1, 64U);
    drawStageSevenGlyph(frame, column, row + 1, 71U);
    drawStageSevenGlyph(frame, column + 1, row + 1, 64U);
    drawStageSevenGlyph(frame, column, row + 2, 90U);
    drawStageSevenGlyph(frame, column - 1, row + 3, 78U);
    drawStageSevenGlyph(frame, column + 1, row + 3, 77U);
}
drawStageSevenPlayfield() · source line 4005
static void drawStageSevenPlayfield(uint16_t *frame)

Calls into this documentation:

  1. FOR loopint column = 10; column <= 28; ++column

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

      1. ExecutedrawStageSevenGlyph(frame, column, 5, 127U);
  2. FOR loopint row = 5; row <= 15; ++row

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

      1. ExecutedrawStageSevenGlyph(frame, 28, row, 127U);
Read original function source
static void drawStageSevenPlayfield(uint16_t *frame)
{
    // Retain the upper platform and right-hand climbing pole throughout the
    // act. Their origin is BASIC lines 220-290, at PET cell (10, 5).
    for (int column = 10; column <= 28; ++column) {
        drawStageSevenGlyph(frame, column, 5, 127U);
    }
    for (int row = 5; row <= 15; ++row) {
        drawStageSevenGlyph(frame, 28, row, 127U);
    }
}
drawStageSevenTitleBox() · source line 4017
static void drawStageSevenTitleBox(uint16_t *frame)

Calls into this documentation:

  1. ExecutedrawStageSevenPlayfield(frame);
  2. FOR loopint row = 5; row <= 15; ++row

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

      1. ExecutedrawStageSevenGlyph(frame, 10, row, 127U);
  3. FOR loopint column = 10; column <= 25; ++column

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

      1. ExecutedrawStageSevenGlyph(frame, column, 15, 127U);
  4. ExecutedrawPet2001Text(frame, 18 * (int)PET2001_FONT_GLYPH_WIDTH, 7 * (int)PET2001_FONT_GLYPH_HEIGHT, "THE", false, DEMO_STAGE_SEVEN_PET_COLOUR, COLOR_BLACK, 1);
  5. ExecutedrawPet2001Text(frame, 12 * (int)PET2001_FONT_GLYPH_WIDTH, 9 * (int)PET2001_FONT_GLYPH_HEIGHT, "FLYING WALLOONS", false, DEMO_STAGE_SEVEN_PET_COLOUR, COLOR_BLACK, 1);
Read original function source
static void drawStageSevenTitleBox(uint16_t *frame)
{
    // The title-only left edge and lower edge make the temporary opening box.
    // They deliberately disappear before the first Walloon begins walking.
    drawStageSevenPlayfield(frame);
    for (int row = 5; row <= 15; ++row) {
        drawStageSevenGlyph(frame, 10, row, 127U);
    }
    for (int column = 10; column <= 25; ++column) {
        drawStageSevenGlyph(frame, column, 15, 127U);
    }
    // The original presents its stunt as "THE FLYING WALLOONS". Split the
    // title over two PET rows so it remains inside this 17-cell-wide frame.
    drawPet2001Text(frame, 18 * (int)PET2001_FONT_GLYPH_WIDTH,
                    7 * (int)PET2001_FONT_GLYPH_HEIGHT, "THE", false,
                    DEMO_STAGE_SEVEN_PET_COLOUR, COLOR_BLACK, 1);
    drawPet2001Text(frame, 12 * (int)PET2001_FONT_GLYPH_WIDTH,
                    9 * (int)PET2001_FONT_GLYPH_HEIGHT, "FLYING WALLOONS", false,
                    DEMO_STAGE_SEVEN_PET_COLOUR, COLOR_BLACK, 1);
}
drawStageSevenSeesaw() · source line 4038
static void drawStageSevenSeesaw(uint16_t *frame, bool left_end_down)

Calls into this documentation:

  1. Executestatic const struct { int column; int row; } left_down_board[] = { {13, 19}, {14, 19}, {15, 19}, {16, 18}, {17, 18}, {18, 18}, {19, 17}, {20, 17}, {21, 17}, {22, 16}, {23, 16}, {24, 16}, };
  2. Executestatic const struct { int column; int row; } right_down_board[] = { {13, 16}, {14, 16}, {15, 16}, {16, 17}, {17, 17}, {18, 17}, {19, 18}, {20, 18}, {21, 18}, {22, 19}, {23, 19}, {24, 19}, };
  3. Decisionleft_end_down
    True
      1. FOR loopsize_t index = 0U; index < sizeof(left_down_board) / sizeof(left_down_board[0]); ++index

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

          1. ExecutedrawStageSevenGlyph(frame, left_down_board[index].column, left_down_board[index].row, DEMO_STAGE_SEVEN_SEESAW_LINE_GLYPH);
    False
      1. FOR loopsize_t index = 0U; index < sizeof(right_down_board) / sizeof(right_down_board[0]); ++index

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

          1. ExecutedrawStageSevenGlyph(frame, right_down_board[index].column, right_down_board[index].row, DEMO_STAGE_SEVEN_SEESAW_LINE_GLYPH);
  4. ExecutedrawStageSevenGlyph(frame, 18, 20, 233U);
  5. ExecutedrawStageSevenGlyph(frame, 19, 20, 223U);
Read original function source
static void drawStageSevenSeesaw(uint16_t *frame, bool left_end_down)
{
    // A single fixed PET-cell pivot replaces the listing's two absolute screen
    // snapshots. The board never slides sideways: only its stepped slope swaps
    // when the other Walloon becomes the one at the low end.
    static const struct { int column; int row; } left_down_board[] = {
        {13, 19}, {14, 19}, {15, 19}, {16, 18}, {17, 18}, {18, 18},
        {19, 17}, {20, 17}, {21, 17}, {22, 16}, {23, 16}, {24, 16},
    };
    static const struct { int column; int row; } right_down_board[] = {
        {13, 16}, {14, 16}, {15, 16}, {16, 17}, {17, 17}, {18, 17},
        {19, 18}, {20, 18}, {21, 18}, {22, 19}, {23, 19}, {24, 19},
    };
    if (left_end_down) {
        for (size_t index = 0U; index < sizeof(left_down_board) / sizeof(left_down_board[0]);
             ++index) {
            drawStageSevenGlyph(frame, left_down_board[index].column,
                                left_down_board[index].row,
                                DEMO_STAGE_SEVEN_SEESAW_LINE_GLYPH);
        }
    } else {
        for (size_t index = 0U; index < sizeof(right_down_board) / sizeof(right_down_board[0]);
             ++index) {
            drawStageSevenGlyph(frame, right_down_board[index].column,
                                right_down_board[index].row,
                                DEMO_STAGE_SEVEN_SEESAW_LINE_GLYPH);
        }
    }
    // The two original PET glyphs make a compact, fixed fulcrum below the board.
    drawStageSevenGlyph(frame, 18, 20, 233U);
    drawStageSevenGlyph(frame, 19, 20, 223U);
}
drawStageSevenWalloons() · source line 4071
static void drawStageSevenWalloons(uint16_t *frame, uint32_t now_ms)

Calls into this documentation:

  1. Decisionstage_seven_phase == WALLOONS_TITLE_CARD
    True
      1. ExecutedrawStageSevenTitleBox(frame);
      2. Exit this functionreturn;
    False

    Continue after this decision.

  2. ExecutedrawStageSevenPlayfield(frame);
  3. Executeconst unsigned steps = walloonsPhaseSteps(now_ms, 14U);
  4. Executeconst bool walking_pose = (steps & 1U) != 0U;
  5. Select a casestage_seven_phase

    Enter matching case; follow source fall-through until a break, return or end.

      1. Case / labelcase WALLOONS_TITLE_CARD:
      2. Exit enclosing loop or switchbreak;
      3. Case / labelcase WALLOONS_LEFT_WALK_IN:
      4. ExecutedrawStageSevenSeesaw(frame, true);
      5. ExecutedrawStageSevenWalloon(frame, 2 + (int)walloonsPhaseSteps(now_ms, 12U), 15, walking_pose);
      6. Exit enclosing loop or switchbreak;
      7. Case / labelcase WALLOONS_PAUSE_ON_SEESAW:
      8. ExecutedrawStageSevenSeesaw(frame, true);
      9. ExecutedrawStageSevenWalloon(frame, 14, 15, false);
      10. Exit enclosing loop or switchbreak;
      11. Case / labelcase WALLOONS_RIGHT_WALK_ON_PLATFORM:
      12. ExecutedrawStageSevenSeesaw(frame, true);
      13. ExecutedrawStageSevenWalloon(frame, 14, 15, false);
      14. ExecutedrawStageSevenWalloon(frame, 38 - (int)steps, 2, walking_pose);
      15. Exit enclosing loop or switchbreak;
      16. Case / labelcase WALLOONS_RIGHT_DROP:
      17. ExecutedrawStageSevenSeesaw(frame, true);
      18. ExecutedrawStageSevenWalloon(frame, 14, 15, false);
      19. ExecutedrawStageSevenWalloon(frame, 24, 2 + (int)walloonsPhaseSteps(now_ms, 13U), false);
      20. Exit enclosing loop or switchbreak;
      21. Case / labelcase WALLOONS_BOUNCE_RIGHT_UP:
      22. Case / labelcase WALLOONS_BOUNCE_RIGHT_DOWN:
      23. ExecutedrawStageSevenSeesaw(frame, true);
      24. ExecutedrawStageSevenWalloon(frame, 15, 15, false);
      25. ExecutedrawStageSevenWalloon(frame, 24, stage_seven_phase == WALLOONS_BOUNCE_RIGHT_UP ? 14 - (int)walloonsPhaseSteps(now_ms, 13U) : 2 + (int)walloonsPhaseSteps(now_ms, 13U), false);
      26. Exit enclosing loop or switchbreak;
      27. Case / labelcase WALLOONS_BOUNCE_LEFT_UP:
      28. ExecutedrawStageSevenSeesaw(frame, false);
      29. ExecutedrawStageSevenWalloon(frame, 24, 14, false);
      30. ExecutedrawStageSevenWalloon(frame, 15, 14 - (int)walloonsPhaseSteps(now_ms, 13U), false);
      31. Exit enclosing loop or switchbreak;
      32. Case / labelcase WALLOONS_BOUNCE_LEFT_DOWN:
      33. ExecutedrawStageSevenSeesaw(frame, false);
      34. ExecutedrawStageSevenWalloon(frame, 24, 14, false);
      35. ExecutedrawStageSevenWalloon(frame, 15, 2 + (int)walloonsPhaseSteps(now_ms, 13U), false);
      36. Exit enclosing loop or switchbreak;
      37. Case / labelcase WALLOONS_LEFT_LANDED:
      38. ExecutedrawStageSevenSeesaw(frame, true);
      39. ExecutedrawStageSevenWalloon(frame, 15, 15, false);
      40. Exit enclosing loop or switchbreak;
      41. Case / labelcase WALLOONS_FINISHED:
      42. ExecutedrawStageSevenSeesaw(frame, true);
      43. ExecutedrawStageSevenWalloon(frame, 15, 15, false);
      44. ExecutedrawStageSevenWalloon(frame, 24, 14, false);
      45. Exit enclosing loop or switchbreak;
Read original function source
static void drawStageSevenWalloons(uint16_t *frame, uint32_t now_ms)
{
    if (stage_seven_phase == WALLOONS_TITLE_CARD) {
        drawStageSevenTitleBox(frame);
        return;
    }
    drawStageSevenPlayfield(frame);

    const unsigned steps = walloonsPhaseSteps(now_ms, 14U);
    const bool walking_pose = (steps & 1U) != 0U;
    switch (stage_seven_phase) {
        case WALLOONS_TITLE_CARD:
            // Handled above so the title card contains no active Walloons.
            break;
        case WALLOONS_LEFT_WALK_IN:
            drawStageSevenSeesaw(frame, true);
            drawStageSevenWalloon(frame, 2 + (int)walloonsPhaseSteps(now_ms, 12U), 15,
                                   walking_pose);
            break;
        case WALLOONS_PAUSE_ON_SEESAW:
            drawStageSevenSeesaw(frame, true);
            drawStageSevenWalloon(frame, 14, 15, false);
            break;
        case WALLOONS_RIGHT_WALK_ON_PLATFORM:
            drawStageSevenSeesaw(frame, true);
            drawStageSevenWalloon(frame, 14, 15, false);
            drawStageSevenWalloon(frame, 38 - (int)steps, 2, walking_pose);
            break;
        case WALLOONS_RIGHT_DROP:
            drawStageSevenSeesaw(frame, true);
            drawStageSevenWalloon(frame, 14, 15, false);
            drawStageSevenWalloon(frame, 24, 2 + (int)walloonsPhaseSteps(now_ms, 13U), false);
            break;
        case WALLOONS_BOUNCE_RIGHT_UP:
        case WALLOONS_BOUNCE_RIGHT_DOWN:
            // The left Walloon remains at the low end while the right Walloon is airborne.
            drawStageSevenSeesaw(frame, true);
            drawStageSevenWalloon(frame, 15, 15, false);
            drawStageSevenWalloon(frame, 24,
                                   stage_seven_phase == WALLOONS_BOUNCE_RIGHT_UP
                                       ? 14 - (int)walloonsPhaseSteps(now_ms, 13U)
                                       : 2 + (int)walloonsPhaseSteps(now_ms, 13U), false);
            break;
        case WALLOONS_BOUNCE_LEFT_UP:
            // The right Walloon remains at the low end while the left Walloon rises.
            drawStageSevenSeesaw(frame, false);
            drawStageSevenWalloon(frame, 24, 14, false);
            drawStageSevenWalloon(frame, 15,
                                   14 - (int)walloonsPhaseSteps(now_ms, 13U), false);
            break;
        case WALLOONS_BOUNCE_LEFT_DOWN:
            drawStageSevenSeesaw(frame, false);
            drawStageSevenWalloon(frame, 24, 14, false);
            drawStageSevenWalloon(frame, 15,
                                   2 + (int)walloonsPhaseSteps(now_ms, 13U), false);
            break;
        case WALLOONS_LEFT_LANDED:
            // The landing phase makes the left-down state visible before the
            // next Walloon is launched from the other end.
            drawStageSevenSeesaw(frame, true);
            drawStageSevenWalloon(frame, 15, 15, false);
            break;
        case WALLOONS_FINISHED:
            drawStageSevenSeesaw(frame, true);
            drawStageSevenWalloon(frame, 15, 15, false);
            drawStageSevenWalloon(frame, 24, 14, false);
            break;
    }
}
drawDemoInstructions() · source line 4143
static void drawDemoInstructions(uint16_t *frame)

Calls into this documentation:

  1. Executeconst uint32_t elapsed_ms = last_update_ms - demo_instructions_started_at;
  2. Executeconst uint32_t remaining_ms = elapsed_ms >= DEMO_INSTRUCTIONS_DURATION_MS ? 0U : DEMO_INSTRUCTIONS_DURATION_MS - elapsed_ms;
  3. Executeconst unsigned seconds_remaining = (unsigned)((remaining_ms + 999U) / 1000U);
  4. Executechar start_text[24];
  5. Executefill_rect(frame, 0, 0, LCD_WIDTH, LCD_HEIGHT, C64_COLOR_BLUE);
  6. Executestarfield_draw(frame, &demo_instruction_starfield);
  7. ExecutedrawPet2001Text(frame, 40, 8, "Old school Demo", false, C64_COLOR_CYAN, C64_COLOR_BLUE, 2);
  8. ExecutedrawPet2001Text(frame, 128, 32, "CONTROLS", false, C64_COLOR_LIGHT_GREY, C64_COLOR_BLUE, 1);
  9. ExecutedrawPet2001Text(frame, 56, 48, "C: NEXT SEGMENT", false, C64_COLOR_YELLOW, C64_COLOR_BLUE, 1);
  10. ExecutedrawPet2001Text(frame, 56, 64, "A: NEXT EFFECT (1)", false, C64_COLOR_WHITE, C64_COLOR_BLUE, 1);
  11. ExecutedrawPet2001Text(frame, 56, 80, "B: ICON/SOLID/PALETTE", false, C64_COLOR_WHITE, C64_COLOR_BLUE, 1);
  12. ExecutedrawPet2001Text(frame, 56, 96, "UP/DOWN: APPROACH (2)", false, C64_COLOR_WHITE, C64_COLOR_BLUE, 1);
  13. ExecutedrawPet2001Text(frame, 56, 112, "LEFT/RIGHT: TICKER SPEED", false, C64_COLOR_WHITE, C64_COLOR_BLUE, 1);
  14. ExecutedrawPet2001Text(frame, 56, 128, "BOOT: RETURN TO MENU", false, C64_COLOR_WHITE, C64_COLOR_BLUE, 1);
  15. Executesnprintf(start_text, sizeof(start_text), "C STARTS IN %u S", seconds_remaining);
  16. ExecutedrawPet2001Text(frame, 96, 152, start_text, false, C64_COLOR_YELLOW, C64_COLOR_BLUE, 1);
Read original function source
static void drawDemoInstructions(uint16_t *frame)
{
    const uint32_t elapsed_ms = last_update_ms - demo_instructions_started_at;
    const uint32_t remaining_ms = elapsed_ms >= DEMO_INSTRUCTIONS_DURATION_MS ? 0U :
        DEMO_INSTRUCTIONS_DURATION_MS - elapsed_ms;
    const unsigned seconds_remaining = (unsigned)((remaining_ms + 999U) / 1000U);
    char start_text[24];

    fill_rect(frame, 0, 0, LCD_WIDTH, LCD_HEIGHT, C64_COLOR_BLUE);
    starfield_draw(frame, &demo_instruction_starfield);
    drawPet2001Text(frame, 40, 8, "Old school Demo", false,
                    C64_COLOR_CYAN, C64_COLOR_BLUE, 2);
    drawPet2001Text(frame, 128, 32, "CONTROLS", false, C64_COLOR_LIGHT_GREY, C64_COLOR_BLUE, 1);
    drawPet2001Text(frame, 56, 48, "C: NEXT SEGMENT", false, C64_COLOR_YELLOW, C64_COLOR_BLUE, 1);
    drawPet2001Text(frame, 56, 64, "A: NEXT EFFECT (1)", false, C64_COLOR_WHITE, C64_COLOR_BLUE, 1);
    drawPet2001Text(frame, 56, 80, "B: ICON/SOLID/PALETTE", false,
                    C64_COLOR_WHITE, C64_COLOR_BLUE, 1);
    drawPet2001Text(frame, 56, 96, "UP/DOWN: APPROACH (2)", false, C64_COLOR_WHITE, C64_COLOR_BLUE, 1);
    drawPet2001Text(frame, 56, 112, "LEFT/RIGHT: TICKER SPEED", false,
                    C64_COLOR_WHITE, C64_COLOR_BLUE, 1);
    drawPet2001Text(frame, 56, 128, "BOOT: RETURN TO MENU", false,
                    C64_COLOR_WHITE, C64_COLOR_BLUE, 1);
    snprintf(start_text, sizeof(start_text), "C STARTS IN %u S", seconds_remaining);
    drawPet2001Text(frame, 96, 152, start_text, false, C64_COLOR_YELLOW, C64_COLOR_BLUE, 1);
}
drawDemoSelector() · source line 4169
static void drawDemoSelector(uint16_t *frame)

Calls into this documentation:

  1. Executeclear(frame, COLOR_BLACK);
  2. Executedraw_text_centred(frame, LCD_WIDTH / 2, 14, "SELECT DEMO SEGMENT", 1, COLOR_ORANGE);
  3. FOR loopsize_t item = 0U; item <= DEMO_STAGE_COUNT; ++item

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

      1. Executeconst int y = DEMO_SELECTOR_FIRST_ROW_Y + (int)item * DEMO_SELECTOR_ROW_HEIGHT;
      2. Executeconst bool selected = item == demo_selector_item;
      3. Decisionselected
        True
        1. Executefill_rect(frame, 0, y - 1, LCD_WIDTH, DEMO_SELECTOR_ROW_HEIGHT, COLOR_DARKBLUE);
        False

        Continue after this decision.

      4. Executedraw_text(frame, 8, y, item == 0U ? "RETURN CURRENT" : demoSelectorStageCodeName((demo_stage_t)(item - 1U)), 1, selected ? COLOR_WHITE : COLOR_LIGHTGREY);
  4. Executedraw_text_centred(frame, LCD_WIDTH / 2, 160, "D/B OR UP/DOWN A/K SELECT", 1, COLOR_LIGHTGREY);
Read original function source
static void drawDemoSelector(uint16_t *frame)
{
    clear(frame, COLOR_BLACK);
    draw_text_centred(frame, LCD_WIDTH / 2, 14, "SELECT DEMO SEGMENT", 1, COLOR_ORANGE);
    for (size_t item = 0U; item <= DEMO_STAGE_COUNT; ++item) {
        const int y = DEMO_SELECTOR_FIRST_ROW_Y + (int)item * DEMO_SELECTOR_ROW_HEIGHT;
        const bool selected = item == demo_selector_item;
        if (selected) fill_rect(frame, 0, y - 1, LCD_WIDTH, DEMO_SELECTOR_ROW_HEIGHT,
                                COLOR_DARKBLUE);
        draw_text(frame, 8, y, item == 0U ? "RETURN CURRENT" :
                  demoSelectorStageCodeName((demo_stage_t)(item - 1U)), 1,
                  selected ? COLOR_WHITE : COLOR_LIGHTGREY);
    }
    draw_text_centred(frame, LCD_WIDTH / 2, 160, "D/B OR UP/DOWN  A/K SELECT", 1,
                      COLOR_LIGHTGREY);
}
drawDemo() · source line 4186
void drawDemo(uint16_t *frame)

Calls into this documentation:

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

    Continue after this decision.

  2. Decisiondemo_selector_active
    True
      1. ExecutedrawDemoSelector(frame);
      2. Exit this functionreturn;
    False

    Continue after this decision.

  3. Decisiondemo_selector_clear_new_stage
    True
      1. Executeclear(frame, COLOR_BLACK);
      2. Executedemo_selector_clear_new_stage = false;
      3. Exit this functionreturn;
    False

    Continue after this decision.

  4. Decisiondemo_instructions_active
    True
      1. ExecutedrawDemoInstructions(frame);
      2. Exit this functionreturn;
    False

    Continue after this decision.

  5. DecisionscreenTransitionActive(&stage_transition)
    True
      1. ExecutescreenTransitionDraw(&stage_transition, frame, last_update_ms);
      2. Exit this functionreturn;
    False

    Continue after this decision.

  6. DecisionscreenRevealActive(&stage_transition)
    True
      1. ExecutescreenRevealDraw(&stage_transition, frame, last_update_ms);
      2. Exit this functionreturn;
    False

    Continue after this decision.

  7. DecisionscreenScrollActive(&stage_transition) && !stage_scroll_in_requested
    True
      1. ExecutescreenScrollDraw(&stage_transition, frame, last_update_ms);
      2. Exit this functionreturn;
    False

    Continue after this decision.

  8. DecisionscreenSliceActive(&stage_transition)
    True
      1. ExecutescreenSliceDraw(&stage_transition, frame, last_update_ms);
      2. Exit this functionreturn;
    False

    Continue after this decision.

  9. Executeclear(frame, COLOR_BLACK);
  10. Decisionselected_stage == DEMO_STAGE_APPROACH
    True
      1. ExecutedrawApproachStage(frame);
    False
    1. Decisionselected_stage == DEMO_STAGE_AMIGA_BALL
      True
        1. ExecutedrawStageThreeBall(frame);
      False
      1. Decisionselected_stage == DEMO_STAGE_SCROLLER
        True
          False
          1. Decisionselected_stage == DEMO_STAGE_REVERSE_SCROLLER
            True
              False
              1. Decisionselected_stage == DEMO_STAGE_GEOMETRIC_SOLID
                True
                  1. ExecutedrawStageSixSolid(frame);
                False
                1. Decisionselected_stage == DEMO_STAGE_WALLOONS
                  True
                    1. ExecutedemoStageWalloonsDraw(frame, last_update_ms);
                  False
                  1. Decisionselected_stage == DEMO_STAGE_KALEIDO
                    True
                      1. ExecutedemoStageKaleidoDraw(frame);
                    False
                    1. Decisionselected_stage == DEMO_STAGE_SPARKLE
                      True
                        1. ExecutedemoStageSparkleDraw(frame);
                      False
                      1. Decisionselected_stage == DEMO_STAGE_SQUARES
                        True
                          1. ExecutedemoStageSquaresDraw(frame);
                        False
                        1. Decisionselected_stage == DEMO_STAGE_RASTER_BANDS
                          True
                            1. ExecutedrawRasterBands(frame);
                          False
                          1. Decisionselected_stage == DEMO_STAGE_SLIDESHOW_LENS
                            True
                              1. ExecutedemoStageSlideshowLensDraw(frame);
                            False
                            1. Decisionselected_stage == DEMO_STAGE_PLASMA
                              True
                                1. ExecutedemoStagePlasmaDraw(frame);
                              False
                              1. Decisionselected_stage == DEMO_STAGE_GAME_OF_LIFE
                                True
                                  1. ExecutedemoStageGameOfLifeDraw(frame);
                                False
                                1. Decisionselected_stage == DEMO_STAGE_LOWER_RASTER_SCROLLER
                                  True
                                    1. ExecutedrawRasterBands(frame);
                                  False
                                  1. Decisionselected_stage == DEMO_STAGE_BATTLE_ZONE_RASTER
                                    True
                                      1. ExecutedrawStageFifteenBattleZoneBackground(frame);
                                      2. ExecutedrawRasterBands(frame);
                                    False
                                    1. Decisionselected_stage == DEMO_STAGE_BEVEL_GEARS
                                      True
                                        1. ExecutedrawStageSeventeenBevelGears(frame);
                                      False
                                      1. Decisionselected_stage == DEMO_STAGE_MOUSE_MAZE
                                        True
                                          1. ExecutedemoStageMouseMazeDraw(frame);
                                        False
                                        1. Decisionselected_effect == DEMO_EFFECT_DOT_GRID || selected_effect == DEMO_EFFECT_DOT_SPARKLE || selected_effect == DEMO_EFFECT_RADIAL_FLAME
                                          True
                                            1. Executeconst bool sparkle_enabled = selected_effect == DEMO_EFFECT_DOT_SPARKLE;
                                            2. Decisionselected_effect == DEMO_EFFECT_RADIAL_FLAME
                                              True
                                                1. Executedraw_radial_flame(frame, &demo_ring_flame, DEMO_CENTRE_X, DEMO_CENTRE_Y, demoRingInnerRadius(), yaw_degrees, demo_ring_colours);
                                              False

                                              Continue after this decision.

                                            3. Executeconst dotted_bitmap_3d_params_t dotted_sprite = { .centre_x = DEMO_CENTRE_X, .centre_y = DEMO_CENTRE_Y, .scale = DEMO_FACE_SCALE, .pitch_degrees = pitch_degrees, .yaw_degrees = yaw_degrees, .roll_degrees = roll_degrees, .depth = (float)DEMO_EXTRUSION_DEPTH, .camera_distance = DEMO_CAMERA_DISTANCE, .depth_dot_spacing = DEMO_DOT_SPACING, .dot_radius = DEMO_DOT_RADIUS, .dot_density_percent = DEMO_DOT_DENSITY_PERCENT, .random_seed = DEMO_DOT_RANDOM_SEED, .sparkle_density_percent = sparkle_enabled ? DEMO_DOT_SPARKLE_DENSITY_PERCENT : 0U, .sparkle_phase = sparkle_phase, .sparkle_radius = DEMO_DOT_SPARKLE_RADIUS, .sparkle_color = DEMO_DOT_SPARKLE_COLOR, .front_color = demo_icon_colour_schemes[demo_icon_colour_scheme_index].front, .back_color = demo_icon_colour_schemes[demo_icon_colour_scheme_index].dotted_back, .side_color = demo_icon_colour_schemes[demo_icon_colour_scheme_index].depth, };
                                            4. Executedraw_bitmap_dotted_3d(frame, active_logo_bitmap, DEMO_LOGO_WIDTH, DEMO_LOGO_HEIGHT, &dotted_sprite);
                                          False
                                            1. Executeconst float pitch_radians = pitch_degrees * DEMO_PI / 180.0f;
                                            2. Executeconst float yaw_radians = yaw_degrees * DEMO_PI / 180.0f;
                                            3. Executeconst float roll_radians = roll_degrees * DEMO_PI / 180.0f;
                                            4. Executeconst demo_rotation_t rotation = { .pitch_cosine = cosf(pitch_radians), .pitch_sine = sinf(pitch_radians), .yaw_cosine = cosf(yaw_radians), .yaw_sine = sinf(yaw_radians), .roll_cosine = cosf(roll_radians), .roll_sine = sinf(roll_radians), };
                                            5. Decisionrotation.pitch_cosine * rotation.yaw_cosine >= 0.0f
                                              True
                                                1. FOR loopunsigned depth = DEMO_EXTRUSION_DEPTH; depth > 0U; depth -= DEMO_EXTRUSION_STEP

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

                                                    1. ExecutedrawLogoSlice(frame, active_logo_bitmap, (float)depth, &rotation, demo_icon_colour_schemes[demo_icon_colour_scheme_index].depth);
                                                2. ExecutedrawLogoSlice(frame, active_logo_bitmap, 0.0f, &rotation, demo_icon_colour_schemes[demo_icon_colour_scheme_index].front);
                                              False
                                                1. ExecutedrawLogoSlice(frame, active_logo_bitmap, 0.0f, &rotation, demo_icon_colour_schemes[demo_icon_colour_scheme_index].front);
                                                2. FOR loopunsigned depth = DEMO_EXTRUSION_STEP; depth <= DEMO_EXTRUSION_DEPTH; depth += DEMO_EXTRUSION_STEP

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

                                                    1. ExecutedrawLogoSlice(frame, active_logo_bitmap, (float)depth, &rotation, demo_icon_colour_schemes[demo_icon_colour_scheme_index].depth);
      2. Decisionselected_stage == DEMO_STAGE_SCROLLER || selected_stage == DEMO_STAGE_REVERSE_SCROLLER
        True
          1. ExecutedrawTextCornerIcons(frame);
        False

        Continue after this decision.

      3. ExecutedrawScrollText(frame);
      4. Decisionstage_scroll_in_requested
        True
          1. DecisionscreenScrollSetIncoming(&stage_transition, frame, stage_scroll_in_direction, DEMO_STAGE_SCROLL_GAP_PIXELS)
            True
              1. ExecutescreenScrollDraw(&stage_transition, frame, last_update_ms);
            False

            Continue after this decision.

          2. Executestage_scroll_in_requested = false;
          3. Exit this functionreturn;
        False

        Continue after this decision.

      5. Decisionstage_reveal_requested
        True
          1. DecisionscreenRevealStart(&stage_transition, frame, last_update_ms, DEMO_STAGE_REBUILD_MS, DEMO_STAGE_REBUILD_PERCENT)
            True
              1. ExecutescreenRevealDraw(&stage_transition, frame, last_update_ms);
            False

            Continue after this decision.

          2. Executestage_reveal_requested = false;
          3. Exit this functionreturn;
        False

        Continue after this decision.

      6. Decisionstage_zoom_in_requested
        True
          1. DecisionscreenTransitionZoomInStart(&stage_transition, frame, last_update_ms, DEMO_STAGE_ZOOM_IN_MS)
            True
              1. Executestage_zooming_in = true;
              2. ExecutescreenTransitionDraw(&stage_transition, frame, last_update_ms);
            False

            Continue after this decision.

          2. Executestage_zoom_in_requested = false;
          3. Exit this functionreturn;
        False

        Continue after this decision.

      7. Decisionstage_change_requested
        True
          1. Select a caseselected_stage_transition

            Enter matching case; follow source fall-through until a break, return or end.

              1. Case / labelcase DEMO_STAGE_TRANSITION_SHRINK_ZOOM:
              2. Decision!screenTransitionStart(&stage_transition, frame, last_update_ms, DEMO_STAGE_TRANSITION_FREEZE_MS, DEMO_STAGE_TRANSITION_ZOOM_MS)
                True
                  1. ExecuteadvanceDemoStage(last_update_ms);
                  2. Executestage_zoom_in_requested = true;
                False

                Continue after this decision.

              3. Exit enclosing loop or switchbreak;
              4. Case / labelcase DEMO_STAGE_TRANSITION_REBUILD:
              5. ExecuteadvanceDemoStage(last_update_ms);
              6. Executestage_reveal_requested = true;
              7. Exit enclosing loop or switchbreak;
              8. Case / labelcase DEMO_STAGE_TRANSITION_SCROLL:
              9. Decision!screenScrollOutStart(&stage_transition, frame, last_update_ms, stage_scroll_out_direction, DEMO_STAGE_SCROLL_STEP_MS, DEMO_STAGE_SCROLL_PIXELS_PER_STEP)
                True
                  1. ExecuteadvanceDemoStage(last_update_ms);
                  2. Executestage_scroll_in_requested = true;
                False
                  1. ExecuteadvanceDemoStage(last_update_ms);
                  2. Executestage_scroll_in_requested = true;
              10. Exit enclosing loop or switchbreak;
              11. Case / labelcase DEMO_STAGE_TRANSITION_SLICES:
              12. Decision!screenSliceStart(&stage_transition, frame, last_update_ms, DEMO_STAGE_SLICE_COUNT, DEMO_STAGE_SLICE_MS)
                True
                  1. ExecuteadvanceDemoStage(last_update_ms);
                  2. Executestage_zoom_in_requested = true;
                False

                Continue after this decision.

              13. Exit enclosing loop or switchbreak;
              14. Case / labeldefault:
              15. Exit enclosing loop or switchbreak;
          2. Executestage_change_requested = false;
        False

        Continue after this decision.

      Read original function source
      void drawDemo(uint16_t *frame)
      {
          if (!running || frame == NULL) return;
          if (demo_selector_active) {
              drawDemoSelector(frame);
              return;
          }
          if (demo_selector_clear_new_stage) {
              clear(frame, COLOR_BLACK);
              demo_selector_clear_new_stage = false;
              return;
          }
          if (demo_instructions_active) {
              drawDemoInstructions(frame);
              return;
          }
          if (screenTransitionActive(&stage_transition)) {
              screenTransitionDraw(&stage_transition, frame, last_update_ms);
              return;
          }
          if (screenRevealActive(&stage_transition)) {
              screenRevealDraw(&stage_transition, frame, last_update_ms);
              return;
          }
          if (screenScrollActive(&stage_transition) && !stage_scroll_in_requested) {
              screenScrollDraw(&stage_transition, frame, last_update_ms);
              return;
          }
          if (screenSliceActive(&stage_transition)) {
              screenSliceDraw(&stage_transition, frame, last_update_ms);
              return;
          }
          clear(frame, COLOR_BLACK);
      
          if (selected_stage == DEMO_STAGE_APPROACH) {
              drawApproachStage(frame);
          } else if (selected_stage == DEMO_STAGE_AMIGA_BALL) {
              drawStageThreeBall(frame);
          } else if (selected_stage == DEMO_STAGE_SCROLLER) {
              // The rainbow magnifier scroller is drawn below on this clear black background.
          } else if (selected_stage == DEMO_STAGE_REVERSE_SCROLLER) {
              // The inverse rainbow magnifier scroller is drawn below on this clear black background.
          } else if (selected_stage == DEMO_STAGE_GEOMETRIC_SOLID) {
              drawStageSixSolid(frame);
          } else if (selected_stage == DEMO_STAGE_WALLOONS) {
              demoStageWalloonsDraw(frame, last_update_ms);
          } else if (selected_stage == DEMO_STAGE_KALEIDO) {
              demoStageKaleidoDraw(frame);
          } else if (selected_stage == DEMO_STAGE_SPARKLE) {
              demoStageSparkleDraw(frame);
          } else if (selected_stage == DEMO_STAGE_SQUARES) {
              demoStageSquaresDraw(frame);
          } else if (selected_stage == DEMO_STAGE_RASTER_BANDS) {
              drawRasterBands(frame);
          } else if (selected_stage == DEMO_STAGE_SLIDESHOW_LENS) {
              demoStageSlideshowLensDraw(frame);
          } else if (selected_stage == DEMO_STAGE_PLASMA) {
              demoStagePlasmaDraw(frame);
          } else if (selected_stage == DEMO_STAGE_GAME_OF_LIFE) {
              demoStageGameOfLifeDraw(frame);
          } else if (selected_stage == DEMO_STAGE_LOWER_RASTER_SCROLLER) {
              drawRasterBands(frame);
          } else if (selected_stage == DEMO_STAGE_BATTLE_ZONE_RASTER) {
              // The real Battle Zone renderer is compressed into the lower half;
              // the compact raster then travels only in the clear upper half.
              drawStageFifteenBattleZoneBackground(frame);
              drawRasterBands(frame);
          } else if (selected_stage == DEMO_STAGE_BEVEL_GEARS) {
              drawStageSeventeenBevelGears(frame);
          } else if (selected_stage == DEMO_STAGE_MOUSE_MAZE) {
              demoStageMouseMazeDraw(frame);
          } else if (selected_effect == DEMO_EFFECT_DOT_GRID ||
              selected_effect == DEMO_EFFECT_DOT_SPARKLE ||
              selected_effect == DEMO_EFFECT_RADIAL_FLAME) {
              const bool sparkle_enabled = selected_effect == DEMO_EFFECT_DOT_SPARKLE;
              if (selected_effect == DEMO_EFFECT_RADIAL_FLAME) {
                  draw_radial_flame(frame, &demo_ring_flame, DEMO_CENTRE_X, DEMO_CENTRE_Y,
                                    demoRingInnerRadius(), yaw_degrees, demo_ring_colours);
              }
              const dotted_bitmap_3d_params_t dotted_sprite = {
                  .centre_x = DEMO_CENTRE_X,
                  .centre_y = DEMO_CENTRE_Y,
                  .scale = DEMO_FACE_SCALE,
                  .pitch_degrees = pitch_degrees,
                  .yaw_degrees = yaw_degrees,
                  .roll_degrees = roll_degrees,
                  .depth = (float)DEMO_EXTRUSION_DEPTH,
                  .camera_distance = DEMO_CAMERA_DISTANCE,
                  .depth_dot_spacing = DEMO_DOT_SPACING,
                  .dot_radius = DEMO_DOT_RADIUS,
                  .dot_density_percent = DEMO_DOT_DENSITY_PERCENT,
                  .random_seed = DEMO_DOT_RANDOM_SEED,
                  .sparkle_density_percent = sparkle_enabled ? DEMO_DOT_SPARKLE_DENSITY_PERCENT : 0U,
                  .sparkle_phase = sparkle_phase,
                  .sparkle_radius = DEMO_DOT_SPARKLE_RADIUS,
                  .sparkle_color = DEMO_DOT_SPARKLE_COLOR,
                  .front_color = demo_icon_colour_schemes[demo_icon_colour_scheme_index].front,
                  .back_color = demo_icon_colour_schemes[demo_icon_colour_scheme_index].dotted_back,
                  .side_color = demo_icon_colour_schemes[demo_icon_colour_scheme_index].depth,
              };
              draw_bitmap_dotted_3d(frame, active_logo_bitmap, DEMO_LOGO_WIDTH,
                                    DEMO_LOGO_HEIGHT, &dotted_sprite);
          } else {
              const float pitch_radians = pitch_degrees * DEMO_PI / 180.0f;
              const float yaw_radians = yaw_degrees * DEMO_PI / 180.0f;
              const float roll_radians = roll_degrees * DEMO_PI / 180.0f;
              const demo_rotation_t rotation = {
                  .pitch_cosine = cosf(pitch_radians),
                  .pitch_sine = sinf(pitch_radians),
                  .yaw_cosine = cosf(yaw_radians),
                  .yaw_sine = sinf(yaw_radians),
                  .roll_cosine = cosf(roll_radians),
                  .roll_sine = sinf(roll_radians),
              };
              // The body depth changes screen depth by pitch_cosine * yaw_cosine.
              // Draw from far to near so the random front colour is never painted through a nearer slice.
              if (rotation.pitch_cosine * rotation.yaw_cosine >= 0.0f) {
                  for (unsigned depth = DEMO_EXTRUSION_DEPTH; depth > 0U;
                       depth -= DEMO_EXTRUSION_STEP) {
                      drawLogoSlice(frame, active_logo_bitmap, (float)depth, &rotation,
                                    demo_icon_colour_schemes[demo_icon_colour_scheme_index].depth);
                  }
                  drawLogoSlice(frame, active_logo_bitmap, 0.0f, &rotation,
                                demo_icon_colour_schemes[demo_icon_colour_scheme_index].front);
              } else {
                  drawLogoSlice(frame, active_logo_bitmap, 0.0f, &rotation,
                                demo_icon_colour_schemes[demo_icon_colour_scheme_index].front);
                  for (unsigned depth = DEMO_EXTRUSION_STEP; depth <= DEMO_EXTRUSION_DEPTH;
                       depth += DEMO_EXTRUSION_STEP) {
                      drawLogoSlice(frame, active_logo_bitmap, (float)depth, &rotation,
                                    demo_icon_colour_schemes[demo_icon_colour_scheme_index].depth);
                  }
              }
          }
      
          if (selected_stage == DEMO_STAGE_SCROLLER ||
              selected_stage == DEMO_STAGE_REVERSE_SCROLLER) {
              drawTextCornerIcons(frame);
          }
          drawScrollText(frame);
          if (stage_scroll_in_requested) {
              // The new stage is complete in the shared frame but has not reached the LCD yet.
              // Capture it as the second moving frame, then scroll both frames together.
              if (screenScrollSetIncoming(&stage_transition, frame, stage_scroll_in_direction,
                                          DEMO_STAGE_SCROLL_GAP_PIXELS)) {
                  screenScrollDraw(&stage_transition, frame, last_update_ms);
              }
              stage_scroll_in_requested = false;
              return;
          }
          if (stage_reveal_requested) {
              // Capture the completed destination before the reusable 8 x 8 rebuild effect hides it.
              if (screenRevealStart(&stage_transition, frame, last_update_ms, DEMO_STAGE_REBUILD_MS,
                                    DEMO_STAGE_REBUILD_PERCENT)) {
                  screenRevealDraw(&stage_transition, frame, last_update_ms);
              }
              stage_reveal_requested = false;
              return;
          }
          if (stage_zoom_in_requested) {
              // This scene has been drawn into the shared frame but not presented yet: capture it
              // and replace it with the first one-pixel frame of the incoming zoom.
              if (screenTransitionZoomInStart(&stage_transition, frame, last_update_ms,
                                              DEMO_STAGE_ZOOM_IN_MS)) {
                  stage_zooming_in = true;
                  screenTransitionDraw(&stage_transition, frame, last_update_ms);
              }
              stage_zoom_in_requested = false;
              return;
          }
          if (stage_change_requested) {
              switch (selected_stage_transition) {
                  case DEMO_STAGE_TRANSITION_SHRINK_ZOOM:
                      // Freeze the completed old stage before updateDemo selects and captures the new one.
                      if (!screenTransitionStart(&stage_transition, frame, last_update_ms,
                                                 DEMO_STAGE_TRANSITION_FREEZE_MS,
                                                 DEMO_STAGE_TRANSITION_ZOOM_MS)) {
                          advanceDemoStage(last_update_ms);
                          stage_zoom_in_requested = true;
                      }
                      break;
                  case DEMO_STAGE_TRANSITION_REBUILD:
                      // Select the destination now; drawDemo captures it next frame for its block reveal.
                      advanceDemoStage(last_update_ms);
                      stage_reveal_requested = true;
                      break;
                  case DEMO_STAGE_TRANSITION_SCROLL:
                      // Capture after the completed outgoing scene, then translate that exact image away.
                      if (!screenScrollOutStart(&stage_transition, frame, last_update_ms,
                                                stage_scroll_out_direction,
                                                DEMO_STAGE_SCROLL_STEP_MS,
                                                DEMO_STAGE_SCROLL_PIXELS_PER_STEP)) {
                          advanceDemoStage(last_update_ms);
                          stage_scroll_in_requested = true;
                      } else {
                          // Select and draw the new stage next frame while the captured old one moves away.
                          advanceDemoStage(last_update_ms);
                          stage_scroll_in_requested = true;
                      }
                      break;
                  case DEMO_STAGE_TRANSITION_SLICES:
                      // Freeze the outgoing stage as alternating horizontal bands before entering the next.
                      if (!screenSliceStart(&stage_transition, frame, last_update_ms,
                                            DEMO_STAGE_SLICE_COUNT, DEMO_STAGE_SLICE_MS)) {
                          advanceDemoStage(last_update_ms);
                          stage_zoom_in_requested = true;
                      }
                      break;
                  default:
                      break;
              }
              stage_change_requested = false;
          }
      }
      demoScreenshotLabel() · source line 4401
      const char *demoScreenshotLabel(void)

      Calls into this documentation:

      1. Exit this functionreturn demoSelectorStageScreenshotLabel(selected_stage);
      Read original function source
      const char *demoScreenshotLabel(void)
      {
          return demoSelectorStageScreenshotLabel(selected_stage);
      }
      Complete original file, including constants and data
      /* Coded By JD(2026) */
      #error "Reference listing only — not provided as build-ready source."
      #include "demo.h"
      
      #include "PET2001_font.h"
      #include "battle_zone.h"
      #include "c64_colors.h"
      #include "demo_selector.h"
      #include "demo_stage_game_of_life.h"
      #include "demo_stage_kaleido.h"
      #include "demo_stage_mouse_maze.h"
      #include "demo_stage_plasma.h"
      #include "demo_stage_sparkle.h"
      #include "demo_stage_slideshow_lens.h"
      #include "demo_stage_squares.h"
      #include "demo_stage_walloons.h"
      #include "esp_attr.h"
      #include "esp_heap_caps.h"
      #include "esp_random.h"
      #include "game_graphics.h"
      #include "image_decoder.h"
      #include "screen_transition.h"
      #include "starfield.h"
      
      #include <dirent.h>
      #include <math.h>
      #include <stdbool.h>
      #include <stdio.h>
      #include <string.h>
      
      // Write-permission check: added on 2026-10-08 at John's request.
      // Second write-permission check: added after VS Code was opened.
      // Segment 1 -- rotating Demo icon showcase. It automatically cycles through the
      // same four visual effects that A selects manually: dot grid, solid 3D icon,
      // sparkling dots, and the radial flame ring. The shared Demo timing and ticker
      // controls come first; the Segment 1-specific effect controls are below.
      // Visual tuning controls. These values can be changed without changing the renderer.
      // Higher frame time reduces CPU work; lower values make motion update more often.
      #define DEMO_FRAME_MS 20U
      // The control reference appears before Segment 1. Change this one value to
      // tune how long it remains visible when Demo starts.
      #define DEMO_INSTRUCTIONS_DURATION_MS 20000U
      // Rotation rates. Different values make the logo tumble rather than turn in lockstep.
      #define DEMO_PITCH_DEGREES_PER_SECOND 37.0f
      #define DEMO_YAW_DEGREES_PER_SECOND 60.0f
      #define DEMO_ROLL_DEGREES_PER_SECOND 23.0f
      // PET ticker controls. Positive speed scrolls left; negative speed scrolls right.
      #define DEMO_SCROLL_INITIAL_PIXELS_PER_SECOND 140.0f
      #define DEMO_SCROLL_MAX_PIXELS_PER_SECOND 420.0f
      // The stick must leave this centre zone before it changes the retained speed.
      #define DEMO_SCROLL_JOYSTICK_DEAD_ZONE 250
      // At this deflection the ticker reaches its full acceleration rate.
      #define DEMO_SCROLL_JOYSTICK_FULL_TRAVEL 1900.0f
      // Full left adds leftward speed; full right removes it and then adds rightward speed.
      #define DEMO_SCROLL_ACCELERATION_PIXELS_PER_SECOND_SQUARED 240.0f
      // Scale 2 makes the PET ticker 16 pixels high; keep its baseline at the display bottom.
      #define DEMO_SCROLL_SCALE 2
      #define DEMO_SCROLL_Y (LCD_HEIGHT - PET2001_FONT_GLYPH_HEIGHT * DEMO_SCROLL_SCALE)
      // The bottom PET ticker uses only this permanent per-character sine wave.
      #define DEMO_SCROLL_EFFECT_WAVE_AMPLITUDE 8 //12 
      // Rainbow ticker tuning. Lower milliseconds move the colour band faster;
      // more glyphs per colour make each visible colour band wider.
      #define DEMO_SCROLL_RAINBOW_MS_PER_COLOUR 90U // 75 ms per colour is 13.3 colours per second     
      #define DEMO_SCROLL_RAINBOW_GLYPHS_PER_COLOUR 4U // 4 glyphs per colour is 10 pixels per colour at scale 2
      // Segment 4 -- centred rainbow magnifying scroller. The normal PET ticker moves
      // through the middle of the screen; glyphs expand as they cross the centre.
      // Segment 5 uses these same values in reverse: glyphs are smallest at centre
      // and largest at the left and right edges.
      #define DEMO_STAGE_FOUR_MAGNIFIER_CENTRE_X (LCD_WIDTH / 2)
      #define DEMO_STAGE_FOUR_MAGNIFIER_CENTRE_Y (LCD_HEIGHT / 2)
      #define DEMO_STAGE_FOUR_MAGNIFIER_RADIUS 160.0f
      #define DEMO_STAGE_FOUR_MAGNIFIER_MAX_SCALE 6.5f
      // Extra horizontal expansion near the lens centre. Keep this below 3.0 so the
      // transformed glyph positions remain in their original left-to-right order.
      #define DEMO_STAGE_FOUR_MAGNIFIER_SPACING_STRENGTH 2.75f
      // Text-stage icon tuning. Slot numbers are zero-based: 0 is icon 1, 1 is
      // icon 2, and 2 is the current icon-3 placeholder. Change this order to
      // repeat one icon in every corner or arrange the available icons differently.
      // Each icon stays on its original top/bottom row and independently glides from
      // its outer corner to the screen-centre X line and back. These controls are
      // deliberately independent of the Stage 4/5 text magnifier and scroll speed.
      #define DEMO_TEXT_CORNER_ICON_SCALE_MIN 0.34f
      #define DEMO_TEXT_CORNER_ICON_SCALE_MAX 0.48f
      #define DEMO_TEXT_CORNER_ICON_DEPTH 12U
      #define DEMO_TEXT_CORNER_ICON_DEPTH_STEP 3U
      // Every icon chooses a new, independent duration in this inclusive range after
      // completing edge -> centre X -> edge. Raise both values for slower travel.
      #define DEMO_TEXT_CORNER_ICON_TRAVEL_MIN_MS 4200U
      #define DEMO_TEXT_CORNER_ICON_TRAVEL_MAX_MS 9800U
      // This only separates colour/size pulse timing. Travel itself is random per icon.
      #define DEMO_TEXT_CORNER_ICON_PULSE_STAGGER_MS 900U
      #define DEMO_TEXT_CORNER_ICON_PULSE_MS 1500U
      #define DEMO_TEXT_CORNER_ICON_COLOUR_MS 220U
      // Each icon picks its own signed rotation speed when the text segment starts.
      // A positive value turns clockwise; a negative value turns anti-clockwise.
      #define DEMO_TEXT_CORNER_ICON_ROTATION_MIN_DEGREES_PER_SECOND 18
      #define DEMO_TEXT_CORNER_ICON_ROTATION_MAX_DEGREES_PER_SECOND 52
      // Segment 6 -- filled rotating geometric solids. A random catalogue mesh and a
      // new colour scheme appear every five seconds; B chooses another mesh immediately.
      // The mesh coordinates are scaled before projection;
      // keep the radius below the camera distance so the solid cannot cross the camera.
      #define DEMO_STAGE_SIX_SOLID_SCALE 56.0f
      #define DEMO_STAGE_SIX_CAMERA_DISTANCE 260.0f
      // This is the centre behind the bottom ticker. Change only these two values to
      // reposition every generic mesh without editing its vertex data.
      #define DEMO_STAGE_SIX_CENTRE_X (LCD_WIDTH / 2)
      #define DEMO_STAGE_SIX_CENTRE_Y (LCD_HEIGHT / 2)
      // Segment 7 -- a direct PET-character tribute to the 1978 "Walloons" listing.
      // Every coordinate below is measured in PET cells: the Mini Arcade display is
      // 40 cells wide by 21 cells high at the PET ROM's native 8 x 8 cell size.
      // Increase the step interval to make the original stop-motion movement slower.
      #define DEMO_STAGE_SEVEN_STEP_MS 120U
      #define DEMO_STAGE_SEVEN_TITLE_MS 2000U
      #define DEMO_STAGE_SEVEN_PAUSE_MS 650U
      #define DEMO_STAGE_SEVEN_LANDING_MS 360U
      #define DEMO_STAGE_SEVEN_BOUNCE_COUNT 3U
      #define DEMO_STAGE_SEVEN_PET_COLOUR C64_COLOR_LIGHT_GREEN
      // Raise the complete PET set by two 8-pixel rows so its lowest fulcrum cell
      // remains above Demo's 16-pixel bottom ticker. Keep this negative while the
      // Walloons segment shares the normal scroller.
      #define DEMO_STAGE_SEVEN_ROW_OFFSET -2
      // PET glyph 64 (0x40) is a one-pixel horizontal line. Do not use the frame's
      // glyph 127 here: it is a block/checker pattern rather than a seesaw beam.
      #define DEMO_STAGE_SEVEN_SEESAW_LINE_GLYPH 64U
      // Segment 11 -- a C64-style rolling raster-band backdrop beneath a centred PET scroller.
      // The scroller remains at normal size and keeps its per-glyph sine wave; move
      // only this centre value to raise or lower the whole text line.
      #define DEMO_STAGE_ELEVEN_SCROLL_CENTRE_Y (LCD_HEIGHT / 2)
      #define DEMO_STAGE_ELEVEN_SCROLL_WAVE_AMPLITUDE 8
      // The full-width band block is one third of the display high and drifts from
      // the top screen edge to the bottom edge. One-pixel bands recreate the fine
      // metallic scanlines of the reference image.
      #define DEMO_STAGE_ELEVEN_BAND_BLOCK_HEIGHT (LCD_HEIGHT / 3)
      #define DEMO_STAGE_ELEVEN_BAND_HEIGHT 1
      #define DEMO_STAGE_ELEVEN_BAND_DRIFT_MS 5200U
      // The metallic profile shifts by one scanline at this interval, independently
      // of the bar's physical travel. Lower values make its colours roll faster.
      #define DEMO_STAGE_ELEVEN_COLOUR_ROLL_MS 28U
      // Segment 15 reuses the metallic rolling palette below, but keeps it fixed
      // across the whole lower half while its larger rainbow text crosses the middle.
      // The larger advance prevents the scale-3 PET letters from overlapping.
      #define DEMO_STAGE_FOURTEEN_BAND_TOP (LCD_HEIGHT / 2)
      #define DEMO_STAGE_FOURTEEN_BAND_HEIGHT (LCD_HEIGHT - DEMO_STAGE_FOURTEEN_BAND_TOP)
      #define DEMO_STAGE_FOURTEEN_SCROLL_SCALE 3.0f
      #define DEMO_STAGE_FOURTEEN_SCROLL_WAVE_AMPLITUDE 10
      // Keep the lowest point of the sine wave this many pixels above the raster bar.
      // The centre position is derived so changing the scale or wave amplitude keeps
      // that clear gap automatically.
      #define DEMO_STAGE_FOURTEEN_SCROLL_GAP_PIXELS 1
      #define DEMO_STAGE_FOURTEEN_SCROLL_PIXEL_HEIGHT \
          (PET2001_FONT_GLYPH_HEIGHT * (int)DEMO_STAGE_FOURTEEN_SCROLL_SCALE)
      #define DEMO_STAGE_FOURTEEN_SCROLL_CENTRE_Y \
          (DEMO_STAGE_FOURTEEN_BAND_TOP - DEMO_STAGE_FOURTEEN_SCROLL_GAP_PIXELS - \
           (DEMO_STAGE_FOURTEEN_SCROLL_PIXEL_HEIGHT / 2) - \
           DEMO_STAGE_FOURTEEN_SCROLL_WAVE_AMPLITUDE)
      // Segment 16 -- render the normal Battle Zone landscape, then scale its ground
      // portion into the lower half of this Demo frame. The compact raster remains in
      // the clear top half, and the scale-2 PET scroller ends at the halfway horizon.
      #define DEMO_STAGE_FIFTEEN_HORIZON_Y (LCD_HEIGHT / 2)
      #define DEMO_STAGE_FIFTEEN_BAND_BLOCK_HEIGHT (LCD_HEIGHT / 6)
      #define DEMO_STAGE_FIFTEEN_BAND_DRIFT_MS 5200U
      // One complete ground-texture journey from the horizon to the viewer. Lower
      // values make the Battle Zone terrain appear to travel forward faster.
      #define DEMO_STAGE_FIFTEEN_FORWARD_CYCLE_MS 3000U
      #define DEMO_STAGE_FIFTEEN_SCROLL_WAVE_AMPLITUDE 8
      #define DEMO_STAGE_FIFTEEN_SCROLL_GAP_PIXELS 1
      #define DEMO_STAGE_FIFTEEN_SCROLL_PIXEL_HEIGHT \
          (PET2001_FONT_GLYPH_HEIGHT * DEMO_SCROLL_SCALE)
      #define DEMO_STAGE_FIFTEEN_SCROLL_CENTRE_Y \
          (DEMO_STAGE_FIFTEEN_HORIZON_Y - DEMO_STAGE_FIFTEEN_SCROLL_GAP_PIXELS - \
           (DEMO_STAGE_FIFTEEN_SCROLL_PIXEL_HEIGHT / 2) - \
           DEMO_STAGE_FIFTEEN_SCROLL_WAVE_AMPLITUDE)
      // Segment 17 -- a triangular bevel-gear mechanism. The three wide, toothed
      // front rims taper into narrower rear tooth rings, so the assembly reads as
      // machinery extending backward into the screen rather than flat linked circles.
      // Visual tuning controls. Keep the tooth count even and the three gear centres
      // close enough that the two driven rims overlap the lead gear at the mesh points.
      #define DEMO_STAGE_SEVENTEEN_TOOTH_COUNT 12U
      #define DEMO_STAGE_SEVENTEEN_FRONT_RADIUS 36.0f
      #define DEMO_STAGE_SEVENTEEN_TOOTH_ROOT_RATIO 0.76f
      #define DEMO_STAGE_SEVENTEEN_REAR_RADIUS 15.0f
      #define DEMO_STAGE_SEVENTEEN_CONE_DEPTH 50.0f
      #define DEMO_STAGE_SEVENTEEN_CAMERA_DISTANCE 310.0f
      #define DEMO_STAGE_SEVENTEEN_CENTRE_X (LCD_WIDTH / 2)
      // Leave the lowest 16 pixels free for the permanent scale-2 PET ticker.
      #define DEMO_STAGE_SEVENTEEN_CENTRE_Y 76
      // The rig tumbles in place only; these rates deliberately do not move it toward
      // the viewer. Individual gear rates stay linked but alternate direction.
      #define DEMO_STAGE_SEVENTEEN_RIG_PITCH_DEGREES_PER_SECOND 12.0f
      #define DEMO_STAGE_SEVENTEEN_RIG_YAW_DEGREES_PER_SECOND 18.0f
      #define DEMO_STAGE_SEVENTEEN_RIG_ROLL_DEGREES_PER_SECOND 7.0f
      #define DEMO_STAGE_SEVENTEEN_GEAR_DEGREES_PER_SECOND 92.0f
      // The upper lead gear meshes with both lower driven gears. The lower pair keeps
      // a narrow deliberate gap: three external gears cannot all mesh pair-to-pair in
      // a closed triangle while retaining their required alternating rotation.
      #define DEMO_STAGE_SEVENTEEN_DRIVEN_X 32.0f
      #define DEMO_STAGE_SEVENTEEN_LEAD_Y -33.0f
      #define DEMO_STAGE_SEVENTEEN_DRIVEN_Y 22.0f
      // Segment 12 -- one randomly selected image from the existing slideshow
      // directory and moves a circular lens across it. The decoded source lives in
      // PSRAM only while this stage is active.
      #define DEMO_STAGE_TWELVE_SLIDESHOW_DIRECTORY "/spiffs/slideshow"
      #define DEMO_STAGE_TWELVE_IMAGE_PATH_MAX 128U
      // Lens size in pixels. Edge-to-edge routes deliberately allow part of this
      // circle to extend beyond the display, so it can travel across the full height.
      #define DEMO_STAGE_TWELVE_LENS_RADIUS 63
      // Image scale inside the lens. Values above 1.0 magnify; larger values show a
      // smaller source area and make the effect stronger.
      #define DEMO_STAGE_TWELVE_LENS_MAGNIFICATION 3.0f
      // Every new random path lasts MIN through MIN + RANGE - 1 milliseconds. Lower
      // both values for quicker movement while retaining the smooth glide between
      // targets. A new target begins immediately after the prior one completes.
      #define DEMO_STAGE_TWELVE_LENS_GLIDE_MIN_MS 1400U
      #define DEMO_STAGE_TWELVE_LENS_GLIDE_RANGE_MS 1200U
      // Reject edge targets closer than this many pixels to the current lens centre.
      // This prevents a random corner-to-corner route from looking like a small hop.
      #define DEMO_STAGE_TWELVE_LENS_MIN_TARGET_DISTANCE 48.0f
      #define DEMO_STAGE_TWELVE_LENS_TARGET_ATTEMPTS 8U
      // While Segment 12 is visible, load a different random slideshow image at
      // this interval. The same PSRAM buffer is reused; no second full-screen image
      // allocation is needed for the timed picture change.
      #define DEMO_STAGE_TWELVE_IMAGE_CHANGE_MS 10000U
      // Width and colour of the circle outline. Keep rim width below the radius.
      #define DEMO_STAGE_TWELVE_LENS_RIM_WIDTH 2
      #define DEMO_STAGE_TWELVE_LENS_RIM_COLOUR C64_COLOR_CYAN
      // Segment selector controls. The selector is an internal Demo pause screen, so its
      // joystick repeat is independent of both the menu and the active segment controls.
      #define DEMO_SELECTOR_JOYSTICK_ENTER 600
      #define DEMO_SELECTOR_JOYSTICK_RELEASE 350
      #define DEMO_SELECTOR_JOYSTICK_REPEAT_START_MS 400U
      #define DEMO_SELECTOR_JOYSTICK_REPEAT_MS 140U
      #define DEMO_SELECTOR_FIRST_ROW_Y 26
      #define DEMO_SELECTOR_ROW_HEIGHT 7
      // This is intentionally wider than the joystick control dead zones. It prevents a
      // slightly off-centre resting stick from continuously resetting the Demo timeout.
      // Lower it if a gentler stick movement should count as user interaction.
      #define DEMO_INACTIVITY_JOYSTICK_DEAD_ZONE 600
      // Stage 6 catalogue solids still change independently; this does not reset the
      // shared inactivity timer because it is an automatic, not user-triggered, change.
      #define DEMO_STAGE_SIX_CHANGE_MS 5000U
      // Move the whole effect. Keep the logo near the centre to avoid screen clipping.
      #define DEMO_CENTRE_X (LCD_WIDTH / 2)
      #define DEMO_CENTRE_Y (LCD_HEIGHT / 2)
      #define DEMO_PI 3.14159265f
      // Bitmap layout controls. Change these only when replacing both Demo bitmap assets.
      #define DEMO_LOGO_WIDTH 64U
      #define DEMO_LOGO_HEIGHT 64U
      #define DEMO_LOGO_ROW_BYTES (DEMO_LOGO_WIDTH / 8U)
      // Set to 1 to use the smaller 112 x 112 plane. This leaves margin during a full
      // Z-axis turn. The default 128 x 128 plane is larger and may briefly clip at its corners.
      #define DEMO_CONSTRAIN_TO_DISPLAY 0
      #if DEMO_CONSTRAIN_TO_DISPLAY
      #define DEMO_FACE_SCALE 1.75f
      #else
      #define DEMO_FACE_SCALE 2.0f
      #endif
      // 3D body width in projected source units. Keep depth an exact multiple of step.
      #define DEMO_EXTRUSION_DEPTH 24U
      // Distance between body slices. Smaller values look smoother but cost more drawing time.
      #define DEMO_EXTRUSION_STEP 3U
      // Perspective strength. Larger values flatten the 3D effect; smaller values exaggerate it.
      // Keep this positive and high enough that the enlarged logo remains on screen.
      #define DEMO_CAMERA_DISTANCE 320.0f
      // Dotted-sprite tuning. Face dots use one bitmap cell (four screen pixels) between
      // centres; this spacing controls only the dotted depth joins.
      #define DEMO_DOT_SPACING 6U // distance in pixles between each dot "face" 
      #define DEMO_DOT_RADIUS 1
      // Percentage of available dots shown on every letter row and depth join.
      // 100 draws all dots; lower values create a stable random sparse pattern.
      #define DEMO_DOT_DENSITY_PERCENT 50U
      // Change this only to choose a different stable sparse-dot pattern.
      #define DEMO_DOT_RANDOM_SEED 0x4A440001U
      // Starfield-style sparkle tuning. A small subset of front dots grows brighter
      // every phase; set density to zero to disable this optional extra.
      #define DEMO_DOT_SPARKLE_DENSITY_PERCENT 10U
      #define DEMO_DOT_SPARKLE_INTERVAL_MS 300U
      #define DEMO_DOT_SPARKLE_COLOR COLOR_LIGHTYELLOW
      #define DEMO_DOT_SPARKLE_RADIUS 2
      // Radial flame tuning. The inner edge follows the projected icon and keeps this gap.
      #define DEMO_RING_ICON_GAP 14.0f
      #define DEMO_RING_SEGMENTS 72U
      #define DEMO_RING_LAYERS 20U
      #define DEMO_RING_LAYER_DEPTH 2.0f
      // Wavy flame tuning. More crests make shorter repeating tongues; extra cooling
      // makes individual flames die out earlier. These values affect only the ring.
      #define DEMO_RING_WAVE_AMPLITUDE 55.0f
      #define DEMO_RING_WAVE_COUNT 5.0f
      #define DEMO_RING_WAVE_DEGREES_PER_SECOND 45.0f
      #define DEMO_RING_EXTRA_COOLING 8U
      // Segment 1 automatic effect timing. It selects the next A-button visual effect
      // after this interval.
      // Lower values make the opening sequence cycle faster; A remains available for manual changes.
      #define DEMO_STAGE_ONE_EFFECT_CHANGE_MS 2000U
      #define DEMO_EFFECT_COUNT 4U
      // Keep this at 0U while B and completed ticker messages may cycle icon slots.
      #define DEMO_LOCK_ICON_TO_PRIMARY 0U
      // Segment 2 -- approaching 3D icon over a perspective grid. Forward reduces
      // range and grows the icon; back increases range.
      #define DEMO_STAGE_TWO_START_DISTANCE 130.0f
      #define DEMO_STAGE_TWO_MIN_DISTANCE 16.0f
      #define DEMO_STAGE_TWO_MAX_DISTANCE 260.0f
      #define DEMO_STAGE_TWO_MAX_ICON_PIXELS 144
      #define DEMO_STAGE_TWO_JOYSTICK_DEAD_ZONE 250
      #define DEMO_STAGE_TWO_APPROACH_UNITS_PER_SECOND 95.0f
      // Segment 2 automatically travels between the distance limits while the vertical
      // stick is centred. Lower values make its approach and retreat more leisurely.
      #define DEMO_STAGE_TWO_AUTO_APPROACH_UNITS_PER_SECOND 35.0f
      // Segment 3 -- the Demo-only Amiga ball. The private ball keeps changing to the next Demo icon
      // at this interval until the shared Demo inactivity timer advances the segment.
      #define DEMO_STAGE_THREE_ICON_CHANGE_MS 5000U
      // Stage 3 ball tuning. These values are private to Demo and do not affect the
      // standalone Boing Ball module. Radius must leave room for the bottom ticker.
      #define DEMO_STAGE_THREE_BALL_RADIUS 32
      #define DEMO_STAGE_THREE_BALL_DIAMETER (DEMO_STAGE_THREE_BALL_RADIUS * 2 + 1)
      #define DEMO_STAGE_THREE_BALL_PIXEL_COUNT \
          (DEMO_STAGE_THREE_BALL_DIAMETER * DEMO_STAGE_THREE_BALL_DIAMETER)
      #define DEMO_STAGE_THREE_BALL_FLOOR_Y 145.0f
      #define DEMO_STAGE_THREE_BALL_GRAVITY 380.0f
      #define DEMO_STAGE_THREE_BALL_BOUNCE_VELOCITY -215.0f
      #define DEMO_STAGE_THREE_BALL_HORIZONTAL_VELOCITY 82.0f
      #define DEMO_STAGE_THREE_BALL_LONGITUDE_BANDS 16U
      #define DEMO_STAGE_THREE_BALL_ROTATION_BANDS_PER_SECOND 12.0f
      // Stage-transition tuning. The old frame pauses, then shrinks to its centre dot.
      #define DEMO_STAGE_TRANSITION_FREEZE_MS 180U
      #define DEMO_STAGE_TRANSITION_ZOOM_MS 620U
      // Incoming-stage tuning. The captured destination frame expands from the centre dot.
      #define DEMO_STAGE_ZOOM_IN_MS 620U
      // Scroll-transition tuning. Use opposite directions on one axis: LEFT/RIGHT,
      // RIGHT/LEFT, UP/DOWN, or DOWN/UP. A horizontal scroll has LCD_WIDTH steps;
      // a vertical scroll has LCD_HEIGHT steps, each of the size below.
      // Match Demo's 20 ms update cadence so each visible scroll update moves exactly this many pixels.
      #define DEMO_STAGE_SCROLL_STEP_MS 20U
      #define DEMO_STAGE_SCROLL_PIXELS_PER_STEP 4U
      // Gap between the two moving frames. Set this above 0U to leave a black band.
      #define DEMO_STAGE_SCROLL_GAP_PIXELS 0U
      // Slice-transition tuning. Slice count controls how many horizontal bands split
      // apart; duration controls how long all bands take to leave the display.
      #define DEMO_STAGE_SLICE_COUNT 12U
      #define DEMO_STAGE_SLICE_MS 620U
      // Rebuild tuning. This is the percentage of the incoming scene exposed as shuffled blocks
      // before its complete frame is shown.
      #define DEMO_STAGE_REBUILD_PERCENT 25U
      #define DEMO_STAGE_REBUILD_MS 420U
      
      typedef enum {
          DEMO_EFFECT_DOT_GRID,
          DEMO_EFFECT_3D_JD,
          DEMO_EFFECT_DOT_SPARKLE,
          DEMO_EFFECT_RADIAL_FLAME,
      } demo_effect_t;
      
      typedef enum {
          STAGE_SEVENTEEN_STYLE_OUTLINE,
          STAGE_SEVENTEEN_STYLE_FLAT,
          STAGE_SEVENTEEN_STYLE_METALLIC,
          STAGE_SEVENTEEN_STYLE_COUNT,
      } stage_seventeen_style_t;
      
      // Every gear contributes one tapered side plus front and rear wedges per
      // perimeter point. Keeping this list in PSRAM avoids a large temporary main-task stack.
      #define DEMO_STAGE_SEVENTEEN_DRAW_FACE_COUNT (3U * DEMO_STAGE_SEVENTEEN_TOOTH_COUNT * 6U)
      typedef struct {
          graphics_point_t points[4];
          float depth;
          uint16_t colour;
          uint8_t point_count;
      } stage_seventeen_draw_face_t;
      
      typedef enum {
          STAGE_TWELVE_LENS_EDGE_TOP,
          STAGE_TWELVE_LENS_EDGE_RIGHT,
          STAGE_TWELVE_LENS_EDGE_BOTTOM,
          STAGE_TWELVE_LENS_EDGE_LEFT,
          STAGE_TWELVE_LENS_EDGE_COUNT,
      } stage_twelve_lens_edge_t;
      
      // The original program moves character-cell address A by one to walk and by
      // forty to rise or fall. These named phases retain that choreography while
      // using elapsed milliseconds instead of BASIC FOR/NEXT delay loops.
      typedef enum {
          WALLOONS_TITLE_CARD,
          WALLOONS_LEFT_WALK_IN,
          WALLOONS_PAUSE_ON_SEESAW,
          WALLOONS_RIGHT_WALK_ON_PLATFORM,
          WALLOONS_RIGHT_DROP,
          WALLOONS_BOUNCE_RIGHT_UP,
          WALLOONS_BOUNCE_RIGHT_DOWN,
          WALLOONS_BOUNCE_LEFT_UP,
          WALLOONS_BOUNCE_LEFT_DOWN,
          WALLOONS_LEFT_LANDED,
          WALLOONS_FINISHED,
      } walloons_phase_t;
      
      typedef enum {
          // Freeze the outgoing frame, shrink it to a dot, then grow the next scene from a dot.
          DEMO_STAGE_TRANSITION_SHRINK_ZOOM,
          // Reveal a partial random 8 x 8 block pattern before showing the complete next scene.
          DEMO_STAGE_TRANSITION_REBUILD,
          // Move the old and new frames together in a randomly selected cardinal direction.
          DEMO_STAGE_TRANSITION_SCROLL,
          // Split the outgoing image into horizontal bands that leave on alternating sides.
          DEMO_STAGE_TRANSITION_SLICES,
          DEMO_STAGE_TRANSITION_EFFECT_COUNT,
      } demo_stage_transition_effect_t;
      
      // Packed 64 x 64 JD-and-circle character. Each row has eight bytes and uses
      // most-significant-bit-first pixel order. Set bits are the visible icon shape.
      static const uint8_t demo_jd_bitmap[DEMO_LOGO_HEIGHT * DEMO_LOGO_ROW_BYTES] = {
          0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
          0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
          0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
          0x00,0x00,0x00,0x3F,0xFC,0x00,0x00,0x00,
          0x00,0x00,0x03,0xFF,0xFF,0xC0,0x00,0x00,
          0x00,0x00,0x0F,0xF0,0x07,0xF0,0x00,0x00,
          0x00,0x00,0x7F,0x00,0x00,0xFE,0x00,0x00,
          0x00,0x00,0xFC,0x00,0x00,0x3F,0x00,0x00,
          0x00,0x01,0xE0,0x00,0x00,0x07,0x80,0x00,
          0x00,0x07,0xC0,0x00,0x00,0x03,0xE0,0x00,
          0x00,0x0F,0x00,0x00,0x00,0x00,0xF0,0x00,
          0x00,0x1E,0x00,0x00,0x00,0x00,0x78,0x00,
          0x00,0x3C,0x3F,0xFF,0xF8,0x00,0x3C,0x00,
          0x00,0x78,0x3F,0xFF,0xFC,0x00,0x1E,0x00,
          0x00,0x70,0x3F,0xFF,0xFC,0x00,0x0E,0x00,
          0x00,0xE0,0x03,0xFF,0x00,0x00,0x07,0x00,
          0x01,0xE0,0x00,0xFC,0x00,0x00,0x03,0x80,
          0x01,0xC0,0x00,0x78,0x00,0x00,0x03,0x80,
          0x03,0x80,0x00,0x78,0x00,0x00,0x01,0xC0,
          0x03,0x80,0x00,0x78,0x00,0x00,0x01,0xC0,
          0x07,0x00,0x00,0x79,0xFF,0xFC,0x00,0xE0,
          0x07,0x00,0x00,0x79,0xFF,0xFF,0x00,0xE0,
          0x0E,0x00,0x00,0x79,0xFF,0xFF,0x80,0x70,
          0x0E,0x00,0x00,0x79,0xF0,0x3F,0xC0,0x70,
          0x0C,0x00,0x00,0x79,0xE0,0x0F,0xE0,0x30,
          0x1C,0x00,0x00,0x79,0xE0,0x03,0xF0,0x38,
          0x1C,0x00,0x00,0x79,0xE0,0x03,0xF8,0x38,
          0x1C,0x00,0x00,0x79,0xE0,0x01,0xF8,0x38,
          0x1C,0x00,0x00,0x79,0xE0,0x00,0xFC,0x38,
          0x1C,0x00,0x00,0x79,0xE0,0x00,0xF8,0x38,
          0x1C,0x00,0x00,0x79,0xE0,0x00,0x7C,0x38,
          0x1C,0x00,0x00,0x79,0xE0,0x00,0x7C,0x38,
          0x1C,0x00,0x00,0x79,0xE0,0x00,0x3C,0x38,
          0x1C,0x00,0x00,0x79,0xE0,0x00,0x3C,0x38,
          0x1C,0x00,0x00,0x79,0xE0,0x00,0x3C,0x38,
          0x1C,0x00,0x00,0x79,0xE0,0x00,0x3C,0x38,
          0x1C,0x00,0x00,0x79,0xE0,0x00,0x3C,0x18,
          0x1C,0x03,0x80,0x79,0xE0,0x00,0x3C,0x38,
          0x1C,0x07,0xC0,0x79,0xE0,0x00,0x3C,0x38,
          0x0C,0x0F,0xE0,0x79,0xE0,0x00,0x7C,0x30,
          0x0E,0x0F,0xE0,0x79,0xE0,0x00,0x7C,0x70,
          0x0E,0x1F,0xE0,0xF9,0xE0,0x00,0xF8,0x70,
          0x07,0x1F,0xE0,0xF9,0xE0,0x00,0xF8,0xE0,
          0x07,0x1F,0x80,0xF1,0xE0,0x01,0xF8,0xE0,
          0x03,0x9E,0x01,0xF1,0xE0,0x03,0xF1,0xC0,
          0x03,0x8F,0x07,0xE1,0xE0,0x07,0xE1,0xC0,
          0x01,0xCF,0xFF,0xC3,0xE0,0x0F,0xC3,0x80,
          0x01,0xE7,0xFF,0x87,0xE0,0x7F,0x87,0x80,
          0x00,0xE1,0xFE,0x0F,0xFF,0xFE,0x0F,0x00,
          0x00,0x70,0x78,0x1F,0xFF,0xFC,0x0E,0x00,
          0x00,0x78,0x00,0x3F,0xFF,0xF0,0x1E,0x00,
          0x00,0x3C,0x00,0xFF,0xFF,0xC0,0x3C,0x00,
          0x00,0x1E,0x00,0x00,0x00,0x00,0xF8,0x00,
          0x00,0x0F,0x00,0x00,0x00,0x00,0xF0,0x00,
          0x00,0x07,0xC0,0x00,0x00,0x03,0xE0,0x00,
          0x00,0x01,0xF0,0x00,0x00,0x0F,0x80,0x00,
          0x00,0x00,0xFC,0x00,0x00,0x3F,0x00,0x00,
          0x00,0x00,0x7F,0x00,0x00,0xFE,0x00,0x00,
          0x00,0x00,0x1F,0xF0,0x0F,0xF8,0x00,0x00,
          0x00,0x00,0x03,0xFF,0xFF,0xC0,0x00,0x00,
          0x00,0x00,0x00,0x3F,0xFC,0x00,0x00,0x00,
          0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
          0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
          0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
      };
      
      // Reserved packed 64 x 64 alternate asset. It shares the primary bitmap's
      // dimensions and bit order, ready for a future replacement without renderer changes.
      static const uint8_t demo_alternate_bitmap[DEMO_LOGO_HEIGHT * DEMO_LOGO_ROW_BYTES] = {
          0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
          0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
          0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
          0x00,0x00,0x00,0xFF,0xFF,0x00,0x00,0x00,
          0x00,0x00,0x07,0xFF,0xFF,0xE0,0x00,0x00,
          0x00,0x00,0x1F,0x80,0x01,0xF8,0x00,0x00,
          0x00,0x00,0x7C,0x00,0x00,0x3E,0x00,0x00,
          0x00,0x00,0xF0,0x00,0x00,0x0F,0x00,0x00,
          0x00,0x03,0xC0,0x00,0x00,0x03,0xC0,0x00,
          0x00,0x07,0x00,0x00,0x00,0x00,0xE0,0x00,
          0x00,0x0E,0x00,0x00,0x00,0x00,0x70,0x00,
          0x00,0x1C,0x00,0x00,0x00,0x00,0x38,0x00,
          0x00,0x38,0x00,0x00,0x00,0x00,0x1C,0x00,
          0x00,0x70,0x00,0x00,0x00,0x00,0x0E,0x00,
          0x00,0xE0,0x00,0x00,0x00,0x00,0x07,0x00,
          0x00,0xC0,0x00,0x00,0x00,0x00,0x03,0x00,
          0x01,0x80,0x00,0x00,0x00,0x00,0x01,0x80,
          0x03,0x80,0x00,0x00,0x00,0x00,0x01,0xC0,
          0x03,0x00,0x00,0x00,0x00,0x00,0x00,0xC0,
          0x07,0x00,0x00,0x00,0x00,0x00,0x00,0xE0,
          0x06,0x07,0xE0,0x0F,0xC1,0xFF,0xE0,0x60,
          0x0E,0x07,0xE0,0x0F,0xC1,0xFF,0xE0,0x70,
          0x0C,0x07,0x70,0x1D,0xC0,0x1E,0x00,0x30,
          0x0C,0x07,0x70,0x1D,0xC0,0x1E,0x00,0x30,
          0x1C,0x07,0x38,0x39,0xC0,0x1E,0x00,0x38,
          0x18,0x07,0x38,0x39,0xC0,0x1E,0x00,0x18,
          0x18,0x07,0x1C,0x71,0xC0,0x1E,0x00,0x18,
          0x18,0x07,0x0E,0xE1,0xC0,0x1E,0x00,0x18,
          0x18,0x07,0x0E,0xE1,0xC0,0x1E,0x00,0x18,
          0x18,0x07,0x07,0xC1,0xC0,0x1E,0x00,0x18,
          0x18,0x07,0x07,0xC1,0xC0,0x1E,0x00,0x18,
          0x18,0x07,0x03,0x81,0xC0,0x1E,0x00,0x18,
          0x18,0x07,0x07,0xC1,0xC0,0x1E,0x00,0x18,
          0x18,0x07,0x00,0x01,0xC0,0x1E,0x00,0x18,
          0x18,0x07,0x00,0x01,0xC0,0x1E,0x00,0x18,
          0x18,0x07,0x00,0x01,0xC0,0x1E,0x00,0x18,
          0x18,0x07,0x00,0x01,0xC0,0x1E,0x00,0x18,
          0x18,0x07,0x00,0x01,0xC0,0x1E,0x00,0x18,
          0x18,0x07,0x00,0x01,0xC0,0x1E,0x00,0x18,
          0x1C,0x07,0x00,0x01,0xC0,0x1E,0x00,0x38,
          0x0C,0x07,0x00,0x01,0xC0,0x1E,0x00,0x30,
          0x0C,0x07,0x00,0x01,0xC0,0x1E,0x00,0x30,
          0x0E,0x07,0x00,0x01,0xC0,0x1E,0x00,0x70,
          0x06,0x07,0x00,0x01,0xC1,0xFF,0xE0,0x60,
          0x07,0x07,0x00,0x01,0xC1,0xFF,0xE0,0xE0,
          0x03,0x07,0x00,0x01,0xC0,0x00,0x00,0xC0,
          0x03,0x80,0x00,0x00,0x00,0x00,0x01,0xC0,
          0x01,0x80,0x00,0x00,0x00,0x00,0x01,0x80,
          0x00,0xC0,0x00,0x00,0x00,0x00,0x03,0x00,
          0x00,0xE0,0x00,0x00,0x00,0x00,0x07,0x00,
          0x00,0x70,0x00,0x00,0x00,0x00,0x0E,0x00,
          0x00,0x38,0x00,0x00,0x00,0x00,0x1C,0x00,
          0x00,0x1C,0x00,0x00,0x00,0x00,0x38,0x00,
          0x00,0x0E,0x00,0x00,0x00,0x00,0x70,0x00,
          0x00,0x07,0x00,0x00,0x00,0x00,0xE0,0x00,
          0x00,0x03,0xC0,0x00,0x00,0x03,0xC0,0x00,
          0x00,0x00,0xF0,0x00,0x00,0x0F,0x00,0x00,
          0x00,0x00,0x7C,0x00,0x00,0x3E,0x00,0x00,
          0x00,0x00,0x1F,0x80,0x01,0xF8,0x00,0x00,
          0x00,0x00,0x07,0xFF,0xFF,0xE0,0x00,0x00,
          0x00,0x00,0x00,0xFF,0xFF,0x00,0x00,0x00,
          0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
          0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
          0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
      };
      
      // Keep three icon slots. The new third slot begins as a duplicate of JD, ready
      // to point at its own 64 x 64 bitmap when the next icon is supplied.
      static const uint8_t *const demo_icon_slots[] = {
          demo_jd_bitmap,
          demo_alternate_bitmap,
          demo_jd_bitmap,
      };
      #define DEMO_ICON_SLOT_COUNT (sizeof(demo_icon_slots) / sizeof(demo_icon_slots[0]))
      
      // Top-left, top-right, bottom-left, bottom-right: user-selected order 1, 2, 3, 2.
      static const uint8_t demo_text_corner_icon_slots[] = {0U, 1U, 2U, 1U};
      static const int demo_text_corner_icon_x[] = {22, LCD_WIDTH - 23, 22, LCD_WIDTH - 23};
      static const int demo_text_corner_icon_y[] = {22, 22, LCD_HEIGHT - 23, LCD_HEIGHT - 23};
      #define DEMO_TEXT_CORNER_ICON_COUNT \
          (sizeof(demo_text_corner_icon_slots) / sizeof(demo_text_corner_icon_slots[0]))
      
      // PET-safe ASCII ticker messages. Curly quotes and the em dash from the source
      // list use normal C punctuation so every character has a PET glyph.
      static const char *const demo_scroll_messages[] = {
          "All that we see or seem is but a dream within a dream. ",
          "Deep into that darkness peering, long I stood there wondering, fearing. ",
          "There is no exquisite beauty without some strangeness in the proportion. ",
          "I became insane, with long intervals of horrible sanity. ",
          "Words have no power to impress the mind without the exquisite horror of their reality. ",
          "Believe nothing you hear, and only one half that you see. ",
          "Those who dream by day are cognizant of many things which escape those who dream only by night. ",
          "Nevermore. ",
          "----I have always loved all of my children and always will,.-----------",
          "And so being young and dipped in folly I fell in love with melancholy. ",
          "The best things in life make you sweaty. ",
          "A mystery and a dream should be enjoyed as such. ",
          "Science has not yet taught us if madness is or is not the sublimity of the intelligence. ",
          "The answer, is 42 ",
          "And Darkness and Decay and the Red Death held illimitable dominion over all. ",
          "I was never really insane except upon occasions when my heart was touched. ",
          "From a proud tower in the town  Death looks gigantically down. ",
          "Sleep, those little slices of death, how I loathe them. ",
          "And all my days are trances, And all my nightly dreams. ",
          "I was never kinder to the old man than during the whole week before I killed him. ",
          "The boundaries which divide Life from Death are at best shadowy and vague. ",
          "There is no beauty without some strangeness. ",
          "Years of love have been forgot, In the hatred of a minute. ",
          "And now was acknowledged the presence of the Red Death. ",
          "The rain came down upon my head , Unsheltered. And the wind rendered me mad. ",
          "The fever called Living is conquered at last. ",
          "Leave my loneliness unbroken. ",
          "Take thy beak from out my heart, and take thy form from off my door! ",
          "Quoth the Raven, Nevermore. ",
          "Deep in earth my love is lying.",
          "Thou wouldst be loved? then let thy heart  From its present pathway part. ",
          "Darkness there, and nothing more. ",
          "From a proud tower in the town  Death looks gigantically down. ",
          "And the life of the ebony bird beguiling my sad fancy into smiling. ",
          "All that we see or seem  Is but a dream within a dream.",
          "I fell in love with melancholy. ",
          "The skies they were ashen and sober. ",
          "And so being young and dipped in folly I fell in love with melancholy. ",
          "The world is a great ocean, upon which we encounter more storms than calms. ",
          "There are chords in the hearts of the most reckless which cannot be touched without emotion. ",
          "To die laughing must be the most glorious of all glorious deaths! ",
          "Stupidly staring at the floor. ",
          "And each separate dying ember wrought its ghost upon the floor. ",
          "Sorrow for the lost Lenore. ",
          "And the silken sad uncertain rustling of each purple curtain. ",
          "Once upon a midnight dreary, while I pondered, weak and weary. ",
          "And my soul from out that shadow that lies floating on the floor, Shall be lifted—nevermore! ",
      };
      #define DEMO_SCROLL_MESSAGE_COUNT \
          (sizeof(demo_scroll_messages) / sizeof(demo_scroll_messages[0]))
      
      // Bright C64 colours ordered around the ticker's moving rainbow band.
      static const uint16_t demo_scroll_rainbow_colours[] = {
          C64_COLOR_RED,
          C64_COLOR_ORANGE,
          C64_COLOR_YELLOW,
          C64_COLOR_LIGHT_GREEN,
          C64_COLOR_CYAN,
          C64_COLOR_LIGHT_PURPLE,
          C64_COLOR_PURPLE,
      };
      
      // A palette stays in flash because it is tiny, read-only data. The full RGB565
      // frame it colours is the large allocation and already lives in PSRAM.
      typedef graphics_raster_band_palette_t demo_raster_band_palette_t;
      
      // Stage 11's stationary-in-the-bar silver/lilac gradient. The block itself
      // moves, but this profile does not cycle: its dark purple edges and broad white
      // centre are the deliberate metallic appearance from the supplied reference.
      static const uint16_t demo_stage_eleven_band_colours[] = {
          C64_COLOR_LIGHT_GREY,
          RGB565(30, 20, 58),
          RGB565(46, 34, 82),
          RGB565(64, 52, 108),
          C64_COLOR_LIGHT_PURPLE,
          RGB565(126, 122, 154),
          C64_COLOR_GREY,
          C64_COLOR_LIGHT_GREY,
          RGB565(220, 220, 230),
          C64_COLOR_WHITE,
          RGB565(220, 220, 230),
          C64_COLOR_LIGHT_GREY,
          C64_COLOR_GREY,
          RGB565(126, 122, 154),
          C64_COLOR_LIGHT_PURPLE,
          RGB565(64, 52, 108),
          RGB565(46, 34, 82),
          RGB565(30, 20, 58),
          C64_COLOR_LIGHT_GREY,
      };
      
      // Segment 15's rolling flame gradient: dark embers at either edge, through
      // red and orange, to a hot yellow-white centre. Add future palettes here, then
      // give their colour table and length to drawRasterBandBlock().
      static const uint16_t demo_raster_fire_band_colours[] = {
          COLOR_BLACK,
          RGB565(48, 0, 0),
          RGB565(104, 0, 0),
          C64_COLOR_RED,
          RGB565(220, 48, 0),
          C64_COLOR_ORANGE,
          C64_COLOR_YELLOW,
          RGB565(255, 232, 128),
          C64_COLOR_WHITE,
          RGB565(255, 232, 128),
          C64_COLOR_YELLOW,
          C64_COLOR_ORANGE,
          RGB565(220, 48, 0),
          C64_COLOR_RED,
          RGB565(104, 0, 0),
          RGB565(48, 0, 0),
          COLOR_BLACK,
      };
      
      // Segment 16's Battle Zone palette: dark terrain green at both edges and a
      // bright lime horizon glow through the centre.
      static const uint16_t demo_raster_green_band_colours[] = {
          COLOR_BLACK,
          RGB565(0, 32, 0),
          COLOR_DARKGREEN,
          RGB565(0, 96, 0),
          C64_COLOR_GREEN,
          COLOR_LIME,
          RGB565(176, 255, 176),
          COLOR_LIME,
          C64_COLOR_GREEN,
          RGB565(0, 96, 0),
          COLOR_DARKGREEN,
          RGB565(0, 32, 0),
          COLOR_BLACK,
      };
      
      static const demo_raster_band_palette_t demo_raster_metallic_palette = {
          .colours = demo_stage_eleven_band_colours,
          .colour_count = sizeof(demo_stage_eleven_band_colours) /
                          sizeof(demo_stage_eleven_band_colours[0]),
      };
      
      static const demo_raster_band_palette_t demo_raster_fire_palette = {
          .colours = demo_raster_fire_band_colours,
          .colour_count = sizeof(demo_raster_fire_band_colours) /
                          sizeof(demo_raster_fire_band_colours[0]),
      };
      
      static const demo_raster_band_palette_t demo_raster_green_palette = {
          .colours = demo_raster_green_band_colours,
          .colour_count = sizeof(demo_raster_green_band_colours) /
                          sizeof(demo_raster_green_band_colours[0]),
      };
      
      // Keep every selectable raster palette in this one order. B advances through
      // the list, so adding a descriptor here automatically makes it selectable.
      #define DEMO_RASTER_METALLIC_PALETTE_INDEX 0U
      #define DEMO_RASTER_FIRE_PALETTE_INDEX 1U
      #define DEMO_RASTER_GREEN_PALETTE_INDEX 2U
      static const demo_raster_band_palette_t *const demo_raster_band_palettes[] = {
          &demo_raster_metallic_palette,
          &demo_raster_fire_palette,
          &demo_raster_green_palette,
      };
      #define DEMO_RASTER_BAND_PALETTE_COUNT \
          (sizeof(demo_raster_band_palettes) / sizeof(demo_raster_band_palettes[0]))
      
      typedef struct {
          uint16_t front;
          uint16_t depth;
          uint16_t dotted_back;
      } demo_icon_colour_scheme_t;
      
      typedef struct {
          uint16_t face_shades[4];
          uint16_t edge;
      } demo_solid_colour_scheme_t;
      
      // Demo icon colour themes. One is chosen at each Demo start and is shared by
      // the stage-1 icon, stage-2 approach icon, dotted icon effects, and corner icons.
      static const demo_icon_colour_scheme_t demo_icon_colour_schemes[] = {
          {C64_COLOR_LIGHT_GREEN, C64_COLOR_GREEN, C64_COLOR_ORANGE_BROWN},
          {C64_COLOR_YELLOW, C64_COLOR_ORANGE, C64_COLOR_ORANGE_BROWN},
          {C64_COLOR_SALMON, C64_COLOR_RED, C64_COLOR_ORANGE_BROWN},
          {C64_COLOR_LIGHT_PURPLE, C64_COLOR_PURPLE, C64_COLOR_BLUE},
          {C64_COLOR_CYAN, COLOR_CYAN, COLOR_DARKCYAN},
      };
      #define DEMO_ICON_COLOUR_SCHEME_COUNT \
          (sizeof(demo_icon_colour_schemes) / sizeof(demo_icon_colour_schemes[0]))
      
      // Segment-6 solid palettes. Faces use the four levels from brightest to darkest;
      // a different palette is selected whenever a new mesh is selected.
      static const demo_solid_colour_scheme_t demo_solid_colour_schemes[] = {
          {{C64_COLOR_LIGHT_GREEN, C64_COLOR_GREEN, RGB565(50, 105, 30), RGB565(18, 50, 14)},
           RGB565(15, 65, 12)},
          {{C64_COLOR_YELLOW, RGB565(180, 165, 35), C64_COLOR_ORANGE_BROWN, RGB565(52, 38, 0)},
           RGB565(72, 48, 0)},
          {{C64_COLOR_SALMON, C64_COLOR_RED, RGB565(105, 42, 35), RGB565(52, 18, 16)},
           RGB565(72, 25, 20)},
          {{C64_COLOR_LIGHT_PURPLE, C64_COLOR_PURPLE, RGB565(78, 40, 105), RGB565(38, 18, 55)},
           RGB565(52, 25, 72)},
          {{RGB565(125, 245, 255), RGB565(30, 185, 225), RGB565(12, 100, 170), RGB565(4, 42, 105)},
           COLOR_DARKCYAN},
      };
      #define DEMO_SOLID_COLOUR_SCHEME_COUNT \
          (sizeof(demo_solid_colour_schemes) / sizeof(demo_solid_colour_schemes[0]))
      
      typedef struct {
          float pitch_cosine;
          float pitch_sine;
          float yaw_cosine;
          float yaw_sine;
          float roll_cosine;
          float roll_sine;
      } demo_rotation_t;
      
      // Generic filled-mesh limits. The largest selected catalogue solid has 60 vertices,
      // 62 faces, and ten corners on a face. The renderer keeps extra headroom at 64.
      #define DEMO_MESH_MAX_VERTICES 64U
      #define DEMO_MESH_MAX_FACES 64U
      #define DEMO_MESH_MAX_FACE_VERTICES 10U
      
      typedef struct {
          float x;
          float y;
          float z;
      } demo_point_3d_t;
      
      typedef struct {
          uint8_t vertex_count;
          const uint8_t *vertex_indices;
      } demo_mesh_face_t;
      
      typedef struct {
          const demo_point_3d_t *vertices;
          size_t vertex_count;
          const demo_mesh_face_t *faces;
          size_t face_count;
      } demo_mesh_t;
      
      typedef struct {
          size_t face_index;
          float depth;
          float brightness;
      } demo_mesh_face_draw_t;
      
      // Stage 6 starts with this regular dodecahedron. Vertices use the standard
      // golden-ratio layout: eight cube corners plus twelve axis-aligned points.
      // Face indices stay separate from the renderer, so a later cube or another
      // solid only needs its own vertex and face tables. ( i tried other combos,. but its not pretty..jd)
      static const demo_point_3d_t demo_dodecahedron_vertices[] = {
          { 1.0f,  1.0f,  1.0f}, { 1.0f,  1.0f, -1.0f},
          { 1.0f, -1.0f,  1.0f}, { 1.0f, -1.0f, -1.0f},
          {-1.0f,  1.0f,  1.0f}, {-1.0f,  1.0f, -1.0f},
          {-1.0f, -1.0f,  1.0f}, {-1.0f, -1.0f, -1.0f},
          { 0.0f,  0.618034f,  1.618034f}, { 0.0f,  0.618034f, -1.618034f},
          { 0.0f, -0.618034f,  1.618034f}, { 0.0f, -0.618034f, -1.618034f},
          { 0.618034f,  1.618034f, 0.0f}, { 0.618034f, -1.618034f, 0.0f},
          {-0.618034f,  1.618034f, 0.0f}, {-0.618034f, -1.618034f, 0.0f},
          { 1.618034f, 0.0f,  0.618034f}, { 1.618034f, 0.0f, -0.618034f},
          {-1.618034f, 0.0f,  0.618034f}, {-1.618034f, 0.0f, -0.618034f},
      };
      
      static const uint8_t demo_dodecahedron_face_indices[][5] = {
          {0U, 8U, 10U, 2U, 16U}, {0U, 16U, 17U, 1U, 12U},
          {0U, 12U, 14U, 4U, 8U}, {8U, 4U, 18U, 6U, 10U},
          {10U, 6U, 15U, 13U, 2U}, {2U, 13U, 3U, 17U, 16U},
          {1U, 17U, 3U, 11U, 9U}, {1U, 9U, 5U, 14U, 12U},
          {4U, 14U, 5U, 19U, 18U}, {6U, 18U, 19U, 7U, 15U},
          {3U, 13U, 15U, 7U, 11U}, {5U, 9U, 11U, 7U, 19U},
      };
      static const demo_mesh_face_t demo_dodecahedron_faces[] = {
          {5U, demo_dodecahedron_face_indices[0]}, {5U, demo_dodecahedron_face_indices[1]},
          {5U, demo_dodecahedron_face_indices[2]}, {5U, demo_dodecahedron_face_indices[3]},
          {5U, demo_dodecahedron_face_indices[4]}, {5U, demo_dodecahedron_face_indices[5]},
          {5U, demo_dodecahedron_face_indices[6]}, {5U, demo_dodecahedron_face_indices[7]},
          {5U, demo_dodecahedron_face_indices[8]}, {5U, demo_dodecahedron_face_indices[9]},
          {5U, demo_dodecahedron_face_indices[10]}, {5U, demo_dodecahedron_face_indices[11]},
      };
      
      static const demo_mesh_t demo_dodecahedron_mesh = {
          .vertices = demo_dodecahedron_vertices,
          .vertex_count = sizeof(demo_dodecahedron_vertices) / sizeof(demo_dodecahedron_vertices[0]),
          .faces = demo_dodecahedron_faces,
          .face_count = sizeof(demo_dodecahedron_faces) / sizeof(demo_dodecahedron_faces[0]),
      };
      
      // The regular solids keep their familiar silhouette at their matching face totals.
      static const demo_point_3d_t demo_cube_vertices[] = {
          {-1.0f, -1.0f, -1.0f}, { 1.0f, -1.0f, -1.0f},
          { 1.0f,  1.0f, -1.0f}, {-1.0f,  1.0f, -1.0f},
          {-1.0f, -1.0f,  1.0f}, { 1.0f, -1.0f,  1.0f},
          { 1.0f,  1.0f,  1.0f}, {-1.0f,  1.0f,  1.0f},
      };
      static const uint8_t demo_cube_indices[][4] = {
          {0U, 1U, 2U, 3U}, {4U, 7U, 6U, 5U}, {0U, 4U, 5U, 1U},
          {1U, 5U, 6U, 2U}, {2U, 6U, 7U, 3U}, {3U, 7U, 4U, 0U},
      };
      static const demo_mesh_face_t demo_cube_faces[] = {
          {4U, demo_cube_indices[0]}, {4U, demo_cube_indices[1]}, {4U, demo_cube_indices[2]},
          {4U, demo_cube_indices[3]}, {4U, demo_cube_indices[4]}, {4U, demo_cube_indices[5]},
      };
      static const demo_mesh_t demo_cube_mesh = {
          demo_cube_vertices, sizeof(demo_cube_vertices) / sizeof(demo_cube_vertices[0]),
          demo_cube_faces, sizeof(demo_cube_faces) / sizeof(demo_cube_faces[0]),
      };
      
      static const demo_point_3d_t demo_octahedron_vertices[] = {
          { 0.0f,  1.414214f, 0.0f}, { 0.0f, -1.414214f, 0.0f},
          { 1.414214f, 0.0f, 0.0f}, {-1.414214f, 0.0f, 0.0f},
          { 0.0f, 0.0f,  1.414214f}, { 0.0f, 0.0f, -1.414214f},
      };
      static const uint8_t demo_octahedron_indices[][3] = {
          {0U, 2U, 4U}, {0U, 4U, 3U}, {0U, 3U, 5U}, {0U, 5U, 2U},
          {1U, 4U, 2U}, {1U, 3U, 4U}, {1U, 5U, 3U}, {1U, 2U, 5U},
      };
      static const demo_mesh_face_t demo_octahedron_faces[] = {
          {3U, demo_octahedron_indices[0]}, {3U, demo_octahedron_indices[1]},
          {3U, demo_octahedron_indices[2]}, {3U, demo_octahedron_indices[3]},
          {3U, demo_octahedron_indices[4]}, {3U, demo_octahedron_indices[5]},
          {3U, demo_octahedron_indices[6]}, {3U, demo_octahedron_indices[7]},
      };
      static const demo_mesh_t demo_octahedron_mesh = {
          demo_octahedron_vertices, sizeof(demo_octahedron_vertices) / sizeof(demo_octahedron_vertices[0]),
          demo_octahedron_faces, sizeof(demo_octahedron_faces) / sizeof(demo_octahedron_faces[0]),
      };
      
      static const demo_point_3d_t demo_icosahedron_vertices[] = {
          {-1.0f, 1.618034f, 0.0f}, { 1.0f, 1.618034f, 0.0f},
          {-1.0f,-1.618034f, 0.0f}, { 1.0f,-1.618034f, 0.0f},
          { 0.0f,-1.0f, 1.618034f}, { 0.0f, 1.0f, 1.618034f},
          { 0.0f,-1.0f,-1.618034f}, { 0.0f, 1.0f,-1.618034f},
          { 1.618034f, 0.0f,-1.0f}, { 1.618034f, 0.0f, 1.0f},
          {-1.618034f, 0.0f,-1.0f}, {-1.618034f, 0.0f, 1.0f},
      };
      static const uint8_t demo_icosahedron_indices[][3] = {
          {0U,11U,5U}, {0U,5U,1U}, {0U,1U,7U}, {0U,7U,10U}, {0U,10U,11U},
          {1U,5U,9U}, {5U,11U,4U}, {11U,10U,2U}, {10U,7U,6U}, {7U,1U,8U},
          {3U,9U,4U}, {3U,4U,2U}, {3U,2U,6U}, {3U,6U,8U}, {3U,8U,9U},
          {4U,9U,5U}, {2U,4U,11U}, {6U,2U,10U}, {8U,6U,7U}, {9U,8U,1U},
      };
      static const demo_mesh_face_t demo_icosahedron_faces[] = {
          {3U, demo_icosahedron_indices[0]}, {3U, demo_icosahedron_indices[1]},
          {3U, demo_icosahedron_indices[2]}, {3U, demo_icosahedron_indices[3]},
          {3U, demo_icosahedron_indices[4]}, {3U, demo_icosahedron_indices[5]},
          {3U, demo_icosahedron_indices[6]}, {3U, demo_icosahedron_indices[7]},
          {3U, demo_icosahedron_indices[8]}, {3U, demo_icosahedron_indices[9]},
          {3U, demo_icosahedron_indices[10]}, {3U, demo_icosahedron_indices[11]},
          {3U, demo_icosahedron_indices[12]}, {3U, demo_icosahedron_indices[13]},
          {3U, demo_icosahedron_indices[14]}, {3U, demo_icosahedron_indices[15]},
          {3U, demo_icosahedron_indices[16]}, {3U, demo_icosahedron_indices[17]},
          {3U, demo_icosahedron_indices[18]}, {3U, demo_icosahedron_indices[19]},
      };
      static const demo_mesh_t demo_icosahedron_mesh = {
          demo_icosahedron_vertices, sizeof(demo_icosahedron_vertices) / sizeof(demo_icosahedron_vertices[0]),
          demo_icosahedron_faces, sizeof(demo_icosahedron_faces) / sizeof(demo_icosahedron_faces[0]),
      };
      
      // Archimedean solid: truncated tetrahedron.
      static const demo_point_3d_t demo_truncated_tetrahedron_vertices[] = {
          {0.209877f, 0.749559f, -0.209877f},
          {0.749559f, 0.209877f, -0.209877f},
          {0.209877f, 0.209877f, -0.749559f},
          {-0.209877f, 0.209877f, 0.749559f},
          {-0.209877f, 0.749559f, 0.209877f},
          {-0.749559f, 0.209877f, 0.209877f},
          {0.749559f, -0.209877f, 0.209877f},
          {0.209877f, -0.209877f, 0.749559f},
          {0.209877f, -0.749559f, 0.209877f},
          {-0.209877f, -0.209877f, -0.749559f},
          {-0.209877f, -0.749559f, -0.209877f},
          {-0.749559f, -0.209877f, -0.209877f},
      };
      static const uint8_t demo_truncated_tetrahedron_face_indices[][6] = {
          {0U, 4U, 3U, 7U, 6U, 1U},
          {1U, 6U, 8U, 10U, 9U, 2U},
          {0U, 2U, 9U, 11U, 5U, 4U},
          {3U, 5U, 11U, 10U, 8U, 7U},
          {0U, 1U, 2U},
          {3U, 4U, 5U},
          {6U, 7U, 8U},
          {9U, 10U, 11U},
      };
      static const demo_mesh_face_t demo_truncated_tetrahedron_faces[] = {
          {6U, demo_truncated_tetrahedron_face_indices[0]},
          {6U, demo_truncated_tetrahedron_face_indices[1]},
          {6U, demo_truncated_tetrahedron_face_indices[2]},
          {6U, demo_truncated_tetrahedron_face_indices[3]},
          {3U, demo_truncated_tetrahedron_face_indices[4]},
          {3U, demo_truncated_tetrahedron_face_indices[5]},
          {3U, demo_truncated_tetrahedron_face_indices[6]},
          {3U, demo_truncated_tetrahedron_face_indices[7]},
      };
      static const demo_mesh_t demo_truncated_tetrahedron_mesh = {
          demo_truncated_tetrahedron_vertices, sizeof(demo_truncated_tetrahedron_vertices) / sizeof(demo_truncated_tetrahedron_vertices[0]),
          demo_truncated_tetrahedron_faces, sizeof(demo_truncated_tetrahedron_faces) / sizeof(demo_truncated_tetrahedron_faces[0]),
      };
      
      // Archimedean solid: cuboctahedron.
      static const demo_point_3d_t demo_cuboctahedron_vertices[] = {
          {-0.707000f, 0.000000f, 0.707000f},
          {0.000000f, -0.707000f, 0.707000f},
          {0.000000f, 0.707000f, 0.707000f},
          {0.707000f, 0.000000f, 0.707000f},
          {0.707000f, -0.707000f, 0.000000f},
          {0.707000f, 0.000000f, -0.707000f},
          {0.707000f, 0.707000f, 0.000000f},
          {0.000000f, 0.707000f, -0.707000f},
          {-0.707000f, 0.707000f, 0.000000f},
          {-0.707000f, 0.000000f, -0.707000f},
          {-0.707000f, -0.707000f, 0.000000f},
          {0.000000f, -0.707000f, -0.707000f},
      };
      static const uint8_t demo_cuboctahedron_face_indices[][4] = {
          {0U, 1U, 3U, 2U},
          {1U, 0U, 10U},
          {3U, 1U, 4U},
          {2U, 3U, 6U},
          {0U, 2U, 8U},
          {6U, 3U, 4U, 5U},
          {5U, 4U, 11U},
          {6U, 5U, 7U},
          {8U, 2U, 6U, 7U},
          {8U, 7U, 9U},
          {10U, 0U, 8U, 9U},
          {10U, 9U, 11U},
          {4U, 1U, 10U, 11U},
          {9U, 7U, 5U, 11U},
      };
      static const demo_mesh_face_t demo_cuboctahedron_faces[] = {
          {4U, demo_cuboctahedron_face_indices[0]},
          {3U, demo_cuboctahedron_face_indices[1]},
          {3U, demo_cuboctahedron_face_indices[2]},
          {3U, demo_cuboctahedron_face_indices[3]},
          {3U, demo_cuboctahedron_face_indices[4]},
          {4U, demo_cuboctahedron_face_indices[5]},
          {3U, demo_cuboctahedron_face_indices[6]},
          {3U, demo_cuboctahedron_face_indices[7]},
          {4U, demo_cuboctahedron_face_indices[8]},
          {3U, demo_cuboctahedron_face_indices[9]},
          {4U, demo_cuboctahedron_face_indices[10]},
          {3U, demo_cuboctahedron_face_indices[11]},
          {4U, demo_cuboctahedron_face_indices[12]},
          {4U, demo_cuboctahedron_face_indices[13]},
      };
      static const demo_mesh_t demo_cuboctahedron_mesh = {
          demo_cuboctahedron_vertices, sizeof(demo_cuboctahedron_vertices) / sizeof(demo_cuboctahedron_vertices[0]),
          demo_cuboctahedron_faces, sizeof(demo_cuboctahedron_faces) / sizeof(demo_cuboctahedron_faces[0]),
      };
      
      // Archimedean solid: truncated cube.
      static const demo_point_3d_t demo_truncated_cube_vertices[] = {
          {0.192025f, 0.624080f, 0.624080f},
          {0.624080f, 0.192025f, 0.624080f},
          {0.624080f, 0.624080f, 0.192025f},
          {-0.624080f, 0.192025f, 0.624080f},
          {-0.192025f, 0.624080f, 0.624080f},
          {-0.624080f, 0.624080f, 0.192025f},
          {-0.192025f, -0.624080f, 0.624080f},
          {-0.624080f, -0.192025f, 0.624080f},
          {-0.624080f, -0.624080f, 0.192025f},
          {0.624080f, -0.192025f, 0.624080f},
          {0.192025f, -0.624080f, 0.624080f},
          {0.624080f, -0.624080f, 0.192025f},
          {0.624080f, -0.192025f, -0.624080f},
          {0.624080f, -0.624080f, -0.192025f},
          {0.192025f, -0.624080f, -0.624080f},
          {0.624080f, 0.624080f, -0.192025f},
          {0.624080f, 0.192025f, -0.624080f},
          {0.192025f, 0.624080f, -0.624080f},
          {-0.624080f, 0.624080f, -0.192025f},
          {-0.192025f, 0.624080f, -0.624080f},
          {-0.624080f, 0.192025f, -0.624080f},
          {-0.624080f, -0.624080f, -0.192025f},
          {-0.624080f, -0.192025f, -0.624080f},
          {-0.192025f, -0.624080f, -0.624080f},
      };
      static const uint8_t demo_truncated_cube_face_indices[][8] = {
          {0U, 4U, 3U, 7U, 6U, 10U, 9U, 1U},
          {1U, 9U, 11U, 13U, 12U, 16U, 15U, 2U},
          {0U, 2U, 15U, 17U, 19U, 18U, 5U, 4U},
          {3U, 5U, 18U, 20U, 22U, 21U, 8U, 7U},
          {6U, 8U, 21U, 23U, 14U, 13U, 11U, 10U},
          {12U, 14U, 23U, 22U, 20U, 19U, 17U, 16U},
          {0U, 1U, 2U},
          {3U, 4U, 5U},
          {6U, 7U, 8U},
          {9U, 10U, 11U},
          {12U, 13U, 14U},
          {15U, 16U, 17U},
          {18U, 19U, 20U},
          {21U, 22U, 23U},
      };
      static const demo_mesh_face_t demo_truncated_cube_faces[] = {
          {8U, demo_truncated_cube_face_indices[0]},
          {8U, demo_truncated_cube_face_indices[1]},
          {8U, demo_truncated_cube_face_indices[2]},
          {8U, demo_truncated_cube_face_indices[3]},
          {8U, demo_truncated_cube_face_indices[4]},
          {8U, demo_truncated_cube_face_indices[5]},
          {3U, demo_truncated_cube_face_indices[6]},
          {3U, demo_truncated_cube_face_indices[7]},
          {3U, demo_truncated_cube_face_indices[8]},
          {3U, demo_truncated_cube_face_indices[9]},
          {3U, demo_truncated_cube_face_indices[10]},
          {3U, demo_truncated_cube_face_indices[11]},
          {3U, demo_truncated_cube_face_indices[12]},
          {3U, demo_truncated_cube_face_indices[13]},
      };
      static const demo_mesh_t demo_truncated_cube_mesh = {
          demo_truncated_cube_vertices, sizeof(demo_truncated_cube_vertices) / sizeof(demo_truncated_cube_vertices[0]),
          demo_truncated_cube_faces, sizeof(demo_truncated_cube_faces) / sizeof(demo_truncated_cube_faces[0]),
      };
      
      // Archimedean solid: truncated octahedron.
      static const demo_point_3d_t demo_truncated_octahedron_vertices[] = {
          {0.000000f, -0.408489f, 0.748896f},
          {0.408489f, 0.000000f, 0.748896f},
          {0.000000f, 0.408489f, 0.748896f},
          {-0.408489f, 0.000000f, 0.748896f},
          {0.748896f, 0.000000f, 0.408489f},
          {0.748896f, -0.408489f, 0.000000f},
          {0.748896f, 0.000000f, -0.408489f},
          {0.748896f, 0.408489f, 0.000000f},
          {0.000000f, 0.748896f, 0.408489f},
          {0.408489f, 0.748896f, 0.000000f},
          {0.000000f, 0.748896f, -0.408489f},
          {-0.408489f, 0.748896f, 0.000000f},
          {-0.748896f, 0.000000f, 0.408489f},
          {-0.748896f, 0.408489f, 0.000000f},
          {-0.748896f, 0.000000f, -0.408489f},
          {-0.748896f, -0.408489f, 0.000000f},
          {0.000000f, -0.748896f, 0.408489f},
          {-0.408489f, -0.748896f, 0.000000f},
          {0.000000f, -0.748896f, -0.408489f},
          {0.408489f, -0.748896f, 0.000000f},
          {0.000000f, 0.408489f, -0.748896f},
          {0.408489f, 0.000000f, -0.748896f},
          {0.000000f, -0.408489f, -0.748896f},
          {-0.408489f, 0.000000f, -0.748896f},
      };
      static const uint8_t demo_truncated_octahedron_face_indices[][6] = {
          {1U, 4U, 7U, 9U, 8U, 2U},
          {2U, 8U, 11U, 13U, 12U, 3U},
          {0U, 3U, 12U, 15U, 17U, 16U},
          {0U, 16U, 19U, 5U, 4U, 1U},
          {5U, 19U, 18U, 22U, 21U, 6U},
          {6U, 21U, 20U, 10U, 9U, 7U},
          {10U, 20U, 23U, 14U, 13U, 11U},
          {14U, 23U, 22U, 18U, 17U, 15U},
          {0U, 1U, 2U, 3U},
          {4U, 5U, 6U, 7U},
          {8U, 9U, 10U, 11U},
          {12U, 13U, 14U, 15U},
          {16U, 17U, 18U, 19U},
          {20U, 21U, 22U, 23U},
      };
      static const demo_mesh_face_t demo_truncated_octahedron_faces[] = {
          {6U, demo_truncated_octahedron_face_indices[0]},
          {6U, demo_truncated_octahedron_face_indices[1]},
          {6U, demo_truncated_octahedron_face_indices[2]},
          {6U, demo_truncated_octahedron_face_indices[3]},
          {6U, demo_truncated_octahedron_face_indices[4]},
          {6U, demo_truncated_octahedron_face_indices[5]},
          {6U, demo_truncated_octahedron_face_indices[6]},
          {6U, demo_truncated_octahedron_face_indices[7]},
          {4U, demo_truncated_octahedron_face_indices[8]},
          {4U, demo_truncated_octahedron_face_indices[9]},
          {4U, demo_truncated_octahedron_face_indices[10]},
          {4U, demo_truncated_octahedron_face_indices[11]},
          {4U, demo_truncated_octahedron_face_indices[12]},
          {4U, demo_truncated_octahedron_face_indices[13]},
      };
      static const demo_mesh_t demo_truncated_octahedron_mesh = {
          demo_truncated_octahedron_vertices, sizeof(demo_truncated_octahedron_vertices) / sizeof(demo_truncated_octahedron_vertices[0]),
          demo_truncated_octahedron_faces, sizeof(demo_truncated_octahedron_faces) / sizeof(demo_truncated_octahedron_faces[0]),
      };
      
      // Archimedean solid: rhombicuboctahedron.
      static const demo_point_3d_t demo_rhombicuboctahedron_vertices[] = {
          {-0.306915f, -0.306915f, 0.745364f},
          {0.306915f, -0.306915f, 0.745364f},
          {-0.745364f, -0.306915f, 0.306915f},
          {0.306915f, -0.745364f, 0.306915f},
          {0.745364f, 0.306915f, 0.306915f},
          {0.306915f, 0.306915f, 0.745364f},
          {-0.306915f, 0.745364f, 0.306915f},
          {-0.306915f, 0.306915f, 0.745364f},
          {0.745364f, -0.306915f, -0.306915f},
          {0.745364f, 0.306915f, -0.306915f},
          {0.745364f, -0.306915f, 0.306915f},
          {0.306915f, -0.745364f, -0.306915f},
          {0.306915f, 0.306915f, -0.745364f},
          {0.306915f, 0.745364f, -0.306915f},
          {-0.306915f, 0.745364f, -0.306915f},
          {0.306915f, 0.745364f, 0.306915f},
          {-0.306915f, 0.306915f, -0.745364f},
          {-0.745364f, 0.306915f, -0.306915f},
          {-0.745364f, -0.306915f, -0.306915f},
          {-0.745364f, 0.306915f, 0.306915f},
          {-0.306915f, -0.306915f, -0.745364f},
          {-0.306915f, -0.745364f, -0.306915f},
          {-0.306915f, -0.745364f, 0.306915f},
          {0.306915f, -0.306915f, -0.745364f},
      };
      static const uint8_t demo_rhombicuboctahedron_face_indices[][4] = {
          {5U, 7U, 0U, 1U},
          {7U, 5U, 15U, 6U},
          {0U, 7U, 19U, 2U},
          {1U, 0U, 22U, 3U},
          {5U, 1U, 10U, 4U},
          {2U, 22U, 0U},
          {22U, 2U, 18U, 21U},
          {3U, 10U, 1U},
          {10U, 3U, 11U, 8U},
          {4U, 15U, 5U},
          {15U, 4U, 9U, 13U},
          {6U, 19U, 7U},
          {19U, 6U, 14U, 17U},
          {8U, 9U, 4U, 10U},
          {9U, 8U, 23U, 12U},
          {11U, 23U, 8U},
          {23U, 11U, 21U, 20U},
          {12U, 13U, 9U},
          {13U, 12U, 16U, 14U},
          {13U, 14U, 6U, 15U},
          {16U, 17U, 14U},
          {17U, 16U, 20U, 18U},
          {17U, 18U, 2U, 19U},
          {20U, 21U, 18U},
          {21U, 11U, 3U, 22U},
          {23U, 20U, 16U, 12U},
      };
      static const demo_mesh_face_t demo_rhombicuboctahedron_faces[] = {
          {4U, demo_rhombicuboctahedron_face_indices[0]},
          {4U, demo_rhombicuboctahedron_face_indices[1]},
          {4U, demo_rhombicuboctahedron_face_indices[2]},
          {4U, demo_rhombicuboctahedron_face_indices[3]},
          {4U, demo_rhombicuboctahedron_face_indices[4]},
          {3U, demo_rhombicuboctahedron_face_indices[5]},
          {4U, demo_rhombicuboctahedron_face_indices[6]},
          {3U, demo_rhombicuboctahedron_face_indices[7]},
          {4U, demo_rhombicuboctahedron_face_indices[8]},
          {3U, demo_rhombicuboctahedron_face_indices[9]},
          {4U, demo_rhombicuboctahedron_face_indices[10]},
          {3U, demo_rhombicuboctahedron_face_indices[11]},
          {4U, demo_rhombicuboctahedron_face_indices[12]},
          {4U, demo_rhombicuboctahedron_face_indices[13]},
          {4U, demo_rhombicuboctahedron_face_indices[14]},
          {3U, demo_rhombicuboctahedron_face_indices[15]},
          {4U, demo_rhombicuboctahedron_face_indices[16]},
          {3U, demo_rhombicuboctahedron_face_indices[17]},
          {4U, demo_rhombicuboctahedron_face_indices[18]},
          {4U, demo_rhombicuboctahedron_face_indices[19]},
          {3U, demo_rhombicuboctahedron_face_indices[20]},
          {4U, demo_rhombicuboctahedron_face_indices[21]},
          {4U, demo_rhombicuboctahedron_face_indices[22]},
          {3U, demo_rhombicuboctahedron_face_indices[23]},
          {4U, demo_rhombicuboctahedron_face_indices[24]},
          {4U, demo_rhombicuboctahedron_face_indices[25]},
      };
      static const demo_mesh_t demo_rhombicuboctahedron_mesh = {
          demo_rhombicuboctahedron_vertices, sizeof(demo_rhombicuboctahedron_vertices) / sizeof(demo_rhombicuboctahedron_vertices[0]),
          demo_rhombicuboctahedron_faces, sizeof(demo_rhombicuboctahedron_faces) / sizeof(demo_rhombicuboctahedron_faces[0]),
      };
      
      // Archimedean solid: truncated cuboctahedron.
      static const demo_point_3d_t demo_truncated_cuboctahedron_vertices[] = {
          {-0.291952f, -0.133283f, 0.538772f},
          {-0.291952f, 0.133283f, 0.538772f},
          {-0.538772f, 0.133283f, 0.291952f},
          {-0.538772f, -0.133283f, 0.291952f},
          {0.133283f, -0.291952f, 0.538772f},
          {-0.133283f, -0.291952f, 0.538772f},
          {-0.133283f, -0.538772f, 0.291952f},
          {0.133283f, -0.538772f, 0.291952f},
          {-0.133283f, 0.291952f, 0.538772f},
          {0.133283f, 0.291952f, 0.538772f},
          {0.133283f, 0.538772f, 0.291952f},
          {-0.133283f, 0.538772f, 0.291952f},
          {0.291952f, 0.133283f, 0.538772f},
          {0.291952f, -0.133283f, 0.538772f},
          {0.538772f, -0.133283f, 0.291952f},
          {0.538772f, 0.133283f, 0.291952f},
          {0.538772f, -0.291952f, 0.133283f},
          {0.291952f, -0.538772f, 0.133283f},
          {0.291952f, -0.538772f, -0.133283f},
          {0.538772f, -0.291952f, -0.133283f},
          {0.538772f, 0.133283f, -0.291952f},
          {0.538772f, -0.133283f, -0.291952f},
          {0.291952f, -0.133283f, -0.538772f},
          {0.291952f, 0.133283f, -0.538772f},
          {0.291952f, 0.538772f, 0.133283f},
          {0.538772f, 0.291952f, 0.133283f},
          {0.538772f, 0.291952f, -0.133283f},
          {0.291952f, 0.538772f, -0.133283f},
          {0.133283f, 0.538772f, -0.291952f},
          {0.133283f, 0.291952f, -0.538772f},
          {-0.133283f, 0.291952f, -0.538772f},
          {-0.133283f, 0.538772f, -0.291952f},
          {-0.538772f, 0.291952f, 0.133283f},
          {-0.291952f, 0.538772f, 0.133283f},
          {-0.291952f, 0.538772f, -0.133283f},
          {-0.538772f, 0.291952f, -0.133283f},
          {-0.538772f, 0.133283f, -0.291952f},
          {-0.291952f, 0.133283f, -0.538772f},
          {-0.291952f, -0.133283f, -0.538772f},
          {-0.538772f, -0.133283f, -0.291952f},
          {-0.291952f, -0.538772f, 0.133283f},
          {-0.538772f, -0.291952f, 0.133283f},
          {-0.538772f, -0.291952f, -0.133283f},
          {-0.291952f, -0.538772f, -0.133283f},
          {0.133283f, -0.291952f, -0.538772f},
          {0.133283f, -0.538772f, -0.291952f},
          {-0.133283f, -0.538772f, -0.291952f},
          {-0.133283f, -0.291952f, -0.538772f},
      };
      static const uint8_t demo_truncated_cuboctahedron_face_indices[][8] = {
          {0U, 5U, 4U, 13U, 12U, 9U, 8U, 1U},
          {0U, 3U, 41U, 40U, 6U, 5U},
          {4U, 7U, 17U, 16U, 14U, 13U},
          {9U, 12U, 15U, 25U, 24U, 10U},
          {1U, 8U, 11U, 33U, 32U, 2U},
          {14U, 16U, 19U, 21U, 20U, 26U, 25U, 15U},
          {18U, 45U, 44U, 22U, 21U, 19U},
          {20U, 23U, 29U, 28U, 27U, 26U},
          {10U, 24U, 27U, 28U, 31U, 34U, 33U, 11U},
          {30U, 37U, 36U, 35U, 34U, 31U},
          {2U, 32U, 35U, 36U, 39U, 42U, 41U, 3U},
          {38U, 47U, 46U, 43U, 42U, 39U},
          {6U, 40U, 43U, 46U, 45U, 18U, 17U, 7U},
          {22U, 44U, 47U, 38U, 37U, 30U, 29U, 23U},
          {0U, 1U, 2U, 3U},
          {4U, 5U, 6U, 7U},
          {8U, 9U, 10U, 11U},
          {12U, 13U, 14U, 15U},
          {16U, 17U, 18U, 19U},
          {20U, 21U, 22U, 23U},
          {24U, 25U, 26U, 27U},
          {28U, 29U, 30U, 31U},
          {32U, 33U, 34U, 35U},
          {36U, 37U, 38U, 39U},
          {40U, 41U, 42U, 43U},
          {44U, 45U, 46U, 47U},
      };
      static const demo_mesh_face_t demo_truncated_cuboctahedron_faces[] = {
          {8U, demo_truncated_cuboctahedron_face_indices[0]},
          {6U, demo_truncated_cuboctahedron_face_indices[1]},
          {6U, demo_truncated_cuboctahedron_face_indices[2]},
          {6U, demo_truncated_cuboctahedron_face_indices[3]},
          {6U, demo_truncated_cuboctahedron_face_indices[4]},
          {8U, demo_truncated_cuboctahedron_face_indices[5]},
          {6U, demo_truncated_cuboctahedron_face_indices[6]},
          {6U, demo_truncated_cuboctahedron_face_indices[7]},
          {8U, demo_truncated_cuboctahedron_face_indices[8]},
          {6U, demo_truncated_cuboctahedron_face_indices[9]},
          {8U, demo_truncated_cuboctahedron_face_indices[10]},
          {6U, demo_truncated_cuboctahedron_face_indices[11]},
          {8U, demo_truncated_cuboctahedron_face_indices[12]},
          {8U, demo_truncated_cuboctahedron_face_indices[13]},
          {4U, demo_truncated_cuboctahedron_face_indices[14]},
          {4U, demo_truncated_cuboctahedron_face_indices[15]},
          {4U, demo_truncated_cuboctahedron_face_indices[16]},
          {4U, demo_truncated_cuboctahedron_face_indices[17]},
          {4U, demo_truncated_cuboctahedron_face_indices[18]},
          {4U, demo_truncated_cuboctahedron_face_indices[19]},
          {4U, demo_truncated_cuboctahedron_face_indices[20]},
          {4U, demo_truncated_cuboctahedron_face_indices[21]},
          {4U, demo_truncated_cuboctahedron_face_indices[22]},
          {4U, demo_truncated_cuboctahedron_face_indices[23]},
          {4U, demo_truncated_cuboctahedron_face_indices[24]},
          {4U, demo_truncated_cuboctahedron_face_indices[25]},
      };
      static const demo_mesh_t demo_truncated_cuboctahedron_mesh = {
          demo_truncated_cuboctahedron_vertices, sizeof(demo_truncated_cuboctahedron_vertices) / sizeof(demo_truncated_cuboctahedron_vertices[0]),
          demo_truncated_cuboctahedron_faces, sizeof(demo_truncated_cuboctahedron_faces) / sizeof(demo_truncated_cuboctahedron_faces[0]),
      };
      
      // Archimedean solid: icosidodecahedron.
      static const demo_point_3d_t demo_icosidodecahedron_vertices[] = {
          {0.356820f, 0.000003f, 0.934167f},
          {0.755758f, 0.309018f, 0.577349f},
          {0.467086f, 0.809013f, 0.356822f},
          {-0.178410f, 0.309011f, 0.934169f},
          {-0.645495f, 0.499990f, 0.577348f},
          {-0.934169f, -0.000004f, 0.356821f},
          {-0.178409f, -0.309009f, 0.934169f},
          {-0.110264f, -0.809007f, 0.577354f},
          {0.755759f, -0.309006f, 0.577350f},
          {0.866024f, -0.499989f, -0.000000f},
          {-0.110267f, 0.809008f, 0.577352f},
          {-0.000002f, 0.999993f, -0.000000f},
          {-0.645495f, -0.499993f, 0.577350f},
          {-0.866022f, -0.499998f, 0.000001f},
          {0.866022f, 0.499998f, -0.000001f},
          {0.645495f, 0.499993f, -0.577350f},
          {0.467089f, -0.809005f, 0.356823f},
          {0.000002f, -0.999993f, 0.000000f},
          {0.110267f, -0.809008f, -0.577352f},
          {-0.866024f, 0.499989f, 0.000000f},
          {-0.755759f, 0.309006f, -0.577350f},
          {0.934169f, 0.000004f, -0.356821f},
          {0.645495f, -0.499990f, -0.577348f},
          {0.178410f, -0.309011f, -0.934169f},
          {-0.467089f, 0.809005f, -0.356823f},
          {0.110264f, 0.809007f, -0.577354f},
          {0.178409f, 0.309009f, -0.934169f},
          {-0.467086f, -0.809013f, -0.356822f},
          {-0.755758f, -0.309018f, -0.577349f},
          {-0.356820f, -0.000003f, -0.934167f},
      };
      static const uint8_t demo_icosidodecahedron_face_indices[][5] = {
          {10U, 3U, 0U, 1U, 2U},
          {3U, 10U, 4U},
          {0U, 3U, 6U},
          {1U, 0U, 8U},
          {2U, 1U, 14U},
          {10U, 2U, 11U},
          {12U, 6U, 3U, 4U, 5U},
          {6U, 12U, 7U},
          {5U, 4U, 19U},
          {12U, 5U, 13U},
          {8U, 0U, 6U, 7U, 16U},
          {16U, 7U, 17U},
          {8U, 16U, 9U},
          {14U, 1U, 8U, 9U, 21U},
          {21U, 9U, 22U},
          {14U, 21U, 15U},
          {19U, 4U, 10U, 11U, 24U},
          {24U, 11U, 25U},
          {19U, 24U, 20U},
          {17U, 7U, 12U, 13U, 27U},
          {27U, 13U, 28U},
          {17U, 27U, 18U},
          {11U, 2U, 14U, 15U, 25U},
          {25U, 15U, 26U},
          {22U, 9U, 16U, 17U, 18U},
          {22U, 18U, 23U},
          {13U, 5U, 19U, 20U, 28U},
          {28U, 20U, 29U},
          {26U, 15U, 21U, 22U, 23U},
          {26U, 23U, 29U},
          {29U, 20U, 24U, 25U, 26U},
          {23U, 18U, 27U, 28U, 29U},
      };
      static const demo_mesh_face_t demo_icosidodecahedron_faces[] = {
          {5U, demo_icosidodecahedron_face_indices[0]},
          {3U, demo_icosidodecahedron_face_indices[1]},
          {3U, demo_icosidodecahedron_face_indices[2]},
          {3U, demo_icosidodecahedron_face_indices[3]},
          {3U, demo_icosidodecahedron_face_indices[4]},
          {3U, demo_icosidodecahedron_face_indices[5]},
          {5U, demo_icosidodecahedron_face_indices[6]},
          {3U, demo_icosidodecahedron_face_indices[7]},
          {3U, demo_icosidodecahedron_face_indices[8]},
          {3U, demo_icosidodecahedron_face_indices[9]},
          {5U, demo_icosidodecahedron_face_indices[10]},
          {3U, demo_icosidodecahedron_face_indices[11]},
          {3U, demo_icosidodecahedron_face_indices[12]},
          {5U, demo_icosidodecahedron_face_indices[13]},
          {3U, demo_icosidodecahedron_face_indices[14]},
          {3U, demo_icosidodecahedron_face_indices[15]},
          {5U, demo_icosidodecahedron_face_indices[16]},
          {3U, demo_icosidodecahedron_face_indices[17]},
          {3U, demo_icosidodecahedron_face_indices[18]},
          {5U, demo_icosidodecahedron_face_indices[19]},
          {3U, demo_icosidodecahedron_face_indices[20]},
          {3U, demo_icosidodecahedron_face_indices[21]},
          {5U, demo_icosidodecahedron_face_indices[22]},
          {3U, demo_icosidodecahedron_face_indices[23]},
          {5U, demo_icosidodecahedron_face_indices[24]},
          {3U, demo_icosidodecahedron_face_indices[25]},
          {5U, demo_icosidodecahedron_face_indices[26]},
          {3U, demo_icosidodecahedron_face_indices[27]},
          {5U, demo_icosidodecahedron_face_indices[28]},
          {3U, demo_icosidodecahedron_face_indices[29]},
          {5U, demo_icosidodecahedron_face_indices[30]},
          {5U, demo_icosidodecahedron_face_indices[31]},
      };
      static const demo_mesh_t demo_icosidodecahedron_mesh = {
          demo_icosidodecahedron_vertices, sizeof(demo_icosidodecahedron_vertices) / sizeof(demo_icosidodecahedron_vertices[0]),
          demo_icosidodecahedron_faces, sizeof(demo_icosidodecahedron_faces) / sizeof(demo_icosidodecahedron_faces[0]),
      };
      
      // Archimedean solid: truncated dodecahedron.
      static const demo_point_3d_t demo_truncated_dodecahedron_vertices[] = {
          {0.267614f, 0.441939f, 0.813805f},
          {0.502744f, 0.118442f, 0.813928f},
          {0.607784f, 0.441949f, 0.603615f},
          {-0.337643f, 0.391405f, 0.813832f},
          {0.042581f, 0.515040f, 0.813805f},
          {-0.232604f, 0.714912f, 0.603519f},
          {-0.476665f, -0.199892f, 0.814011f},
          {-0.476674f, 0.199962f, 0.813946f},
          {-0.751850f, -0.000020f, 0.603725f},
          {0.042818f, -0.514910f, 0.814050f},
          {-0.337533f, -0.391332f, 0.814013f},
          {-0.232358f, -0.714892f, 0.603828f},
          {0.502851f, -0.118211f, 0.814033f},
          {0.267856f, -0.441782f, 0.814039f},
          {0.608026f, -0.441772f, 0.603849f},
          {0.878844f, -0.391315f, 0.062268f},
          {0.708719f, -0.514894f, 0.402495f},
          {0.643850f, -0.714886f, 0.062274f},
          {0.941012f, 0.199998f, -0.062204f},
          {0.941075f, -0.199882f, -0.062184f},
          {0.876143f, 0.000005f, -0.402425f},
          {0.708468f, 0.515057f, 0.402268f},
          {0.878666f, 0.391447f, 0.062151f},
          {0.643536f, 0.714944f, 0.062027f},
          {0.100405f, 0.956756f, -0.062493f},
          {0.480726f, 0.833243f, -0.062453f},
          {0.270603f, 0.833146f, -0.402610f},
          {-0.271066f, 0.833211f, 0.402147f},
          {-0.100964f, 0.956749f, 0.061963f},
          {-0.481189f, 0.833114f, 0.061990f},
          {-0.878883f, 0.391350f, -0.062352f},
          {-0.643890f, 0.714806f, -0.062362f},
          {-0.708781f, 0.514889f, -0.402536f},
          {-0.876215f, -0.000020f, 0.402340f},
          {-0.941115f, 0.199917f, 0.062101f},
          {-0.941106f, -0.199937f, 0.062166f},
          {-0.643850f, -0.714886f, -0.062274f},
          {-0.878874f, -0.391370f, -0.062287f},
          {-0.708750f, -0.514949f, -0.402513f},
          {-0.270813f, -0.833220f, 0.402459f},
          {-0.481040f, -0.833220f, 0.062196f},
          {-0.100688f, -0.956799f, 0.062233f},
          {0.481040f, -0.833220f, -0.062196f},
          {0.100688f, -0.956799f, -0.062233f},
          {0.270813f, -0.833220f, -0.402459f},
          {0.476570f, 0.199942f, -0.814066f},
          {0.751779f, 0.000005f, -0.603810f},
          {0.476633f, -0.199937f, -0.814046f},
          {0.232263f, 0.714837f, -0.603871f},
          {0.337438f, 0.391382f, -0.814067f},
          {-0.042882f, 0.514895f, -0.814107f},
          {0.337501f, -0.391376f, -0.814048f},
          {0.232358f, -0.714892f, -0.603828f},
          {-0.042850f, -0.514955f, -0.814085f},
          {-0.608088f, 0.441767f, -0.603890f},
          {-0.267920f, 0.441767f, -0.814096f},
          {-0.502913f, 0.118311f, -0.814086f},
          {-0.608056f, -0.441827f, -0.603867f},
          {-0.502913f, -0.118311f, -0.814086f},
          {-0.267888f, -0.441827f, -0.814074f},
      };
      static const uint8_t demo_truncated_dodecahedron_face_indices[][10] = {
          {0U, 4U, 3U, 7U, 6U, 10U, 9U, 13U, 12U, 1U},
          {1U, 12U, 14U, 16U, 15U, 19U, 18U, 22U, 21U, 2U},
          {0U, 2U, 21U, 23U, 25U, 24U, 28U, 27U, 5U, 4U},
          {3U, 5U, 27U, 29U, 31U, 30U, 34U, 33U, 8U, 7U},
          {6U, 8U, 33U, 35U, 37U, 36U, 40U, 39U, 11U, 10U},
          {9U, 11U, 39U, 41U, 43U, 42U, 17U, 16U, 14U, 13U},
          {18U, 20U, 46U, 45U, 49U, 48U, 26U, 25U, 23U, 22U},
          {15U, 17U, 42U, 44U, 52U, 51U, 47U, 46U, 20U, 19U},
          {24U, 26U, 48U, 50U, 55U, 54U, 32U, 31U, 29U, 28U},
          {30U, 32U, 54U, 56U, 58U, 57U, 38U, 37U, 35U, 34U},
          {36U, 38U, 57U, 59U, 53U, 52U, 44U, 43U, 41U, 40U},
          {45U, 47U, 51U, 53U, 59U, 58U, 56U, 55U, 50U, 49U},
          {0U, 1U, 2U},
          {3U, 4U, 5U},
          {6U, 7U, 8U},
          {9U, 10U, 11U},
          {12U, 13U, 14U},
          {15U, 16U, 17U},
          {18U, 19U, 20U},
          {21U, 22U, 23U},
          {24U, 25U, 26U},
          {27U, 28U, 29U},
          {30U, 31U, 32U},
          {33U, 34U, 35U},
          {36U, 37U, 38U},
          {39U, 40U, 41U},
          {42U, 43U, 44U},
          {45U, 46U, 47U},
          {48U, 49U, 50U},
          {51U, 52U, 53U},
          {54U, 55U, 56U},
          {57U, 58U, 59U},
      };
      static const demo_mesh_face_t demo_truncated_dodecahedron_faces[] = {
          {10U, demo_truncated_dodecahedron_face_indices[0]},
          {10U, demo_truncated_dodecahedron_face_indices[1]},
          {10U, demo_truncated_dodecahedron_face_indices[2]},
          {10U, demo_truncated_dodecahedron_face_indices[3]},
          {10U, demo_truncated_dodecahedron_face_indices[4]},
          {10U, demo_truncated_dodecahedron_face_indices[5]},
          {10U, demo_truncated_dodecahedron_face_indices[6]},
          {10U, demo_truncated_dodecahedron_face_indices[7]},
          {10U, demo_truncated_dodecahedron_face_indices[8]},
          {10U, demo_truncated_dodecahedron_face_indices[9]},
          {10U, demo_truncated_dodecahedron_face_indices[10]},
          {10U, demo_truncated_dodecahedron_face_indices[11]},
          {3U, demo_truncated_dodecahedron_face_indices[12]},
          {3U, demo_truncated_dodecahedron_face_indices[13]},
          {3U, demo_truncated_dodecahedron_face_indices[14]},
          {3U, demo_truncated_dodecahedron_face_indices[15]},
          {3U, demo_truncated_dodecahedron_face_indices[16]},
          {3U, demo_truncated_dodecahedron_face_indices[17]},
          {3U, demo_truncated_dodecahedron_face_indices[18]},
          {3U, demo_truncated_dodecahedron_face_indices[19]},
          {3U, demo_truncated_dodecahedron_face_indices[20]},
          {3U, demo_truncated_dodecahedron_face_indices[21]},
          {3U, demo_truncated_dodecahedron_face_indices[22]},
          {3U, demo_truncated_dodecahedron_face_indices[23]},
          {3U, demo_truncated_dodecahedron_face_indices[24]},
          {3U, demo_truncated_dodecahedron_face_indices[25]},
          {3U, demo_truncated_dodecahedron_face_indices[26]},
          {3U, demo_truncated_dodecahedron_face_indices[27]},
          {3U, demo_truncated_dodecahedron_face_indices[28]},
          {3U, demo_truncated_dodecahedron_face_indices[29]},
          {3U, demo_truncated_dodecahedron_face_indices[30]},
          {3U, demo_truncated_dodecahedron_face_indices[31]},
      };
      static const demo_mesh_t demo_truncated_dodecahedron_mesh = {
          demo_truncated_dodecahedron_vertices, sizeof(demo_truncated_dodecahedron_vertices) / sizeof(demo_truncated_dodecahedron_vertices[0]),
          demo_truncated_dodecahedron_faces, sizeof(demo_truncated_dodecahedron_faces) / sizeof(demo_truncated_dodecahedron_faces[0]),
      };
      
      // Archimedean solid: truncated icosahedron.
      static const demo_point_3d_t demo_truncated_icosahedron_vertices[] = {
          {-0.010218f, 0.366446f, 0.844223f},
          {0.322463f, 0.174378f, 0.844222f},
          {0.570430f, 0.366451f, 0.622436f},
          {0.391000f, 0.677230f, 0.485365f},
          {0.032140f, 0.677226f, 0.622438f},
          {-0.312247f, -0.192069f, 0.844221f},
          {-0.312247f, 0.192070f, 0.844221f},
          {-0.602570f, 0.310775f, 0.622435f},
          {-0.781999f, -0.000002f, 0.485362f},
          {-0.602570f, -0.310777f, 0.622436f},
          {0.322464f, -0.174372f, 0.844221f},
          {-0.010217f, -0.366443f, 0.844223f},
          {0.032141f, -0.677222f, 0.622438f},
          {0.391002f, -0.677222f, 0.485365f},
          {0.570431f, -0.366441f, 0.622436f},
          {0.795550f, 0.192078f, 0.421081f},
          {0.795551f, -0.192065f, 0.421081f},
          {0.864087f, -0.310772f, 0.062222f},
          {0.906443f, 0.000004f, -0.159566f},
          {0.864086f, 0.310783f, 0.062221f},
          {-0.564117f, 0.592920f, 0.421082f},
          {-0.231437f, 0.784995f, 0.421083f},
          {-0.162901f, 0.903704f, 0.062222f},
          {-0.453225f, 0.784993f, -0.159567f},
          {-0.701189f, 0.592919f, 0.062222f},
          {-0.231435f, -0.784996f, 0.421085f},
          {-0.564116f, -0.592924f, 0.421084f},
          {-0.701188f, -0.592928f, 0.062223f},
          {-0.453222f, -0.785001f, -0.159566f},
          {-0.162898f, -0.903706f, 0.062223f},
          {0.453222f, 0.785001f, 0.159566f},
          {0.701188f, 0.592928f, -0.062223f},
          {0.564116f, 0.592924f, -0.421084f},
          {0.231435f, 0.784996f, -0.421085f},
          {0.162898f, 0.903706f, -0.062223f},
          {0.701189f, -0.592919f, -0.062222f},
          {0.453225f, -0.784993f, 0.159567f},
          {0.162901f, -0.903704f, -0.062222f},
          {0.231437f, -0.784995f, -0.421083f},
          {0.564117f, -0.592920f, -0.421082f},
          {-0.906443f, -0.000004f, 0.159566f},
          {-0.864087f, 0.310772f, -0.062222f},
          {-0.795551f, 0.192065f, -0.421081f},
          {-0.795550f, -0.192078f, -0.421081f},
          {-0.864086f, -0.310783f, -0.062221f},
          {0.602570f, 0.310777f, -0.622436f},
          {0.781999f, 0.000002f, -0.485362f},
          {0.602570f, -0.310775f, -0.622435f},
          {0.312247f, -0.192070f, -0.844221f},
          {0.312247f, 0.192069f, -0.844221f},
          {-0.570431f, 0.366441f, -0.622436f},
          {-0.391002f, 0.677222f, -0.485365f},
          {-0.032141f, 0.677222f, -0.622438f},
          {0.010217f, 0.366443f, -0.844223f},
          {-0.322464f, 0.174372f, -0.844221f},
          {-0.032140f, -0.677226f, -0.622438f},
          {-0.391000f, -0.677230f, -0.485365f},
          {-0.570430f, -0.366451f, -0.622436f},
          {-0.322463f, -0.174378f, -0.844222f},
          {0.010218f, -0.366446f, -0.844223f},
      };
      static const uint8_t demo_truncated_icosahedron_face_indices[][6] = {
          {0U, 6U, 5U, 11U, 10U, 1U},
          {1U, 10U, 14U, 16U, 15U, 2U},
          {0U, 4U, 21U, 20U, 7U, 6U},
          {5U, 9U, 26U, 25U, 12U, 11U},
          {2U, 15U, 19U, 31U, 30U, 3U},
          {13U, 36U, 35U, 17U, 16U, 14U},
          {7U, 20U, 24U, 41U, 40U, 8U},
          {3U, 30U, 34U, 22U, 21U, 4U},
          {12U, 25U, 29U, 37U, 36U, 13U},
          {8U, 40U, 44U, 27U, 26U, 9U},
          {18U, 46U, 45U, 32U, 31U, 19U},
          {17U, 35U, 39U, 47U, 46U, 18U},
          {23U, 51U, 50U, 42U, 41U, 24U},
          {22U, 34U, 33U, 52U, 51U, 23U},
          {28U, 56U, 55U, 38U, 37U, 29U},
          {27U, 44U, 43U, 57U, 56U, 28U},
          {32U, 45U, 49U, 53U, 52U, 33U},
          {38U, 55U, 59U, 48U, 47U, 39U},
          {42U, 50U, 54U, 58U, 57U, 43U},
          {48U, 59U, 58U, 54U, 53U, 49U},
          {0U, 1U, 2U, 3U, 4U},
          {5U, 6U, 7U, 8U, 9U},
          {10U, 11U, 12U, 13U, 14U},
          {15U, 16U, 17U, 18U, 19U},
          {20U, 21U, 22U, 23U, 24U},
          {25U, 26U, 27U, 28U, 29U},
          {30U, 31U, 32U, 33U, 34U},
          {35U, 36U, 37U, 38U, 39U},
          {40U, 41U, 42U, 43U, 44U},
          {45U, 46U, 47U, 48U, 49U},
          {50U, 51U, 52U, 53U, 54U},
          {55U, 56U, 57U, 58U, 59U},
      };
      static const demo_mesh_face_t demo_truncated_icosahedron_faces[] = {
          {6U, demo_truncated_icosahedron_face_indices[0]},
          {6U, demo_truncated_icosahedron_face_indices[1]},
          {6U, demo_truncated_icosahedron_face_indices[2]},
          {6U, demo_truncated_icosahedron_face_indices[3]},
          {6U, demo_truncated_icosahedron_face_indices[4]},
          {6U, demo_truncated_icosahedron_face_indices[5]},
          {6U, demo_truncated_icosahedron_face_indices[6]},
          {6U, demo_truncated_icosahedron_face_indices[7]},
          {6U, demo_truncated_icosahedron_face_indices[8]},
          {6U, demo_truncated_icosahedron_face_indices[9]},
          {6U, demo_truncated_icosahedron_face_indices[10]},
          {6U, demo_truncated_icosahedron_face_indices[11]},
          {6U, demo_truncated_icosahedron_face_indices[12]},
          {6U, demo_truncated_icosahedron_face_indices[13]},
          {6U, demo_truncated_icosahedron_face_indices[14]},
          {6U, demo_truncated_icosahedron_face_indices[15]},
          {6U, demo_truncated_icosahedron_face_indices[16]},
          {6U, demo_truncated_icosahedron_face_indices[17]},
          {6U, demo_truncated_icosahedron_face_indices[18]},
          {6U, demo_truncated_icosahedron_face_indices[19]},
          {5U, demo_truncated_icosahedron_face_indices[20]},
          {5U, demo_truncated_icosahedron_face_indices[21]},
          {5U, demo_truncated_icosahedron_face_indices[22]},
          {5U, demo_truncated_icosahedron_face_indices[23]},
          {5U, demo_truncated_icosahedron_face_indices[24]},
          {5U, demo_truncated_icosahedron_face_indices[25]},
          {5U, demo_truncated_icosahedron_face_indices[26]},
          {5U, demo_truncated_icosahedron_face_indices[27]},
          {5U, demo_truncated_icosahedron_face_indices[28]},
          {5U, demo_truncated_icosahedron_face_indices[29]},
          {5U, demo_truncated_icosahedron_face_indices[30]},
          {5U, demo_truncated_icosahedron_face_indices[31]},
      };
      static const demo_mesh_t demo_truncated_icosahedron_mesh = {
          demo_truncated_icosahedron_vertices, sizeof(demo_truncated_icosahedron_vertices) / sizeof(demo_truncated_icosahedron_vertices[0]),
          demo_truncated_icosahedron_faces, sizeof(demo_truncated_icosahedron_faces) / sizeof(demo_truncated_icosahedron_faces[0]),
      };
      
      // Archimedean solid: rhombicosidodecahedron.
      static const demo_point_3d_t demo_rhombicosidodecahedron_vertices[] = {
          {0.563455f, 0.555699f, 0.522353f},
          {0.199519f, 0.765814f, 0.522354f},
          {0.517118f, 0.215722f, 0.764976f},
          {-0.071742f, 0.555694f, 0.764977f},
          {-0.249092f, 0.000001f, 0.914925f},
          {0.124546f, 0.215720f, 0.914925f},
          {0.517119f, -0.215714f, 0.764976f},
          {0.841949f, 0.339980f, 0.273260f},
          {0.276494f, 0.899137f, 0.119313f},
          {-0.762977f, 0.210114f, 0.522352f},
          {-0.762976f, -0.210118f, 0.522353f},
          {-0.445379f, 0.339974f, 0.764976f},
          {-0.445378f, -0.339974f, 0.764976f},
          {-0.715406f, 0.559154f, 0.273260f},
          {-0.916924f, -0.210120f, 0.119313f},
          {0.199523f, -0.765810f, 0.522355f},
          {0.124547f, -0.215716f, 0.914925f},
          {-0.071740f, -0.555692f, 0.764978f},
          {-0.126544f, -0.899135f, 0.273263f},
          {0.563458f, -0.555690f, 0.522354f},
          {0.916925f, -0.210112f, -0.119313f},
          {0.795612f, 0.000005f, 0.515884f},
          {0.841951f, -0.339970f, 0.273261f},
          {0.715406f, -0.559154f, -0.273260f},
          {0.916924f, 0.210120f, -0.119313f},
          {-0.276498f, 0.899134f, -0.119314f},
          {-0.397809f, 0.689015f, 0.515885f},
          {-0.126548f, 0.899135f, 0.273261f},
          {0.126544f, 0.899135f, -0.273263f},
          {-0.640433f, 0.689013f, -0.119314f},
          {-0.640430f, -0.689020f, -0.119312f},
          {-0.397806f, -0.689016f, 0.515886f},
          {-0.715404f, -0.559159f, 0.273262f},
          {-0.841949f, -0.339980f, -0.273260f},
          {-0.276494f, -0.899137f, -0.119313f},
          {0.397806f, 0.689016f, -0.515886f},
          {0.640430f, 0.689020f, 0.119312f},
          {0.715404f, 0.559159f, -0.273262f},
          {0.445378f, 0.339974f, -0.764976f},
          {0.126548f, -0.899135f, -0.273261f},
          {0.397809f, -0.689015f, -0.515885f},
          {0.640433f, -0.689013f, 0.119314f},
          {0.276498f, -0.899134f, 0.119314f},
          {0.071742f, -0.555694f, -0.764977f},
          {-0.795612f, -0.000005f, -0.515884f},
          {-0.916925f, 0.210112f, 0.119313f},
          {-0.841951f, 0.339970f, -0.273261f},
          {-0.517119f, 0.215714f, -0.764976f},
          {0.445379f, -0.339974f, -0.764976f},
          {0.249092f, -0.000001f, -0.914925f},
          {0.762976f, 0.210118f, -0.522353f},
          {0.762977f, -0.210114f, -0.522352f},
          {-0.124546f, -0.215720f, -0.914925f},
          {0.071740f, 0.555692f, -0.764978f},
          {-0.124547f, 0.215716f, -0.914925f},
          {-0.563458f, 0.555690f, -0.522354f},
          {-0.199523f, 0.765810f, -0.522355f},
          {-0.517118f, -0.215722f, -0.764976f},
          {-0.199519f, -0.765814f, -0.522354f},
          {-0.563455f, -0.555699f, -0.522353f},
      };
      static const uint8_t demo_rhombicosidodecahedron_face_indices[][5] = {
          {0U, 1U, 3U, 5U, 2U},
          {1U, 0U, 36U, 8U},
          {3U, 1U, 27U, 26U},
          {5U, 3U, 11U, 4U},
          {2U, 5U, 16U, 6U},
          {0U, 2U, 21U, 7U},
          {26U, 11U, 3U},
          {11U, 26U, 13U, 9U},
          {4U, 16U, 5U},
          {16U, 4U, 12U, 17U},
          {6U, 21U, 2U},
          {21U, 6U, 19U, 22U},
          {7U, 36U, 0U},
          {36U, 7U, 24U, 37U},
          {8U, 27U, 1U},
          {27U, 8U, 28U, 25U},
          {9U, 10U, 12U, 4U, 11U},
          {10U, 9U, 45U, 14U},
          {12U, 10U, 32U, 31U},
          {31U, 17U, 12U},
          {17U, 31U, 18U, 15U},
          {13U, 45U, 9U},
          {45U, 13U, 29U, 46U},
          {14U, 32U, 10U},
          {32U, 14U, 33U, 30U},
          {15U, 19U, 6U, 16U, 17U},
          {19U, 15U, 42U, 41U},
          {18U, 42U, 15U},
          {42U, 18U, 34U, 39U},
          {41U, 22U, 19U},
          {22U, 41U, 23U, 20U},
          {20U, 24U, 7U, 21U, 22U},
          {24U, 20U, 51U, 50U},
          {23U, 51U, 20U},
          {51U, 23U, 40U, 48U},
          {50U, 37U, 24U},
          {37U, 50U, 38U, 35U},
          {25U, 29U, 13U, 26U, 27U},
          {29U, 25U, 56U, 55U},
          {28U, 56U, 25U},
          {56U, 28U, 35U, 53U},
          {55U, 46U, 29U},
          {46U, 55U, 47U, 44U},
          {30U, 34U, 18U, 31U, 32U},
          {34U, 30U, 59U, 58U},
          {33U, 59U, 30U},
          {59U, 33U, 44U, 57U},
          {58U, 39U, 34U},
          {39U, 58U, 43U, 40U},
          {35U, 28U, 8U, 36U, 37U},
          {38U, 53U, 35U},
          {53U, 38U, 49U, 54U},
          {39U, 40U, 23U, 41U, 42U},
          {43U, 48U, 40U},
          {48U, 43U, 52U, 49U},
          {44U, 33U, 14U, 45U, 46U},
          {47U, 57U, 44U},
          {57U, 47U, 54U, 52U},
          {48U, 49U, 38U, 50U, 51U},
          {52U, 54U, 49U},
          {53U, 54U, 47U, 55U, 56U},
          {57U, 52U, 43U, 58U, 59U},
      };
      static const demo_mesh_face_t demo_rhombicosidodecahedron_faces[] = {
          {5U, demo_rhombicosidodecahedron_face_indices[0]},
          {4U, demo_rhombicosidodecahedron_face_indices[1]},
          {4U, demo_rhombicosidodecahedron_face_indices[2]},
          {4U, demo_rhombicosidodecahedron_face_indices[3]},
          {4U, demo_rhombicosidodecahedron_face_indices[4]},
          {4U, demo_rhombicosidodecahedron_face_indices[5]},
          {3U, demo_rhombicosidodecahedron_face_indices[6]},
          {4U, demo_rhombicosidodecahedron_face_indices[7]},
          {3U, demo_rhombicosidodecahedron_face_indices[8]},
          {4U, demo_rhombicosidodecahedron_face_indices[9]},
          {3U, demo_rhombicosidodecahedron_face_indices[10]},
          {4U, demo_rhombicosidodecahedron_face_indices[11]},
          {3U, demo_rhombicosidodecahedron_face_indices[12]},
          {4U, demo_rhombicosidodecahedron_face_indices[13]},
          {3U, demo_rhombicosidodecahedron_face_indices[14]},
          {4U, demo_rhombicosidodecahedron_face_indices[15]},
          {5U, demo_rhombicosidodecahedron_face_indices[16]},
          {4U, demo_rhombicosidodecahedron_face_indices[17]},
          {4U, demo_rhombicosidodecahedron_face_indices[18]},
          {3U, demo_rhombicosidodecahedron_face_indices[19]},
          {4U, demo_rhombicosidodecahedron_face_indices[20]},
          {3U, demo_rhombicosidodecahedron_face_indices[21]},
          {4U, demo_rhombicosidodecahedron_face_indices[22]},
          {3U, demo_rhombicosidodecahedron_face_indices[23]},
          {4U, demo_rhombicosidodecahedron_face_indices[24]},
          {5U, demo_rhombicosidodecahedron_face_indices[25]},
          {4U, demo_rhombicosidodecahedron_face_indices[26]},
          {3U, demo_rhombicosidodecahedron_face_indices[27]},
          {4U, demo_rhombicosidodecahedron_face_indices[28]},
          {3U, demo_rhombicosidodecahedron_face_indices[29]},
          {4U, demo_rhombicosidodecahedron_face_indices[30]},
          {5U, demo_rhombicosidodecahedron_face_indices[31]},
          {4U, demo_rhombicosidodecahedron_face_indices[32]},
          {3U, demo_rhombicosidodecahedron_face_indices[33]},
          {4U, demo_rhombicosidodecahedron_face_indices[34]},
          {3U, demo_rhombicosidodecahedron_face_indices[35]},
          {4U, demo_rhombicosidodecahedron_face_indices[36]},
          {5U, demo_rhombicosidodecahedron_face_indices[37]},
          {4U, demo_rhombicosidodecahedron_face_indices[38]},
          {3U, demo_rhombicosidodecahedron_face_indices[39]},
          {4U, demo_rhombicosidodecahedron_face_indices[40]},
          {3U, demo_rhombicosidodecahedron_face_indices[41]},
          {4U, demo_rhombicosidodecahedron_face_indices[42]},
          {5U, demo_rhombicosidodecahedron_face_indices[43]},
          {4U, demo_rhombicosidodecahedron_face_indices[44]},
          {3U, demo_rhombicosidodecahedron_face_indices[45]},
          {4U, demo_rhombicosidodecahedron_face_indices[46]},
          {3U, demo_rhombicosidodecahedron_face_indices[47]},
          {4U, demo_rhombicosidodecahedron_face_indices[48]},
          {5U, demo_rhombicosidodecahedron_face_indices[49]},
          {3U, demo_rhombicosidodecahedron_face_indices[50]},
          {4U, demo_rhombicosidodecahedron_face_indices[51]},
          {5U, demo_rhombicosidodecahedron_face_indices[52]},
          {3U, demo_rhombicosidodecahedron_face_indices[53]},
          {4U, demo_rhombicosidodecahedron_face_indices[54]},
          {5U, demo_rhombicosidodecahedron_face_indices[55]},
          {3U, demo_rhombicosidodecahedron_face_indices[56]},
          {4U, demo_rhombicosidodecahedron_face_indices[57]},
          {5U, demo_rhombicosidodecahedron_face_indices[58]},
          {3U, demo_rhombicosidodecahedron_face_indices[59]},
          {5U, demo_rhombicosidodecahedron_face_indices[60]},
          {5U, demo_rhombicosidodecahedron_face_indices[61]},
      };
      static const demo_mesh_t demo_rhombicosidodecahedron_mesh = {
          demo_rhombicosidodecahedron_vertices, sizeof(demo_rhombicosidodecahedron_vertices) / sizeof(demo_rhombicosidodecahedron_vertices[0]),
          demo_rhombicosidodecahedron_faces, sizeof(demo_rhombicosidodecahedron_faces) / sizeof(demo_rhombicosidodecahedron_faces[0]),
      };
      
      static const demo_point_3d_t demo_tetrahedron_vertices[] = {
          { 1.0f,  1.0f,  1.0f}, {-1.0f, -1.0f,  1.0f},
          {-1.0f,  1.0f, -1.0f}, { 1.0f, -1.0f, -1.0f},
      };
      static const uint8_t demo_tetrahedron_face_indices[][3] = {
          {0U, 1U, 2U}, {0U, 3U, 1U}, {0U, 2U, 3U}, {1U, 3U, 2U},
      };
      static const demo_mesh_face_t demo_tetrahedron_faces[] = {
          {3U, demo_tetrahedron_face_indices[0]}, {3U, demo_tetrahedron_face_indices[1]},
          {3U, demo_tetrahedron_face_indices[2]}, {3U, demo_tetrahedron_face_indices[3]},
      };
      static const demo_mesh_t demo_tetrahedron_mesh = {
          demo_tetrahedron_vertices, sizeof(demo_tetrahedron_vertices) / sizeof(demo_tetrahedron_vertices[0]),
          demo_tetrahedron_faces, sizeof(demo_tetrahedron_faces) / sizeof(demo_tetrahedron_faces[0]),
      };
      
      // These five Platonic plus ten display-friendly Archimedean solids replace the
      // old variable-sided pyramids. Every listed mesh is a real closed solid.
      static const demo_mesh_t *const demo_stage_six_mesh_catalog[] = {
          &demo_tetrahedron_mesh, &demo_cube_mesh, &demo_octahedron_mesh,
          &demo_dodecahedron_mesh, &demo_icosahedron_mesh,
          &demo_truncated_tetrahedron_mesh, &demo_cuboctahedron_mesh,
          &demo_truncated_cube_mesh, &demo_truncated_octahedron_mesh,
          &demo_rhombicuboctahedron_mesh, &demo_truncated_cuboctahedron_mesh,
          &demo_icosidodecahedron_mesh, &demo_truncated_dodecahedron_mesh,
          &demo_truncated_icosahedron_mesh, &demo_rhombicosidodecahedron_mesh,
      };
      #define DEMO_STAGE_SIX_MESH_CATALOG_COUNT \
          (sizeof(demo_stage_six_mesh_catalog) / sizeof(demo_stage_six_mesh_catalog[0]))
      
      static float pitch_degrees;
      static float yaw_degrees;
      static float roll_degrees;
      static float scroll_text_x;
      static float scroll_speed_pixels_per_second;
      static const char *scroll_text;
      static size_t scroll_text_width;
      static size_t scroll_message_index = DEMO_SCROLL_MESSAGE_COUNT;
      static size_t active_icon_slot;
      static const uint8_t *active_logo_bitmap;
      static size_t demo_icon_colour_scheme_index;
      static uint32_t scroll_text_effect_elapsed_ms;
      // Segments 11, 14, and 15 share this independent raster/text clock. It is separate
      // from ticker messages, so neither a new message nor its width can make the
      // metallic colour roll or text wave visibly hop.
      static uint32_t raster_band_effect_elapsed_ms;
      // B selects an entry from the shared raster palette list while either raster
      // segment is active. Each segment restores its own default on entry.
      static size_t raster_band_palette_index;
      static uint32_t last_update_ms;
      static uint32_t sparkle_phase;
      static uint32_t stage_one_next_effect_change_at;
      static uint32_t demo_last_interaction_at;
      static uint32_t demo_instructions_started_at;
      // USER pauses Demo by freezing this virtual clock. On return, real elapsed time
      // is added here so every segment timer resumes from its exact previous instant.
      static uint32_t demo_paused_total_ms;
      static uint32_t demo_selector_started_at;
      static uint32_t demo_selector_last_move_at;
      static size_t demo_selector_item;
      static int demo_selector_joystick_direction;
      static bool demo_selector_repeating;
      static bool demo_selector_active;
      static bool demo_selector_clear_new_stage;
      static demo_effect_t selected_effect;
      static demo_stage_t selected_stage;
      static float stage_two_distance;
      static bool stage_two_auto_approaching;
      static uint32_t stage_three_next_icon_change_at;
      static size_t stage_three_icon_slot;
      static const uint8_t *stage_three_icon_bitmap;
      static float stage_three_ball_x;
      static float stage_three_ball_y;
      static float stage_three_ball_velocity_x;
      static float stage_three_ball_velocity_y;
      static float stage_three_ball_rotation_phase;
      static bool stage_three_ball_map_ready;
      // Cached sphere geometry keeps the stage responsive: rendering uses these maps
      // rather than recalculating trigonometry for every ball pixel on every frame.
      EXT_RAM_BSS_ATTR static uint8_t stage_three_ball_longitude_map[DEMO_STAGE_THREE_BALL_PIXEL_COUNT];
      EXT_RAM_BSS_ATTR static uint8_t stage_three_ball_latitude_map[DEMO_STAGE_THREE_BALL_PIXEL_COUNT];
      EXT_RAM_BSS_ATTR static uint8_t stage_three_ball_shade_map[DEMO_STAGE_THREE_BALL_PIXEL_COUNT];
      static const demo_mesh_t *stage_six_current_mesh;
      static uint32_t stage_six_next_change_at;
      static size_t stage_six_colour_scheme_index = DEMO_SOLID_COLOUR_SCHEME_COUNT;
      // Each text-stage icon owns one record. These records are deliberately in
      // PSRAM, as requested, so this optional animation state does not consume
      // internal RAM if more icon slots or per-icon tuning are added later.
      typedef struct {
          uint32_t cycle_started_at;
          uint32_t cycle_duration_ms;
          uint32_t rotation_started_at;
          float rotation_start_degrees;
          int16_t rotation_degrees_per_second;
      } demo_text_corner_icon_cycle_t;
      EXT_RAM_BSS_ATTR static demo_text_corner_icon_cycle_t
          demo_text_corner_icon_cycles[DEMO_TEXT_CORNER_ICON_COUNT];
      static walloons_phase_t stage_seven_phase;
      static uint32_t stage_seven_phase_started_at;
      static uint8_t stage_seven_completed_bounces;
      #if 0 // Moved to demo_stage_slideshow_lens.c with its complete PSRAM lifecycle.
      // The lens source is deliberately allocated when Stage 12 opens and released
      // when it closes. It must not become a permanent full-screen global allocation.
      static uint16_t *stage_twelve_image;
      static bool stage_twelve_image_ready;
      static char stage_twelve_image_path[DEMO_STAGE_TWELVE_IMAGE_PATH_MAX];
      static float stage_twelve_lens_x;
      static float stage_twelve_lens_y;
      static float stage_twelve_lens_start_x;
      static float stage_twelve_lens_start_y;
      static float stage_twelve_lens_target_x;
      static float stage_twelve_lens_target_y;
      static stage_twelve_lens_edge_t stage_twelve_lens_current_edge;
      static stage_twelve_lens_edge_t stage_twelve_lens_target_edge;
      static uint32_t stage_twelve_next_image_change_at;
      static uint32_t stage_twelve_lens_glide_started_at;
      static uint32_t stage_twelve_lens_glide_duration_ms;
      #endif
      // Segment 16 keeps a ground-only PSRAM scratch source while active. Battle Zone
      // renders afresh every Demo frame, then the scaler reads the fresh ground rows
      // from this buffer so wrapped movement cannot corrupt its source.
      static uint16_t *stage_fifteen_ground_source;
      // Stage 17 owns only compact animation state. Its conical gear geometry is
      // generated directly into the existing RGB565 frame, so it needs no PSRAM buffer.
      static float stage_seventeen_rig_pitch_degrees;
      static float stage_seventeen_rig_yaw_degrees;
      static float stage_seventeen_rig_roll_degrees;
      static float stage_seventeen_gear_degrees[3];
      static stage_seventeen_style_t stage_seventeen_style;
      EXT_RAM_BSS_ATTR static stage_seventeen_draw_face_t
          stage_seventeen_draw_faces[DEMO_STAGE_SEVENTEEN_DRAW_FACE_COUNT];
      static size_t stage_seventeen_draw_face_count;
      // Three lighting levels preserve the round form while the logo replaces the red checks.
      static const uint16_t stage_three_red_shades[] = {
          RGB565(105, 10, 10), RGB565(190, 22, 20), RGB565(250, 70, 60),
      };
      static const uint16_t stage_three_white_shades[] = {
          RGB565(105, 115, 135), RGB565(210, 220, 235), RGB565(255, 255, 255),
      };
      static screen_transition_t stage_transition;
      static bool stage_change_requested;
      static bool stage_zoom_in_requested;
      static bool stage_zooming_in;
      static bool stage_scroll_in_requested;
      static bool stage_reveal_requested;
      static demo_stage_transition_effect_t selected_stage_transition;
      static screen_transition_scroll_direction_t stage_scroll_out_direction;
      static screen_transition_scroll_direction_t stage_scroll_in_direction;
      static bool running;
      static bool demo_instructions_active;
      // The instruction page owns this reusable colour starfield. It is deliberately
      // separate from the visual effects used by the active Demo segments.
      static starfield_t demo_instruction_starfield;
      static radial_flame_t demo_ring_flame;
      static uint8_t demo_ring_heat[DEMO_RING_SEGMENTS * DEMO_RING_LAYERS];
      static const uint16_t demo_ring_colours[5] = {
          COLOR_DARKRED, COLOR_RED, COLOR_ORANGE, COLOR_YELLOW, COLOR_WHITE,
      };
      
      static bool bitmapPixelSet(const uint8_t *bitmap, unsigned x, unsigned y)
      {
          const size_t byte_index = (size_t)y * DEMO_LOGO_ROW_BYTES + x / 8U;
          const uint8_t bit = (uint8_t)(7U - x % 8U);
          return (bitmap[byte_index] & (uint8_t)(1U << bit)) != 0U;
      }
      
      static void advanceIconSlot(void)
      {
          active_icon_slot = (active_icon_slot + 1U) % DEMO_ICON_SLOT_COUNT;
          active_logo_bitmap = demo_icon_slots[active_icon_slot];
      }
      
      static void selectDemoIconColourScheme(void)
      {
          demo_icon_colour_scheme_index =
              (size_t)esp_random() % DEMO_ICON_COLOUR_SCHEME_COUNT;
      }
      
      static uint32_t demoStageInactivityTimeout(void)
      {
          return demoSelectorStageInactivityTimeout(selected_stage);
      }
      
      static void buildStageThreeBallMap(void)
      {
          if (stage_three_ball_map_ready) return;
          for (int y = 0; y < DEMO_STAGE_THREE_BALL_DIAMETER; ++y) {
              for (int x = 0; x < DEMO_STAGE_THREE_BALL_DIAMETER; ++x) {
                  const int index = y * DEMO_STAGE_THREE_BALL_DIAMETER + x;
                  const float normal_x = (float)(x - DEMO_STAGE_THREE_BALL_RADIUS) /
                                         (float)DEMO_STAGE_THREE_BALL_RADIUS;
                  const float normal_y = (float)(y - DEMO_STAGE_THREE_BALL_RADIUS) /
                                         (float)DEMO_STAGE_THREE_BALL_RADIUS;
                  const float radius_squared = normal_x * normal_x + normal_y * normal_y;
                  if (radius_squared > 1.0f) {
                      stage_three_ball_shade_map[index] = 0U;
                      continue;
                  }
                  const float depth = sqrtf(1.0f - radius_squared);
                  const float longitude = (atan2f(normal_x, depth) + DEMO_PI) / (2.0f * DEMO_PI);
                  const float latitude = (asinf(normal_y) + DEMO_PI * 0.5f) / DEMO_PI;
                  stage_three_ball_longitude_map[index] = (uint8_t)(longitude * 255.0f);
                  stage_three_ball_latitude_map[index] = (uint8_t)(latitude * 255.0f);
                  stage_three_ball_shade_map[index] = depth > 0.78f ? 3U : depth > 0.42f ? 2U : 1U;
              }
          }
          stage_three_ball_map_ready = true;
      }
      
      // Segment 3 setup: begin the icon-textured ball at its initial bounce position.
      static void startStageThreeBall(uint32_t now_ms)
      {
          buildStageThreeBallMap();
          stage_three_next_icon_change_at = now_ms + DEMO_STAGE_THREE_ICON_CHANGE_MS;
          stage_three_icon_slot = 0U;
          stage_three_icon_bitmap = demo_icon_slots[stage_three_icon_slot];
          stage_three_ball_x = 82.0f;
          stage_three_ball_y = 52.0f;
          stage_three_ball_velocity_x = DEMO_STAGE_THREE_BALL_HORIZONTAL_VELOCITY;
          stage_three_ball_velocity_y = 0.0f;
          stage_three_ball_rotation_phase = 0.0f;
      }
      
      static void selectStageSixMesh(void)
      {
          const demo_mesh_t *next_mesh;
          do {
              next_mesh = demo_stage_six_mesh_catalog[esp_random() % DEMO_STAGE_SIX_MESH_CATALOG_COUNT];
          } while (next_mesh == stage_six_current_mesh);
          stage_six_current_mesh = next_mesh;
      
          size_t next_colour_scheme =
              (size_t)esp_random() % DEMO_SOLID_COLOUR_SCHEME_COUNT;
          if (stage_six_colour_scheme_index < DEMO_SOLID_COLOUR_SCHEME_COUNT &&
              DEMO_SOLID_COLOUR_SCHEME_COUNT > 1U) {
              next_colour_scheme = (size_t)esp_random() % (DEMO_SOLID_COLOUR_SCHEME_COUNT - 1U);
              if (next_colour_scheme >= stage_six_colour_scheme_index) ++next_colour_scheme;
          }
          stage_six_colour_scheme_index = next_colour_scheme;
      }
      
      // Segment 6 setup: select the first random filled solid and start its five-second cycle.
      static void startStageSixSolid(uint32_t now_ms)
      {
          stage_six_next_change_at = now_ms + DEMO_STAGE_SIX_CHANGE_MS;
          stage_six_current_mesh = NULL;
          selectStageSixMesh();
      }
      
      static uint32_t randomTextCornerIconTravelDuration(void)
      {
          // Keep the range inclusive so setting MIN and MAX equal later creates a
          // fixed, repeatable period without changing the animation code.
          const uint32_t range = DEMO_TEXT_CORNER_ICON_TRAVEL_MAX_MS -
                                 DEMO_TEXT_CORNER_ICON_TRAVEL_MIN_MS + 1U;
          return DEMO_TEXT_CORNER_ICON_TRAVEL_MIN_MS + esp_random() % range;
      }
      
      static int16_t randomTextCornerIconRotationSpeed(void)
      {
          const uint32_t range = (uint32_t)(DEMO_TEXT_CORNER_ICON_ROTATION_MAX_DEGREES_PER_SECOND -
                                            DEMO_TEXT_CORNER_ICON_ROTATION_MIN_DEGREES_PER_SECOND + 1);
          const int16_t speed = (int16_t)(DEMO_TEXT_CORNER_ICON_ROTATION_MIN_DEGREES_PER_SECOND +
                                          (int)(esp_random() % range));
          // Give neighbouring icons visibly different movement by independently
          // choosing their clockwise or anti-clockwise direction as well as speed.
          return (esp_random() & 1U) != 0U ? speed : (int16_t)-speed;
      }
      
      static void startTextCornerIconCycles(uint32_t now_ms)
      {
          // A text segment always starts with every icon at its own outer corner.
          // The first random duration then separates the icons naturally as they
          // travel toward the centre X line at different rates.
          for (size_t corner = 0U; corner < DEMO_TEXT_CORNER_ICON_COUNT; ++corner) {
              demo_text_corner_icon_cycles[corner].cycle_started_at = now_ms;
              demo_text_corner_icon_cycles[corner].cycle_duration_ms =
                  randomTextCornerIconTravelDuration();
              // Rotation does not restart with every travel cycle: it stays smooth
              // while movement chooses a new random duration at each outer edge.
              demo_text_corner_icon_cycles[corner].rotation_started_at = now_ms;
              demo_text_corner_icon_cycles[corner].rotation_start_degrees =
                  (float)(esp_random() % 360U);
              demo_text_corner_icon_cycles[corner].rotation_degrees_per_second =
                  randomTextCornerIconRotationSpeed();
          }
      }
      
      static void updateTextCornerIconCycles(uint32_t now_ms)
      {
          for (size_t corner = 0U; corner < DEMO_TEXT_CORNER_ICON_COUNT; ++corner) {
              demo_text_corner_icon_cycle_t *const cycle = &demo_text_corner_icon_cycles[corner];
              // A late frame can cross more than one complete cycle. Preserve the
              // remaining time by advancing from the old start, rather than snapping
              // the icon back to its corner at the current frame time.
              while ((uint32_t)(now_ms - cycle->cycle_started_at) >= cycle->cycle_duration_ms) {
                  cycle->cycle_started_at += cycle->cycle_duration_ms;
                  cycle->cycle_duration_ms = randomTextCornerIconTravelDuration();
              }
          }
      }
      
      static void startStageSevenWalloons(uint32_t now_ms)
      {
          // Begin with the boxed PET title. After the short title interval, the box
          // disappears and the original action begins with the left Walloon walking
          // from PET cell (2, 15) to (14, 15).
          stage_seven_phase = WALLOONS_TITLE_CARD;
          stage_seven_phase_started_at = now_ms;
          stage_seven_completed_bounces = 0U;
      }
      
      #if 0 // Moved to demo_stage_slideshow_lens.c.
      static void releaseStageTwelveImage(void)
      {
          heap_caps_free(stage_twelve_image);
          stage_twelve_image = NULL;
          stage_twelve_image_ready = false;
          stage_twelve_image_path[0] = '\0';
          stage_twelve_next_image_change_at = 0U;
      }
      #endif
      
      static void releaseStageFifteenGroundSource(void)
      {
          heap_caps_free(stage_fifteen_ground_source);
          stage_fifteen_ground_source = NULL;
      }
      
      static void startStageFifteenBattleZoneRaster(void)
      {
          releaseStageFifteenGroundSource();
          const size_t ground_pixels = (size_t)LCD_WIDTH *
              (LCD_HEIGHT - BATTLE_ZONE_HORIZON_Y);
          stage_fifteen_ground_source = heap_caps_malloc(ground_pixels *
                                                         sizeof(stage_fifteen_ground_source[0]),
                                                         MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
      }
      
      #if 0 // Moved to demo_stage_slideshow_lens.c.
      static void chooseStageTwelveLensEdgePoint(stage_twelve_lens_edge_t edge,
                                                 float *x, float *y)
      {
          // The centre is always on one screen edge. The lens radius can therefore
          // extend outside the display, while every sampled source pixel stays valid.
          switch (edge) {
              case STAGE_TWELVE_LENS_EDGE_TOP:
                  *x = (float)(esp_random() % LCD_WIDTH);
                  *y = 0.0f;
                  break;
              case STAGE_TWELVE_LENS_EDGE_RIGHT:
                  *x = (float)(LCD_WIDTH - 1);
                  *y = (float)(esp_random() % LCD_HEIGHT);
                  break;
              case STAGE_TWELVE_LENS_EDGE_BOTTOM:
                  *x = (float)(esp_random() % LCD_WIDTH);
                  *y = (float)(LCD_HEIGHT - 1);
                  break;
              case STAGE_TWELVE_LENS_EDGE_LEFT:
              default:
                  *x = 0.0f;
                  *y = (float)(esp_random() % LCD_HEIGHT);
                  break;
          }
      }
      
      static void chooseStageTwelveLensTarget(void)
      {
          // Pick a different edge and a visibly long route. The bounded retry count
          // guarantees progress even if future tuning makes the distance too strict.
          for (unsigned attempt = 0U; attempt < DEMO_STAGE_TWELVE_LENS_TARGET_ATTEMPTS; ++attempt) {
              do {
                  stage_twelve_lens_target_edge =
                      (stage_twelve_lens_edge_t)(esp_random() % STAGE_TWELVE_LENS_EDGE_COUNT);
              } while (stage_twelve_lens_target_edge == stage_twelve_lens_current_edge);
              chooseStageTwelveLensEdgePoint(stage_twelve_lens_target_edge,
                                             &stage_twelve_lens_target_x,
                                             &stage_twelve_lens_target_y);
              const float dx = stage_twelve_lens_target_x - stage_twelve_lens_x;
              const float dy = stage_twelve_lens_target_y - stage_twelve_lens_y;
              const float distance_squared = dx * dx + dy * dy;
              const float minimum_squared = DEMO_STAGE_TWELVE_LENS_MIN_TARGET_DISTANCE *
                                            DEMO_STAGE_TWELVE_LENS_MIN_TARGET_DISTANCE;
              if (distance_squared >= minimum_squared) return;
          }
      }
      
      static bool selectStageTwelveSlideshowImage(char *path, size_t path_size,
                                                  const char *excluded_path)
      {
          DIR *const directory = opendir(DEMO_STAGE_TWELVE_SLIDESHOW_DIRECTORY);
          if (directory == NULL) return false;
      
          // Reservoir sampling gives every supported file an equal chance without
          // storing the slideshow directory's full filename list in RAM.
          char selected_name[DEMO_STAGE_TWELVE_IMAGE_PATH_MAX] = {0};
          size_t supported_count = 0U;
          struct dirent *entry;
          while ((entry = readdir(directory)) != NULL) {
              if (!imageIsSupportedName(entry->d_name)) continue;
              char candidate_path[DEMO_STAGE_TWELVE_IMAGE_PATH_MAX];
              const int candidate_written = snprintf(candidate_path, sizeof(candidate_path), "%s/%s",
                                                     DEMO_STAGE_TWELVE_SLIDESHOW_DIRECTORY, entry->d_name);
              if (candidate_written < 0 || (size_t)candidate_written >= sizeof(candidate_path)) continue;
              // Do not immediately repeat the current picture when the directory
              // contains another supported file to choose from.
              if (excluded_path != NULL && strcmp(candidate_path, excluded_path) == 0) continue;
              ++supported_count;
              if (esp_random() % supported_count != 0U) continue;
              const int written = snprintf(selected_name, sizeof(selected_name), "%s", entry->d_name);
              if (written < 0 || (size_t)written >= sizeof(selected_name)) {
                  closedir(directory);
                  return false;
              }
          }
          closedir(directory);
          if (supported_count == 0U) return false;
      
          const int written = snprintf(path, path_size, "%s/%s",
                                       DEMO_STAGE_TWELVE_SLIDESHOW_DIRECTORY, selected_name);
          return written >= 0 && (size_t)written < path_size;
      }
      
      static void beginStageTwelveLensGlide(uint32_t now_ms)
      {
          stage_twelve_lens_start_x = stage_twelve_lens_x;
          stage_twelve_lens_start_y = stage_twelve_lens_y;
          chooseStageTwelveLensTarget();
          stage_twelve_lens_glide_started_at = now_ms;
          stage_twelve_lens_glide_duration_ms = DEMO_STAGE_TWELVE_LENS_GLIDE_MIN_MS +
              (esp_random() % DEMO_STAGE_TWELVE_LENS_GLIDE_RANGE_MS);
      }
      
      static void startStageTwelveSlideshowLens(uint32_t now_ms)
      {
          releaseStageTwelveImage();
          stage_twelve_image = heap_caps_malloc((size_t)LCD_WIDTH * LCD_HEIGHT *
                                                 sizeof(stage_twelve_image[0]),
                                                 MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
          if (stage_twelve_image == NULL) return;
      
          char image_path[DEMO_STAGE_TWELVE_IMAGE_PATH_MAX];
          if (!selectStageTwelveSlideshowImage(image_path, sizeof(image_path), NULL) ||
              !imageDecodeFile(image_path, stage_twelve_image)) {
              releaseStageTwelveImage();
              return;
          }
      
          stage_twelve_image_ready = true;
          snprintf(stage_twelve_image_path, sizeof(stage_twelve_image_path), "%s", image_path);
          stage_twelve_next_image_change_at = now_ms + DEMO_STAGE_TWELVE_IMAGE_CHANGE_MS;
          stage_twelve_lens_current_edge =
              (stage_twelve_lens_edge_t)(esp_random() % STAGE_TWELVE_LENS_EDGE_COUNT);
          chooseStageTwelveLensEdgePoint(stage_twelve_lens_current_edge,
                                         &stage_twelve_lens_x, &stage_twelve_lens_y);
          beginStageTwelveLensGlide(now_ms);
      }
      
      static void updateStageTwelveSlideshowLens(uint32_t now_ms)
      {
          if (!stage_twelve_image_ready) return;
          if ((int32_t)(now_ms - stage_twelve_next_image_change_at) >= 0) {
              char image_path[DEMO_STAGE_TWELVE_IMAGE_PATH_MAX];
              // Reuse the existing PSRAM image buffer. If a new file cannot be
              // selected or decoded, leave the already visible picture in place.
              if (selectStageTwelveSlideshowImage(image_path, sizeof(image_path),
                                                   stage_twelve_image_path) &&
                  imageDecodeFile(image_path, stage_twelve_image)) {
                  snprintf(stage_twelve_image_path, sizeof(stage_twelve_image_path), "%s", image_path);
              }
              stage_twelve_next_image_change_at = now_ms + DEMO_STAGE_TWELVE_IMAGE_CHANGE_MS;
          }
          const uint32_t elapsed_ms = now_ms - stage_twelve_lens_glide_started_at;
          if (elapsed_ms >= stage_twelve_lens_glide_duration_ms) {
              stage_twelve_lens_x = stage_twelve_lens_target_x;
              stage_twelve_lens_y = stage_twelve_lens_target_y;
              stage_twelve_lens_current_edge = stage_twelve_lens_target_edge;
              beginStageTwelveLensGlide(now_ms);
              return;
          }
      
          const float progress = (float)elapsed_ms / (float)stage_twelve_lens_glide_duration_ms;
          // Use constant-speed interpolation rather than smoothstep. Smoothstep
          // reaches zero speed at every target, which made the lens visibly pause.
          stage_twelve_lens_x = stage_twelve_lens_start_x +
              (stage_twelve_lens_target_x - stage_twelve_lens_start_x) * progress;
          stage_twelve_lens_y = stage_twelve_lens_start_y +
              (stage_twelve_lens_target_y - stage_twelve_lens_start_y) * progress;
      }
      #endif
      
      static unsigned walloonsPhaseSteps(uint32_t now_ms, unsigned maximum_steps)
      {
          const unsigned elapsed_steps = (now_ms - stage_seven_phase_started_at) /
                                         DEMO_STAGE_SEVEN_STEP_MS;
          return elapsed_steps < maximum_steps ? elapsed_steps : maximum_steps;
      }
      
      static void setWalloonsPhase(walloons_phase_t phase, uint32_t now_ms)
      {
          stage_seven_phase = phase;
          stage_seven_phase_started_at = now_ms;
      }
      
      static void updateStageSevenWalloons(uint32_t now_ms)
      {
          const uint32_t elapsed = now_ms - stage_seven_phase_started_at;
          const uint32_t walk_duration = 12U * DEMO_STAGE_SEVEN_STEP_MS;
          const uint32_t platform_duration = 14U * DEMO_STAGE_SEVEN_STEP_MS;
          // There are twelve row moves. Hold the twelfth (landing) cell for one full
          // step interval instead of changing phase before drawDemo can show it.
          const uint32_t vertical_duration = 13U * DEMO_STAGE_SEVEN_STEP_MS;
      
          switch (stage_seven_phase) {
              case WALLOONS_TITLE_CARD:
                  if (elapsed >= DEMO_STAGE_SEVEN_TITLE_MS) {
                      setWalloonsPhase(WALLOONS_LEFT_WALK_IN, now_ms);
                  }
                  break;
              case WALLOONS_LEFT_WALK_IN:
                  if (elapsed >= walk_duration) {
                      setWalloonsPhase(WALLOONS_PAUSE_ON_SEESAW, now_ms);
                  }
                  break;
              case WALLOONS_PAUSE_ON_SEESAW:
                  if (elapsed >= DEMO_STAGE_SEVEN_PAUSE_MS) {
                      setWalloonsPhase(WALLOONS_RIGHT_WALK_ON_PLATFORM, now_ms);
                  }
                  break;
              case WALLOONS_RIGHT_WALK_ON_PLATFORM:
                  if (elapsed >= platform_duration) {
                      setWalloonsPhase(WALLOONS_RIGHT_DROP, now_ms);
                  }
                  break;
              case WALLOONS_RIGHT_DROP:
                  if (elapsed >= vertical_duration) {
                      setWalloonsPhase(WALLOONS_BOUNCE_RIGHT_UP, now_ms);
                  }
                  break;
              case WALLOONS_BOUNCE_RIGHT_UP:
                  if (elapsed >= vertical_duration) {
                      setWalloonsPhase(WALLOONS_BOUNCE_RIGHT_DOWN, now_ms);
                  }
                  break;
              case WALLOONS_BOUNCE_RIGHT_DOWN:
                  if (elapsed >= vertical_duration) {
                      setWalloonsPhase(WALLOONS_BOUNCE_LEFT_UP, now_ms);
                  }
                  break;
              case WALLOONS_BOUNCE_LEFT_UP:
                  if (elapsed >= vertical_duration) {
                      setWalloonsPhase(WALLOONS_BOUNCE_LEFT_DOWN, now_ms);
                  }
                  break;
              case WALLOONS_BOUNCE_LEFT_DOWN:
                  if (elapsed >= vertical_duration) {
                      setWalloonsPhase(WALLOONS_LEFT_LANDED, now_ms);
                  }
                  break;
              case WALLOONS_LEFT_LANDED:
                  // Keep the left Walloon visibly on the new low end before launching
                  // the right Walloon. This is the original stunt's missing landing beat.
                  if (elapsed >= DEMO_STAGE_SEVEN_LANDING_MS) {
                      ++stage_seven_completed_bounces;
                      if (stage_seven_completed_bounces >= DEMO_STAGE_SEVEN_BOUNCE_COUNT) {
                          setWalloonsPhase(WALLOONS_FINISHED, now_ms);
                      } else {
                          setWalloonsPhase(WALLOONS_BOUNCE_RIGHT_UP, now_ms);
                      }
                  }
                  break;
              case WALLOONS_FINISHED:
                  // Hold the completed stunt until C or the shared inactivity timer advances Demo.
                  break;
          }
      }
      
      static uint32_t demoTimeNow(uint32_t now_ms)
      {
          return now_ms - demo_paused_total_ms;
      }
      
      static void enterDemoStage(demo_stage_t stage, uint32_t now_ms)
      {
          // Stage 12 owns a full-screen PSRAM source only while it is visible.
          if (selected_stage == DEMO_STAGE_SLIDESHOW_LENS) demoStageSlideshowLensStop();
          if (selected_stage == DEMO_STAGE_GAME_OF_LIFE) demoStageGameOfLifeStop();
          if (selected_stage == DEMO_STAGE_BATTLE_ZONE_RASTER) releaseStageFifteenGroundSource();
          selected_stage = stage;
          // Entering any segment begins a fresh shared inactivity interval.
          demo_last_interaction_at = now_ms;
          if (selected_stage == DEMO_STAGE_ICON) {
              // A full Demo loop begins a fresh automatic Stage-1 effect interval.
              stage_one_next_effect_change_at = now_ms + DEMO_STAGE_ONE_EFFECT_CHANGE_MS;
          }
          if (selected_stage == DEMO_STAGE_AMIGA_BALL) startStageThreeBall(now_ms);
          if (selected_stage == DEMO_STAGE_GEOMETRIC_SOLID) startStageSixSolid(now_ms);
          if (selected_stage == DEMO_STAGE_SCROLLER ||
              selected_stage == DEMO_STAGE_REVERSE_SCROLLER) startTextCornerIconCycles(now_ms);
          if (selected_stage == DEMO_STAGE_WALLOONS) demoStageWalloonsStart(now_ms);
          if (selected_stage == DEMO_STAGE_KALEIDO) demoStageKaleidoStart(now_ms);
          if (selected_stage == DEMO_STAGE_SPARKLE) demoStageSparkleStart(now_ms);
          if (selected_stage == DEMO_STAGE_SQUARES) demoStageSquaresStart(now_ms);
          if (selected_stage == DEMO_STAGE_BATTLE_ZONE_RASTER) startStageFifteenBattleZoneRaster();
          if (selected_stage == DEMO_STAGE_BEVEL_GEARS) {
              stage_seventeen_rig_pitch_degrees = -12.0f;
              stage_seventeen_rig_yaw_degrees = 18.0f;
              stage_seventeen_rig_roll_degrees = 0.0f;
              stage_seventeen_gear_degrees[0] = 0.0f;
              // Each driven gear begins half a tooth out of phase with the lead gear
              // at its fixed mesh contact. Their later angles are derived by motion.
              stage_seventeen_gear_degrees[1] = 286.0f;
              stage_seventeen_gear_degrees[2] = 226.0f;
              // Start with the clearest filled treatment; B cycles outline, flat-retro,
              // and shaded-metal versions without restarting the rig.
              stage_seventeen_style = STAGE_SEVENTEEN_STYLE_FLAT;
          }
          if (selected_stage == DEMO_STAGE_MOUSE_MAZE) demoStageMouseMazeStart(now_ms);
          if (selected_stage == DEMO_STAGE_RASTER_BANDS ||
              selected_stage == DEMO_STAGE_LOWER_RASTER_SCROLLER ||
              selected_stage == DEMO_STAGE_BATTLE_ZONE_RASTER) {
              raster_band_effect_elapsed_ms = 0U;
              // Each raster segment starts on its own fitting palette. B can then
              // cycle every palette from the shared list without interrupting motion.
              if (selected_stage == DEMO_STAGE_LOWER_RASTER_SCROLLER) {
                  raster_band_palette_index = DEMO_RASTER_FIRE_PALETTE_INDEX;
              } else if (selected_stage == DEMO_STAGE_BATTLE_ZONE_RASTER) {
                  raster_band_palette_index = DEMO_RASTER_GREEN_PALETTE_INDEX;
              } else {
                  raster_band_palette_index = DEMO_RASTER_METALLIC_PALETTE_INDEX;
              }
          }
          if (selected_stage == DEMO_STAGE_LOWER_RASTER_SCROLLER && scroll_text != NULL) {
              // This segment is wider than the shared scale-2 ticker. Restart the
              // current message just outside its travel edge using its scale-3 width.
              scroll_text_width = strlen(scroll_text) * PET2001_FONT_GLYPH_WIDTH *
                                  (size_t)DEMO_STAGE_FOURTEEN_SCROLL_SCALE;
              scroll_text_x = scroll_speed_pixels_per_second >= 0.0f
                  ? (float)LCD_WIDTH : -(float)scroll_text_width;
          }
          if (selected_stage == DEMO_STAGE_SLIDESHOW_LENS) demoStageSlideshowLensStart(now_ms);
          if (selected_stage == DEMO_STAGE_PLASMA) demoStagePlasmaStart();
          if (selected_stage == DEMO_STAGE_GAME_OF_LIFE) demoStageGameOfLifeStart(now_ms);
      }
      
      static void advanceDemoStage(uint32_t now_ms)
      {
          enterDemoStage(demoSelectorNextAutomaticStage(selected_stage), now_ms);
      }
      
      static void updateStageThreeBall(float elapsed_seconds)
      {
          stage_three_ball_x += stage_three_ball_velocity_x * elapsed_seconds;
          stage_three_ball_y += stage_three_ball_velocity_y * elapsed_seconds;
          stage_three_ball_velocity_y += DEMO_STAGE_THREE_BALL_GRAVITY * elapsed_seconds;
          stage_three_ball_rotation_phase +=
              DEMO_STAGE_THREE_BALL_ROTATION_BANDS_PER_SECOND * elapsed_seconds;
          if (stage_three_ball_rotation_phase >= (float)DEMO_STAGE_THREE_BALL_LONGITUDE_BANDS) {
              stage_three_ball_rotation_phase -= (float)DEMO_STAGE_THREE_BALL_LONGITUDE_BANDS;
          }
          if (stage_three_ball_x < (float)DEMO_STAGE_THREE_BALL_RADIUS) {
              stage_three_ball_x = (float)DEMO_STAGE_THREE_BALL_RADIUS;
              stage_three_ball_velocity_x = DEMO_STAGE_THREE_BALL_HORIZONTAL_VELOCITY;
          } else if (stage_three_ball_x >
                     (float)(LCD_WIDTH - 1 - DEMO_STAGE_THREE_BALL_RADIUS)) {
              stage_three_ball_x = (float)(LCD_WIDTH - 1 - DEMO_STAGE_THREE_BALL_RADIUS);
              stage_three_ball_velocity_x = -DEMO_STAGE_THREE_BALL_HORIZONTAL_VELOCITY;
          }
          if (stage_three_ball_y > DEMO_STAGE_THREE_BALL_FLOOR_Y -
                                   (float)DEMO_STAGE_THREE_BALL_RADIUS) {
              stage_three_ball_y = DEMO_STAGE_THREE_BALL_FLOOR_Y -
                                   (float)DEMO_STAGE_THREE_BALL_RADIUS;
              stage_three_ball_velocity_y = DEMO_STAGE_THREE_BALL_BOUNCE_VELOCITY;
          }
      }
      
      static void selectRandomStageTransition(void)
      {
          selected_stage_transition = (demo_stage_transition_effect_t)
              (esp_random() % DEMO_STAGE_TRANSITION_EFFECT_COUNT);
          if (selected_stage_transition != DEMO_STAGE_TRANSITION_SCROLL) return;
      
          // Each pair keeps both frames on the same axis, travelling in opposite directions.
          // The index is random so each scroll transition can go left, right, up, or down.
          switch (esp_random() % 4U) {
              case 0U:
                  stage_scroll_out_direction = SCREEN_TRANSITION_SCROLL_LEFT;
                  stage_scroll_in_direction = SCREEN_TRANSITION_SCROLL_RIGHT;
                  break;
              case 1U:
                  stage_scroll_out_direction = SCREEN_TRANSITION_SCROLL_RIGHT;
                  stage_scroll_in_direction = SCREEN_TRANSITION_SCROLL_LEFT;
                  break;
              case 2U:
                  stage_scroll_out_direction = SCREEN_TRANSITION_SCROLL_UP;
                  stage_scroll_in_direction = SCREEN_TRANSITION_SCROLL_DOWN;
                  break;
              default:
                  stage_scroll_out_direction = SCREEN_TRANSITION_SCROLL_DOWN;
                  stage_scroll_in_direction = SCREEN_TRANSITION_SCROLL_UP;
                  break;
          }
      }
      
      static void selectScrollMessage(bool switch_logo)
      {
          size_t next = (size_t)esp_random() % DEMO_SCROLL_MESSAGE_COUNT;
          if (scroll_message_index < DEMO_SCROLL_MESSAGE_COUNT) {
              next = (size_t)esp_random() % (DEMO_SCROLL_MESSAGE_COUNT - 1U);
              if (next >= scroll_message_index) ++next;
          }
          scroll_message_index = next;
          scroll_text = demo_scroll_messages[scroll_message_index];
          const size_t scroll_scale = selected_stage == DEMO_STAGE_LOWER_RASTER_SCROLLER
              ? (size_t)DEMO_STAGE_FOURTEEN_SCROLL_SCALE
              : DEMO_SCROLL_SCALE;
          scroll_text_width = strlen(scroll_text) * PET2001_FONT_GLYPH_WIDTH * scroll_scale;
          scroll_text_x = scroll_speed_pixels_per_second >= 0.0f
              ? (float)LCD_WIDTH : -(float)scroll_text_width;
          if (switch_logo && !DEMO_LOCK_ICON_TO_PRIMARY) {
              advanceIconSlot();
          }
          scroll_text_effect_elapsed_ms = 0U;
      }
      
      static bool dimFlamesBehindTickerGlyph(uint16_t *frame, int centre_x, int centre_y,
                                             float glyph_scale)
      {
          if (selected_effect != DEMO_EFFECT_RADIAL_FLAME || glyph_scale <= 0.0f) return false;
          const int half_width = (int)ceilf((float)PET2001_FONT_GLYPH_WIDTH * glyph_scale * 0.5f);
          const int half_height = (int)ceilf((float)PET2001_FONT_GLYPH_HEIGHT * glyph_scale * 0.5f);
          bool touches_flame = false;
          for (int y = centre_y - half_height; y <= centre_y + half_height; ++y) {
              if (y < 0 || y >= LCD_HEIGHT) continue;
              for (int x = centre_x - half_width; x <= centre_x + half_width; ++x) {
                  if (x < 0 || x >= LCD_WIDTH) continue;
                  uint16_t *pixel = &frame[(size_t)y * LCD_WIDTH + (size_t)x];
                  for (size_t colour = 0U; colour < sizeof(demo_ring_colours) /
                                                    sizeof(demo_ring_colours[0]); ++colour) {
                      if (*pixel != demo_ring_colours[colour]) continue;
                      // Retain the moving fire shape but step it down for white PET text.
                      *pixel = colour == 0U ? COLOR_BLACK : demo_ring_colours[colour - 1U];
                      touches_flame = true;
                      break;
                  }
              }
          }
          return touches_flame;
      }
      
      static uint16_t demoScrollRainbowColour(unsigned glyph_index)
      {
          const unsigned colour_count = sizeof(demo_scroll_rainbow_colours) /
                                       sizeof(demo_scroll_rainbow_colours[0]);
          const unsigned time_step = scroll_text_effect_elapsed_ms /
                                     DEMO_SCROLL_RAINBOW_MS_PER_COLOUR;
          const unsigned glyph_step = glyph_index / DEMO_SCROLL_RAINBOW_GLYPHS_PER_COLOUR;
          return demo_scroll_rainbow_colours[(glyph_step + time_step) % colour_count];
      }
      
      // Segment 4 renderer helpers: enlarge and spread glyphs around the centre lens.
      static float stageFourMagnifierScale(int glyph_centre_x)
      {
          const float distance = fabsf((float)glyph_centre_x -
                                       (float)DEMO_STAGE_FOUR_MAGNIFIER_CENTRE_X);
          float influence = 1.0f - distance / DEMO_STAGE_FOUR_MAGNIFIER_RADIUS;
          if (influence < 0.0f) influence = 0.0f;
          // Squaring concentrates the largest letters around the exact screen centre.
          influence *= influence;
          return (float)DEMO_SCROLL_SCALE +
                 (DEMO_STAGE_FOUR_MAGNIFIER_MAX_SCALE - (float)DEMO_SCROLL_SCALE) * influence;
      }
      
      static float stageFourMagnifierPosition(int glyph_centre_x)
      {
          const float centre = (float)DEMO_STAGE_FOUR_MAGNIFIER_CENTRE_X;
          const float offset = (float)glyph_centre_x - centre;
          const float distance = fabsf(offset);
          if (distance >= DEMO_STAGE_FOUR_MAGNIFIER_RADIUS) return (float)glyph_centre_x;
          const float influence = 1.0f - distance / DEMO_STAGE_FOUR_MAGNIFIER_RADIUS;
          // Spread the glyph centres with the lens so expanded neighbouring letters do not overlap.
          return centre + offset * (1.0f + DEMO_STAGE_FOUR_MAGNIFIER_SPACING_STRENGTH *
                                    influence * influence);
      }
      
      // Segment 5 renderer helpers: the same scroller in reverse, shrinking at centre
      // and expanding toward both edges.
      static float stageFiveMagnifierScale(int glyph_centre_x)
      {
          float influence = fabsf((float)glyph_centre_x -
                                  (float)DEMO_STAGE_FOUR_MAGNIFIER_CENTRE_X) /
                            DEMO_STAGE_FOUR_MAGNIFIER_RADIUS;
          if (influence > 1.0f) influence = 1.0f;
          // Inverse lens: smallest at centre, largest at the screen edges and beyond.
          return (float)DEMO_SCROLL_SCALE +
                 (DEMO_STAGE_FOUR_MAGNIFIER_MAX_SCALE - (float)DEMO_SCROLL_SCALE) *
                 influence * influence;
      }
      
      static float stageFiveMagnifierPosition(int glyph_centre_x)
      {
          const float centre = (float)DEMO_STAGE_FOUR_MAGNIFIER_CENTRE_X;
          const float offset = (float)glyph_centre_x - centre;
          float influence = fabsf(offset) / DEMO_STAGE_FOUR_MAGNIFIER_RADIUS;
          if (influence > 1.0f) influence = 1.0f;
          // Keep the maximum expansion beyond both screen edges so large letters enter
          // and leave cleanly instead of snapping back to their ordinary spacing.
          return centre + offset * (1.0f + DEMO_STAGE_FOUR_MAGNIFIER_SPACING_STRENGTH *
                                    influence * influence);
      }
      
      // Shared ticker renderer. Segments 4, 5, 11, 14, and 15 select their own positioning
      // or magnification above; every other segment receives the normal bottom ticker.
      static void drawScrollText(uint16_t *frame)
      {
          if (scroll_text == NULL) return;
      
          const unsigned text_length = (unsigned)strlen(scroll_text);
          const int base_x = (int)lroundf(scroll_text_x);
          const bool magnifier_active = selected_stage == DEMO_STAGE_SCROLLER ||
                                        selected_stage == DEMO_STAGE_REVERSE_SCROLLER;
          const bool raster_band_scroller = selected_stage == DEMO_STAGE_RASTER_BANDS ||
                                            selected_stage == DEMO_STAGE_LOWER_RASTER_SCROLLER ||
                                            selected_stage == DEMO_STAGE_BATTLE_ZONE_RASTER;
          const bool lower_raster_band_scroller =
              selected_stage == DEMO_STAGE_LOWER_RASTER_SCROLLER;
          const bool battle_zone_raster_scroller =
              selected_stage == DEMO_STAGE_BATTLE_ZONE_RASTER;
          const int advance = PET2001_FONT_GLYPH_WIDTH *
              (lower_raster_band_scroller ? (int)DEMO_STAGE_FOURTEEN_SCROLL_SCALE :
                                           DEMO_SCROLL_SCALE);
          const uint32_t text_wave_elapsed_ms = raster_band_scroller ?
              raster_band_effect_elapsed_ms : scroll_text_effect_elapsed_ms;
          const bool reverse_magnifier = selected_stage == DEMO_STAGE_REVERSE_SCROLLER;
          for (unsigned index = 0U; index < text_length; ++index) {
              const int unscaled_centre_x = base_x + (int)index * advance + advance / 2;
              const int centre_x = !magnifier_active ? unscaled_centre_x :
                  (int)lroundf(reverse_magnifier ? stageFiveMagnifierPosition(unscaled_centre_x) :
                                                       stageFourMagnifierPosition(unscaled_centre_x));
              const float glyph_scale = !magnifier_active ?
                  (lower_raster_band_scroller ? DEMO_STAGE_FOURTEEN_SCROLL_SCALE :
                                                (float)DEMO_SCROLL_SCALE) :
                  (reverse_magnifier ? stageFiveMagnifierScale(unscaled_centre_x) :
                                       stageFourMagnifierScale(unscaled_centre_x));
              const int glyph_margin = (int)ceilf((float)PET2001_FONT_GLYPH_WIDTH * glyph_scale);
              const int centre_y = magnifier_active ? DEMO_STAGE_FOUR_MAGNIFIER_CENTRE_Y :
                  (lower_raster_band_scroller ? DEMO_STAGE_FOURTEEN_SCROLL_CENTRE_Y :
                   (battle_zone_raster_scroller ? DEMO_STAGE_FIFTEEN_SCROLL_CENTRE_Y :
                    (raster_band_scroller ? DEMO_STAGE_ELEVEN_SCROLL_CENTRE_Y : DEMO_SCROLL_Y))) +
                  graphics_text_effect_wave_offset(text_wave_elapsed_ms, index,
                      lower_raster_band_scroller ? DEMO_STAGE_FOURTEEN_SCROLL_WAVE_AMPLITUDE :
                      (battle_zone_raster_scroller ? DEMO_STAGE_FIFTEEN_SCROLL_WAVE_AMPLITUDE :
                       (raster_band_scroller ? DEMO_STAGE_ELEVEN_SCROLL_WAVE_AMPLITUDE :
                                             DEMO_SCROLL_EFFECT_WAVE_AMPLITUDE))) +
                  (raster_band_scroller ? 0 : advance / 2);
              // Long messages contain many off-screen glyphs; skip their transform work.
              if (centre_x + glyph_margin < 0 || centre_x - glyph_margin >= LCD_WIDTH ||
                  centre_y + glyph_margin < 0 || centre_y - glyph_margin >= LCD_HEIGHT) {
                  continue;
              }
              const uint16_t glyph_color = dimFlamesBehindTickerGlyph(frame, centre_x, centre_y,
                                                                       glyph_scale)
                  ? COLOR_WHITE : demoScrollRainbowColour(index);
              drawPet2001AsciiCharTransformed(frame, centre_x, centre_y, scroll_text[index],
                                               glyph_scale, 0.0f, glyph_color);
          }
      }
      
      static graphics_point_t projectPoint(float source_x, float source_y, float depth,
                                           const demo_rotation_t *rotation)
      {
          const float scaled_x = source_x * DEMO_FACE_SCALE;
          const float scaled_y = source_y * DEMO_FACE_SCALE;
          const float pitched_y = scaled_y * rotation->pitch_cosine -
                                  depth * rotation->pitch_sine;
          const float pitched_z = scaled_y * rotation->pitch_sine +
                                  depth * rotation->pitch_cosine;
          const float yawed_x = scaled_x * rotation->yaw_cosine +
                                pitched_z * rotation->yaw_sine;
          const float yawed_z = -scaled_x * rotation->yaw_sine +
                                pitched_z * rotation->yaw_cosine;
          const float rotated_x = yawed_x * rotation->roll_cosine -
                                  pitched_y * rotation->roll_sine;
          const float rotated_y = yawed_x * rotation->roll_sine +
                                  pitched_y * rotation->roll_cosine;
          const float perspective = DEMO_CAMERA_DISTANCE / (DEMO_CAMERA_DISTANCE + yawed_z);
          const graphics_point_t result = {
              .x = DEMO_CENTRE_X + (int)lroundf(rotated_x * perspective),
              .y = DEMO_CENTRE_Y + (int)lroundf(rotated_y * perspective),
          };
          return result;
      }
      
      static float demoRingInnerRadius(void)
      {
          const float pitch = pitch_degrees * DEMO_PI / 180.0f;
          const float yaw = yaw_degrees * DEMO_PI / 180.0f;
          const float roll = roll_degrees * DEMO_PI / 180.0f;
          const demo_rotation_t rotation = {
              cosf(pitch), sinf(pitch), cosf(yaw), sinf(yaw), cosf(roll), sinf(roll),
          };
          float outer_radius = 0.0f;
          for (unsigned sample = 0U; sample < 16U; ++sample) {
              const float angle = (float)sample * DEMO_PI * 2.0f / 16.0f;
              for (unsigned depth_index = 0U; depth_index < 2U; ++depth_index) {
                  const graphics_point_t point = projectPoint(cosf(angle) * 30.0f,
                      sinf(angle) * 30.0f, depth_index == 0U ? 0.0f : DEMO_EXTRUSION_DEPTH,
                      &rotation);
                  const float dx = (float)point.x - DEMO_CENTRE_X;
                  const float dy = (float)point.y - DEMO_CENTRE_Y;
                  const float radius = sqrtf(dx * dx + dy * dy);
                  if (radius > outer_radius) outer_radius = radius;
              }
          }
          return outer_radius + DEMO_RING_ICON_GAP;
      }
      
      static void drawLogoSlice(uint16_t *frame, const uint8_t *bitmap, float depth,
                                const demo_rotation_t *rotation, uint16_t color)
      {
          for (unsigned y = 0U; y < DEMO_LOGO_HEIGHT; ++y) {
              unsigned run_start = 0U;
              while (run_start < DEMO_LOGO_WIDTH) {
                  while (run_start < DEMO_LOGO_WIDTH && !bitmapPixelSet(bitmap, run_start, y)) {
                      ++run_start;
                  }
                  if (run_start == DEMO_LOGO_WIDTH) break;
                  unsigned run_end = run_start + 1U;
                  while (run_end < DEMO_LOGO_WIDTH && bitmapPixelSet(bitmap, run_end, y)) {
                      ++run_end;
                  }
      
                  const float left = (float)run_start - (float)DEMO_LOGO_WIDTH * 0.5f;
                  const float right = (float)run_end - (float)DEMO_LOGO_WIDTH * 0.5f;
                  const float top = (float)y - (float)DEMO_LOGO_HEIGHT * 0.5f;
                  const graphics_point_t run[4] = {
                      projectPoint(left, top, depth, rotation),
                      projectPoint(right, top, depth, rotation),
                      projectPoint(right, top + 1.0f, depth, rotation),
                      projectPoint(left, top + 1.0f, depth, rotation),
                  };
                  fill_convex_polygon(frame, run, 4U, color);
                  run_start = run_end;
              }
          }
      }
      
      static graphics_point_t projectApproachPoint(float source_x, float source_y, float depth,
                                                   const demo_rotation_t *rotation, float scale,
                                                   int centre_x, int centre_y)
      {
          const float scaled_x = source_x * scale;
          const float scaled_y = source_y * scale;
          const float scaled_depth = depth * scale;
          const float pitched_y = scaled_y * rotation->pitch_cosine -
                                  scaled_depth * rotation->pitch_sine;
          const float pitched_z = scaled_y * rotation->pitch_sine +
                                  scaled_depth * rotation->pitch_cosine;
          const float yawed_x = scaled_x * rotation->yaw_cosine +
                                pitched_z * rotation->yaw_sine;
          const float yawed_z = -scaled_x * rotation->yaw_sine +
                                pitched_z * rotation->yaw_cosine;
          const float rotated_x = yawed_x * rotation->roll_cosine -
                                  pitched_y * rotation->roll_sine;
          const float rotated_y = yawed_x * rotation->roll_sine +
                                  pitched_y * rotation->roll_cosine;
          const float perspective = DEMO_CAMERA_DISTANCE / (DEMO_CAMERA_DISTANCE + yawed_z);
          const graphics_point_t result = {
              .x = centre_x + (int)lroundf(rotated_x * perspective),
              .y = centre_y + (int)lroundf(rotated_y * perspective),
          };
          return result;
      }
      
      static void drawApproachLogoSlice(uint16_t *frame, const uint8_t *bitmap, float depth,
                                        const demo_rotation_t *rotation, float scale,
                                        int centre_x, int centre_y, uint16_t color)
      {
          for (unsigned y = 0U; y < DEMO_LOGO_HEIGHT; ++y) {
              unsigned run_start = 0U;
              while (run_start < DEMO_LOGO_WIDTH) {
                  while (run_start < DEMO_LOGO_WIDTH && !bitmapPixelSet(bitmap, run_start, y)) {
                      ++run_start;
                  }
                  if (run_start == DEMO_LOGO_WIDTH) break;
                  unsigned run_end = run_start + 1U;
                  while (run_end < DEMO_LOGO_WIDTH && bitmapPixelSet(bitmap, run_end, y)) {
                      ++run_end;
                  }
                  const float left = (float)run_start - (float)DEMO_LOGO_WIDTH * 0.5f;
                  const float right = (float)run_end - (float)DEMO_LOGO_WIDTH * 0.5f;
                  const float top = (float)y - (float)DEMO_LOGO_HEIGHT * 0.5f;
                  const graphics_point_t run[4] = {
                      projectApproachPoint(left, top, depth, rotation, scale, centre_x, centre_y),
                      projectApproachPoint(right, top, depth, rotation, scale, centre_x, centre_y),
                      projectApproachPoint(right, top + 1.0f, depth, rotation, scale, centre_x, centre_y),
                      projectApproachPoint(left, top + 1.0f, depth, rotation, scale, centre_x, centre_y),
                  };
                  fill_convex_polygon(frame, run, 4U, color);
                  run_start = run_end;
              }
          }
      }
      
      // Leave the pre-Demo control page and begin Segment 1 with a fresh timeout and
      // automatic-effect schedule. This deliberately does not use a stage transition:
      // the instruction page is not one of the sixteen Demo segments.
      static void beginDemoSegments(uint32_t now_ms)
      {
          demo_instructions_active = false;
          demo_last_interaction_at = now_ms;
          stage_one_next_effect_change_at = now_ms + DEMO_STAGE_ONE_EFFECT_CHANGE_MS;
      }
      
      void startDemo(uint32_t now_ms)
      {
          demoStageSlideshowLensStop();
          demoStageGameOfLifeStop();
          releaseStageFifteenGroundSource();
          demoStagePlasmaInitialize();
          pitch_degrees = 0.0f;
          yaw_degrees = 0.0f;
          roll_degrees = 0.0f;
          active_icon_slot = 0U;
          active_logo_bitmap = demo_icon_slots[active_icon_slot];
          selectDemoIconColourScheme();
          scroll_speed_pixels_per_second = DEMO_SCROLL_INITIAL_PIXELS_PER_SECOND;
          selected_stage = DEMO_STAGE_ICON;
          stage_two_distance = DEMO_STAGE_TWO_START_DISTANCE;
          stage_two_auto_approaching = true;
          stage_three_next_icon_change_at = 0U;
          stage_three_icon_slot = 0U;
          stage_three_icon_bitmap = NULL;
          stage_six_next_change_at = 0U;
          stage_six_current_mesh = NULL;
          stage_six_colour_scheme_index = DEMO_SOLID_COLOUR_SCHEME_COUNT;
          raster_band_palette_index = DEMO_RASTER_METALLIC_PALETTE_INDEX;
          demo_paused_total_ms = 0U;
          demo_selector_active = false;
          demo_selector_clear_new_stage = false;
          demo_selector_item = 0U;
          demo_selector_joystick_direction = 0;
          demo_selector_repeating = false;
          demo_instructions_active = true;
          demo_instructions_started_at = now_ms;
          starfield_initialize(&demo_instruction_starfield, 60U, 0, 0, LCD_WIDTH, LCD_HEIGHT,
                                STARFIELD_COLOUR);
          stage_one_next_effect_change_at = 0U;
          demo_last_interaction_at = now_ms;
          screenTransitionReset(&stage_transition);
          stage_change_requested = false;
          stage_zoom_in_requested = false;
          stage_zooming_in = false;
          stage_scroll_in_requested = false;
          stage_reveal_requested = false;
          selected_stage_transition = DEMO_STAGE_TRANSITION_SHRINK_ZOOM;
          stage_scroll_out_direction = SCREEN_TRANSITION_SCROLL_LEFT;
          stage_scroll_in_direction = SCREEN_TRANSITION_SCROLL_RIGHT;
          selectScrollMessage(false);
          last_update_ms = now_ms;
          sparkle_phase = now_ms / DEMO_DOT_SPARKLE_INTERVAL_MS;
          radial_flame_initialize(&demo_ring_flame, demo_ring_heat, DEMO_RING_SEGMENTS,
                                  DEMO_RING_LAYERS, DEMO_RING_LAYER_DEPTH, esp_random());
          // Start with the outward fire ring; A/K then selects the remaining effects.
          selected_effect = DEMO_EFFECT_RADIAL_FLAME;
          running = true;
      }
      
      void stopDemo(void)
      {
          screenTransitionDeinitialize(&stage_transition);
          demoStageSlideshowLensStop();
          demoStageGameOfLifeStop();
          releaseStageFifteenGroundSource();
          running = false;
      }
      
      static void moveDemoSelector(int direction, uint32_t now_ms)
      {
          const size_t item_count = DEMO_STAGE_COUNT + 1U;
          if (direction < 0) {
              demo_selector_item = demo_selector_item == 0U ? item_count - 1U :
                  demo_selector_item - 1U;
          } else if (direction > 0) {
              demo_selector_item = (demo_selector_item + 1U) % item_count;
          }
          demo_selector_last_move_at = now_ms;
      }
      
      static void closeDemoSelector(uint32_t now_ms)
      {
          demo_paused_total_ms += now_ms - demo_selector_started_at;
          demo_selector_active = false;
          demo_selector_joystick_direction = 0;
          demo_selector_repeating = false;
          last_update_ms = demoTimeNow(now_ms);
      }
      
      static void openDemoSelector(uint32_t now_ms)
      {
          demo_selector_active = true;
          demo_selector_started_at = now_ms;
          demo_selector_item = 0U;
          demo_selector_joystick_direction = 0;
          demo_selector_repeating = false;
      }
      
      void demoAction(bool next_effect, bool toggle_icon, bool next_stage, bool user_long_pressed,
                      bool selector_previous, bool selector_next, uint32_t now_ms)
      {
          if (!running) return;
          if (demo_selector_active) {
              if (selector_previous) {
                  moveDemoSelector(-1, now_ms);
              } else if (selector_next) {
                  moveDemoSelector(1, now_ms);
              } else if (next_effect) {
                  const size_t selected_item = demo_selector_item;
                  closeDemoSelector(now_ms);
                  if (selected_item != 0U) {
                      screenTransitionReset(&stage_transition);
                      stage_change_requested = false;
                      stage_zoom_in_requested = false;
                      stage_zooming_in = false;
                      stage_scroll_in_requested = false;
                      stage_reveal_requested = false;
                      demo_instructions_active = false;
                      enterDemoStage((demo_stage_t)(selected_item - 1U), demoTimeNow(now_ms));
                      // Present one clean black frame before the newly initialized segment draws.
                      demo_selector_clear_new_stage = true;
                  }
              }
              return;
          }
          if (user_long_pressed) {
              openDemoSelector(now_ms);
              return;
          }
          now_ms = demoTimeNow(now_ms);
          if (demo_instructions_active) {
              // C skips the control page and starts Segment 1. A, B, and the stick
              // remain inactive here so the Demo always begins from its known state.
              if (next_stage) beginDemoSegments(now_ms);
              return;
          }
          if (toggle_icon) {
              if (selected_stage == DEMO_STAGE_GEOMETRIC_SOLID) {
                  // B previews another catalogue solid and restarts the shared inactivity timer.
                  // Deliberately leave the independent five-second automatic change deadline alone.
                  selectStageSixMesh();
                  demo_last_interaction_at = now_ms;
              } else if (selected_stage == DEMO_STAGE_RASTER_BANDS ||
                         selected_stage == DEMO_STAGE_LOWER_RASTER_SCROLLER ||
                         selected_stage == DEMO_STAGE_BATTLE_ZONE_RASTER) {
                  // B cycles the shared palette list without disturbing the bar's
                  // travel, colour-roll phase, or the scroller's wave.
                  raster_band_palette_index =
                      (raster_band_palette_index + 1U) % DEMO_RASTER_BAND_PALETTE_COUNT;
                  demo_last_interaction_at = now_ms;
              } else if (selected_stage == DEMO_STAGE_PLASMA) {
                  // B changes only the plasma ramp; wave phases and ticker movement continue.
                  demoStagePlasmaNextPalette();
                  demo_last_interaction_at = now_ms;
              } else if (selected_stage == DEMO_STAGE_BEVEL_GEARS) {
                  // B changes only the presentation treatment; gear and rig phases stay continuous.
                  stage_seventeen_style = (stage_seventeen_style_t)
                      (((unsigned)stage_seventeen_style + 1U) % STAGE_SEVENTEEN_STYLE_COUNT);
                  demo_last_interaction_at = now_ms;
              } else if (selected_stage != DEMO_STAGE_AMIGA_BALL &&
                         selected_stage != DEMO_STAGE_SCROLLER &&
                         selected_stage != DEMO_STAGE_REVERSE_SCROLLER &&
                         selected_stage != DEMO_STAGE_WALLOONS &&
                         selected_stage != DEMO_STAGE_KALEIDO &&
                         selected_stage != DEMO_STAGE_SPARKLE &&
                         selected_stage != DEMO_STAGE_SQUARES &&
                         selected_stage != DEMO_STAGE_SLIDESHOW_LENS &&
                         !DEMO_LOCK_ICON_TO_PRIMARY) {
                  advanceIconSlot();
                  demo_last_interaction_at = now_ms;
              }
          }
          if (next_stage && !stage_change_requested && !stage_zoom_in_requested &&
              !stage_scroll_in_requested && !stage_reveal_requested &&
              !screenTransitionActive(&stage_transition) && !screenRevealActive(&stage_transition) &&
              !screenScrollActive(&stage_transition) && !screenSliceActive(&stage_transition)) {
              // Capture happens after this update, once drawDemo has completed the outgoing frame.
              selectRandomStageTransition();
              stage_change_requested = true;
          } else if (next_effect && selected_stage == DEMO_STAGE_ICON) {
              selected_effect = (demo_effect_t)(((unsigned)selected_effect + 1U) %
                                                DEMO_EFFECT_COUNT);
              demo_last_interaction_at = now_ms;
          }
      }
      
      void updateDemo(int joystick_x, int joystick_y, bool joystick_valid, uint32_t now_ms)
      {
          if (demo_selector_active) {
              int direction = 0;
              if (joystick_valid) {
                  if (joystick_y > DEMO_SELECTOR_JOYSTICK_ENTER) direction = -1;
                  else if (joystick_y < -DEMO_SELECTOR_JOYSTICK_ENTER) direction = 1;
              }
              if (direction == 0) {
                  if (joystick_y >= -DEMO_SELECTOR_JOYSTICK_RELEASE &&
                      joystick_y <= DEMO_SELECTOR_JOYSTICK_RELEASE) {
                      demo_selector_joystick_direction = 0;
                      demo_selector_repeating = false;
                  }
                  return;
              }
              if (direction != demo_selector_joystick_direction) {
                  demo_selector_joystick_direction = direction;
                  demo_selector_repeating = false;
                  moveDemoSelector(direction, now_ms);
              } else if (now_ms - demo_selector_last_move_at >=
                         (demo_selector_repeating ? DEMO_SELECTOR_JOYSTICK_REPEAT_MS :
                          DEMO_SELECTOR_JOYSTICK_REPEAT_START_MS)) {
                  demo_selector_repeating = true;
                  moveDemoSelector(direction, now_ms);
              }
              return;
          }
          now_ms = demoTimeNow(now_ms);
          if (!running || now_ms - last_update_ms < DEMO_FRAME_MS) return;
          uint32_t elapsed_ms = now_ms - last_update_ms;
          if (elapsed_ms > 50U) elapsed_ms = 50U;
          last_update_ms = now_ms;
          if (demo_instructions_active) {
              // The control page has its own fixed duration and does not consume any
              // of Segment 1's normal inactivity timeout.
              starfield_update(&demo_instruction_starfield, elapsed_ms);
              if ((uint32_t)(now_ms - demo_instructions_started_at) >=
                  DEMO_INSTRUCTIONS_DURATION_MS) {
                  beginDemoSegments(now_ms);
              }
              return;
          }
          if (screenTransitionActive(&stage_transition)) {
              if (screenTransitionComplete(&stage_transition)) {
                  // The transition's finished frame was shown. Either resume the new stage or
                  // select it and request its off-screen capture for the incoming zoom.
                  screenTransitionReset(&stage_transition);
                  if (stage_zooming_in) {
                      stage_zooming_in = false;
                  } else {
                      advanceDemoStage(now_ms);
                      stage_zoom_in_requested = true;
                  }
              }
              return;
          }
          if (screenRevealActive(&stage_transition)) {
              if (screenRevealComplete(&stage_transition)) {
                  screenRevealReset(&stage_transition);
              }
              return;
          }
          if (screenScrollActive(&stage_transition)) {
              if (screenScrollComplete(&stage_transition)) {
                  screenScrollReset(&stage_transition);
              }
              return;
          }
          if (screenSliceActive(&stage_transition)) {
              if (screenSliceComplete(&stage_transition)) {
                  screenSliceReset(&stage_transition);
                  advanceDemoStage(now_ms);
                  stage_zoom_in_requested = true;
              }
              return;
          }
          pitch_degrees += DEMO_PITCH_DEGREES_PER_SECOND * (float)elapsed_ms / 1000.0f;
          yaw_degrees += DEMO_YAW_DEGREES_PER_SECOND * (float)elapsed_ms / 1000.0f;
          roll_degrees += DEMO_ROLL_DEGREES_PER_SECOND * (float)elapsed_ms / 1000.0f;
          const float elapsed_seconds = (float)elapsed_ms / 1000.0f;
          if (selected_stage == DEMO_STAGE_AMIGA_BALL) {
              updateStageThreeBall(elapsed_seconds);
              // This signed comparison continues to work if the millisecond counter wraps.
              if ((int32_t)(now_ms - stage_three_next_icon_change_at) >= 0) {
                  stage_three_icon_slot = (stage_three_icon_slot + 1U) % DEMO_ICON_SLOT_COUNT;
                  stage_three_icon_bitmap = demo_icon_slots[stage_three_icon_slot];
                  stage_three_next_icon_change_at += DEMO_STAGE_THREE_ICON_CHANGE_MS;
              }
          }
          if (selected_stage == DEMO_STAGE_GEOMETRIC_SOLID) {
              if ((int32_t)(now_ms - stage_six_next_change_at) >= 0) {
                  selectStageSixMesh();
                  stage_six_next_change_at += DEMO_STAGE_SIX_CHANGE_MS;
              }
          }
          if (selected_stage == DEMO_STAGE_WALLOONS) {
              demoStageWalloonsUpdate(now_ms);
          }
          if (selected_stage == DEMO_STAGE_KALEIDO) {
              demoStageKaleidoUpdate(now_ms);
          }
          if (selected_stage == DEMO_STAGE_SPARKLE) {
              demoStageSparkleUpdate(now_ms);
          }
          if (selected_stage == DEMO_STAGE_SQUARES) {
              demoStageSquaresUpdate(now_ms);
          }
          if (selected_stage == DEMO_STAGE_RASTER_BANDS ||
              selected_stage == DEMO_STAGE_LOWER_RASTER_SCROLLER ||
              selected_stage == DEMO_STAGE_BATTLE_ZONE_RASTER) {
              raster_band_effect_elapsed_ms += elapsed_ms;
          }
          if (selected_stage == DEMO_STAGE_SLIDESHOW_LENS) {
              demoStageSlideshowLensUpdate(now_ms);
          }
          if (selected_stage == DEMO_STAGE_PLASMA) {
              demoStagePlasmaUpdate(elapsed_ms);
          }
          if (selected_stage == DEMO_STAGE_GAME_OF_LIFE) {
              demoStageGameOfLifeUpdate(elapsed_ms);
          }
          if (selected_stage == DEMO_STAGE_BEVEL_GEARS) {
              const float angle_step = DEMO_STAGE_SEVENTEEN_GEAR_DEGREES_PER_SECOND *
                                       elapsed_seconds;
              stage_seventeen_rig_pitch_degrees +=
                  DEMO_STAGE_SEVENTEEN_RIG_PITCH_DEGREES_PER_SECOND * elapsed_seconds;
              stage_seventeen_rig_yaw_degrees +=
                  DEMO_STAGE_SEVENTEEN_RIG_YAW_DEGREES_PER_SECOND * elapsed_seconds;
              stage_seventeen_rig_roll_degrees +=
                  DEMO_STAGE_SEVENTEEN_RIG_ROLL_DEGREES_PER_SECOND * elapsed_seconds;
              // The two driven gears reverse from the common lead phase, preserving
              // their fixed tooth-to-gap relationship at both mesh contacts.
              stage_seventeen_gear_degrees[0] += angle_step;
              stage_seventeen_gear_degrees[1] -= angle_step;
              stage_seventeen_gear_degrees[2] -= angle_step;
          }
          if (selected_stage == DEMO_STAGE_MOUSE_MAZE) {
              demoStageMouseMazeUpdate(now_ms);
          }
          if (selected_stage == DEMO_STAGE_SCROLLER ||
              selected_stage == DEMO_STAGE_REVERSE_SCROLLER) {
              updateTextCornerIconCycles(now_ms);
          }
          if (selected_stage == DEMO_STAGE_ICON &&
              (int32_t)(now_ms - stage_one_next_effect_change_at) >= 0) {
              // Keep the automatic sequence in the same enum order as a manual A press.
              selected_effect = (demo_effect_t)(((unsigned)selected_effect + 1U) % DEMO_EFFECT_COUNT);
              stage_one_next_effect_change_at = now_ms + DEMO_STAGE_ONE_EFFECT_CHANGE_MS;
          }
          if (joystick_valid &&
              (joystick_x < -DEMO_INACTIVITY_JOYSTICK_DEAD_ZONE ||
               joystick_x > DEMO_INACTIVITY_JOYSTICK_DEAD_ZONE ||
               (selected_stage == DEMO_STAGE_APPROACH &&
                (joystick_y < -DEMO_INACTIVITY_JOYSTICK_DEAD_ZONE ||
                 joystick_y > DEMO_INACTIVITY_JOYSTICK_DEAD_ZONE)))) {
              demo_last_interaction_at = now_ms;
          }
          if (joystick_valid && (joystick_x < -DEMO_SCROLL_JOYSTICK_DEAD_ZONE ||
                                 joystick_x > DEMO_SCROLL_JOYSTICK_DEAD_ZONE)) {
              float deflection = (float)(joystick_x < 0 ? -joystick_x : joystick_x) -
                                 (float)DEMO_SCROLL_JOYSTICK_DEAD_ZONE;
              deflection /= DEMO_SCROLL_JOYSTICK_FULL_TRAVEL -
                            (float)DEMO_SCROLL_JOYSTICK_DEAD_ZONE;
              if (deflection > 1.0f) deflection = 1.0f;
              const float direction = joystick_x < 0 ? 1.0f : -1.0f;
              scroll_speed_pixels_per_second += direction *
                                                DEMO_SCROLL_ACCELERATION_PIXELS_PER_SECOND_SQUARED *
                                                deflection * elapsed_seconds;
              if (scroll_speed_pixels_per_second > DEMO_SCROLL_MAX_PIXELS_PER_SECOND) {
                  scroll_speed_pixels_per_second = DEMO_SCROLL_MAX_PIXELS_PER_SECOND;
              } else if (scroll_speed_pixels_per_second < -DEMO_SCROLL_MAX_PIXELS_PER_SECOND) {
                  scroll_speed_pixels_per_second = -DEMO_SCROLL_MAX_PIXELS_PER_SECOND;
              }
          }
          if (selected_stage == DEMO_STAGE_APPROACH && joystick_valid &&
              (joystick_y < -DEMO_STAGE_TWO_JOYSTICK_DEAD_ZONE ||
               joystick_y > DEMO_STAGE_TWO_JOYSTICK_DEAD_ZONE)) {
              float deflection = (float)(joystick_y < 0 ? -joystick_y : joystick_y) -
                                 (float)DEMO_STAGE_TWO_JOYSTICK_DEAD_ZONE;
              deflection /= DEMO_SCROLL_JOYSTICK_FULL_TRAVEL -
                            (float)DEMO_STAGE_TWO_JOYSTICK_DEAD_ZONE;
              if (deflection > 1.0f) deflection = 1.0f;
              const float direction = joystick_y > 0 ? 1.0f : -1.0f;
              stage_two_distance -= direction * DEMO_STAGE_TWO_APPROACH_UNITS_PER_SECOND *
                                    deflection * elapsed_seconds;
              if (stage_two_distance < DEMO_STAGE_TWO_MIN_DISTANCE) {
                  stage_two_distance = DEMO_STAGE_TWO_MIN_DISTANCE;
              } else if (stage_two_distance > DEMO_STAGE_TWO_MAX_DISTANCE) {
                  stage_two_distance = DEMO_STAGE_TWO_MAX_DISTANCE;
              }
          }
          if (selected_stage == DEMO_STAGE_APPROACH &&
              (!joystick_valid ||
               (joystick_y >= -DEMO_STAGE_TWO_JOYSTICK_DEAD_ZONE &&
                joystick_y <= DEMO_STAGE_TWO_JOYSTICK_DEAD_ZONE))) {
              // With no vertical-stick input, travel toward the current end and reverse there.
              const float auto_distance_step = DEMO_STAGE_TWO_AUTO_APPROACH_UNITS_PER_SECOND *
                                               elapsed_seconds;
              if (stage_two_auto_approaching) {
                  stage_two_distance -= auto_distance_step;
                  if (stage_two_distance <= DEMO_STAGE_TWO_MIN_DISTANCE) {
                      stage_two_distance = DEMO_STAGE_TWO_MIN_DISTANCE;
                      stage_two_auto_approaching = false;
                  }
              } else {
                  stage_two_distance += auto_distance_step;
                  if (stage_two_distance >= DEMO_STAGE_TWO_MAX_DISTANCE) {
                      stage_two_distance = DEMO_STAGE_TWO_MAX_DISTANCE;
                      stage_two_auto_approaching = true;
                  }
              }
          }
          if (now_ms - demo_last_interaction_at >= demoStageInactivityTimeout() &&
              !stage_change_requested) {
              // Match C's normal route so every automatic advance uses a regular transition.
              selectRandomStageTransition();
              stage_change_requested = true;
          }
          scroll_text_x -= scroll_speed_pixels_per_second * elapsed_seconds;
          scroll_text_effect_elapsed_ms += elapsed_ms;
          if (pitch_degrees >= 360.0f) pitch_degrees -= 360.0f;
          if (yaw_degrees >= 360.0f) yaw_degrees -= 360.0f;
          if (roll_degrees >= 360.0f) roll_degrees -= 360.0f;
          if (stage_seventeen_rig_pitch_degrees >= 360.0f) stage_seventeen_rig_pitch_degrees -= 360.0f;
          if (stage_seventeen_rig_yaw_degrees >= 360.0f) stage_seventeen_rig_yaw_degrees -= 360.0f;
          if (stage_seventeen_rig_roll_degrees >= 360.0f) stage_seventeen_rig_roll_degrees -= 360.0f;
          for (size_t gear = 0U; gear < 3U; ++gear) {
              while (stage_seventeen_gear_degrees[gear] >= 360.0f) {
                  stage_seventeen_gear_degrees[gear] -= 360.0f;
              }
              while (stage_seventeen_gear_degrees[gear] < 0.0f) {
                  stage_seventeen_gear_degrees[gear] += 360.0f;
              }
          }
          if ((scroll_speed_pixels_per_second > 0.0f &&
               scroll_text_x <= -(float)scroll_text_width) ||
              (scroll_speed_pixels_per_second < 0.0f && scroll_text_x >= (float)LCD_WIDTH)) {
              selectScrollMessage(selected_stage != DEMO_STAGE_SCROLLER &&
                                  selected_stage != DEMO_STAGE_REVERSE_SCROLLER &&
                                  selected_stage != DEMO_STAGE_RASTER_BANDS &&
                                  selected_stage != DEMO_STAGE_BATTLE_ZONE_RASTER);
          }
          sparkle_phase = now_ms / DEMO_DOT_SPARKLE_INTERVAL_MS;
          radial_flame_update_wavy(&demo_ring_flame, DEMO_RING_WAVE_AMPLITUDE,
                                   DEMO_RING_WAVE_COUNT,
                                   (float)now_ms * DEMO_RING_WAVE_DEGREES_PER_SECOND / 1000.0f,
                                   DEMO_RING_EXTRA_COOLING);
      }
      
      // Segment 2 renderer: a rotating icon approaches and retreats over a perspective grid.
      static void drawApproachStage(uint16_t *frame)
      {
          static const int terrain_bands[] = {58, 76, 101, 134};
          const int horizon = 42;
          for (size_t index = 0U; index < sizeof(terrain_bands) / sizeof(terrain_bands[0]); ++index) {
              draw_line(frame, 0, terrain_bands[index], LCD_WIDTH - 1, terrain_bands[index],
                        COLOR_DARKGREEN);
          }
          for (int lane = -3; lane <= 3; ++lane) {
              draw_line(frame, DEMO_CENTRE_X + lane * 10, horizon,
                        DEMO_CENTRE_X + lane * 50, LCD_HEIGHT - 1, COLOR_DARKGREEN);
          }
          draw_line(frame, 0, horizon, LCD_WIDTH - 1, horizon, COLOR_LIME);
      
          int icon_size = (int)lroundf(2400.0f / stage_two_distance);
          if (icon_size < 4) icon_size = 4;
          if (icon_size > DEMO_STAGE_TWO_MAX_ICON_PIXELS) icon_size = DEMO_STAGE_TWO_MAX_ICON_PIXELS;
          const int icon_base_y = horizon + (int)lroundf(2000.0f / stage_two_distance);
          const float pitch_radians = pitch_degrees * DEMO_PI / 180.0f;
          const float yaw_radians = yaw_degrees * DEMO_PI / 180.0f;
          const float roll_radians = roll_degrees * DEMO_PI / 180.0f;
          const demo_rotation_t rotation = {
              .pitch_cosine = cosf(pitch_radians),
              .pitch_sine = sinf(pitch_radians),
              .yaw_cosine = cosf(yaw_radians),
              .yaw_sine = sinf(yaw_radians),
              .roll_cosine = cosf(roll_radians),
              .roll_sine = sinf(roll_radians),
          };
          const float scale = (float)icon_size / (float)DEMO_LOGO_WIDTH;
          const int centre_y = icon_base_y - icon_size / 2;
          if (rotation.pitch_cosine * rotation.yaw_cosine >= 0.0f) {
              for (unsigned depth = DEMO_EXTRUSION_DEPTH; depth > 0U;
                   depth -= DEMO_EXTRUSION_STEP) {
                  drawApproachLogoSlice(frame, active_logo_bitmap, (float)depth, &rotation, scale,
                                        DEMO_CENTRE_X, centre_y,
                                        demo_icon_colour_schemes[demo_icon_colour_scheme_index].depth);
              }
              drawApproachLogoSlice(frame, active_logo_bitmap, 0.0f, &rotation, scale,
                                    DEMO_CENTRE_X, centre_y,
                                    demo_icon_colour_schemes[demo_icon_colour_scheme_index].front);
          } else {
              drawApproachLogoSlice(frame, active_logo_bitmap, 0.0f, &rotation, scale,
                                    DEMO_CENTRE_X, centre_y,
                                    demo_icon_colour_schemes[demo_icon_colour_scheme_index].front);
              for (unsigned depth = DEMO_EXTRUSION_STEP; depth <= DEMO_EXTRUSION_DEPTH;
                   depth += DEMO_EXTRUSION_STEP) {
                  drawApproachLogoSlice(frame, active_logo_bitmap, (float)depth, &rotation, scale,
                                        DEMO_CENTRE_X, centre_y,
                                        demo_icon_colour_schemes[demo_icon_colour_scheme_index].depth);
              }
          }
      }
      
      static void drawStageThreeBall(uint16_t *frame)
      {
          // Keep the classic blue field, while the scrolling PET text is layered later by drawDemo.
          clear(frame, RGB565(0, 32, 175));
          const int centre_x = (int)lroundf(stage_three_ball_x);
          const int centre_y = (int)lroundf(stage_three_ball_y);
          const uint8_t rotation = (uint8_t)stage_three_ball_rotation_phase;
          const uint16_t shadow = RGB565(0, 0, 80);
          fill_rect(frame, centre_x - 13, (int)DEMO_STAGE_THREE_BALL_FLOOR_Y, 27, 1, shadow);
          fill_rect(frame, centre_x - 19, (int)DEMO_STAGE_THREE_BALL_FLOOR_Y + 1, 39, 2, shadow);
          fill_rect(frame, centre_x - 15, (int)DEMO_STAGE_THREE_BALL_FLOOR_Y + 3, 31, 1, shadow);
      
          const uint8_t *bitmap = stage_three_icon_bitmap != NULL ? stage_three_icon_bitmap :
                                                                   demo_jd_bitmap;
          for (int y = 0; y < DEMO_STAGE_THREE_BALL_DIAMETER; ++y) {
              const int screen_y = centre_y - DEMO_STAGE_THREE_BALL_RADIUS + y;
              if (screen_y < 0 || screen_y >= LCD_HEIGHT) continue;
              for (int x = 0; x < DEMO_STAGE_THREE_BALL_DIAMETER; ++x) {
                  const int index = y * DEMO_STAGE_THREE_BALL_DIAMETER + x;
                  const uint8_t shade = stage_three_ball_shade_map[index];
                  if (shade == 0U) continue;
                  const int screen_x = centre_x - DEMO_STAGE_THREE_BALL_RADIUS + x;
                  if (screen_x < 0 || screen_x >= LCD_WIDTH) continue;
      
                  // The packed Demo icon is a rotating spherical texture. Set pixels replace
                  // the Amiga ball's red checks; clear pixels retain the shaded white surface.
                  const unsigned texture_x = ((((unsigned)stage_three_ball_longitude_map[index] *
                                                DEMO_LOGO_WIDTH) >> 8U) +
                                              (unsigned)rotation * DEMO_LOGO_WIDTH /
                                              DEMO_STAGE_THREE_BALL_LONGITUDE_BANDS) % DEMO_LOGO_WIDTH;
                  const unsigned texture_y = ((unsigned)stage_three_ball_latitude_map[index] *
                                              DEMO_LOGO_HEIGHT) >> 8U;
                  const size_t byte_index = (size_t)texture_y * DEMO_LOGO_ROW_BYTES + texture_x / 8U;
                  const uint8_t bit = (uint8_t)(7U - texture_x % 8U);
                  const bool icon_pixel = (bitmap[byte_index] & (uint8_t)(1U << bit)) != 0U;
                  frame[(size_t)screen_y * LCD_WIDTH + (size_t)screen_x] = icon_pixel
                      ? stage_three_red_shades[shade - 1U] : stage_three_white_shades[shade - 1U];
              }
          }
      }
      
      static demo_point_3d_t rotateMeshPoint(demo_point_3d_t point, const demo_rotation_t *rotation)
      {
          const float pitched_y = point.y * rotation->pitch_cosine - point.z * rotation->pitch_sine;
          const float pitched_z = point.y * rotation->pitch_sine + point.z * rotation->pitch_cosine;
          const float yawed_x = point.x * rotation->yaw_cosine + pitched_z * rotation->yaw_sine;
          const float yawed_z = -point.x * rotation->yaw_sine + pitched_z * rotation->yaw_cosine;
          const demo_point_3d_t result = {
              .x = yawed_x * rotation->roll_cosine - pitched_y * rotation->roll_sine,
              .y = yawed_x * rotation->roll_sine + pitched_y * rotation->roll_cosine,
              .z = yawed_z,
          };
          return result;
      }
      
      static graphics_point_t projectMeshPoint(const demo_point_3d_t *point)
      {
          const float perspective = DEMO_STAGE_SIX_CAMERA_DISTANCE /
                                    (DEMO_STAGE_SIX_CAMERA_DISTANCE + point->z);
          const graphics_point_t result = {
              .x = DEMO_STAGE_SIX_CENTRE_X + (int)lroundf(point->x * perspective),
              .y = DEMO_STAGE_SIX_CENTRE_Y + (int)lroundf(point->y * perspective),
          };
          return result;
      }
      
      static uint16_t meshFaceColour(float brightness)
      {
          const demo_solid_colour_scheme_t *scheme =
              &demo_solid_colour_schemes[stage_six_colour_scheme_index];
          if (brightness > 0.76f) return scheme->face_shades[0];
          if (brightness > 0.52f) return scheme->face_shades[1];
          if (brightness > 0.28f) return scheme->face_shades[2];
          return scheme->face_shades[3];
      }
      
      static float meshFaceBrightness(const demo_point_3d_t *first, const demo_point_3d_t *second,
                                      const demo_point_3d_t *third)
      {
          const demo_point_3d_t first_edge = {
              .x = second->x - first->x, .y = second->y - first->y, .z = second->z - first->z,
          };
          const demo_point_3d_t second_edge = {
              .x = third->x - first->x, .y = third->y - first->y, .z = third->z - first->z,
          };
          const demo_point_3d_t normal = {
              .x = first_edge.y * second_edge.z - first_edge.z * second_edge.y,
              .y = first_edge.z * second_edge.x - first_edge.x * second_edge.z,
              .z = first_edge.x * second_edge.y - first_edge.y * second_edge.x,
          };
          const float length = sqrtf(normal.x * normal.x + normal.y * normal.y + normal.z * normal.z);
          if (length <= 0.0f) return 0.0f;
          // Both sides are shaded because depth sorting, rather than culling, is what lets
          // one generic face table render solids with either winding direction.
          const float light = fabsf((normal.x * 0.35f - normal.y * 0.45f + normal.z * 0.82f) / length);
          return light > 1.0f ? 1.0f : light;
      }
      
      static void drawFilledMesh(uint16_t *frame, const demo_mesh_t *mesh,
                                 const demo_rotation_t *rotation)
      {
          if (mesh == NULL || mesh->vertices == NULL || mesh->faces == NULL ||
              mesh->vertex_count > DEMO_MESH_MAX_VERTICES || mesh->face_count > DEMO_MESH_MAX_FACES) {
              return;
          }
      
          demo_point_3d_t rotated_vertices[DEMO_MESH_MAX_VERTICES];
          demo_mesh_face_draw_t draw_order[DEMO_MESH_MAX_FACES];
          for (size_t vertex = 0U; vertex < mesh->vertex_count; ++vertex) {
              demo_point_3d_t scaled = mesh->vertices[vertex];
              scaled.x *= DEMO_STAGE_SIX_SOLID_SCALE;
              scaled.y *= DEMO_STAGE_SIX_SOLID_SCALE;
              scaled.z *= DEMO_STAGE_SIX_SOLID_SCALE;
              rotated_vertices[vertex] = rotateMeshPoint(scaled, rotation);
          }
      
          for (size_t face = 0U; face < mesh->face_count; ++face) {
              const demo_mesh_face_t *source_face = &mesh->faces[face];
              if (source_face->vertex_indices == NULL || source_face->vertex_count < 3U ||
                  source_face->vertex_count > DEMO_MESH_MAX_FACE_VERTICES) {
                  return;
              }
              float total_depth = 0.0f;
              for (size_t corner = 0U; corner < source_face->vertex_count; ++corner) {
                  const uint8_t vertex = source_face->vertex_indices[corner];
                  if (vertex >= mesh->vertex_count) return;
                  total_depth += rotated_vertices[vertex].z;
              }
              draw_order[face] = (demo_mesh_face_draw_t) {
                  .face_index = face,
                  .depth = total_depth / (float)source_face->vertex_count,
                  .brightness = meshFaceBrightness(&rotated_vertices[source_face->vertex_indices[0]],
                                                   &rotated_vertices[source_face->vertex_indices[1]],
                                                   &rotated_vertices[source_face->vertex_indices[2]]),
              };
          }
      
          // Painter's order draws the farthest polygon first, allowing nearer filled faces
          // to cover it without a full per-pixel depth buffer.
          for (size_t pass = 0U; pass + 1U < mesh->face_count; ++pass) {
              for (size_t index = 0U; index + 1U < mesh->face_count - pass; ++index) {
                  if (draw_order[index].depth <= draw_order[index + 1U].depth) continue;
                  const demo_mesh_face_draw_t temporary = draw_order[index];
                  draw_order[index] = draw_order[index + 1U];
                  draw_order[index + 1U] = temporary;
              }
          }
      
          for (size_t order = 0U; order < mesh->face_count; ++order) {
              const demo_mesh_face_t *source_face = &mesh->faces[draw_order[order].face_index];
              graphics_point_t polygon[DEMO_MESH_MAX_FACE_VERTICES];
              for (size_t corner = 0U; corner < source_face->vertex_count; ++corner) {
                  polygon[corner] = projectMeshPoint(&rotated_vertices[source_face->vertex_indices[corner]]);
              }
              fill_convex_polygon(frame, polygon, source_face->vertex_count,
                                  meshFaceColour(draw_order[order].brightness));
              for (size_t corner = 0U; corner < source_face->vertex_count; ++corner) {
                  const size_t next_corner = (corner + 1U) % source_face->vertex_count;
                  draw_line(frame, polygon[corner].x, polygon[corner].y,
                            polygon[next_corner].x, polygon[next_corner].y,
                            demo_solid_colour_schemes[stage_six_colour_scheme_index].edge);
              }
          }
      }
      
      static void drawStageSixSolid(uint16_t *frame)
      {
          const float pitch_radians = pitch_degrees * DEMO_PI / 180.0f;
          const float yaw_radians = yaw_degrees * DEMO_PI / 180.0f;
          const float roll_radians = roll_degrees * DEMO_PI / 180.0f;
          const demo_rotation_t rotation = {
              .pitch_cosine = cosf(pitch_radians), .pitch_sine = sinf(pitch_radians),
              .yaw_cosine = cosf(yaw_radians), .yaw_sine = sinf(yaw_radians),
              .roll_cosine = cosf(roll_radians), .roll_sine = sinf(roll_radians),
          };
          drawFilledMesh(frame, stage_six_current_mesh, &rotation);
      }
      
      static demo_rotation_t stageSeventeenRotation(float pitch_degrees, float yaw_degrees,
                                                    float roll_degrees)
      {
          const float pitch = pitch_degrees * DEMO_PI / 180.0f;
          const float yaw = yaw_degrees * DEMO_PI / 180.0f;
          const float roll = roll_degrees * DEMO_PI / 180.0f;
          return (demo_rotation_t) {
              .pitch_cosine = cosf(pitch), .pitch_sine = sinf(pitch),
              .yaw_cosine = cosf(yaw), .yaw_sine = sinf(yaw),
              .roll_cosine = cosf(roll), .roll_sine = sinf(roll),
          };
      }
      
      static demo_point_3d_t stageSeventeenGearOrigin(unsigned gear)
      {
          switch (gear) {
              case 0U: return (demo_point_3d_t) {.x = 0.0f, .y = DEMO_STAGE_SEVENTEEN_LEAD_Y,
                                                   .z = 0.0f};
              case 1U: return (demo_point_3d_t) {.x = -DEMO_STAGE_SEVENTEEN_DRIVEN_X,
                                                   .y = DEMO_STAGE_SEVENTEEN_DRIVEN_Y, .z = 0.0f};
              default: return (demo_point_3d_t) {.x = DEMO_STAGE_SEVENTEEN_DRIVEN_X,
                                                  .y = DEMO_STAGE_SEVENTEEN_DRIVEN_Y, .z = 0.0f};
          }
      }
      
      static demo_rotation_t stageSeventeenGearAxisRotation(unsigned gear)
      {
          // Each backward axis converges toward the triangular centre. The front
          // centres therefore remain at the wide-rim mesh contacts while the narrow
          // rear centres close by the same ratio as the tapering tooth radii.
          switch (gear) {
              case 0U: return stageSeventeenRotation(-22.5f, 0.0f, 0.0f);
              case 1U: return stageSeventeenRotation(14.8f, 22.5f, 0.0f);
              default: return stageSeventeenRotation(14.8f, -22.5f, 0.0f);
          }
      }
      
      static demo_point_3d_t stageSeventeenTransformPoint(demo_point_3d_t point,
                                                           const demo_point_3d_t *origin,
                                                           const demo_rotation_t *axis_rotation,
                                                           const demo_rotation_t *rig_rotation)
      {
          demo_point_3d_t transformed = rotateMeshPoint(point, axis_rotation);
          transformed.x += origin->x;
          transformed.y += origin->y;
          transformed.z += origin->z;
          return rotateMeshPoint(transformed, rig_rotation);
      }
      
      static graphics_point_t stageSeventeenProjectPoint(const demo_point_3d_t *point)
      {
          const float perspective = DEMO_STAGE_SEVENTEEN_CAMERA_DISTANCE /
                                    (DEMO_STAGE_SEVENTEEN_CAMERA_DISTANCE + point->z);
          return (graphics_point_t) {
              .x = DEMO_STAGE_SEVENTEEN_CENTRE_X + (int)lroundf(point->x * perspective),
              .y = DEMO_STAGE_SEVENTEEN_CENTRE_Y + (int)lroundf(point->y * perspective),
          };
      }
      
      static uint16_t stageSeventeenFaceColour(void)
      {
          if (stage_seventeen_style == STAGE_SEVENTEEN_STYLE_FLAT) return RGB565(230, 195, 30);
          if (stage_seventeen_style == STAGE_SEVENTEEN_STYLE_METALLIC) return RGB565(255, 220, 70);
          return COLOR_BLACK;
      }
      
      static uint16_t stageSeventeenRearColour(void)
      {
          if (stage_seventeen_style == STAGE_SEVENTEEN_STYLE_FLAT) return RGB565(170, 135, 10);
          if (stage_seventeen_style == STAGE_SEVENTEEN_STYLE_METALLIC) return RGB565(210, 175, 30);
          return COLOR_BLACK;
      }
      
      static uint16_t stageSeventeenSideColour(unsigned segment)
      {
          if (stage_seventeen_style == STAGE_SEVENTEEN_STYLE_FLAT) {
              return (segment & 2U) == 0U ? RGB565(160, 120, 8) : RGB565(90, 68, 2);
          }
          if (stage_seventeen_style == STAGE_SEVENTEEN_STYLE_METALLIC) {
              return (segment & 2U) == 0U ? RGB565(195, 155, 18) : RGB565(115, 85, 3);
          }
          return COLOR_BLACK;
      }
      
      static uint16_t stageSeventeenEdgeColour(void)
      {
          return stage_seventeen_style == STAGE_SEVENTEEN_STYLE_METALLIC ? RGB565(255, 245, 160) :
              RGB565(255, 220, 50);
      }
      
      static void stageSeventeenAppendFace(const graphics_point_t *points, uint8_t point_count,
                                           float depth, uint16_t colour)
      {
          if (stage_seventeen_draw_face_count >= DEMO_STAGE_SEVENTEEN_DRAW_FACE_COUNT ||
              point_count < 3U || point_count > 4U) return;
          stage_seventeen_draw_face_t *const face =
              &stage_seventeen_draw_faces[stage_seventeen_draw_face_count++];
          for (uint8_t point = 0U; point < point_count; ++point) face->points[point] = points[point];
          face->depth = depth;
          face->colour = colour;
          face->point_count = point_count;
      }
      
      static void stageSeventeenBuildGear(unsigned gear, const demo_rotation_t *rig_rotation)
      {
          const unsigned point_count = DEMO_STAGE_SEVENTEEN_TOOTH_COUNT * 2U;
          graphics_point_t front[DEMO_STAGE_SEVENTEEN_TOOTH_COUNT * 2U];
          graphics_point_t rear[DEMO_STAGE_SEVENTEEN_TOOTH_COUNT * 2U];
          demo_point_3d_t front_points[DEMO_STAGE_SEVENTEEN_TOOTH_COUNT * 2U];
          demo_point_3d_t rear_points[DEMO_STAGE_SEVENTEEN_TOOTH_COUNT * 2U];
          const demo_point_3d_t origin = stageSeventeenGearOrigin(gear);
          const demo_rotation_t axis_rotation = stageSeventeenGearAxisRotation(gear);
          const float spin = stage_seventeen_gear_degrees[gear] * DEMO_PI / 180.0f;
          // Origins are the wide, front-face centres. Each gear then narrows and
          // travels inward through its complete depth toward the shared rear mesh.
          const float front_z = 0.0f;
          const float rear_z = DEMO_STAGE_SEVENTEEN_CONE_DEPTH;
          demo_point_3d_t front_centre = {.x = 0.0f, .y = 0.0f, .z = front_z};
          demo_point_3d_t rear_centre = {.x = 0.0f, .y = 0.0f, .z = rear_z};
          front_centre = stageSeventeenTransformPoint(front_centre, &origin, &axis_rotation, rig_rotation);
          rear_centre = stageSeventeenTransformPoint(rear_centre, &origin, &axis_rotation, rig_rotation);
          const graphics_point_t projected_front_centre = stageSeventeenProjectPoint(&front_centre);
          const graphics_point_t projected_rear_centre = stageSeventeenProjectPoint(&rear_centre);
      
          for (unsigned point = 0U; point < point_count; ++point) {
              const float angle = spin + (float)point * DEMO_PI / (float)DEMO_STAGE_SEVENTEEN_TOOTH_COUNT;
              const float front_radius = (point & 1U) == 0U ? DEMO_STAGE_SEVENTEEN_FRONT_RADIUS :
                  DEMO_STAGE_SEVENTEEN_FRONT_RADIUS * DEMO_STAGE_SEVENTEEN_TOOTH_ROOT_RATIO;
              // Preserve the same tooth/gap rhythm on the narrow end. Joining these
              // matching corners makes every tooth a full tapered ridge, rather than
              // letting the front cog collapse into a smooth conical rear hub.
              const float rear_radius = (point & 1U) == 0U ? DEMO_STAGE_SEVENTEEN_REAR_RADIUS :
                  DEMO_STAGE_SEVENTEEN_REAR_RADIUS * DEMO_STAGE_SEVENTEEN_TOOTH_ROOT_RATIO;
              demo_point_3d_t front_point = {
                  .x = cosf(angle) * front_radius, .y = sinf(angle) * front_radius, .z = front_z,
              };
              demo_point_3d_t rear_point = {
                  .x = cosf(angle) * rear_radius,
                  .y = sinf(angle) * rear_radius, .z = rear_z,
              };
              front_points[point] =
                  stageSeventeenTransformPoint(front_point, &origin, &axis_rotation, rig_rotation);
              rear_points[point] =
                  stageSeventeenTransformPoint(rear_point, &origin, &axis_rotation, rig_rotation);
              front[point] = stageSeventeenProjectPoint(&front_points[point]);
              rear[point] = stageSeventeenProjectPoint(&rear_points[point]);
          }
      
          for (unsigned point = 0U; point < point_count; ++point) {
              const unsigned next = (point + 1U) % point_count;
              const graphics_point_t side[] = {rear[point], rear[next], front[next], front[point]};
              stageSeventeenAppendFace(side, 4U,
                  (rear_points[point].z + rear_points[next].z + front_points[next].z +
                   front_points[point].z) * 0.25f, stageSeventeenSideColour(point));
              // A cog outline is concave at the tooth roots, so its front remains a
              // set of separately sorted centre wedges rather than one polygon.
              const graphics_point_t wedge[] = {projected_front_centre, front[point], front[next]};
              stageSeventeenAppendFace(wedge, 3U,
                  (front_centre.z + front_points[point].z + front_points[next].z) / 3.0f,
                  stageSeventeenFaceColour());
              // The rear is also concave at its tooth roots. Its individual wedges
              // close the narrow end without crossing the adjacent tapered ridges.
              const graphics_point_t rear_wedge[] = {projected_rear_centre, rear[next], rear[point]};
              stageSeventeenAppendFace(rear_wedge, 3U,
                  (rear_centre.z + rear_points[point].z + rear_points[next].z) / 3.0f,
                  stageSeventeenRearColour());
          }
      }
      
      static void drawStageSeventeenBevelGears(uint16_t *frame)
      {
          const demo_rotation_t rig_rotation = stageSeventeenRotation(stage_seventeen_rig_pitch_degrees,
              stage_seventeen_rig_yaw_degrees, stage_seventeen_rig_roll_degrees);
          stage_seventeen_draw_face_count = 0U;
          for (unsigned gear = 0U; gear < 3U; ++gear) stageSeventeenBuildGear(gear, &rig_rotation);
          // Positive Z is farther from this camera. Sorting every tooth face, rather
          // than three whole gears, preserves the interlock throughout the rig tumble.
          for (size_t pass = 0U; pass + 1U < stage_seventeen_draw_face_count; ++pass) {
              for (size_t index = 0U; index + 1U < stage_seventeen_draw_face_count - pass; ++index) {
                  if (stage_seventeen_draw_faces[index].depth >=
                      stage_seventeen_draw_faces[index + 1U].depth) continue;
                  const stage_seventeen_draw_face_t temporary = stage_seventeen_draw_faces[index];
                  stage_seventeen_draw_faces[index] = stage_seventeen_draw_faces[index + 1U];
                  stage_seventeen_draw_faces[index + 1U] = temporary;
              }
          }
          for (size_t face_index = 0U; face_index < stage_seventeen_draw_face_count; ++face_index) {
              const stage_seventeen_draw_face_t *const face = &stage_seventeen_draw_faces[face_index];
              if (stage_seventeen_style != STAGE_SEVENTEEN_STYLE_OUTLINE) {
                  fill_convex_polygon(frame, face->points, face->point_count, face->colour);
              }
              for (uint8_t point = 0U; point < face->point_count; ++point) {
                  const uint8_t next = (uint8_t)((point + 1U) % face->point_count);
                  draw_line(frame, face->points[point].x, face->points[point].y,
                            face->points[next].x, face->points[next].y, stageSeventeenEdgeColour());
              }
          }
      }
      
      static void drawTextCornerIcons(uint16_t *frame)
      {
          // No value below comes from the main Demo rotation or either text effect.
          // Every icon's travel, pulse, colour, and rotation come from its own state.
          const unsigned colour_count = sizeof(demo_scroll_rainbow_colours) /
                                       sizeof(demo_scroll_rainbow_colours[0]);
          for (size_t corner = 0U; corner < sizeof(demo_text_corner_icon_slots) /
                                             sizeof(demo_text_corner_icon_slots[0]); ++corner) {
              const size_t slot = (size_t)demo_text_corner_icon_slots[corner] % DEMO_ICON_SLOT_COUNT;
              const uint8_t *bitmap = demo_icon_slots[slot];
              const demo_text_corner_icon_cycle_t *const cycle =
                  &demo_text_corner_icon_cycles[corner];
              const float rotation_degrees = cycle->rotation_start_degrees +
                  (float)cycle->rotation_degrees_per_second *
                  (float)(last_update_ms - cycle->rotation_started_at) / 1000.0f;
              const float rotation_radians = rotation_degrees * DEMO_PI / 180.0f;
              const demo_rotation_t rotation = {
                  .pitch_cosine = 1.0f, .pitch_sine = 0.0f,
                  .yaw_cosine = 1.0f, .yaw_sine = 0.0f,
                  .roll_cosine = cosf(rotation_radians),
                  .roll_sine = sinf(rotation_radians),
              };
              // updateTextCornerIconCycles() restarts this cycle at every completed
              // trip with a newly random duration. This fraction therefore describes
              // one complete, independent edge -> centre -> edge journey.
              const float travel_phase = (float)(last_update_ms - cycle->cycle_started_at) /
                                         (float)cycle->cycle_duration_ms;
              // 0.0 at the outer corner, 1.0 at the centre X line, then back out.
              const float centre_progress = 0.5f - 0.5f * cosf(travel_phase * 2.0f * DEMO_PI);
              const int centre_x = (int)lroundf((float)demo_text_corner_icon_x[corner] +
                                                ((float)(LCD_WIDTH / 2) -
                                                 (float)demo_text_corner_icon_x[corner]) *
                                                centre_progress);
              const uint32_t pulse_offset = (uint32_t)corner * DEMO_TEXT_CORNER_ICON_PULSE_STAGGER_MS;
              const float pulse_phase = (float)((last_update_ms + pulse_offset) %
                                                DEMO_TEXT_CORNER_ICON_PULSE_MS) /
                                        (float)DEMO_TEXT_CORNER_ICON_PULSE_MS;
              const float pulse = 0.5f + 0.5f * sinf(pulse_phase * 2.0f * DEMO_PI);
              const float scale = DEMO_TEXT_CORNER_ICON_SCALE_MIN +
                                  (DEMO_TEXT_CORNER_ICON_SCALE_MAX -
                                   DEMO_TEXT_CORNER_ICON_SCALE_MIN) * pulse;
              const unsigned colour_index = (unsigned)(last_update_ms /
                                            DEMO_TEXT_CORNER_ICON_COLOUR_MS + corner * 2U) % colour_count;
              const uint16_t colour = demo_scroll_rainbow_colours[colour_index];
      
              // Draw far slices first so the pulsing coloured face remains in front.
              for (unsigned depth = DEMO_TEXT_CORNER_ICON_DEPTH; depth > 0U;
                   depth -= DEMO_TEXT_CORNER_ICON_DEPTH_STEP) {
                  drawApproachLogoSlice(frame, bitmap, (float)depth, &rotation, scale,
                                        centre_x, demo_text_corner_icon_y[corner], C64_COLOR_DARK_GREY);
              }
              drawApproachLogoSlice(frame, bitmap, 0.0f, &rotation, scale,
                                    centre_x, demo_text_corner_icon_y[corner], colour);
          }
      }
      
      // Render Battle Zone normally every frame, copy its fresh ground rows to PSRAM,
      // then scale and wrap that current source into Demo's lower half. This avoids a
      // full-screen scratch allocation while keeping forward motion safe.
      static void drawStageFifteenBattleZoneBackground(uint16_t *frame)
      {
          const int source_top = BATTLE_ZONE_HORIZON_Y;
          const int source_height = LCD_HEIGHT - source_top;
          const int destination_top = DEMO_STAGE_FIFTEEN_HORIZON_Y;
          const int destination_height = LCD_HEIGHT - destination_top;
          // Moving the source lookup upward makes terrain features travel down from
          // the horizon toward the viewer. A wrapped source introduces the next
          // ground pattern at the fixed horizon.
          const int forward_source_offset = (int)(
              (raster_band_effect_elapsed_ms % DEMO_STAGE_FIFTEEN_FORWARD_CYCLE_MS) *
              (uint32_t)source_height / DEMO_STAGE_FIFTEEN_FORWARD_CYCLE_MS);
      
          drawBattleZoneLandscape(frame);
          if (stage_fifteen_ground_source != NULL) {
              memcpy(stage_fifteen_ground_source, &frame[(size_t)source_top * LCD_WIDTH],
                     (size_t)LCD_WIDTH * (size_t)source_height * sizeof(frame[0]));
              // The captured first ground row is Battle Zone's lime horizon. Segment
              // 15 draws its own fixed horizon below, so remove this copy before it
              // can wrap through the moving texture as an unwanted bright stripe.
              for (int x = 0; x < LCD_WIDTH; ++x) {
                  stage_fifteen_ground_source[x] = COLOR_BLACK;
              }
          }
      
          if (stage_fifteen_ground_source == NULL) {
              // A PSRAM allocation failure leaves a static, safely compressed
              // landscape instead of risking corrupt rows with wrapped in-place reads.
              for (int destination_y = LCD_HEIGHT - 1; destination_y >= destination_top;
                   --destination_y) {
                  const int source_y = source_top +
                      (destination_y - destination_top) * (source_height - 1) /
                      (destination_height - 1);
                  memcpy(&frame[(size_t)destination_y * LCD_WIDTH],
                         &frame[(size_t)source_y * LCD_WIDTH], LCD_WIDTH * sizeof(frame[0]));
              }
          } else {
              for (int destination_y = LCD_HEIGHT - 1; destination_y >= destination_top;
                   --destination_y) {
                  const int unscrolled_source_offset =
                      (destination_y - destination_top) * (source_height - 1) /
                      (destination_height - 1);
                  const int source_row =
                      (unscrolled_source_offset - forward_source_offset + source_height) % source_height;
                  memcpy(&frame[(size_t)destination_y * LCD_WIDTH],
                         &stage_fifteen_ground_source[(size_t)source_row * LCD_WIDTH],
                         LCD_WIDTH * sizeof(frame[0]));
              }
          }
          // The original unscaled sky and terrain in the top half are no longer part
          // of this segment, leaving the bar and horizon-anchored scroller clear.
          fill_rect(frame, 0, 0, LCD_WIDTH, destination_top, COLOR_BLACK);
          // The wrapped texture moves, but the bright green horizon remains a stable
          // reference line exactly beneath the scroller.
          draw_line(frame, 0, destination_top, LCD_WIDTH - 1, destination_top, COLOR_LIME);
      }
      
      // Segment wrapper: preserves the shared motion timing and chooses each raster
      // segment's geometry. Segment 11 starts metallic, Segment 15 fire, and Segment
      // 16 green; B chooses another shared palette without interrupting that motion.
      static void drawRasterBands(uint16_t *frame)
      {
          const bool lower_half_stage = selected_stage == DEMO_STAGE_LOWER_RASTER_SCROLLER;
          const bool battle_zone_stage = selected_stage == DEMO_STAGE_BATTLE_ZONE_RASTER;
          const int block_height = lower_half_stage ? DEMO_STAGE_FOURTEEN_BAND_HEIGHT :
              (battle_zone_stage ? DEMO_STAGE_FIFTEEN_BAND_BLOCK_HEIGHT :
                                   DEMO_STAGE_ELEVEN_BAND_BLOCK_HEIGHT);
          const uint32_t drift_ms = battle_zone_stage ? DEMO_STAGE_FIFTEEN_BAND_DRIFT_MS :
                                                        DEMO_STAGE_ELEVEN_BAND_DRIFT_MS;
          // Segment 16 reserves its lower half for the scaled Battle Zone ground.
          // Its bar may touch the halfway horizon but cannot enter that landscape.
          const int travel_height = battle_zone_stage ? DEMO_STAGE_FIFTEEN_HORIZON_Y :
                                                        LCD_HEIGHT;
          const float drift_phase = (float)(raster_band_effect_elapsed_ms % drift_ms) /
                                    (float)drift_ms;
          const int band_top = lower_half_stage ? DEMO_STAGE_FOURTEEN_BAND_TOP :
              (int)lroundf((float)(travel_height - block_height) *
                  (0.5f + 0.5f * sinf(drift_phase * 2.0f * DEMO_PI)));
          const demo_raster_band_palette_t *palette =
              demo_raster_band_palettes[raster_band_palette_index];
          draw_rolling_raster_band(frame, band_top, block_height,
                                   DEMO_STAGE_ELEVEN_BAND_HEIGHT,
                                   raster_band_effect_elapsed_ms,
                                   DEMO_STAGE_ELEVEN_COLOUR_ROLL_MS, palette);
      }
      
      #if 0 // Moved to demo_stage_slideshow_lens.c.
      static void drawStageTwelveSlideshowLens(uint16_t *frame)
      {
          if (!stage_twelve_image_ready || stage_twelve_image == NULL) {
              drawPet2001TextCentredTransparent(frame, LCD_WIDTH / 2, LCD_HEIGHT / 2,
                                                "NO SLIDESHOW IMAGE", C64_COLOR_LIGHT_GREY, 1);
              return;
          }
      
          const size_t frame_bytes = (size_t)LCD_WIDTH * LCD_HEIGHT * sizeof(frame[0]);
          memcpy(frame, stage_twelve_image, frame_bytes);
      
          const int centre_x = (int)lroundf(stage_twelve_lens_x);
          const int centre_y = (int)lroundf(stage_twelve_lens_y);
          const int radius = DEMO_STAGE_TWELVE_LENS_RADIUS;
          const int outer_radius_squared = radius * radius;
          const int inner_radius = radius - DEMO_STAGE_TWELVE_LENS_RIM_WIDTH;
          const int inner_radius_squared = inner_radius * inner_radius;
      
          // Sample nearer to the lens centre inside the circle. Clamp the visible
          // part to the framebuffer because edge destinations deliberately place
          // part of the lens beyond the screen.
          const int first_y = centre_y - radius < 0 ? 0 : centre_y - radius;
          const int last_y = centre_y + radius >= LCD_HEIGHT ? LCD_HEIGHT - 1 :
                                                              centre_y + radius;
          const int first_x = centre_x - radius < 0 ? 0 : centre_x - radius;
          const int last_x = centre_x + radius >= LCD_WIDTH ? LCD_WIDTH - 1 :
                                                             centre_x + radius;
          for (int y = first_y; y <= last_y; ++y) {
              for (int x = first_x; x <= last_x; ++x) {
                  const int dx = x - centre_x;
                  const int dy = y - centre_y;
                  const int distance_squared = dx * dx + dy * dy;
                  if (distance_squared > outer_radius_squared) continue;
      
                  if (distance_squared > inner_radius_squared) {
                      frame[(size_t)y * LCD_WIDTH + (size_t)x] = DEMO_STAGE_TWELVE_LENS_RIM_COLOUR;
                      continue;
                  }
      
                  const int source_x = (int)lroundf((float)centre_x +
                      (float)dx / DEMO_STAGE_TWELVE_LENS_MAGNIFICATION);
                  const int source_y = (int)lroundf((float)centre_y +
                      (float)dy / DEMO_STAGE_TWELVE_LENS_MAGNIFICATION);
                  frame[(size_t)y * LCD_WIDTH + (size_t)x] =
                      stage_twelve_image[(size_t)source_y * LCD_WIDTH + (size_t)source_x];
              }
          }
      }
      #endif
      
      static void drawStageSevenGlyph(uint16_t *frame, int column, int row, uint8_t glyph)
      {
          const int display_row = row + DEMO_STAGE_SEVEN_ROW_OFFSET;
          if (column < 0 || column >= 40 || display_row < 0 || display_row >= 21) return;
          drawPet2001Glyph(frame, column * (int)PET2001_FONT_GLYPH_WIDTH,
                           display_row * (int)PET2001_FONT_GLYPH_HEIGHT, glyph, false,
                           DEMO_STAGE_SEVEN_PET_COLOUR, COLOR_BLACK, 1);
      }
      
      static void drawStageSevenWalloon(uint16_t *frame, int column, int row, bool walking)
      {
          // This is the original seven-cell Walloon from BASIC lines 1800-1820.
          // The walking top cell alternates the original 87 and 81 glyphs, while the
          // rest of the PET-character sprite remains exactly as the listing defines it.
          drawStageSevenGlyph(frame, column, row, walking ? 81U : 87U);
          drawStageSevenGlyph(frame, column - 1, row + 1, 64U);
          drawStageSevenGlyph(frame, column, row + 1, 71U);
          drawStageSevenGlyph(frame, column + 1, row + 1, 64U);
          drawStageSevenGlyph(frame, column, row + 2, 90U);
          drawStageSevenGlyph(frame, column - 1, row + 3, 78U);
          drawStageSevenGlyph(frame, column + 1, row + 3, 77U);
      }
      
      static void drawStageSevenPlayfield(uint16_t *frame)
      {
          // Retain the upper platform and right-hand climbing pole throughout the
          // act. Their origin is BASIC lines 220-290, at PET cell (10, 5).
          for (int column = 10; column <= 28; ++column) {
              drawStageSevenGlyph(frame, column, 5, 127U);
          }
          for (int row = 5; row <= 15; ++row) {
              drawStageSevenGlyph(frame, 28, row, 127U);
          }
      }
      
      static void drawStageSevenTitleBox(uint16_t *frame)
      {
          // The title-only left edge and lower edge make the temporary opening box.
          // They deliberately disappear before the first Walloon begins walking.
          drawStageSevenPlayfield(frame);
          for (int row = 5; row <= 15; ++row) {
              drawStageSevenGlyph(frame, 10, row, 127U);
          }
          for (int column = 10; column <= 25; ++column) {
              drawStageSevenGlyph(frame, column, 15, 127U);
          }
          // The original presents its stunt as "THE FLYING WALLOONS". Split the
          // title over two PET rows so it remains inside this 17-cell-wide frame.
          drawPet2001Text(frame, 18 * (int)PET2001_FONT_GLYPH_WIDTH,
                          7 * (int)PET2001_FONT_GLYPH_HEIGHT, "THE", false,
                          DEMO_STAGE_SEVEN_PET_COLOUR, COLOR_BLACK, 1);
          drawPet2001Text(frame, 12 * (int)PET2001_FONT_GLYPH_WIDTH,
                          9 * (int)PET2001_FONT_GLYPH_HEIGHT, "FLYING WALLOONS", false,
                          DEMO_STAGE_SEVEN_PET_COLOUR, COLOR_BLACK, 1);
      }
      
      static void drawStageSevenSeesaw(uint16_t *frame, bool left_end_down)
      {
          // A single fixed PET-cell pivot replaces the listing's two absolute screen
          // snapshots. The board never slides sideways: only its stepped slope swaps
          // when the other Walloon becomes the one at the low end.
          static const struct { int column; int row; } left_down_board[] = {
              {13, 19}, {14, 19}, {15, 19}, {16, 18}, {17, 18}, {18, 18},
              {19, 17}, {20, 17}, {21, 17}, {22, 16}, {23, 16}, {24, 16},
          };
          static const struct { int column; int row; } right_down_board[] = {
              {13, 16}, {14, 16}, {15, 16}, {16, 17}, {17, 17}, {18, 17},
              {19, 18}, {20, 18}, {21, 18}, {22, 19}, {23, 19}, {24, 19},
          };
          if (left_end_down) {
              for (size_t index = 0U; index < sizeof(left_down_board) / sizeof(left_down_board[0]);
                   ++index) {
                  drawStageSevenGlyph(frame, left_down_board[index].column,
                                      left_down_board[index].row,
                                      DEMO_STAGE_SEVEN_SEESAW_LINE_GLYPH);
              }
          } else {
              for (size_t index = 0U; index < sizeof(right_down_board) / sizeof(right_down_board[0]);
                   ++index) {
                  drawStageSevenGlyph(frame, right_down_board[index].column,
                                      right_down_board[index].row,
                                      DEMO_STAGE_SEVEN_SEESAW_LINE_GLYPH);
              }
          }
          // The two original PET glyphs make a compact, fixed fulcrum below the board.
          drawStageSevenGlyph(frame, 18, 20, 233U);
          drawStageSevenGlyph(frame, 19, 20, 223U);
      }
      
      static void drawStageSevenWalloons(uint16_t *frame, uint32_t now_ms)
      {
          if (stage_seven_phase == WALLOONS_TITLE_CARD) {
              drawStageSevenTitleBox(frame);
              return;
          }
          drawStageSevenPlayfield(frame);
      
          const unsigned steps = walloonsPhaseSteps(now_ms, 14U);
          const bool walking_pose = (steps & 1U) != 0U;
          switch (stage_seven_phase) {
              case WALLOONS_TITLE_CARD:
                  // Handled above so the title card contains no active Walloons.
                  break;
              case WALLOONS_LEFT_WALK_IN:
                  drawStageSevenSeesaw(frame, true);
                  drawStageSevenWalloon(frame, 2 + (int)walloonsPhaseSteps(now_ms, 12U), 15,
                                         walking_pose);
                  break;
              case WALLOONS_PAUSE_ON_SEESAW:
                  drawStageSevenSeesaw(frame, true);
                  drawStageSevenWalloon(frame, 14, 15, false);
                  break;
              case WALLOONS_RIGHT_WALK_ON_PLATFORM:
                  drawStageSevenSeesaw(frame, true);
                  drawStageSevenWalloon(frame, 14, 15, false);
                  drawStageSevenWalloon(frame, 38 - (int)steps, 2, walking_pose);
                  break;
              case WALLOONS_RIGHT_DROP:
                  drawStageSevenSeesaw(frame, true);
                  drawStageSevenWalloon(frame, 14, 15, false);
                  drawStageSevenWalloon(frame, 24, 2 + (int)walloonsPhaseSteps(now_ms, 13U), false);
                  break;
              case WALLOONS_BOUNCE_RIGHT_UP:
              case WALLOONS_BOUNCE_RIGHT_DOWN:
                  // The left Walloon remains at the low end while the right Walloon is airborne.
                  drawStageSevenSeesaw(frame, true);
                  drawStageSevenWalloon(frame, 15, 15, false);
                  drawStageSevenWalloon(frame, 24,
                                         stage_seven_phase == WALLOONS_BOUNCE_RIGHT_UP
                                             ? 14 - (int)walloonsPhaseSteps(now_ms, 13U)
                                             : 2 + (int)walloonsPhaseSteps(now_ms, 13U), false);
                  break;
              case WALLOONS_BOUNCE_LEFT_UP:
                  // The right Walloon remains at the low end while the left Walloon rises.
                  drawStageSevenSeesaw(frame, false);
                  drawStageSevenWalloon(frame, 24, 14, false);
                  drawStageSevenWalloon(frame, 15,
                                         14 - (int)walloonsPhaseSteps(now_ms, 13U), false);
                  break;
              case WALLOONS_BOUNCE_LEFT_DOWN:
                  drawStageSevenSeesaw(frame, false);
                  drawStageSevenWalloon(frame, 24, 14, false);
                  drawStageSevenWalloon(frame, 15,
                                         2 + (int)walloonsPhaseSteps(now_ms, 13U), false);
                  break;
              case WALLOONS_LEFT_LANDED:
                  // The landing phase makes the left-down state visible before the
                  // next Walloon is launched from the other end.
                  drawStageSevenSeesaw(frame, true);
                  drawStageSevenWalloon(frame, 15, 15, false);
                  break;
              case WALLOONS_FINISHED:
                  drawStageSevenSeesaw(frame, true);
                  drawStageSevenWalloon(frame, 15, 15, false);
                  drawStageSevenWalloon(frame, 24, 14, false);
                  break;
          }
      }
      
      // Pre-Demo control reference. PET characters and the blue C64-style background
      // keep this separate page visually consistent with the retro Demo segments.
      static void drawDemoInstructions(uint16_t *frame)
      {
          const uint32_t elapsed_ms = last_update_ms - demo_instructions_started_at;
          const uint32_t remaining_ms = elapsed_ms >= DEMO_INSTRUCTIONS_DURATION_MS ? 0U :
              DEMO_INSTRUCTIONS_DURATION_MS - elapsed_ms;
          const unsigned seconds_remaining = (unsigned)((remaining_ms + 999U) / 1000U);
          char start_text[24];
      
          fill_rect(frame, 0, 0, LCD_WIDTH, LCD_HEIGHT, C64_COLOR_BLUE);
          starfield_draw(frame, &demo_instruction_starfield);
          drawPet2001Text(frame, 40, 8, "Old school Demo", false,
                          C64_COLOR_CYAN, C64_COLOR_BLUE, 2);
          drawPet2001Text(frame, 128, 32, "CONTROLS", false, C64_COLOR_LIGHT_GREY, C64_COLOR_BLUE, 1);
          drawPet2001Text(frame, 56, 48, "C: NEXT SEGMENT", false, C64_COLOR_YELLOW, C64_COLOR_BLUE, 1);
          drawPet2001Text(frame, 56, 64, "A: NEXT EFFECT (1)", false, C64_COLOR_WHITE, C64_COLOR_BLUE, 1);
          drawPet2001Text(frame, 56, 80, "B: ICON/SOLID/PALETTE", false,
                          C64_COLOR_WHITE, C64_COLOR_BLUE, 1);
          drawPet2001Text(frame, 56, 96, "UP/DOWN: APPROACH (2)", false, C64_COLOR_WHITE, C64_COLOR_BLUE, 1);
          drawPet2001Text(frame, 56, 112, "LEFT/RIGHT: TICKER SPEED", false,
                          C64_COLOR_WHITE, C64_COLOR_BLUE, 1);
          drawPet2001Text(frame, 56, 128, "BOOT: RETURN TO MENU", false,
                          C64_COLOR_WHITE, C64_COLOR_BLUE, 1);
          snprintf(start_text, sizeof(start_text), "C STARTS IN %u S", seconds_remaining);
          drawPet2001Text(frame, 96, 152, start_text, false, C64_COLOR_YELLOW, C64_COLOR_BLUE, 1);
      }
      
      static void drawDemoSelector(uint16_t *frame)
      {
          clear(frame, COLOR_BLACK);
          drawPet2001TextCentredTransparent(frame, LCD_WIDTH / 2, 14,
                                            "SELECT DEMO SEGMENT", COLOR_ORANGE, 1);
          for (size_t item = 0U; item <= DEMO_STAGE_COUNT; ++item) {
              const int y = DEMO_SELECTOR_FIRST_ROW_Y + (int)item * DEMO_SELECTOR_ROW_HEIGHT;
              const bool selected = item == demo_selector_item;
              if (selected) fill_rect(frame, 0, y - 1, LCD_WIDTH, DEMO_SELECTOR_ROW_HEIGHT,
                                      COLOR_DARKBLUE);
              drawPet2001TextTransparent(frame, 8, y, item == 0U ? "RETURN CURRENT" :
                                        demoSelectorStageCodeName((demo_stage_t)(item - 1U)),
                                        selected ? COLOR_WHITE : COLOR_LIGHTGREY, 1);
          }
          drawPet2001TextCentredTransparent(frame, LCD_WIDTH / 2, 160,
                                            "D/B OR UP/DOWN  A/K SELECT", COLOR_LIGHTGREY, 1);
      }
      
      void drawDemo(uint16_t *frame)
      {
          if (!running || frame == NULL) return;
          if (demo_selector_active) {
              drawDemoSelector(frame);
              return;
          }
          if (demo_selector_clear_new_stage) {
              clear(frame, COLOR_BLACK);
              demo_selector_clear_new_stage = false;
              return;
          }
          if (demo_instructions_active) {
              drawDemoInstructions(frame);
              return;
          }
          if (screenTransitionActive(&stage_transition)) {
              screenTransitionDraw(&stage_transition, frame, last_update_ms);
              return;
          }
          if (screenRevealActive(&stage_transition)) {
              screenRevealDraw(&stage_transition, frame, last_update_ms);
              return;
          }
          if (screenScrollActive(&stage_transition) && !stage_scroll_in_requested) {
              screenScrollDraw(&stage_transition, frame, last_update_ms);
              return;
          }
          if (screenSliceActive(&stage_transition)) {
              screenSliceDraw(&stage_transition, frame, last_update_ms);
              return;
          }
          clear(frame, COLOR_BLACK);
      
          if (selected_stage == DEMO_STAGE_APPROACH) {
              drawApproachStage(frame);
          } else if (selected_stage == DEMO_STAGE_AMIGA_BALL) {
              drawStageThreeBall(frame);
          } else if (selected_stage == DEMO_STAGE_SCROLLER) {
              // The rainbow magnifier scroller is drawn below on this clear black background.
          } else if (selected_stage == DEMO_STAGE_REVERSE_SCROLLER) {
              // The inverse rainbow magnifier scroller is drawn below on this clear black background.
          } else if (selected_stage == DEMO_STAGE_GEOMETRIC_SOLID) {
              drawStageSixSolid(frame);
          } else if (selected_stage == DEMO_STAGE_WALLOONS) {
              demoStageWalloonsDraw(frame, last_update_ms);
          } else if (selected_stage == DEMO_STAGE_KALEIDO) {
              demoStageKaleidoDraw(frame);
          } else if (selected_stage == DEMO_STAGE_SPARKLE) {
              demoStageSparkleDraw(frame);
          } else if (selected_stage == DEMO_STAGE_SQUARES) {
              demoStageSquaresDraw(frame);
          } else if (selected_stage == DEMO_STAGE_RASTER_BANDS) {
              drawRasterBands(frame);
          } else if (selected_stage == DEMO_STAGE_SLIDESHOW_LENS) {
              demoStageSlideshowLensDraw(frame);
          } else if (selected_stage == DEMO_STAGE_PLASMA) {
              demoStagePlasmaDraw(frame);
          } else if (selected_stage == DEMO_STAGE_GAME_OF_LIFE) {
              demoStageGameOfLifeDraw(frame);
          } else if (selected_stage == DEMO_STAGE_LOWER_RASTER_SCROLLER) {
              drawRasterBands(frame);
          } else if (selected_stage == DEMO_STAGE_BATTLE_ZONE_RASTER) {
              // The real Battle Zone renderer is compressed into the lower half;
              // the compact raster then travels only in the clear upper half.
              drawStageFifteenBattleZoneBackground(frame);
              drawRasterBands(frame);
          } else if (selected_stage == DEMO_STAGE_BEVEL_GEARS) {
              drawStageSeventeenBevelGears(frame);
          } else if (selected_stage == DEMO_STAGE_MOUSE_MAZE) {
              demoStageMouseMazeDraw(frame);
          } else if (selected_effect == DEMO_EFFECT_DOT_GRID ||
              selected_effect == DEMO_EFFECT_DOT_SPARKLE ||
              selected_effect == DEMO_EFFECT_RADIAL_FLAME) {
              const bool sparkle_enabled = selected_effect == DEMO_EFFECT_DOT_SPARKLE;
              if (selected_effect == DEMO_EFFECT_RADIAL_FLAME) {
                  draw_radial_flame(frame, &demo_ring_flame, DEMO_CENTRE_X, DEMO_CENTRE_Y,
                                    demoRingInnerRadius(), yaw_degrees, demo_ring_colours);
              }
              const dotted_bitmap_3d_params_t dotted_sprite = {
                  .centre_x = DEMO_CENTRE_X,
                  .centre_y = DEMO_CENTRE_Y,
                  .scale = DEMO_FACE_SCALE,
                  .pitch_degrees = pitch_degrees,
                  .yaw_degrees = yaw_degrees,
                  .roll_degrees = roll_degrees,
                  .depth = (float)DEMO_EXTRUSION_DEPTH,
                  .camera_distance = DEMO_CAMERA_DISTANCE,
                  .depth_dot_spacing = DEMO_DOT_SPACING,
                  .dot_radius = DEMO_DOT_RADIUS,
                  .dot_density_percent = DEMO_DOT_DENSITY_PERCENT,
                  .random_seed = DEMO_DOT_RANDOM_SEED,
                  .sparkle_density_percent = sparkle_enabled ? DEMO_DOT_SPARKLE_DENSITY_PERCENT : 0U,
                  .sparkle_phase = sparkle_phase,
                  .sparkle_radius = DEMO_DOT_SPARKLE_RADIUS,
                  .sparkle_color = DEMO_DOT_SPARKLE_COLOR,
                  .front_color = demo_icon_colour_schemes[demo_icon_colour_scheme_index].front,
                  .back_color = demo_icon_colour_schemes[demo_icon_colour_scheme_index].dotted_back,
                  .side_color = demo_icon_colour_schemes[demo_icon_colour_scheme_index].depth,
              };
              draw_bitmap_dotted_3d(frame, active_logo_bitmap, DEMO_LOGO_WIDTH,
                                    DEMO_LOGO_HEIGHT, &dotted_sprite);
          } else {
              const float pitch_radians = pitch_degrees * DEMO_PI / 180.0f;
              const float yaw_radians = yaw_degrees * DEMO_PI / 180.0f;
              const float roll_radians = roll_degrees * DEMO_PI / 180.0f;
              const demo_rotation_t rotation = {
                  .pitch_cosine = cosf(pitch_radians),
                  .pitch_sine = sinf(pitch_radians),
                  .yaw_cosine = cosf(yaw_radians),
                  .yaw_sine = sinf(yaw_radians),
                  .roll_cosine = cosf(roll_radians),
                  .roll_sine = sinf(roll_radians),
              };
              // The body depth changes screen depth by pitch_cosine * yaw_cosine.
              // Draw from far to near so the random front colour is never painted through a nearer slice.
              if (rotation.pitch_cosine * rotation.yaw_cosine >= 0.0f) {
                  for (unsigned depth = DEMO_EXTRUSION_DEPTH; depth > 0U;
                       depth -= DEMO_EXTRUSION_STEP) {
                      drawLogoSlice(frame, active_logo_bitmap, (float)depth, &rotation,
                                    demo_icon_colour_schemes[demo_icon_colour_scheme_index].depth);
                  }
                  drawLogoSlice(frame, active_logo_bitmap, 0.0f, &rotation,
                                demo_icon_colour_schemes[demo_icon_colour_scheme_index].front);
              } else {
                  drawLogoSlice(frame, active_logo_bitmap, 0.0f, &rotation,
                                demo_icon_colour_schemes[demo_icon_colour_scheme_index].front);
                  for (unsigned depth = DEMO_EXTRUSION_STEP; depth <= DEMO_EXTRUSION_DEPTH;
                       depth += DEMO_EXTRUSION_STEP) {
                      drawLogoSlice(frame, active_logo_bitmap, (float)depth, &rotation,
                                    demo_icon_colour_schemes[demo_icon_colour_scheme_index].depth);
                  }
              }
          }
      
          if (selected_stage == DEMO_STAGE_SCROLLER ||
              selected_stage == DEMO_STAGE_REVERSE_SCROLLER) {
              drawTextCornerIcons(frame);
          }
          drawScrollText(frame);
          if (stage_scroll_in_requested) {
              // The new stage is complete in the shared frame but has not reached the LCD yet.
              // Capture it as the second moving frame, then scroll both frames together.
              if (screenScrollSetIncoming(&stage_transition, frame, stage_scroll_in_direction,
                                          DEMO_STAGE_SCROLL_GAP_PIXELS)) {
                  screenScrollDraw(&stage_transition, frame, last_update_ms);
              }
              stage_scroll_in_requested = false;
              return;
          }
          if (stage_reveal_requested) {
              // Capture the completed destination before the reusable 8 x 8 rebuild effect hides it.
              if (screenRevealStart(&stage_transition, frame, last_update_ms, DEMO_STAGE_REBUILD_MS,
                                    DEMO_STAGE_REBUILD_PERCENT)) {
                  screenRevealDraw(&stage_transition, frame, last_update_ms);
              }
              stage_reveal_requested = false;
              return;
          }
          if (stage_zoom_in_requested) {
              // This scene has been drawn into the shared frame but not presented yet: capture it
              // and replace it with the first one-pixel frame of the incoming zoom.
              if (screenTransitionZoomInStart(&stage_transition, frame, last_update_ms,
                                              DEMO_STAGE_ZOOM_IN_MS)) {
                  stage_zooming_in = true;
                  screenTransitionDraw(&stage_transition, frame, last_update_ms);
              }
              stage_zoom_in_requested = false;
              return;
          }
          if (stage_change_requested) {
              switch (selected_stage_transition) {
                  case DEMO_STAGE_TRANSITION_SHRINK_ZOOM:
                      // Freeze the completed old stage before updateDemo selects and captures the new one.
                      if (!screenTransitionStart(&stage_transition, frame, last_update_ms,
                                                 DEMO_STAGE_TRANSITION_FREEZE_MS,
                                                 DEMO_STAGE_TRANSITION_ZOOM_MS)) {
                          advanceDemoStage(last_update_ms);
                          stage_zoom_in_requested = true;
                      }
                      break;
                  case DEMO_STAGE_TRANSITION_REBUILD:
                      // Select the destination now; drawDemo captures it next frame for its block reveal.
                      advanceDemoStage(last_update_ms);
                      stage_reveal_requested = true;
                      break;
                  case DEMO_STAGE_TRANSITION_SCROLL:
                      // Capture after the completed outgoing scene, then translate that exact image away.
                      if (!screenScrollOutStart(&stage_transition, frame, last_update_ms,
                                                stage_scroll_out_direction,
                                                DEMO_STAGE_SCROLL_STEP_MS,
                                                DEMO_STAGE_SCROLL_PIXELS_PER_STEP)) {
                          advanceDemoStage(last_update_ms);
                          stage_scroll_in_requested = true;
                      } else {
                          // Select and draw the new stage next frame while the captured old one moves away.
                          advanceDemoStage(last_update_ms);
                          stage_scroll_in_requested = true;
                      }
                      break;
                  case DEMO_STAGE_TRANSITION_SLICES:
                      // Freeze the outgoing stage as alternating horizontal bands before entering the next.
                      if (!screenSliceStart(&stage_transition, frame, last_update_ms,
                                            DEMO_STAGE_SLICE_COUNT, DEMO_STAGE_SLICE_MS)) {
                          advanceDemoStage(last_update_ms);
                          stage_zoom_in_requested = true;
                      }
                      break;
                  default:
                      break;
              }
              stage_change_requested = false;
          }
      }
      
      const char *demoScreenshotLabel(void)
      {
          return demoSelectorStageScreenshotLabel(selected_stage);
      }